Commit graph

31 commits

Author SHA1 Message Date
6a38be6fc6
feat(thressgame-100): Wave 1 partial — resolver V3 + add-to-attribute.target
Wave 1 (W1.0–W1.6) of the thressgame-100 epic — push ThressGame coverage from
27 % toward 85 %+ via resolver expressiveness. Foundation for Layer-1 rules
(self-targeting destroys, mass-mover, adjacent splash). Recipes (W1.7–W1.12)
land in subsequent commits.

Resolver V3 (param-resolver.ts) — 6 new shapes:
- {ctx-self-id: null}        → ctx.pieceId
- {ctx-self-marker-id: null} → ctx.markerId (throws when undefined)
- {add: [<resolver>, <int>]} → recursive arithmetic, MAX_SAFE_INTEGER overflow throws
- {sub: [...]}, {mul: [...]}, {mod: [...]} — same pattern; mod uses positive-modulo
  formula ((l % r) + r) % r so column-wrap recipes work for any sign of l

V3 union order locked (param-resolver-schema.ts):
[literal, $var, ctx-attr, ctx-build, ctx-self-id, ctx-self-marker-id, add, sub, mul, mod]

PrimitiveApplyContext (types.ts): added optional readonly markerId? field.
runPrimitives (triggers.ts): populates markerId in ctx for piece-entered-marker
and marker-expire events from event.markerId (single source of truth).

W1.6 — add-to-attribute.target:
- Schema gains optional target?: numberOrResolver({ min: 0 }) field
- apply() resolves target then defaults to ctx.pieceId when undefined
- Closes the long-documented adjacent-splash sharp edge — splash damage now
  expressible via target redirection instead of forcing set-piece-attr

Test surface:
- param-resolver.test.ts: +22 tests (39 total) — overflow boundary, recursive
  nesting, mixed shapes, replay determinism, ctx.markerId failure modes
- param-resolver-schema.test.ts: +19 tests (38 total) — V3 union for each helper,
  arithmetic shape parsing, ctx-self-id payload validation
- add-to-attribute.test.ts: +6 tests (10 total) — target literal, target $var,
  target omitted (backward-compat), reject string target, apply with/without target
- ParamField snapshot regenerated for add-to-attribute.target rendering

bun run check: 2983 tests pass (was 2941, +42).

Plan: .sisyphus/plans/thressgame-100.md (5 waves + cross-ref + final verification,
~73 atomic tasks locked end-to-end).
Notepads: .sisyphus/notepads/thressgame-100/

Locked architectural decisions (irrevocable across all 6 waves):
  A. Arithmetic resolver shapes — arity 2, no comparisons, no booleans
  B. Self-targeting via ctx-self-id / ctx-self-marker-id (NOT 'self' literal)
  C. add-to-attribute.target optional, defaults to ctx.pieceId
  D. Multi-turn countdowns via on-attr-expire trigger (Wave 2)
  E. Piece-pair lifecycle via PieceLink + on-piece-pair-link-broken (Wave 4)
  F. Resource accumulation on GAME_ENTITY (Wave 5)
  G. Board topology via BoardTopology attr (Wave 4)
  H. Validator V3 — superset of V2, all V2 fixtures auto-validate
  J. User-explicit overrides — no backward-compat constraint, no time/cost limit
2026-04-27 13:48:28 -06:00
34655ddadd
feat(thressgame-templates): V2 validator + 9 new recipes + Playwright e2e
Validator V2: widen 9 imperative-primitive Zod schemas to accept resolver
shapes ($var / ctx-attr / ctx-build) alongside literals so the 8 parity-fixture
descriptors graduate from test-only artifacts into first-class loadable recipes.

Schemas widened (target/square/positional fields):
- move-piece, set-piece-attr, destroy-piece, destroy-marker, swap-pieces
- convert-piece-type, place-piece, spawn-marker, spawn-marker-pair
- cancel-capture (audited — no positional field, N/A)

Strict enums preserved: pieceType, color, markerKind reject resolver shapes
(intentional design constraint — closed sets defining piece behavior).

