houserules/packages/chess/e2e
Joey Yakimowich-Payne a2c38a9ad2
feat(thressgame-100): Wave 5 \u2014 economy + score chips + 3 recipes + e2e
Wave 5 of thressgame-100 epic complete \u2014 paradigm-break wave per oracle.
User-authorized despite the cost. Coverage: 47/51 \u2192 50/51 = 98 %.

PARADIGM-BREAK CONTEXT (oracle pre-flagged):
Oracle classified Wave 5 as 'paradigm break \u2014 game-level mutable state'. User
explicitly authorized via 'no time/cost limit, no backward-compat constraint,
just fucking get it all done'. Design choice: scores live alongside RngStream
on GAME_ENTITY \u2014 same pattern as existing global state (RngStream,
ChoiceTimeoutPolicy, BoardTopology, BlockAllExceptKing), not new architecture.

ENGINE WORK (W5.0\u2013W5.4):

Score subsystem (locked decision F):
- WhiteScore + BlackScore attrs on GAME_ENTITY \u2014 type 'number', default 0
- Initialized at engine construction (engine.ts:632\u2013634) so fact-log inclusion
  is mechanical \u2014 state-hash auto-includes scores via existing infrastructure
- N=100 byte-identical replay determinism verified

3 NEW PRIMITIVES:
- add-resource(player, amount)         imperative; synchronous fire of
                                        on-resource-changed hooks
- spend-resource(player, amount,       imperative; selfRecurse:true \u2014 only the
                  then, else)           chosen arm runs (walker doesn't auto-recurse
                                        into both); cascade-depth-limited (8)
- on-resource-changed(player,          trigger; fires on threshold crossing in
                       threshold,       direction up/down/any; snapshot-before-iterate
                       direction,       so hook arm registering more hooks doesn't
                       primitives)      fire on the same crossing

WS PROTOCOL (W5.5) \u2014 Scenario A chosen: ZERO protocol changes:
- broadcast.ts uses session.allFacts() which iterates ALL facts on ALL entities
  including GAME_ENTITY
- WhiteScore/BlackScore mutations land in game.state and game.delta frames
  automatically; client PredictionManager receives them out-of-the-box
- Zod FactSchema has untyped .unknown() value field on the wire \u2014 number values
  serialize implicitly

UI WORK (W5.6) \u2014 GameView.tsx score chips:
- Two chips: data-testid='score-white' (\u26aa W) and data-testid='score-black'
  (\u26ab B), bordered chip styling matching ActionMenu sibling vocabulary
- HIDE-WHEN-ZERO rule: chips hidden unless either score !== 0 OR
  OnResourceChangedHooks contains entries (pure-chess games visually unchanged)
- Real-time updates via existing useMultiplayerGame hook \u2014 broadcasts trigger
  re-render, scores update without page reload

3 NEW RECIPES (W5.7):

Batch M \u2014 economy:
- tpl-treasure-chest        \u2014 capture spawns treasure marker; entering treasure
                              awards white +5 score and consumes marker
                              (SIMPLIFIED: arm 2 hardcodes player='white' \u2014
                              no chooser-color resolver; LastModifierChooser
                              semantics = rule applier not current mover)
- tpl-cash-grab             \u2014 every turn-end, white+1 + black+1 (passive income)
                              (SIMPLIFIED: V3 has no eq shape \u2014 random-pick result
                              can't be compared against piece Position; ship the
                              dual-add-resource pattern instead of canonical
                              random-square-rewards-piece-owner semantics)
- tpl-summoning-ritual      \u2014 spend 5 score to summon white knight at e4
                              (SIMPLIFIED: single-shot at activation; repeatable
                              summoning needs request-choice loop \u2014 W6+ scope)

TEST SURFACE:
- add-resource.test.ts:                             ~10 unit tests
- spend-resource.test.ts:                           ~12 unit tests
- on-resource-changed.test.ts:                      ~15 unit tests
- economy-integration.test.ts:                       8 integration tests
                                                    (incl. N=100 determinism)
- wave5-recipes-real.test.ts:                       14 runtime tests
- recipes.test.ts:                                   5 \u00d7 62 = 513 expect calls
- wave5-economy.spec.ts (Playwright):                5 e2e tests
                                                    (3 load + summoning-ritual
                                                    success-path runtime +
                                                    cash-grab turn-end driven)

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

KEY FINDINGS (recorded in learnings.md):
- spend-resource selfRecurse:true correctly gates walker auto-recursion \u2014
  place-piece inside 'then' only runs when spend succeeds
- ctx-attr.entity:'self' resolves cleanly to ctx.pieceId in resolver
- fireOnTurnEndHooks (and other per-piece dispatchers) iterate pieces \u2014 hooks
  on GAME_ENTITY are dead-seeded; apply-descriptor needs targetSquare for any
  recipe rooted at a per-piece trigger
