The blind-mode CasualBrain heuristic ignored the moderator's
'<own>_in_check' announcement and scored moves on capture/advance/development
signals uncorrelated with check resolution. chess.js rejected every
non-resolving attempt, BotDriver's RETRY_CAP=5 fired, and the bot resigned.
100-game blind self-play: 100% resignations at avg ply 26.
Fix:
- CasualBrain.detectOwnCheck() scans newAnnouncements for the own-color
in_check tag; when set, heuristicPick() applies a +5000 boost to king
moves so they're tried first. Information stays within the public
moderator vocabulary — no oracle access, view-filter invariant intact.
- RETRY_CAP raised 5 -> 25. Vanilla never hits the cap (chess.js verbose
moves are guaranteed legal); blind needs more budget to find a legal
move through pseudo-legal candidates.
- BotDriver.botResign() now logs '[bot resign]' with gameId/color/mode/ply/
reason/detail. Previously silent — operator had no signal in journald.
Verification (100-game blind Casual-vs-Casual self-play):
- avgPly: 26 -> 90 (3.5x)
- Resignations: 100% -> 17%
- Checkmates: 0% -> 42%
- Threefold draws: 0% -> 41%
Vanilla regression check (80 games combined): 0 resigns either way,
strength unchanged (Casual still wins 98% vs random).
78 tests pass (was 75; +2 new check-resolution tests, +1 retry-cap test
updated to match new cap).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>