Commit Graph

54 Commits

Author SHA1 Message Date
claude (blind_chess) 0c0e739bd3 docs: session handoff — table-fidelity batch code-complete
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:00:20 -04:00
claude (blind_chess) 2e808008b1 docs: record table-fidelity feature batch as code-complete
- DECISIONS.md: new "Table-fidelity features" section + deferred items
  (smart-tracker rejected, highlight/phantom coupling deferred,
  abandoned-game localStorage cleanup deferred).
- CLAUDE.md: current state, test count 78->87, key files, known gaps.
- spec: record that the driver unit test covers the bot-suppression
  path in place of the considered-and-dropped ai-game-casual integration
  test (resolves a spec/implementation drift the final review flagged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:57:02 -04:00
claude (blind_chess) 59717b3b5b docs: amend plan to reflect code-review fixes
Tasks 8/10/11 received review fixes during execution; the plan's code
blocks are updated to match what shipped:
- Task 8: drag controller handles pointercancel + idempotent start.
- Task 10: palette pieces are plain spans + svelte-ignore (no
  focusable-but-not-operable role/tabindex).
- Task 11: phantom-load effect keyed on gameId; drag ghost gated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:51:37 -04:00
claude (blind_chess) 82a69d8812 fix(client): key phantom-load effect on gameId, gate the drag ghost
Re-key the phantom-load effect on `loadedFor` (tracks gameId) so it
reloads if the same <Game> instance is reused for a different game
without a remount. Also gate the drag-ghost block behind
`phantomLayerEnabled` for consistency with all other phantom UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:50:13 -04:00
claude (blind_chess) 313837eb21 feat(client): wire the phantom opponent-model layer into the game view
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:45:40 -04:00
claude (blind_chess) 816f89be36 feat(client): phantom-piece palette component
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:41:43 -04:00
claude (blind_chess) c65db03cfa chore(client): suppress phantom-span a11y warning with documented svelte-ignore
Phantom pieces are a pointer-only overlay; adding role/tabindex would create
a focusable element with no keyboard equivalent (worse a11y, not better).
The real game remains fully keyboard-operable via the square <button>.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:39:37 -04:00
claude (blind_chess) 599dc17f44 feat(client): render and drag phantom pieces on the board
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:35:58 -04:00
claude (blind_chess) 4b3e587f6c fix(client): handle pointercancel and make drag-start idempotent
Add onCancel handler for browser/OS gesture takeover (scroll, palm
rejection) that previously leaked window listeners and left drag stuck.
Extract detach() helper called by onUp, onCancel, and start() for
idempotency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:32:20 -04:00
claude (blind_chess) f52f7dbb8f feat(client): pointer-event drag controller for the phantom layer
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:28:37 -04:00
claude (blind_chess) bd98315fe3 fix(client): guard phantom-store mutations against unset game and no-op move
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:27:25 -04:00
claude (blind_chess) 0583984723 feat(client): local-only phantom-layer store
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:24:13 -04:00
claude (blind_chess) 2ae2c8013c test(shared): cover null-valued entry in deserializePhantoms
Adds a null-valued entry under a valid square key (d3) to the
'keeps valid entries and drops invalid ones' fixture, proving the
typeof/null guard branch in deserializePhantoms is exercised.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:23:17 -04:00
claude (blind_chess) a574100e25 feat(shared): pure phantom-model helpers (seed positions, deserialize)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:20:46 -04:00
claude (blind_chess) 783d85a40c feat(client): capture-tally panel
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:16:18 -04:00
claude (blind_chess) 3169995d7f refactor(server): type captureTally accumulators as PieceTally
Tighten the local byYou/byOpponent accumulators from the wider
Record<string, number> to PieceTally, matching the return type's fields
and preventing silent invalid-key writes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:14:26 -04:00
claude (blind_chess) ce36755a89 feat(server): per-viewer capture tally on joined and update messages
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:09:26 -04:00
claude (blind_chess) 0498f1de43 feat(client): label attempted-move announcements by player
Attempted-move lines (no_such_piece, no_legal_moves, wont_help, illegal_move)
now show "White — " or "Black — " prefix derived from ply parity. Removed
alarm-red err styling; replaced with neutral dim+italic via .entry.attempt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:04:49 -04:00
claude (blind_chess) 5282237027 refactor(bot): hoist the rejection announcement to a single local
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:03:37 -04:00
claude (blind_chess) 558891ed37 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) <noreply@anthropic.com>
2026-05-18 20:00:25 -04:00
claude (blind_chess) 76717cf52e docs(server): correct translateMove audience docs after the 'both' change
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 19:58:48 -04:00
claude (blind_chess) 41b3ab93bb feat(server): moderator announces every move and attempt to both players
All move-event announcements in translator.ts and all attempted-move
announcements in commit.ts now use audience 'both' so the moderator
panel is a complete shared transcript for both players.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 19:54:34 -04:00
claude (blind_chess) be8ecd96b6 docs: implementation plan for table-fidelity feature batch
12-task TDD plan in two increments:
- Increment 1 (Tasks 1-5): announce-all-to-both + capture tally.
- Increment 2 (Tasks 6-11): client-local phantom opponent-piece layer.