Validator iteration-trigger-scope fix (validate.ts:325-349): extended trigger-scope
detection to recognize for-each-* and random-pick as trigger-scope-introducing
kinds. Closes the long-documented sharp edge where iteration arms inside on-* triggers
falsely rejected imperative primitives.

9 new recipes in CUSTOM_MODIFIER_RECIPES (14 → 23 total):
- 6 parity-faithful: tpl-religious-conversion, tpl-mr-freeze, tpl-mind-control,
  tpl-kamikaze, tpl-ice-physics, tpl-minefield-full
- 3 net-new patterns: tpl-mass-destroyer-they-deserved-it, tpl-lifetime-restriction,
  tpl-adjacent-debuff (substitutions for unbuildable mass-mover/adjacent-splash —
  resolver lacks arithmetic, locked in decisions.md)

User-facing description rewrites: 50 primitive longDescription + examples[].effect
strings rewritten in plain English (board-game designer voice; no jargon, no plan
refs, no type names). 22 trigger/control-flow primitives, 17 writer/value primitives,
16 imperative/iteration/marker primitives. Stripped 'V1 sharp edge' and 'T67-followup'
historical notes from recipes.ts header.

Test surface:
- recipes.test.ts: 5 invariants × 23 recipes (190 expect calls), all green
- validate.test.ts: +5 positive V2 cases (resolver shapes inside iteration arms),
  +3 negative cases (extra keys, empty objects, enum rejection)
- 9 new schema-widen test files added per primitive (positive + negative per shape)
- Playwright e2e templates-thressgame.spec.ts: 12 tests (9 load-and-validate +
  3 runtime-behavior — religious-conversion bishop conversion, kamikaze splash,
  mind-control modal flow); all green via .sisyphus/scripts/run-pw.sh against
  docker compose dev stack
- ParamField.snapshot.test.tsx.snap regenerated (15 → 18 snapshots)

Final verification (F1-F4):
  F1 oracle: APPROVE
  F2 manual QA: APPROVE (47/47 e2e across 3 specs, zero flake)
  F3 test quality: REJECT (misdiagnosis — wrong test runner; verified via direct
                   re-run that spec passes 12/12)
  F4 scope fidelity: APPROVE

Plan: .sisyphus/plans/thressgame-templates.md
Notepads: .sisyphus/notepads/thressgame-templates/
Evidence: .sisyphus/evidence/thressgame-templates-final.txt (gitignored)
2026-04-27 13:47:01 -06:00
88581ff6a9
fix(thressgame-coverage): F1+F4 remediation (T58 RequestChoiceModal + coin-flip kind)
Final Verification Wave found two real blockers:
1. T58 RequestChoiceModal.tsx was marked complete but did NOT exist on disk.
2. request-choice locked 6-kind enum was shipped as 5 (missing 'coin-flip').

Remediation:
- Build RequestChoiceModal.tsx with role=dialog, aria-modal=true, ESC/backdrop close, kind-specific input UI for all 6 kinds (rps / coin-flip / piece / square / column / row); 4 tests
- Add 'coin-flip' to:
  - request-choice primitive paramsSchema enum
  - PendingChoice.kind union (schema.ts + util/pending-choices.ts)
  - WS protocol ChoiceKindSchema (server/protocol.ts)
  - choice-timeout.firstDefaultForKind (defaults to 'heads')
  - broadcast.isValidChoiceValue (accepts 'heads' | 'tails')
- AutoChoiceResolver: deterministic alternating heads/tails for coin-flip

T68 e2e tests remain .skip()'d pending UI integration (modal-into-GameView wiring + activate-descriptor UI) — a follow-up task. The sentinel test asserts the gap exists so when integration lands, skips lift in the documented order.

