无法预测的骰子:哪里出了问题,我们改了什么
我们发现,在线对局的骰子原则上可以在掷出前被算出来:它的种子由每位玩家都能看到的信息构成。我们没发现有人这样做过。自 10 月 1 日起,每次掷骰的种子都来自只有服务器掌握的密钥,旧的预测如今每 36 次才对 1 次——纯属运气。

通俗版
以下是我们的发现。在线对局里一切随机的东西——两颗骰子、抽机会卡、大奖骰子——都来自一个随机数生成器,而生成器必须从一个种子开始。我们的种子由每位玩家都能看到的两样东西构成:对局 id(就在你的地址栏里),以及这一步的编号(游戏在每次操作后都会返回它)。生成器本身是普通的公开代码。把这些拼在一起,懂行的人本可以在掷骰之前就算出结果——原则上甚至还能挑选接下来几次掷骰中自己拿到哪一次。
我们是在真实对局上验证的,而不是纸上谈兵。修复前的最后 2,000 次掷骰中,有 1,985 次——即 99.3%——与这些公开信息的预测完全一致。
有人利用过它吗?
接着我们排查了是否有人利用过它,结果一个也没有。最能直接从“预知掷骰结果”中获利的,是大奖投注。修复前的七天里,2,354 次大奖投注的胜率为 31.4%,而纯靠运气的预期是 31.1%,也没有任何玩家的记录显得异常。
常见问题
opolyx 的骰子公平吗?
公平。在 2026 年 10 月 2 日至 6 日的 247,968 次掷骰中,每颗骰子的每个点数出现的比例都在 16.54% 到 16.77% 之间——完美骰子的理论值是 16.67%——对子出现的比例为 16.67%。
opolyx 的骰子能被预测吗?
不能——自 2026 年 10 月 1 日起就不能了。每次掷骰的种子都来自一个只存在于服务器上的密钥,旧的预测如今只能命中 2.76% 的掷骰,正是闭眼瞎猜两颗骰子时会得到的 2.78%。
有人利用过骰子的旧漏洞吗?
我们没有发现任何迹象。修复前的七天里,2,354 次大奖投注的胜率为 31.4%,而纯靠运气的预期是 31.1%,也没有任何玩家的结果显得异常。
掷骰结果是在哪里决定的?
在游戏服务器上,在你掷骰的那一刻,由一个随机数生成器决定,它的种子来自一个别人都没有的密钥。结果会写进对局日志,回放和统计读取的是日志,而不是重新掷一次骰子。
技术细节
种子原本是一个形如 <对局 id>:<步骤编号> 的标签,先用 FNV-1a 做哈希,再交给 mulberry32。这种设计是刻意的——它让每一局都能依据自己的日志完整复现——但复现从来不需要种子是公开的。每个结果都写在产生它的那个事件里,回放和统计读取的是这些事件,而不是重新掷一次骰子。
// before: anyone who knew the game id and the move number could do this
rng = mulberry32(fnv1a(`${gameId}:${seq}`))
// after: the same label, signed with a key that never leaves the server
rng = mulberry32(uint32(hmacSha256(serverSecret, `${gameId}:${seq}`)))现在,这个标签会先经过 HMAC-SHA256,密钥是一段只存在于服务器内部的 32 字节秘密值。默认情况下,服务器每次启动都会重新生成这个密钥。没有任何东西需要它在重启后保留下来,因为过去的掷骰从不会被重新计算——而一个从未被写下来的密钥,就是一个不可能泄露的密钥。
游戏中每一次起决定作用的随机抽取都以这种方式签名:骰子、机会卡、大奖骰子,以及谁先行动的选择。
骰子也正如骰子应有的那样公平。在 10 月 2 日以来的全部掷骰中——共 247,968 次——每颗骰子的每个点数出现的比例都在 16.54% 到 16.77% 之间,而完美骰子的理论值是 16.67%;对子出现的比例为 16.67%:
| 点数 | 第一颗骰子 | 第二颗骰子 |
|---|---|---|
| 点数 1 | 16.75% | 16.72% |
| 点数 2 | 16.77% | 16.65% |
| 点数 3 | 16.56% | 16.61% |
| 点数 4 | 16.54% | 16.70% |
| 点数 5 | 16.67% | 16.70% |
| 点数 6 | 16.72% | 16.62% |
修复用与问题相同的标准做了检验。修复前命中率达 99.3% 的那套预测,在修复后又用一整天的数据重新跑了一遍:它在 50,212 次中命中了 1,388 次,即 2.76%——正是闭眼瞎猜两颗骰子时应有的 2.78%。
