feat(thressgame-100): Wave 4 \u2014 topology + piece-pairing + 5 recipes + e2e

Wave 4 of thressgame-100 epic complete \u2014 highest-risk wave (move-gen
topology) survived with 0 regression. Coverage: 44/51 \u2192 47/51 = 92 %.

ENGINE WORK (W4.0\u2013W4.7):

Topology subsystem (locked decision G \u2014 opt-in via BoardTopology attr):
- New attr: BoardTopology on GAME_ENTITY \u2014 'standard' | 'wrap-files' | 'wrap-all'
- New helper: util/topology.ts \u2014 wrapSquare(rawCol, rawRow, topology) +
  readBoardTopology(session)
- coord.ts threads optional topology through slidingSquares / knightSquares /
  kingSquares (default 'standard' preserves all pre-W4 output bit-identical)
- rules/primitives.ts threads topology through rookCandidates, bishopCandidates,
  queenCandidates, knightCandidates, kingCandidates, pawn{Single,Double}Advance,
  pawnCaptureSqares
- rules/{sliding,knight,king,pawn}.ts read topology via readBoardTopology and
  thread through candidate generators
- presets/core-piece-types.ts pawn attackProbe reads topology
- New imperative: set-board-topology(value) \u2014 modifies the GAME_ENTITY attr

Pairing subsystem (locked decision E):
- New attrs: PieceLink (per-piece, EntityId[]), OnPiecePairLinkBrokenHooks
  (game-level)
- New imperative: link-pieces(a, b) \u2014 symmetric (adds B to A's links AND
  A to B's links); self-link no-op
- New imperative: unlink-pieces(a, b) \u2014 symmetric removal
- New trigger: on-piece-pair-link-broken(target, primitives) \u2014 fires when
  a linked partner is destroyed; introduces 'linkedPieceId' binding
- Cascade integration: engine.ts:dealDamage and destroy-piece.ts both fire
  the on-piece-pair-link-broken cascade when a linked piece is destroyed.
  PieceLink + the partner's link entry are auto-unlinked BEFORE the trigger
  fires (prevents reentrant infinite loop \u2014 the cascade-loop fix).

Two critical bugs found and fixed (full walkthrough in evidence file):
1. CASCADE LOOP: original draft fired hooks BEFORE auto-unlinking; reentrant
   destroy-piece(survivor) re-fired the hook on original \u2192 infinite loop.
   Fix: clear dying piece's PieceLink AND prune partners' lists FIRST, then
   fire hooks. Pinned by cascade test in on-piece-pair-link-broken.test.ts.
2. TOROIDAL CYCLE: wrap-all sliding rays cycle every 8 squares (queen on a1
   loops back to a1 going horizontally). Added 7-cell cycle guard in
   slidingSquares matching BASE_RANGE; king's extended-reach mode uses a
   Set<Square> per ray to detect re-visits.

5 NEW RECIPES (W4.8\u2013W4.9):

Batch K \u2014 topology:
- tpl-pacman-style-cross-ref \u2014 modifier-shaped equivalent of wrap-board preset;
                                cross-references RULES.md#wrap-board in summary
- tpl-bouncing-ricochet      \u2014 first multi-armed recipe in codebase: activation
                                seeds wrap-files, expire arm reverts to standard

Batch L \u2014 pairing:
- tpl-down-with-the-ship     \u2014 white king linked to all white rooks; killing
                                the king cascade-destroys both rooks
- tpl-soul-link              \u2014 cross-color knight linking; either dies, both
                                die (SIMPLIFIED: link ALL white knights to ALL
                                black knights since recipe library has no
                                per-piece pick mechanism in activation arms)
- tpl-hot-drop               \u2014 spawn 2 white queens at e4+d4, link them;
                                killing one cascade-destroys the other
                                (SIMPLIFIED: hardcoded queens at d4/e4 \u2014
                                place-piece pieceType/color/square strict)

TEST SURFACE:
- util/topology.test.ts:                     14 unit tests
- rules/topology.test.ts:                    30 boundary tests across 6 piece
                                              types \u00d7 3 topologies
- set-board-topology.test.ts:                 9 unit tests
- link-pieces.test.ts:                        9 unit tests
- unlink-pieces.test.ts:                      8 unit tests
- on-piece-pair-link-broken.test.ts:         11 unit tests
- wave4-recipes-real.test.ts:                22 runtime tests
- wave4-topology-pairing.spec.ts e2e:         7 tests (5 load + 2 runtime,
                                              including PredictionManager attr
                                              probe + DOM piece visibility for
                                              the linked-queens spawn)

bun run check: 3192 tests pass (was 3081, +111). 0 regressions.
e2e: 7/7 green via .sisyphus/scripts/run-pw.sh against docker compose dev stack.

BACKWARD-INCOMPAT TESTS UPDATED (per locked decision J):
- registry-count.test.ts: 52 \u2192 56 (4 new primitives)
- ParamField.snapshot.test.tsx: SAMPLE_PARAMS exhaustiveness for 4 new kinds
- wave3-recipes-real.test.ts: count assertion widened toBe \u2192 toBeGreaterThanOrEqual

Plan: .sisyphus/plans/thressgame-100.md
Notepads: .sisyphus/notepads/thressgame-100/
Evidence: .sisyphus/evidence/thressgame-100-wave4.txt (gitignored, 1076 lines)
This commit is contained in:
Joey Yakimowich-Payne 2026-04-27 16:34:01 -06:00
commit 3a4bd394aa
No known key found for this signature in database
35 changed files with 3733 additions and 64 deletions

View file

@ -138,7 +138,11 @@
"ses_22f358609ffekrF7uFjsJEyxu6",
"ses_22f2b6cbdffe8YcUpThdI6JHfM",
"ses_22f21845affeRgoo73nIvI1GUa",
"ses_22f20fba6ffe5fJyOi7HJg8CZQ"
"ses_22f20fba6ffe5fJyOi7HJg8CZQ",
"ses_22f1823c1ffek8p7La3fBMqslC",
"ses_22efe07c7ffe1eTZ6kvrkGLVYl",
"ses_22ef4380affeXjumRiXI4u1KSK",
"ses_22ef3b48bffeJQKH6xj5U4fuTi"
],
"plan_name": "thressgame-coverage",
"agent": "atlas"