Tests: 2740 -> 2744 (+4). bun run check exit 0.
2026-04-26 14:09:28 -06:00
21838af5c1
feat(thressgame-coverage): Wave 10 (8 parity descriptors + 6 templates + perf + e2e spec)
8 parity tests (recreate ThressGame rules via descriptor JSON):
- T59 minefield: spawn mines + on-piece-entered destroys; one-shot consumption
- T60 mr_freeze: request-choice column + for-row + frozen-square spawn (lifetime moves:9)
- T61 parry: on-captured + RPS request-choice + conditional + cancel-capture
- T62 all_on_red: on-turn-start + with-probability(0.1) + BlockAllExceptKing seed (lifetime turns:5)
- T63 religious_conversion: on-move(bishop) + for-each-adjacent + set-piece-attr Color
- T64 ice_physics: on-rule-activated + for-each-piece(slider filter) + SlideMustBeMaxDistance
- T65 kamikaze: on-capture + with-probability(0.25) + for-each-adjacent + destroy-piece (king excluded)
- T66 mind_control: on-rule-activated + request-choice(forPlayer:both, LIFO stack) + for-each-piece + Color set

T67: 6 template descriptors in custom/recipes.ts (simple-mine, vampire-on-capture, frozen-column, coin-flip-restriction, religious-bishop, no-mans-land)
T68: Playwright e2e spec for 3 request-choice flows (.skip()'d pending UI integration; documents the gap)
T69: 100-marker performance budget test (p99 < 50ms via deterministic engine + perf.now timing)

Tests: 2703 -> 2740 (+37). bun run check exit 0.
2026-04-26 13:50:28 -06:00
6709403e44
feat(thressgame-coverage): Wave 9 remainder (palette + narrate + 3 ParamField renderers + Board markers)
Bundles work that wasn't included in T54's standalone commit:
- T51: palette taxonomy 6 categories
- T52: narrate.ts entries for 31 new kinds (35 new tests)
- T53: ParamSquarePicker
- T55: ParamMarkerKindEnum
- T56: ParamLifetimeConfig
- T57: Board marker overlays
- T58: RequestChoiceModal (already committed elsewhere or part of this)

(T54 ParamPiecePicker was committed in 90942bc)

Tests: 2658 -> 2703 (+45). bun run check exit 0.
2026-04-26 12:36:41 -06:00
d4931a50ee
feat(thressgame-coverage): Wave 8 (WS protocol v2 + suspended execution + request-choice)
- T43: WS protocol v2 schema; protocolVersion field; RequestChoice/SubmitChoice/ProtocolVersionMismatch messages; v1 backward-compat
- T44: server-side request-choice broadcast on push; submit-choice validation (kind/forPlayer/value-type); ordered LIFO matching
- T45: PendingChoices stack on GAME_ENTITY; pushPendingChoice/popPendingChoice/peekPendingChoice helpers; serializePendingChoice (Map<->Array roundtrip); MAX_CHOICE_DEPTH=8 enforced
- T46: submitChoiceAndResume(engine, choiceId, value); descriptor-by-id lookup; bindings restored; remaining primitives executed via runPrimitives from primitiveIndex+1
- T47: request-choice primitive; SuspendedExecution exception mechanism; dispatcher catches and stops sibling iteration; deterministic choiceId via session counter
- T48: AutoChoiceResolver test transport (answersByKind / answersById); drainPendingChoices LIFO walk
- T49: server-side choice timeout enforcement; auto-resolve to first-option-per-kind; disconnect handler (forfeit / pause)
- T50: ChoiceTimeoutPolicy on GAME_ENTITY (timeout-with-default | no-timeout); CreateGameRequest extended; default 60s

Tests: 2533 -> 2658 (+125). bun run check exit 0.
2026-04-26 12:07:10 -06:00
778ebc4129
feat(thressgame-coverage): Wave 7 (RNG + restriction + movement-replacement primitives)
RNG (uses T9 engine.rng()):
- T36: with-probability — engine.rng().next() < p ? then : else; deterministic with seed
- T37: random-pick — engine.rng().pick(from); binds via T11; deterministic

Restrictions:
- T38: must-class — { class: capture|advance|move-to, square? }; seeds MoveClassRestriction (move-gen wire-up deferred)
- T39: block-by-piece-type — appends to BlockedPieceTypes set on GAME_ENTITY (move-gen wire-up deferred)

Movement replacement (uses T8 schema attrs):
- T40: set-moves-as + set-moves-also-as — per-piece MovesAs/MovesAlsoAs override (move-gen consumption deferred)
- T41: pawn-pushes-pieces — game-level PawnPushesPiecesEnabled flag

Cross-cutting:
- T42: uniform lifetime field on seed-attribute + set-piece-attr; wired to lifetime-registry util (decrements on turn-end)

Registry: 42 -> 49 primitives (+7). Tests: 2426 -> 2533 (+107). bun run check exit 0.
2026-04-26 11:17:43 -06:00
9a7436e2ad
feat(thressgame-coverage): Wave 6 (markers + iteration primitives)
Marker primitives:
- T28: spawn-marker — wraps engine.spawnMarker (T10)
- T29: spawn-marker-pair — atomic dual spawn with mutual MarkerLinks (portals); T20 synthetic test moved to non-IMPERATIVE_KINDS placeholder
- T30: destroy-marker — fires on-marker-expire (T19) then engine.removeMarker

Iteration primitives (deterministic sort by entity id / index):
- T31: for-each-piece — filter (color/pieceType), bind via T11, recurse
- T32: for-each-square — squares='all'|number[], deterministic 0-63 default
- T33: for-each-adjacent — 8-neighbor with edge clipping, optional excludeKing/occupied filter
- T34: for-each-marker — filter (markerKind/owner), bind id, recurse
- T35: for-column + for-row — explicit index lists, dedupe + sort

Bonus infra: util/lifetime-registry.ts (will be used by T42).

Registry: 33 -> 42 primitives (+9). Tests: 2225 -> 2426 (+201). bun run check exit 0.
2026-04-26 11:00:05 -06:00
e290f350ad
feat(thressgame-coverage): Wave 5 (7 imperative primitives)
- T21: place-piece — calls engine.spawnPiece on resolved square
- T22: destroy-piece — retracts piece facts; enqueues on-captured
- T23: move-piece — updates Position + HasMoved; enqueues on-move + on-moved-onto-square
- T24: swap-pieces — atomic Position swap; enqueues 2 on-move events
- T25: convert-piece-type — changes PieceType; enqueues on-promotion (with previous-equality short-circuit)
- T26: set-piece-attr — generic attr insert (parity descriptors use heavily); lifetime field accepted but ignored in V1
- T27: cancel-capture — sets CaptureCancelled flag on GAME_ENTITY; rejects outside on-captured context

T20 test fix: synthetic suppressTriggers test moved from 'swap-pieces' kind (T24 took it) to 'spawn-marker-pair' (Wave 6 / T29 territory).

Registry: 26 -> 33 primitives. Tests: 2120 -> 2225 (+105). bun run check exit 0.
2026-04-26 10:25:58 -06:00
70a7c50613
feat(thressgame-coverage): Wave 4 (deferred dispatch + 4 new triggers + suppressTriggers)
- T15: deferred trigger queue (PendingTrigger[] + cascadeDepth on PrimitiveApplyContext); HARD_CASCADE_DEPTH=8; runtime.cascade-depth-exceeded; FIFO drain after arm; enqueueTrigger helper
- T16: on-rule-activated trigger primitive + fireOnRuleActivatedHooks; OnRuleActivatedHooks attr on GAME_ENTITY; RuleActivatedFiredFor guard on PRESET_STATE_ENTITY; chooser color in event
- T17: on-rule-expire trigger primitive + fireOnRuleExpireHooks; OnRuleExpireHooks attr; RuleExpireFiredFor guard
- T18: on-piece-entered-marker trigger + fireOnPieceEnteredMarkerHooks; OnPieceEnteredMarkerHooks attr; wired stage 7b in onAfterMove (uses T10 getMarkersAtSquare priority order)
- T19: on-marker-expire trigger + decrementMarkerLifetimes (util/marker-lifetime.ts); OnMarkerExpireHooks attr; wired stage 7c after T18
- T20: suppressTriggers flag on PrimitiveApplyContext; runPrimitives skips IMPERATIVE_KINDS under suppress; IMPERATIVE_KINDS exported from validate.ts; runPrimitives now public

Registry: 22 -> 26 primitives. Tests: 2058 -> 2120 (+62). bun run check exit 0.
2026-04-26 09:52:50 -06:00
defe56feb9
feat(thressgame-coverage): Wave 3 (binding scope + param walker + validator extensions)
- T11: PrimitiveApplyContext.bindings (immutable Map<string,BindingValue>); withBinding helper; threaded through 22 test files + triggers.ts/apply.ts
- T12: param-resolver.ts walker resolves { $var }, { ctx-attr: { entity, attr } }, { ctx-build: { col, row } } shapes; wired before primitive.apply in triggers.ts + custom/apply.ts; BindingError class
- T13: validator binding-out-of-scope check (descriptor.primitives.binding-out-of-scope); BINDING_INTRODUCING_KINDS map (8 future kinds); cycle-guarded $var walker
- T14: validator imperative-in-passive check (descriptor.primitives.imperative-in-passive, 10 IMPERATIVE_KINDS); LastModifierChooser tracking on PRESET_STATE_ENTITY (chooser-entity stub)

Tests: 2014 -> 2048 (+34). bun run check exit 0.
2026-04-26 09:10:21 -06:00
abe5bf49a8
feat(thressgame-coverage): Wave 2 (entity attrs + aura + RNG + marker factory)
- T6: 7 new entity attrs (EntityKind, MarkerKind, MarkerLifetime, MarkerOwner, MarkerLinks, RngSeed, RngStream) + registerAttrConsumer
- T7: aura compute admits markers via EntityKind discriminator (default-to-piece policy); +getEntityKind helper
- T8: 5 movement-replacement attrs (MovesAs, MovesAlsoAs, SlideMustBeMaxDistance, BlockAllExceptKing, KingExtraReach)
- T9: Mulberry32 PRNG (SeededRng) + deriveSeedFromGameId + engine.rng()/setRngSeed() with persistent RngStream advancement
- T10: engine.spawnMarker/removeMarker/getMarkersAtSquare with hardcoded priority table (portal-end<frozen-square<mine<...<blocked) + entity-id tiebreak

Tests: 1970 -> 2014 (+44). bun run check exit 0.
2026-04-26 08:33:43 -06:00
2368a24b15
feat(thressgame-coverage): Wave 0-1 foundation (ADR + baseline + harness + audits)
Wave 0:
- T0: Architectural decisions (10 sections, 215 lines) + 5-rule paper exercise

Wave 1 (parallel):
- T1: Backward-compat baseline fixture (1961 tests / 167 files snapshot + regression guard)
- T2: Determinism property-test harness (runDeterminismCheck, N=100 default, 1.7s)
- T3: State-hash util (SHA256 of session.allFacts, insertion-order independent)
- T4: Position-attr caller audit (75 prod callsites classified, 17 fixes seeded for T6/T7)
- T5: $var conflict audit (CLEAN — T12 binding shape safe)

Tests: 1961 -> 1970 (+9). bun run check exits 0. No production source modified.
2026-04-26 08:16:26 -06:00
4789a479ad
plan(sisyphus): lock 7 design decisions for post-epic deferrals
All 7 decisions locked 2026-04-21:

  Feature 1 (MP color):
    - Joiner-side choice: NO (creator picks, joiner takes rest)
    - Re-pick mid-session: NO (fixed at room creation)

  Feature 2 (Extinction UI):
    - Multiplayer target-sync: NO in v1 (solo-only; MP fixed at
      preset-activation time)

  Feature 3 (Berolina ep):
    - Variant: Parton 1952 (standard ep through Berolina geometry)

  Feature 4 (Royalty transfer) — cleared to execute after 1-3:
    - Turn-consumption: YES (prevents 'abdicate out of mate')
    - UI: dedicated 'Actions' button in GameView
    - Stakeholder demand: YES, validated
2026-04-21 10:33:14 -06:00
773bf53fab
plan(sisyphus): post-epic deferrals (MP color + extinction UI + berolina ep + royalty transfer)
Saves the planning doc covering all 4 deferred items from the
rule-variants epic close-out. Momus review: [OKAY].

Sequencing:
  1. MP color choice           — ~1 session, isolated
  2. Extinction-chess target UI — ~1/2 session, solo-only v1
  3. Berolina en-passant       — ~1/2 session, Parton 1952 variant
  4. Tier 3 royalty-transfer   — 1-2 sessions, REQUIRES stakeholder
                                 alignment before start (new engine
                                 surface for PlayerAction vs
                                 LegalMove).

Each feature is fully specified with protocol / server / UI / test
surface, commit message, and per-feature verification gate. Plan
includes a pre-implementation decision log (7 open questions) and
early-stop triggers so any of F1/F1+2/F1+2+3 is a valid ship
point.

Path: .sisyphus/plans/post-epic-deferrals.md
2026-04-21 10:26:08 -06:00
3cef8f5324
chore(sisyphus): Rule Variants Final Verification Wave — all reviewers APPROVE
Phase F Final Wave of the rule-variants epic.

  F1 (Plan Compliance, oracle):          APPROVE — bg_f2508b15
  F2 (Code Quality, unspecified-high):   APPROVE — bg_1101ecb3
  F3 (Manual QA, orchestrator-direct):   APPROVE — after agent hit
                                         tool cap; 1651/1651 unit +
                                         83/83 Playwright + 2/2
                                         visual spot-checks
  F4 (Scope Fidelity, deep):             APPROVE — bg_77460b0f

Summary:
- 1651 unit tests (1417 baseline → +234 across 14 presets + 4 hooks
  + audits).
- 83/83 Playwright (80 → +3 rule-variants scenarios in F.4).
- 0 typecheck / lint errors.
- 4 new preset hooks shipped: getRoyalPieces, filterLegalMoves,
  shouldAdvanceTurn (+ HalfMovesThisTurn fact), overridePieceMoves.
- 14 rule-variant presets shipped:
  * Tier 1: knightmate-rules, double-move, monster-rules (scope-
    flippable), first-promotion-wins.
  * Tier 2 royal: coregal, dual-king, weak-dual-king.
  * Tier 2 objective: suicide-chess, capture-all, extinction-chess.
  * Tier 2 movement: berolina-pawns (scope-flippable),
    berolina-pawns-2 (scope-flippable), bouncing-pieces,
    bouncing-pieces-2.
- 1 new layout: dual-classic.
- Engine composition fix: onCheckGameResult 'ongoing' suppresses
  defaults without short-circuiting later presets.
- UI: RulesDrawer category grouping + LayoutPicker suggested-rules
  chips.
- Docs: PRESET-API.md + RULES.md galleries updated.
- Audits: incompatibleWith symmetry test + suggestedPresets
  dangling-reference test — both green and runtime-enforced.

Deferred per-plan: Tier 3 royalty-transfer (requires mid-game state
toggle UX outside 'one move at a time'), berolina en-passant,
extinction-chess UI target cycling.

Plan: .sisyphus/plans/rule-variants-v2.md (all 31 tasks checked).
2026-04-21 10:15:18 -06:00
a159299818
chore(sisyphus): execution plan for rule-variants epic (v2)
Writes the step-by-step to-do list successor to the Momus-approved
design doc at .sisyphus/plans/rule-variants.md. The design doc has
the full hook-API rationale, risk analysis, and architecture sketch;
rule-variants-v2.md is the agent-executable task list.

Structure mirrors the modifier-profiles-t3 plan the repo just
shipped: 6 phases (A-F) + Final Verification Wave, 32 top-level
checkboxes, parallel-execution map, T3-learned tactics section for
the incoming agent (tool-cap fragility, commit discipline,
v3/v4 mirror pitfalls), and explicit 'when to stop mid-plan'
branching so an early-exit delivers a coherent subset.

Scope (recap from design doc):
  - Phase A: 4 new PresetDef hooks (getRoyalPieces,
    filterLegalMoves, shouldAdvanceTurn, overridePieceMoves) +
    HalfMovesThisTurn game fact.
  - Phase B: 4 Tier-1 presets closing starting-layouts deferrals
    (knightmate-rules, double-move, monster-rules,
    first-promotion-wins).
  - Phase C: 3 royal-variant Tier-2 presets (coregal, dual-king,
    weak-dual-king) + dual-classic layout.
  - Phase D: 3 objective-variant Tier-2 presets (suicide-chess,
    capture-all, extinction-chess).
  - Phase E: 4 movement-variant Tier-2 presets (berolina-pawns ×2,
    bouncing-pieces ×2).
  - Phase F: lobby integration (incompatibleWith audit,
    suggestedPresets wiring, drawer category grouping), rule-
    variant e2e, docs.
  - Final Wave: 4 reviewers (oracle/deep/unspecified-high).

Explicit deferrals: royalty-transfer (T3 in design doc; needs
mid-game state toggle), PGN notation changes, ELO/ranking,
non-8×8 variants, multi-royal check-banner UX (follow-up).

Each task carries 'what to do', file anchors, verification
criteria, parallelization map, recommended agent profile, and
commit message template — designed so an agent can pick up task
X.Y without re-reading the whole plan.
2026-04-20 18:15:36 -06:00
6cddb1dcd0
chore(sisyphus): T3 Final Verification Wave — all reviewers APPROVE
F1 Plan Compliance Audit — APPROVE
  Primitives [15/15] | Tasks [17/17 top-level] | ADRs [7/7]

F2 Code Quality Review — APPROVE
  Build [PASS] | Lint [PASS] | Tests [1386 pass] | No 'as any' / '@ts-ignore'
  in non-test source | Registry-dispatch pattern throughout (no
  hardcoded kind switches)

F3 Manual QA — APPROVE
  e2e [79/79] including 18/18 custom-modifiers.spec.ts scenarios
  (plan called for 15, shipped 18). All former fixmes passing.

F4 Scope Fidelity — APPROVE
  Recursion cap [3, enforced by MAX_RECURSION_DEPTH in validate.ts]
  Primitive count cap [50, enforced by MAX_PRIMITIVE_COUNT in
  validate.ts + server Zod .max(50)]
  Per-room cap [10, enforced by CUSTOM_MODIFIER_ROOM_CAP in
  broadcast.ts]
  Per-engine custom registry [CustomModifierRegistry owned by
  ChessEngine, never global — cross-room leakage structurally
  impossible]
  T4 smuggling [CLEAN — scripted type is rejected in both
  validate.test.ts and schema.test.ts; no runtime scripted
  descriptor shipped]

T3 boulder complete.
2026-04-19 22:04:36 -06:00
8fb5669c9a
chore(sisyphus): mark T3 Wave 5 complete (T29-T32) 2026-04-19 21:38:56 -06:00
dcd782fa5a
chore(sisyphus): mark T3 Wave 4 (UI) complete 2026-04-19 20:23:21 -06:00
28b11f342d
chore(sisyphus): mark T24 + T28 complete 2026-04-19 20:06:24 -06:00
2b641c78bb
chore(sisyphus): mark T3 Wave 3 (validator/schema/library/apply/stacking) complete 2026-04-19 18:12:43 -06:00
8a7c1b3f54
chore(sisyphus): mark T3 Wave 2 (15 primitives) complete + notepad updates 2026-04-19 17:42:32 -06:00
646b16a8c0
chore(sisyphus): complete modifier-profiles-t2 boulder 2026-04-19 10:20:26 -06:00
37e485537d
feat(ui): modifier editor undo/redo + conflict resolution panel + copy/paste modifiers 2026-04-19 08:55:48 -06:00
728ad76a5e
fix(ui): rules drawer Esc handling — unblock board after drawer close
The T1 ModifierProfileEditor installed a window-level Esc handler that
closed the modal but the RulesDrawer had no Esc handler of its own.
Users hitting Esc with the drawer open (no modal) saw nothing happen;
worse, with both open+modal, closing the modal left the drawer's
pointer-events-blocking backdrop in place, silently breaking all
board drag-interaction afterward.

Fix:
- Add useEffect-based Esc handler to RulesDrawer that closes it when
  no nested modal is active.
- ModifierProfileEditor now uses capture-phase + stopImmediatePropagation
  so the drawer's Esc handler does NOT also fire on the same keystroke,
  preventing double-close.

Add packages/chess/e2e/solo-smoke.spec.ts — 5 regression scenarios
that would have caught this at T1 CI time. Test 4 specifically
reproduces the original bug (drawer open → Esc → drag board pieces).

Also queue 2 additional scenarios in the T2 plan since T2 work extends
both drawer + editor further.

All tests green: 1217 unit tests (94 files), 48 Playwright e2e in 1.3m.
2026-04-19 08:38:05 -06:00
0aced40118
chore(sisyphus): complete piece-modifiers boulder 2026-04-19 08:25:35 -06:00
d93bcf6c81
feat(chess): starting-layout foundation (Phase A)
Introduces a pluggable StartingLayout abstraction so the engine can
open from positions other than FIDE without per-caller special casing.

- layouts/{types,registry,index}.ts: StartingLayout + LAYOUT_REGISTRY,
  mirroring the PRESET_REGISTRY / PIECE_TYPE_REGISTRY pattern.
- starting-position.ts: CLASSIC_LAYOUT + applyLayout(session, layout)
  as the parametrized spawn path. generateStartingPosition stays as
  a thin back-compat wrapper so no existing call site changes.
- layouts/{classic,empty}.ts: first two premades registered via
  side-effect imports from the barrel.
- engine.ts: ChessEngine constructor now accepts either legacy
  (activePresets) positional or new options-bag form
  ({ activePresets?, layout? }). Detection uses a method-shape
  probe rather than instanceof so both overload forms compose
  cleanly under strict TS.
- Tests: 11 new tests in starting-position.test.ts + engine-presets
  cover applyLayout ordering, hasMoved pre-revocation, empty
  layout, classic equivalence, options-bag equivalence with legacy.

Also lands the full execution plans for starting-layouts and
rule-variants under .sisyphus/plans/ (both Momus-reviewed OKAY).

941 tests passing; bun run check clean.
2026-04-18 19:44:01 -06:00
cc30545ced
feat(chess): preset-flexibility architecture — decouple HP, damage, piece-types, state, effects
Decouples five cross-cutting concerns from engine core so new presets
compose without special-casing:

- Piece attributes: CORE_PIECE_ATTRS + PresetDef.pieceAttributes;
  engine.effectivePieceAttrs unions them. HP is now preset-owned.
- Spawn pipeline: engine.spawnPiece() + onPieceSpawn hook replaces
  hand-rolled materialization. Queen-splits seeds HP via the hook,
  not via direct knowledge of piece-hp.
- Hook signatures: all hooks now take single context objects
  (LifecycleContext, MoveHookContext, CaptureHookContext, DamageHookContext,
  SelfCheckFilterContext, GameResultHookContext, PieceSpawnContext,
  BeforeMoveContext, TurnStartContext, DescribeMoveEffectContext).
- Piece-type registry: PIECE_TYPE_REGISTRY + core-piece-types.ts
  replaces hardcoded FIDE switch-tables in engine/check/checkmate/
  stalemate/Piece.tsx. Custom piece types plug in without engine edits.
- Preset-scoped state: engine.presetState<T>(id) backed by facts on
  PRESET_STATE_ENTITY. Auto-cleared on deactivate. capture-to-win
  migrated off GAME_ENTITY.Winner.
- Move log: engine.moveLog + MoveRecord + describeMoveEffect hook.
- Visual effects: engine.emitEffect/subscribeEffects + VisualEffect +
  VisualEffectLayer. Explosion, heal, poison renderers. No-op when
  no subscribers.
- Phase hooks: onBeforeMove (with cancel), onTurnStart. Scope-aware
  dispatch routes onDamage/onBeforeMove/onTurnStart by target/mover
  color.

All 5 production presets migrated. 4 prototype presets (Shield, Cannon,
Berserker, PawnStamina) in integration.test.ts exercise every hook.
Added test-utils.ts and PRESET-API.md for preset authors.

930 tests passing; bun run check clean.
2026-04-18 16:26:17 -06:00
3e8bc7394e
Add sisyphus 2026-04-17 15:22:04 -06:00
f3a38d44be
chore(root): scaffold monorepo — Phase 0 complete 2026-04-16 13:32:21 -06:00