- Snapshot-before-iterate in fireOnResourceChangedHooks prevents reentrant
  cascade fires on the same crossing

BACKWARD-INCOMPAT TESTS UPDATED (per locked decision J):
- registry-count.test.ts: 56 \u2192 59 (3 new primitives)
- ParamField.snapshot.test.tsx: SAMPLE_PARAMS exhaustiveness for 3 new kinds
- GameView snapshot: regenerated for new score-chip section

Plan: .sisyphus/plans/thressgame-100.md
Notepads: .sisyphus/notepads/thressgame-100/
Evidence: .sisyphus/evidence/thressgame-100-wave5.txt (gitignored, 1074 lines)
2026-04-27 17:22:55 -06:00
..
choice-kinds.spec.ts feat(thressgame-coverage): Waves 16-18 (Playwright e2e for choice-kinds + move-gen attrs + orphan primitives) 2026-04-26 18:43:01 -06:00
custom-modifier-visual.spec.ts test(chess/e2e): expand visual-builder Playwright suite to 11 scenarios 2026-04-21 19:47:21 -06:00
custom-modifiers.spec.ts feat(ui): AttrCombobox declare vs. consume semantics 2026-04-21 13:07:27 -06:00
full-flow.spec.d.ts Preset refactors for HP 2026-04-17 18:59:11 -06:00
full-flow.spec.d.ts.map Preset refactors for HP 2026-04-17 18:59:11 -06:00
full-flow.spec.js Preset refactors for HP 2026-04-17 18:59:11 -06:00
full-flow.spec.js.map Preset refactors for HP 2026-04-17 18:59:11 -06:00
full-flow.spec.ts test(chess): e2e full-flow scenario; tag Phase 3 (P3.15) 2026-04-16 16:55:28 -06:00
layouts.spec.ts feat(layouts): toggle-delete when clicking same brush-piece 2026-04-21 13:32:46 -06:00
modifier-profiles.spec.ts feat(ui): hide modifier tooltip on pieces with no active modifiers 2026-04-19 16:58:04 -06:00
move-gen-attrs.spec.ts feat(thressgame-coverage): Wave 19 (close 6 production gaps, lift all fixmes) 2026-04-26 21:39:13 -06:00
multiplayer.spec.ts feat(multiplayer): game.action WS message for PlayerActions 2026-04-21 11:56:35 -06:00
orphan-primitives.spec.ts feat(thressgame-coverage): Wave 19 (close 6 production gaps, lift all fixmes) 2026-04-26 21:39:13 -06:00
parity-religious.spec.ts feat(thressgame-coverage): Wave 15 (e2e for 5 parity rules + lift T68/3 parry) 2026-04-26 18:06:05 -06:00
parity-rules.spec.ts feat(thressgame-coverage): Wave 15 (e2e for 5 parity rules + lift T68/3 parry) 2026-04-26 18:06:05 -06:00
profile-layout-binding.spec.ts feat(modifiers): bundle preset activations on profiles + editor Presets tab 2026-04-21 14:03:52 -06:00
request-choice.spec.ts feat(thressgame-coverage): Wave 15 (e2e for 5 parity rules + lift T68/3 parry) 2026-04-26 18:06:05 -06:00
rule-variants.spec.ts feat(ui): Actions menu + royalty-transfer target selection in GameView 2026-04-21 12:04:55 -06:00
solo-smoke.spec.ts fix(lobby): clear stale MP creds on Play Solo to avoid blank-screen trap 2026-04-19 17:03:21 -06:00
templates-thressgame.spec.ts feat(thressgame-templates): V2 validator + 9 new recipes + Playwright e2e 2026-04-27 13:47:01 -06:00
wave1-recipes.spec.ts feat(thressgame-100): Wave 1 complete \u2014 13 recipes + e2e + real-tests 2026-04-27 14:25:51 -06:00
wave2-countdowns.spec.ts feat(thressgame-100): Wave 2 \u2014 multi-turn state + 10 countdown recipes + e2e 2026-04-27 15:22:18 -06:00
wave3-choices.spec.ts feat(thressgame-100): Wave 3 \u2014 player-choice patterns + 8 recipes + e2e 2026-04-27 15:43:26 -06:00
wave4-topology-pairing.spec.ts feat(thressgame-100): Wave 4 \u2014 topology + piece-pairing + 5 recipes + e2e 2026-04-27 16:34:01 -06:00
wave5-economy.spec.ts feat(thressgame-100): Wave 5 \u2014 economy + score chips + 3 recipes + e2e 2026-04-27 17:22:55 -06:00