Misty 按标准象棋规则记录吃过路兵格
python-chess 默认以 EnPassantMode.LEGAL 写出吃过路兵字段:只在这步吃子在标准象棋中合法时才记录该格,其中包括不让吃子一方的王处于被将军状态。Misty 的 Rust 热点路径有意照搬了同样的过滤条件,好让它的字符串与 python-chess 逐字节一致。
所以该格会在一种局面中丢失:一个兵刚走了两格,停在敌方兵的旁边,而吃过路兵会让吃子一方的王处于被攻击状态,比如那个王本来就正被将军时。该格一丢,重建出来的局面就看不见落点格,也看不见它可以吃的那个兵,于是真实棋盘不再与 Misty 观察到的信息相符,从 P 中掉了出去。这个触发条件在12,866着中出现1着,发生在273局已完成对局中的1局。
一局棋在第29回合丢失了真实棋盘
对局 e751c6c4,Misty 执黑,第29回合。白方把 g 兵推进两格,落在黑方 f4 兵的旁边。白方位于 d6 的后正攻击着黑王。
黑方正被将军,所以标准象棋禁止 f4xg3 吃过路兵。在迷雾规则下这是一步普通的着法,而且它决定了黑方能看见什么。
按标准规则,该格被丢掉了:
stored 5k2/2Q4p/P2Qp3/4N3/5pP1/B7/4PP1P/RN2KB1R b KQ - 0 29
needed 5k2/2Q4p/P2Qp3/4N3/5pP1/B7/4PP1P/RN2KB1R b KQ g3 0 29Misty 在没有该格的情况下构建了 29.g4 之后的局面,数出黑方可见12个格子,与真实观察中的14个比较,于是排除了真实的着法。P 在那一着之内从625个棋盘降到41个,而真实棋盘不在这41个之中。Misty 这局最后四次决策的搜索根节点都是从这41个中抽取的。
把黑王挪到旁边一格,那里没有任何棋子攻击它,其余一概不变:该格就会被记录为 g3,真实棋盘也会留在 P 中。
修正后,只要有兵能吃就记录该格
只要有兵能吃就记录该格,不管吃下去是否明智。这就是 EnPassantMode.XFEN,也正是迷雾规则。当旁边没有敌方兵时它仍然会省略该格,这样信念就不会因为一个谁都用不上的格子而分裂。
Rust 路径现在只检查被推进的兵旁边是否站着敌方兵。在 Python 一侧,每个写入 P 的棋盘都经过同一个函数 belief_fen,而成员检查接收一个棋盘,并以同样的方式写出它,因为直接传入原始的 board.fen() 正是这个错误混进来的方式。
在出错的那一着,P 现在是从625变到617,真相得以保留。重放全部271局,抽样局面之外不再报告任何丢失真相的决策,此前是四例。
为什么没有任何机制发现它
枚举器只在 P 变空时抛出异常,而从41个的集合里丢掉真相是无声的,而且必然如此:对局中引擎从不知道真相。比较 Rust 移植版与 python-chess 的测试在178,145次着法应用中一直是绿色的,因为两者犯了同一个错误。重放会记录真相是否在 P 中,但那一列只在超过抽样上限的对局上被读取过,而在那种情况下丢失真相是预期行为。
已于2026-09-10在引擎中修正,并随 Misty 0.1.3 发布。上面每一张棋盘都是引擎自己的可见性代码从该局面绘制的;绘图脚本一旦发现两个视图的差异不再恰好是 g3 和 g4,就会拒绝运行。