Each task has exact files, complete code, and verification commands.
Server/shared tasks are TDD'd with vitest; client tasks use svelte-check
plus manual verification (no client test harness, by design).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 19:17:23 -04:00
claude (blind_chess) f8faa11b6d docs: design spec for table-fidelity feature batch
Three features requested by Andrew Freiberg (physical-game player) and
refined by Seth, bringing digital blind chess closer to the physical
table:

1. Moderator announces every move and attempted move to both players
   (widen announcement audience to 'both'; suppress bot retry churn).
2. Running capture tally (server-derived per-viewer protocol field).
3. Phantom opponent pieces — a client-local, drag-and-drop opponent-model
   overlay, blind mode only, never sent to the server.

Spec only; no implementation. Phased: F1+F2 then F3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:52:00 -04:00
claude (blind_chess) b01f324c3b feat(deploy): local chess.local instance for VDJ-RIG
A second, LAN-only deploy alongside the CT 690 / chess.sethpc.xyz
instance. Runs on VDJ-RIG as a persistent systemd daemon, served on
port 80 and reachable at http://chess.local via an mDNS alias.

- blind-chess-local.service: server unit; binds port 80 as the
  non-root blindchess user via CAP_NET_BIND_SERVICE.
- chess-mdns-alias{,.service}: publishes the chess.local mDNS name
  with avahi-publish -a -R (-R skips the reverse PTR, which would
  otherwise collide with the host's own <hostname>.local record).
- install-local.sh: idempotent root-side installer (Node 22 via
  NodeSource, avahi-utils, blindchess user, /opt/blind-chess, units).
- CLAUDE.md: documents the local instance under Operations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:40:02 -04:00
claude (blind_chess) e75f5fff7b docs: CLAUDE.md current state reflects blind-Casual check fix
Phase line gains the 2026-04-29 fix; test count 75→78; observability gap
narrowed to note `[bot resign]` is now grep-able while metrics/tracing
remain deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:41:42 -04:00
claude (blind_chess) 04494fcdee docs: handoff for blind Casual check-resolution fix
Captures session state: root cause, fix, verification numbers (blind 100%
-> 17% resignation, avg ply 26 -> 90), preserved view-filter invariant,
deferred Phase 2 work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 06:05:21 -04:00
claude (blind_chess) f00164ebbb chore: gitignore tmp/ for self-play transcripts
Self-play transcripts produced by `pnpm selfplay --transcripts` land in
`tmp/selfplay-runs/<timestamp>/` — operator scratch, not part of source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 05:56:14 -04:00
claude (blind_chess) dc7f8adcdf fix(bot): blind Casual no longer resigns prematurely under check
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>
2026-04-29 05:56:02 -04:00
claude (blind_chess) 1213ec8fb1 docs: handoff reflects final merged state 2026-04-28 15:25:03 -04:00
claude (blind_chess) 1674695eef docs: AI Phase 1 shipped — context, decisions, handoff
- CLAUDE.md: phase line moved to "Phase 1 deployed"; key files lists
  the new bot module, game-end extraction, and selfplay harness.
- DECISIONS.md: new "Phase 1 implementation outcomes" subsection records
  the CasualBrain-engine reversal, the FEN-vanilla-only invariant, why
  blind keeps heuristic, and the bot-slot token randomization. The
  earlier "Stockfish deferred" entry is partially superseded.
- .claude/handoffs/: handoff document for the next session.
2026-04-28 15:20:24 -04:00
claude (blind_chess) 7c18725586 feat(bot): vanilla CasualBrain delegates to js-chess-engine
The hand-rolled scoring heuristic lost to a random-move baseline 7-7 in
self-play — far below the spec's >=80% acceptance bar. Swap in a real
chess engine (js-chess-engine, MIT, ~400KB, no native deps) for vanilla
mode at level 2 with randomness=30 to break threefold cycles.

- BrainInput.fen added; driver populates it ONLY in vanilla mode.
  Blind mode omits the FEN so the engine path can't smuggle opponent
  positions past the view filter.
- CasualBrain in vanilla: convert FEN -> EngineGame -> ai({level: 2});
  validate the engine's move is in legalCandidates; fall back to
  heuristic on miss.
- Blind mode unchanged (engine isn't useful when only own pieces are
  visible — that's Phase 2 Recon's territory).

Self-play vs RandomBrain (100 games each direction, vanilla):
  - Casual(W) vs Random(B): W=97%
  - Random(W) vs Casual(B): B=96%
Casual-vs-Casual vanilla balanced, ~5-30ms/move. All 54 tests still pass.

Refresh .secrets.baseline (stale) to allow new pnpm-lock.yaml hashes.
2026-04-28 15:14:12 -04:00
claude (blind_chess) dc5e6678b9 feat(bot): self-play harness with Casual and random baselines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 14:52:10 -04:00
claude (blind_chess) 06bd144f7c feat(client): AI badge and bot-moving turn indicator
Track aiOpponent in game store; show a pill badge in the topbar for AI
games, update turn label to "<Brain> is moving…" on the bot's turn,
and suppress the disconnected-opponent banner when the opponent is a bot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 14:26:25 -04:00
claude (blind_chess) 31f68db654 feat(client): two-section landing — friend vs Casual bot 2026-04-28 14:24:06 -04:00
claude (blind_chess) cb8e017792 fix(bot): wire aiOpponent into joined and update server messages 2026-04-28 14:22:41 -04:00
claude (blind_chess) 73d5d0cb93 test(bot): integration tests for Casual vs human 2026-04-28 14:21:27 -04:00
claude (blind_chess) 88bc23b0d0 fix(bot): harden ws.ts integration seam
- maybeAbandon Promise no longer floats from setTimeout
- broadcastSinceLast loses dead extra parameter
- bot-slot token is randomized so a third party can't hijack the
  bot's color by guessing a fixed placeholder
2026-04-28 14:17:46 -04:00
claude (blind_chess) a9660c0694 feat(bot): pokeBot + broadcastSinceLast hooks into ws.ts handlers
Replace broadcastNewAnnouncements/broadcastUpdate with watermark-based
broadcastSinceLast; add pokeBot helper; make all state-mutating handlers
async; hook pokeBot after every mutation so the CasualBrain fires on
each turn without oracle access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 14:13:24 -04:00
claude (blind_chess) 58e1fc5bd8 feat(bot): POST /api/games instantiates CasualBrain + BotDriver 2026-04-28 14:10:19 -04:00
claude (blind_chess) 9a837ec319 feat(bot): vsAi/aiOpponent protocol fields and bot-driver registry 2026-04-28 14:07:01 -04:00
claude (blind_chess) 4407110147 fix(bot): finalize game on bot checkmate; harden driver dispatch
Extract endGame/finalizeIfEnded to game-end.ts so driver.ts can call
finalizeIfEnded after an applied move (fix: bot checkmate was not
setting game.status='finished'). Wrap entire dispatch() call in
try/catch for exception safety. Move lastSeenAnnouncementCount advance
to after successful dispatch so retry attempts see FSM rejection
announcements. Add checkmate-finalize test; lock retry-cap at 5 calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 14:04:22 -04:00
claude (blind_chess) 3798b9c00d feat(bot): BotDriver with mutex, retry cap, and dispatch
Wires Brain to Game: init/onStateChange/dispose lifecycle, in-flight
mutex, 5-attempt retry loop with attemptHistory, resign-on-cap. Also
adds Game.aiOpponent? field to state.ts for Task 5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 13:56:28 -04:00
claude (blind_chess) ebd1463b0a docs(bot): clarify when scoreMove early-return fires 2026-04-28 13:52:22 -04:00
claude (blind_chess) aa7bc30ee1 feat(bot): CasualBrain with capture/development/center heuristics 2026-04-28 13:48:34 -04:00
claude (blind_chess) f48e0a9cdf feat(bot): legalCandidates for vanilla and blind modes 2026-04-28 13:42:37 -04:00
claude (blind_chess) bc954f4748 feat(bot): scaffold Brain interface and types 2026-04-28 13:38:16 -04:00
claude (blind_chess) 6d457a2321 docs(plan): defer in-game chat, add Phase 1 (Casual) implementation plan
- DECISIONS.md: in-game chat (player↔player and human↔Gemma) deferred
  indefinitely. Blind-mode chat is a side channel that defeats the
  moderator-vocabulary security boundary; chat with Gemma leaks belief
  state mid-game. Resolvable but expensive — revisit only on demand.
- Spec: same deferral noted in "Out of scope".
- New plan: docs/superpowers/plans/2026-04-28-ai-player-phase-1-casual.md
  — 13 tasks, 80 sub-steps. Phase 1 only (Casual bot end-to-end). Phase 2
  (Recon) gets its own plan once Phase 1 outcomes inform Recon's target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 13:31:12 -04:00
claude (blind_chess) 729199097e docs: log AI-player spec approval, update context, add handoff
Updates CLAUDE.md "Current State" + "Key files" to point at the new spec.
Adds DECISIONS.md "AI / computer player" section (11 settled decisions).
Strikes through the prior "Client-side AI / hint generation — out of scope"
row with a "partially superseded" note: the reversal applies only to the
human-vs-AI path. Adds 7 new Deferred/Rejected rows for AI-feature scope.

Handoff at .claude/handoffs/2026-04-28-170713-ai-player-spec.md captures
session state for the next pickup (writing-plans → Phase 1 implementation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 13:12:04 -04:00
claude (blind_chess) 288693fcd6 docs(spec): add AI/computer player design spec
Two-phase plan: Casual bot first (algorithmic, ~200 LoC), then Recon bot
(gemma4:26b chat agent) with persistent private chat history per game.

Bots play through the same view filter and FSM as humans — no oracle access.
Endpoint priority: steel141 RTX 3090 Ti primary, pve197 V100 fallback. Mid-game
GPU failover allowed (one-way). Reasoning hidden from user during play, revealed
in collapsible post-game panel.

Reverses the 2026-04-28 DECISIONS.md row "Client-side AI / hint generation —
explicitly out of scope." Reversal is partial: human-vs-human hint generation
remains rejected; this spec only adds AI in the human-vs-AI path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 12:37:48 -04:00