From 558891ed3784af6979b6f57a7854c86735d70125 Mon Sep 17 00:00:00 2001 From: "claude (blind_chess)" Date: Mon, 18 May 2026 20:00:25 -0400 Subject: [PATCH] feat(bot): suppress bot retry-search churn from the moderator log Pop intermediate wont_help/illegal_move/no_such_piece/no_legal_moves announcements produced during the bot's decision cycle before any broadcast reaches the human opponent. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/server/src/bot/driver.ts | 10 ++++++++++ packages/server/test/unit/bot/driver.test.ts | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/packages/server/src/bot/driver.ts b/packages/server/src/bot/driver.ts index 178650e..4e561b9 100644 --- a/packages/server/src/bot/driver.ts +++ b/packages/server/src/bot/driver.ts @@ -165,6 +165,16 @@ export class BotDriver { const text = result.announcements[0]!.text; if (text === 'wont_help' || text === 'illegal_move' || text === 'no_such_piece' || text === 'no_legal_moves') { + // Attempted-move announcements are audience 'both'. The bot's + // intermediate retry rejections are internal search churn, not + // deliberate probing — suppress them so they don't broadcast to + // the human. The bot tracks its own rejections via attemptHistory, + // so removing the announcement is safe. The whole decision cycle + // runs before ws.ts broadcasts, so this pop always happens before + // any broadcast. + const rejection = result.announcements[0]!; + const anns = this.game.announcements; + if (anns[anns.length - 1] === rejection) anns.pop(); return { kind: 'retry', entry: { diff --git a/packages/server/test/unit/bot/driver.test.ts b/packages/server/test/unit/bot/driver.test.ts index 041c7b8..1684fb4 100644 --- a/packages/server/test/unit/bot/driver.test.ts +++ b/packages/server/test/unit/bot/driver.test.ts @@ -172,6 +172,24 @@ describe('BotDriver', () => { expect(brain.dispose).toHaveBeenCalled(); }); + it('suppresses the bot intermediate retry rejection from the moderator log', async () => { + // Pinned-bishop position: first action is rejected (wont_help), second + // is a legal king move. The wont_help must NOT survive in announcements. + const fen = '4k2K/4b3/8/8/8/8/8/4R3 b - - 0 1'; + game = makeGame({ fen }); + brain = new StubBrain(); + driver = new BotDriver({ game, brain, color: 'b' }); + await driver.init(); + brain.enqueue( + { type: 'commit', from: 'e7', to: 'd6' }, // rejected: wont_help + { type: 'commit', from: 'e8', to: 'f8' }, // legal king move + ); + await driver.onStateChange(); + const texts = game.announcements.map((a) => a.text); + expect(texts).not.toContain('wont_help'); + expect(texts).toContain('black_moved'); + }); + it('bot move that delivers checkmate finalizes game.status', async () => { // FEN: '1k6/8/1K6/8/8/8/8/7Q w - - 0 1' // White king b6, white queen h1, black king b8.