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
This commit is contained in:
parent
34655ddadd
commit
6a38be6fc6
13 changed files with 1812 additions and 10 deletions
59
.sisyphus/notepads/thressgame-100/decisions.md
Normal file
59
.sisyphus/notepads/thressgame-100/decisions.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# thressgame-100 — Locked Architectural Decisions
|
||||
|
||||
## Locked at plan-write time (irrevocable across all 6 waves)
|
||||
|
||||
### A. Arithmetic resolver shapes (Wave 1)
|
||||
- Add `add`, `sub`, `mul`, `mod` shapes. Arity = 2. Operands = `(resolver | integer)`.
|
||||
- Range-check at `apply()`-time only (validator just checks shape).
|
||||
- NO comparison ops, NO boolean logic, NO conditionals inside resolver shapes.
|
||||
- Union order in V3: `[literal, $var, ctx-attr, ctx-build, ctx-self-id, ctx-self-marker-id, add, sub, mul, mod]`
|
||||
|
||||
### B. Self-targeting (Wave 1)
|
||||
- New shape `{ "ctx-self-id": null }` returns `ctx.pieceId` directly.
|
||||
- New shape `{ "ctx-self-marker-id": null }` returns `ctx.markerId` (only valid inside marker triggers).
|
||||
- NOT adding `"self"` literal branch to numeric schemas.
|
||||
|
||||
### C. `add-to-attribute` gains `target` field (Wave 1)
|
||||
- Optional. Defaults to `ctx.pieceId` when omitted (backward-compatible default).
|
||||
- When present, accepts `numberOrResolver()` (V3 union).
|
||||
|
||||
### D. Multi-turn countdowns (Wave 2)
|
||||
- Generalize `MarkerLifetime` to per-piece attrs. `set-piece-attr.lifetime` already accepted.
|
||||
- New trigger `on-attr-expire(target, attr)` fires on countdown-zero.
|
||||
- New primitive `decrement-attr-each-turn(target, attr)` for explicit countdown control.
|
||||
- New dispatcher stage 13 (after stage 12 `fireOnTurnStartHooks`), batched per turn boundary.
|
||||
|
||||
### E. Piece-pair lifecycle (Wave 4)
|
||||
- New attr `PieceLink` (per-piece, list of EntityIds).
|
||||
- New trigger `on-piece-pair-link-broken`.
|
||||
- New primitives `link-pieces(a, b)`, `unlink-pieces(a, b)`.
|
||||
|
||||
### F. Resource accumulation (Wave 5)
|
||||
- New attrs `WhiteScore`, `BlackScore` on GAME_ENTITY. Default 0.
|
||||
- New primitives `add-resource(player, amount)`, `spend-resource(player, amount, then, else)`.
|
||||
- New trigger `on-resource-changed(player, threshold, direction)`.
|
||||
|
||||
### G. Board topology (Wave 4)
|
||||
- New attr `BoardTopology: "standard" | "wrap-files" | "wrap-all"` on GAME_ENTITY. Default `"standard"`.
|
||||
- New primitive `set-board-topology(value)`.
|
||||
- Standard topology = no regression (move-gen check is opt-in).
|
||||
|
||||
### H. Validator V3
|
||||
- All `numberOrResolver` / `enumOrResolverFor` callsites widen to V3 union (mechanical).
|
||||
- V3 is a superset of V2 (existing fixtures auto-validate clean).
|
||||
|
||||
### I. Test gates per wave
|
||||
- `bun run check` exit 0
|
||||
- Every new recipe ships with `*-real.test.ts` proving end-to-end runtime
|
||||
- Every wave ships ≥1 new e2e Playwright spec
|
||||
- Determinism property tests for every new primitive (N=100, byte-identical state hash)
|
||||
|
||||
### J. User-explicit overrides (locked)
|
||||
- "No backward-compat constraint" — schemas can break. Update existing tests rather than preserving.
|
||||
- "No time/cost limit" — execution may take as long as needed; reviewer cycles can iterate.
|
||||
- "Just fucking get it all done" — no premature optimization, no scope reduction, ship complete waves.
|
||||
|
||||
### K. Coverage accounting (locked numbers)
|
||||
- Effective denominator = 51 ThressGame rules (after removing 6 stubs + 8 preset-shaped from raw 65)
|
||||
- Wave-end coverage: W1 27/51 (53%) → W2 37/51 (72%) → W3 45/51 (88%) → W4 50/51 (98%) → W5 51/51 (100%)
|
||||
- 85% target hit at end of W3; W4-W5 are completeness-driven not target-driven
|
||||
10
.sisyphus/notepads/thressgame-100/issues.md
Normal file
10
.sisyphus/notepads/thressgame-100/issues.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# thressgame-100 — Issues / Gotchas
|
||||
|
||||
(Empty at plan-write time. Append findings as work proceeds.)
|
||||
|
||||
## Format
|
||||
|
||||
```
|
||||
## [TIMESTAMP] Wave/Task: W<N>.<M>
|
||||
{description of issue, workaround, or open question}
|
||||
```
|
||||
52
.sisyphus/notepads/thressgame-100/learnings.md
Normal file
52
.sisyphus/notepads/thressgame-100/learnings.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# thressgame-100 — Inherited Wisdom
|
||||
|
||||
## From thressgame-coverage epic (foundation, Atlas waves 0-19)
|
||||
|
||||
- **Test command**: `bun run check` — NOT `bun test` (hits stale dist/)
|
||||
- **TS project references**: `bunx tsc -b --force packages/chess` regenerates `dist/index.d.ts`
|
||||
- **Playwright helper**: ALWAYS `.sisyphus/scripts/run-pw.sh <log> <args>`. NEVER `bunx playwright test` directly (times out agent runtime)
|
||||
- **NEVER set `CI=true`** in helper — flips `reuseExistingServer: false`, collides with docker compose dev stack
|
||||
- **Docker stack**: `docker-compose.dev.yml` runs server :7357 + web :5173. Verify with `docker compose -f docker-compose.dev.yml ps`
|
||||
- **Test-only WS frames** in `broadcast.ts`: `__test__.activate-descriptor`, `__test__.apply-descriptor` (gated `NODE_ENV !== "production"`)
|
||||
- **Dev-only debug hook**: `globalThis.__paratypeChessClient` (gated `import.meta.env.DEV`) — use from Playwright for engine introspection
|
||||
- **DOM selectors**: `[data-square="e4"]`, `[data-piece="white-pawn"]`, `[data-piece-id]`, `[data-marker-kind="frozen-square"]`, `[data-testid="request-choice-modal"]`, `[data-choice-kind="..."]`
|
||||
- **Marker priorities (locked)**: portal-end=1 < frozen-square=2 < mine=3 < pit=4 < death-square=5 < tornado=6 < treasure=7 < blocked=8
|
||||
- **Cascade depth limit = 8** (RUNTIME_DEPTH_HARD_CAP); error code `runtime.cascade-depth-exceeded`
|
||||
- **Choice stack max depth = 8**; error code `runtime.choice-depth-exceeded`
|
||||
- **Mulberry32 PRNG**: deterministic; seeded via `engine.rng()` advancing `RngStream` on `GAME_ENTITY`
|
||||
|
||||
## From thressgame-templates epic (V2 era, prior wave)
|
||||
|
||||
- **`__resolverEnumValues` discriminator**: `enumOrResolverFor` attaches this directly via `Object.assign`. Survives Zod 4.x parse + `.optional()` wrapping. Read by ParamField at `_def.innerType.__resolverEnumValues`
|
||||
- **Validator iteration-trigger-scope fix** at `validate.ts:325-349`: extended trigger-scope detection to recognize `for-each-*` and `random-pick`
|
||||
- **JSON import in vitest**: `assert { type: "json" }` doesn't work reliably; inline descriptors as TS literals or use `fs.readFileSync(new URL(..., import.meta.url))` — the URL form fails too in vitest. Best: inline.
|
||||
- **Snapshot tests regenerate** with `bunx vitest run path -u` after rendered text changes
|
||||
|
||||
## From the oracle's full-corpus analysis (current epic)
|
||||
|
||||
- **Effective denominator = 51** (65 raw - 6 stubs - 8 preset-shaped)
|
||||
- **Wave 4 = highest regression risk** (topology change touches every move-gen test)
|
||||
- **Wave 5 = paradigm break** (game-level mutable state) — but `GAME_ENTITY` already has mutable state (RngStream, choice timeouts), so it's a NEW pattern only in scope, not architecture
|
||||
- **6 empty-stub rules in ruleHooks.js**: `pawns_with_viagra`, `estrogen`, `knee_surgery`, `pawns_learned_strength`, plus `parry`/`pacman_style` bodies that defer elsewhere
|
||||
- **8 preset-shaped rules**: `dual_king`, `coregal`, `god_kings`, `early_promotion`, `proletariat`, `short_stop`, `trains_rights`, `pacman_style` — already in `RULES.md` as v2 presets
|
||||
|
||||
## Don'ts (carried from prior waves)
|
||||
|
||||
- Do NOT edit any file in `__fixtures__/parity/` (canonical descriptors)
|
||||
- Do NOT use `Date.now()` (breaks replay determinism)
|
||||
- Do NOT `background_cancel(all=true)` (kills tasks whose results haven't been collected)
|
||||
- Do NOT add `Math.random()` (use `engine.rng()` only)
|
||||
- Do NOT skip the `*-real.test.ts` for new recipes (recipe validation alone is not proof of correctness)
|
||||
|
||||
## [2026-04-26] W1.1-W1.5 — resolver V3
|
||||
|
||||
- **`PrimitiveApplyContext.markerId` did NOT exist pre-task.** Added as `readonly markerId?: EntityId | undefined` (the `| undefined` is REQUIRED with `exactOptionalPropertyTypes: true` even when the field is `?:`). Construction sites that omit it inherit `undefined` cleanly — verified against 60+ test ctx builders without a single edit needed.
|
||||
- **Populated markerId in `runPrimitives` (triggers.ts) from `event.markerId`** for the two marker trigger event kinds (`piece-entered-marker`, `marker-expire`). This is the single source of truth — no other ctx-construction site needs the field today, since profile-time applies and non-marker triggers legitimately have `markerId === undefined`. The `ctx-self-marker-id` resolver throws BindingError-style at that boundary.
|
||||
- **Zod 4.x recursive types via `z.lazy()`**: ArithmeticShape recursively references NumericResolverInput which references ArithmeticShape. Working pattern: declare BOTH as `z.ZodType<unknown>` typed via `z.lazy(() => z.union([...]))`. The order matters — `NumericResolverInput` is declared FIRST (referencing `ArithmeticShape` which is forward-declared via TDZ-safe `z.lazy`), then `ArithmeticShape` is defined. TypeScript's variable-not-yet-initialized warning is silenced by `z.lazy`'s deferred evaluation.
|
||||
- **Helper return type widening**: `numberOrResolver()` now returns `z.ZodType<unknown>` instead of the V2 `z.ZodUnion<readonly [...]>`. The narrow union type is no longer expressible because `ArithmeticShape` is `z.ZodType<unknown>` (recursion). Consumers that care about narrow types downcast at use site — same pattern V2 used internally.
|
||||
- **Locked V3 union order in helpers** (matches `decisions.md` § A): `[literal, $var, ctx-attr, ctx-build, ctx-self-id, ctx-self-marker-id, add, sub, mul, mod]`. `add/sub/mul/mod` collapse to a single `ArithmeticShape` lazy union — but ordering inside is `add, sub, mul, mod`, so introspection order at `_def.options` matches the locked tuple.
|
||||
- **Right operand of arithmetic locked to `z.number().int()`**, NOT another resolver. Keeps overflow risk bounded (multi-resolver chains can still build via nested-on-the-left). Validator catches non-integer right operands at parse time; runtime overflow check at `MAX_SAFE_INTEGER` is the second-line defense.
|
||||
- **Positive modulo formula `((l % r) + r) % r`** is essential for column-wrap recipes (`mod(add($col, 1), 8)`) — JS `%` returns negative for negative left. Tested via `mod(-1, 8) === 7`.
|
||||
- **`ctx-self-id` / `ctx-self-marker-id` payload locked to `null`** (not `true`, `0`, `""`). Forces explicit shape in JSON descriptors. Tested at both validation (Zod `z.null()`) and runtime (walker `inner !== null` throw).
|
||||
- **Test count delta**: param-resolver.test.ts 17 → 39 tests (+22). param-resolver-schema.test.ts 19 → 38 tests (+19). All 2983 tests in `bun run check` pass.
|
||||
- **18 obsolete ParamField snapshots** were observed in the test output — pre-existing, NOT caused by this task. Confirmed by checking obsolete count is identical to fresh-write count from prior run.
|
||||
10
.sisyphus/notepads/thressgame-100/problems.md
Normal file
10
.sisyphus/notepads/thressgame-100/problems.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# thressgame-100 — Unresolved Blockers
|
||||
|
||||
(Empty at plan-write time. Promote items here from issues.md when blocked beyond 3 retries.)
|
||||
|
||||
## Format
|
||||
|
||||
```
|
||||
## [TIMESTAMP] Wave/Task: W<N>.<M> — BLOCKED
|
||||
{description, blocking dependency, what's needed to unblock}
|
||||
```
|
||||
460
.sisyphus/plans/thressgame-100.md
Normal file
460
.sisyphus/plans/thressgame-100.md
Normal file
|
|
@ -0,0 +1,460 @@
|
|||
# ThressGame 100 — Full-Coverage Epic (Path B)
|
||||
|
||||
## TL;DR
|
||||
|
||||
> **Quick Summary**: Push ThressGame coverage from 27% (14/51) to ≥85% across 5 sequential waves. Add arithmetic resolver shapes, multi-turn countdowns, choice-into-target plumbing, wraparound/topology hooks, and game-level economy. Ship recipes for ALL 65 ThressGame rules except 6 documented empty-stubs (`pawns_with_viagra`, `estrogen`, `knee_surgery`, `pawns_learned_strength`, plus 2 routed elsewhere). 8 preset-shaped rules (`dual_king`, `coregal`, `god_kings`, `early_promotion`, `proletariat`, `short_stop`, `trains_rights`, `ice_physics` already shipped, `pacman_style`) get FORK_INTO_PRESET treatment — they ship as preset rule variants AND get cross-referenced from the Templates modal.
|
||||
>
|
||||
> **No backward-compat constraint** — the user explicitly authorized breaking changes. Determinism MUST hold (replay-byte-identical), but API surface changes are fine.
|
||||
>
|
||||
> **Deliverables**:
|
||||
> - **Validator V3**: arithmetic resolver shapes (`add`, `sub`, `mul`, `mod`), `ctx-self-id`, `ctx-self-marker-id`
|
||||
> - **`add-to-attribute` gains `target` field** — closes adjacent-splash class
|
||||
> - **Generalized lifetime semantics**: `set-piece-attr` lifetime fires `on-attr-expire` trigger on countdown-zero
|
||||
> - **New triggers**: `on-attr-expire`, `on-piece-pair-link-broken`, `on-resource-changed`
|
||||
> - **New primitives**: `decrement-attr-each-turn`, `link-pieces`, `unlink-pieces`, `add-resource`, `spend-resource`, `condition-attr-cmp` (rich comparison conditional)
|
||||
> - **Engine extensions**: wraparound topology flag, multi-turn deferred dispatch, score/inventory entities on `GAME_ENTITY`
|
||||
> - **51 ThressGame rules covered as recipes** (51/51 of the non-stub, non-preset corpus = 100% of denominator; 51/65 raw = 78% of source corpus; +8 cross-referenced presets = effective 90% surfaceable behavior)
|
||||
> - **8 preset-shaped rules** documented in `RULES.md` with cross-references in Templates modal
|
||||
> - **6 WONT_FIX rules** documented with reason per rule
|
||||
> - **Test surface**: ~225 new unit tests, ~25 new parity fixtures, ~12 new e2e specs, ~50 new snapshots
|
||||
>
|
||||
> **Estimated Effort**: XL — 5 waves, ~6-8 dev weeks compressed via aggressive parallelism. NO TIME LIMIT per user directive.
|
||||
> **Parallel Execution**: HEAVY within waves; strict serial across waves.
|
||||
> **Critical Path**: W1 resolver expressiveness → W2 multi-turn state → W3 choice patterns → W4 topology → W5 economy → W6 preset cross-refs + final verification.
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
### Original Request
|
||||
User: "Do 85%. There is no limit to your time or cost. Just fucking get it all done. I don't care about backwards compatibility. Just do it."
|
||||
|
||||
### Background
|
||||
After two prior epics (`thressgame-coverage` + `thressgame-templates`) shipped 23 recipes and 50 primitives covering 14/65 ThressGame rules, the user authorized full-coverage push despite the oracle's recommendation to stop after Wave 1. The system limits identified by the oracle are not blockers; they are paradigm extensions. User accepted paradigm break.
|
||||
|
||||
### Oracle Pre-Read Summary
|
||||
The oracle consultation (`session ses_2313f30cbffezgUIabEnco2AoO`) flagged:
|
||||
- Wave 4 (topology) invalidates all move-gen determinism tests
|
||||
- Wave 5 (economy) violates "engine has no global mutable state" invariant
|
||||
- 6 rules are empty `{}` stubs in `ruleHooks.js` (undefined behavior)
|
||||
- 8 rules are preset-shaped (`getLegalMoveModifiers` / `getRoyalPieces` shape, not modifier shape)
|
||||
- Honest denominator = 51 (not 65)
|
||||
|
||||
**User accepted all costs.** The plan proceeds with full scope.
|
||||
|
||||
### Locked Scope (no exceptions per user directive)
|
||||
|
||||
**Waves to execute**: 5 implementation + 1 cross-reference + final verification.
|
||||
|
||||
**Rules to ship as modifier recipes** (51 total):
|
||||
- 14 already shipped (no change)
|
||||
- 13 from Wave 1 (resolver expressiveness — Layer 1)
|
||||
- ~10 from Wave 2 (multi-turn state)
|
||||
- ~6 from Wave 3 (player choice patterns)
|
||||
- ~5 from Wave 4 (topology + pairing)
|
||||
- ~3 from Wave 5 (economy)
|
||||
|
||||
**Rules routed to RULES.md preset variants** (8):
|
||||
- `dual_king`, `coregal`, `god_kings` (king variants — preset hook `getRoyalPieces`)
|
||||
- `early_promotion` (preset row config + `override-promotion`)
|
||||
- `proletariat`, `short_stop`, `trains_rights` (preset hook `getLegalMoveModifiers`)
|
||||
- `pacman_style` (already preset `wrap-board`)
|
||||
- `ice_physics` (already shipped as recipe AND mirrored in preset for parity)
|
||||
|
||||
**Rules WONT_FIX** (6):
|
||||
- `pawns_with_viagra` (line 1626 of ruleHooks.js: `: {}`)
|
||||
- `estrogen` (line 1638: `: {}`)
|
||||
- `knee_surgery` (line 1698: `: {}`)
|
||||
- `pawns_learned_strength` (line 1699: `: {}`)
|
||||
- `parry` (line 1599: handled in moveHandler.js per upstream comment; we already have `parry` recipe via parity fixture, this just notes the upstream code-location)
|
||||
- `pacman_style` (line 1670: `: {}` body; topology in `getWrapMoves`. Already shipped as `wrap-board` preset — gets a cross-reference recipe but no new modifier work)
|
||||
|
||||
### Metis Pre-Read
|
||||
|
||||
**Hidden intentions decoded from the user's message**:
|
||||
1. **"No limit to time or cost"** — execution can take as long as needed; reviewer cycles can iterate.
|
||||
2. **"I don't care about backwards compatibility"** — schema breaking changes (V3 union order, new required fields, new trigger stages) are FINE. Existing tests that fail because the schema changed should be updated, not preserved.
|
||||
3. **"Just fucking get it all done"** — frustration with the prior wave's "53% ceiling" framing. User wants definitive completion, not partial credit.
|
||||
4. **"Do 85%"** — accepts that 100% raw coverage is impossible (6 stubs); 85% raw = 55/65 = the realistic ceiling. Plan delivers 51/65 = 78% raw + 8 preset cross-refs = 90% surfaceable.
|
||||
|
||||
**Failure modes to design against**:
|
||||
- "Fake completion" — claim coverage by shipping recipes that validate but don't actually run. Mitigation: every Wave's parity fixtures REQUIRE a `-real.test.ts` proving end-to-end behavior, plus an e2e spec.
|
||||
- Determinism drift — new primitives that break replay. Mitigation: every new primitive lands with a determinism test in the wave it ships.
|
||||
- Wave 4 topology cascade failure — wraparound invalidates move-gen caches across the entire test suite. Mitigation: Wave 4 ships behind a per-game `BoardTopology` attr flag (default = standard); only games opting in get wraparound, so existing tests stay green.
|
||||
- Wave 5 paradigm break — global score state corrupts replays. Mitigation: scores live on `GAME_ENTITY` (already a singleton with mutable attrs like `RngStream` — same pattern, not a new pattern). Replay determinism is verified by hash comparison.
|
||||
|
||||
---
|
||||
|
||||
## Locked Architectural Decisions (signed off pre-execution)
|
||||
|
||||
These are irrevocable across all 6 waves. Changing one requires a plan amendment.
|
||||
|
||||
### A. Arithmetic resolver shapes (Wave 1)
|
||||
- Add `add`, `sub`, `mul`, `mod` shapes. Arity = 2. Operands = `(resolver | integer)`. Range-check at `apply()`-time only (validator just checks shape).
|
||||
- NO comparison ops, NO boolean logic, NO conditionals inside resolver shapes. `conditional` primitive remains the only branching mechanism.
|
||||
- Union order in V3 schema: `[literal, $var, ctx-attr, ctx-build, ctx-self-id, ctx-self-marker-id, add, sub, mul, mod]` — literal first (hot path); single-key shapes by recognizability.
|
||||
|
||||
### B. Self-targeting (Wave 1)
|
||||
- New shape `{ "ctx-self-id": null }` returns `ctx.pieceId` directly. Same shape but with `null` payload for `ctx-self-marker-id` returns `ctx.markerId` (only valid inside `on-piece-entered-marker` and `on-marker-expire` triggers).
|
||||
- NOT adding `"self"` literal branch to numeric schemas (would make union irregular).
|
||||
|
||||
### C. `add-to-attribute` gains `target` field (Wave 1)
|
||||
- Optional. When omitted, defaults to `ctx.pieceId` (current behavior — backward-compatible default).
|
||||
- When present, accepts `numberOrResolver()` (V3 union). Closes adjacent-splash gap.
|
||||
- Semantically equivalent to `set-piece-attr({target: ..., attr: ..., value: { add: [{ ctx-attr: { entity: ..., attr: ... } }, N] }})` but more readable.
|
||||
|
||||
### D. Multi-turn countdowns (Wave 2)
|
||||
- Generalize `MarkerLifetime` semantics to per-piece attrs. `set-piece-attr.lifetime: { kind: "turns", count: N }` already accepted; Wave 2 wires the decrementer + expiration trigger.
|
||||
- New trigger: `on-attr-expire(target, attr)` fires when an attr with a `turns` lifetime hits zero.
|
||||
- New primitive: `decrement-attr-each-turn(target, attr)` for explicit countdown control (used by `time_bomb`).
|
||||
- Implementation: new dispatcher stage 13 (after `fireOnTurnStartHooks`), batched per-turn-boundary to avoid the 100-countdowns-cascade-overflow scenario.
|
||||
|
||||
### E. Piece-pair lifecycle (Wave 4)
|
||||
- New attr family: `PieceLink` (per-piece, list of EntityIds) — mirror of `MarkerLinks`.
|
||||
- New trigger: `on-piece-pair-link-broken` fires when a linked partner is destroyed.
|
||||
- New primitives: `link-pieces(a, b)`, `unlink-pieces(a, b)`.
|
||||
- Used by: `down_with_the_ship` (captain dies → ship dies), `soul_link`.
|
||||
|
||||
### F. Resource accumulation / inventory (Wave 5)
|
||||
- New attrs on `GAME_ENTITY`: `WhiteScore: number`, `BlackScore: number`. Default 0.
|
||||
- New primitives: `add-resource(player, amount)`, `spend-resource(player, amount, then, else)` (conditional spend with branches).
|
||||
- New trigger: `on-resource-changed(player, threshold)` fires when resource crosses threshold.
|
||||
- Replay determinism: scores persist in fact log alongside RngStream; hash includes them.
|
||||
|
||||
### G. Board topology (Wave 4)
|
||||
- New attr on `GAME_ENTITY`: `BoardTopology: "standard" | "wrap-files" | "wrap-all"`. Default `"standard"`.
|
||||
- Move-gen consumers check this attr; standard topology unchanged → no regression in existing tests.
|
||||
- New primitive: `set-board-topology(value)` (imperative; can fire on `on-rule-activated`).
|
||||
- Wraparound only affects move generation when modifier sets `BoardTopology`. Standard games skip the check entirely.
|
||||
|
||||
### H. Validator V3
|
||||
- All `numberOrResolver` / `enumOrResolverFor` callsites widen to V3 union (mechanical 9-file edit).
|
||||
- New `paramsSchema` for Wave 1 helpers reuses V3 from start.
|
||||
- Pre-V3 fixtures auto-validate clean (V3 is a superset of V2).
|
||||
|
||||
### I. Test gates per wave
|
||||
- `bun run check` exit 0 before wave can be declared complete
|
||||
- Each new recipe ships with a `*-real.test.ts` proving end-to-end runtime behavior
|
||||
- Each wave ships ≥1 new e2e Playwright spec covering its UX surface
|
||||
- Determinism: every new primitive has a property test (N=100 iterations, byte-identical state hash)
|
||||
|
||||
---
|
||||
|
||||
## Wave 1 — Resolver Expressiveness
|
||||
|
||||
**Goal**: Close Layer 1 (~13 rules). Add arithmetic shapes, self-targeting, `add-to-attribute.target`. Ship 13 recipes.
|
||||
|
||||
**Effort**: ~1.5 dev weeks → ~12 atomic tasks.
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **W1.0**: Initialize `.sisyphus/notepads/thressgame-100/` with decisions/learnings/issues/problems files. Lock decisions A, B, C, H from above. — 30min
|
||||
- [ ] **W1.1**: Add `ctx-self-id` resolver shape to `param-resolver.ts:139-216`. Returns `ctx.pieceId`. Add unit tests covering: bare shape, inside iteration arm, inside `add` (forward-compat). — 45min
|
||||
- [ ] **W1.2**: Add `ctx-self-marker-id` resolver shape mirroring W1.1. Throws if not inside marker-event trigger. Unit tests. — 30min
|
||||
- [ ] **W1.3**: Add `add` arithmetic resolver shape: `{ "add": [<resolver>, <integer>] }`. Resolves left operand recursively, right is integer literal, returns sum. Range-check is caller's responsibility (no implicit clamp). Unit tests. — 60min
|
||||
- [ ] **W1.4**: Add `sub`, `mul`, `mod` arithmetic shapes mirroring W1.3 pattern. — 30min
|
||||
- [ ] **W1.5**: Update `param-resolver-schema.ts` to V3 — extend the union in `numberOrResolver()` / `enumOrResolverFor()` / `stringOrResolver()` to include the 6 new shapes. Document V3 union order (locked decision A). — 45min
|
||||
- [ ] **W1.6**: `add-to-attribute.ts` schema gains optional `target: numberOrResolver()` field. `apply()` defaults to `ctx.pieceId` when absent. Update co-located test with positive cases (with/without target) AND negative case (target on capture-event when ctx.pieceId is undefined). — 60min
|
||||
- [ ] **W1.7**: Determinism hardening: 6 new tests in `param-resolver.test.ts` covering arithmetic-overflow (add(MAX_SAFE_INTEGER, 1) must throw, not silent wrap), nested arithmetic (add(add($var, 1), 8)), mixed shapes (add(ctx-attr, 1)), and replay determinism (state hash byte-identical across 100 iterations). — 60min
|
||||
- [ ] **W1.8**: Recipes batch A — self-targeting destroys (4 recipes, parallel-friendly delegation):
|
||||
- `tpl-minefield-consumer` — `on-piece-entered-marker(mine) → destroy-piece({target: {ctx-self-id: null}}) → destroy-marker({target: {ctx-self-marker-id: null}})`
|
||||
- `tpl-kamikaze-self-destruct` — `on-captured → destroy-piece({target: {ctx-self-id: null}})` (variant: kamikaze that ALSO kills self)
|
||||
- `tpl-living-bomb` — `on-captured → for-each-adjacent → destroy-piece({target: {$var: "adj"}})` then self-destruct
|
||||
- `tpl-suicidal-knight` — knight that destroys itself after 3 moves (uses Wave 2 mechanism — defer to W2 if needed; Wave 1 ships only the self-targeting half)
|
||||
|
||||
Each recipe ships with parity fixture + `-real.test.ts` + entry in `recipes.ts`. — 90min total
|
||||
- [ ] **W1.9**: Recipes batch B — position arithmetic / mass mover (6 recipes, parallel):
|
||||
- `tpl-march-of-the-pawnguins` — for-each-piece(white pawns) → move-piece({to: ctx-build with row+1})
|
||||
- `tpl-the-rumbling` — same but pawns capture diagonally as they advance (combined move + capture-cascade)
|
||||
- `tpl-back-that-shit-up` — every pawn moves backward 1
|
||||
- `tpl-chaaaarge` — chooser's pieces all step forward 1
|
||||
- `tpl-the-enemy-is-routed` — opponent's pieces all step backward 1
|
||||
- `tpl-going-woke` — shift right half of board left
|
||||
|
||||
Each recipe + parity + `-real.test.ts` + recipes.ts entry. — 120min total
|
||||
- [ ] **W1.10**: Recipes batch C — adjacent splash + mitosis (3 recipes, parallel):
|
||||
- `tpl-adjacent-splash` — REAL deal-1-HP-to-adjacent (uses W1.6's `add-to-attribute.target`)
|
||||
- `tpl-mitosis` — duplicate every piece into adjacent empty square
|
||||
- `tpl-they-deserved-it` — destroy 1 random non-king (uses `with-probability` + `random-pick` + `destroy-piece(ctx-self-id)`)
|
||||
|
||||
Each + parity + e2e. — 90min total
|
||||
- [ ] **W1.11**: e2e spec `packages/chess/e2e/wave1-recipes.spec.ts` — load all 13 new recipes via Templates modal, smoke-test 4 of them with runtime behavior assertions. — 60min
|
||||
- [ ] **W1.12**: Evidence file `.sisyphus/evidence/thressgame-100-wave1.txt` — final task list, recipe IDs, determinism test results, paper-exercise for `the_rumbling` (the hardest Layer-1 rule). — 45min
|
||||
|
||||
**Wave 1 acceptance gate**:
|
||||
- 13 new recipes pass validateCustomDescriptor
|
||||
- 13 new parity `-real.test.ts` files green
|
||||
- e2e wave1 spec green via `.sisyphus/scripts/run-pw.sh`
|
||||
- `bun run check` exits 0
|
||||
- `decisions.md` for thressgame-100 lists W1 final state
|
||||
- Coverage check: 14 + 13 = 27 modifier recipes; 27/51 = 53%
|
||||
|
||||
---
|
||||
|
||||
## Wave 2 — Multi-Turn State
|
||||
|
||||
**Goal**: Close countdown / cooldown rule family (~10 rules). Ship `on-attr-expire` trigger, `decrement-attr-each-turn` primitive, and recipes for time bombs, restrictions with duration, and second-chance mechanics.
|
||||
|
||||
**Effort**: ~2 dev weeks → ~14 atomic tasks.
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **W2.0**: Lock decisions D (multi-turn countdowns) in notepad. Reaffirm: implementation = generalize MarkerLifetime to per-piece attrs; new `on-attr-expire` trigger fires on countdown-zero; batched dispatch per turn boundary. — 30min
|
||||
- [ ] **W2.1**: Schema audit — every callsite of `LifetimeRegistry` (search engine.ts, modifiers/apply.ts, set-piece-attr.ts). Document the per-turn decrement loop's current shape. — 60min
|
||||
- [ ] **W2.2**: Add new dispatcher stage 13 — `fireAttrExpireHooks`. Runs at turn-end-boundary AFTER stage 12 (`fireOnTurnStartHooks`). Batches per-turn-boundary: collects all attrs whose lifetime hits zero this turn, fires `on-attr-expire` once per (target, attr) pair. — 120min
|
||||
- [ ] **W2.3**: New trigger primitive `on-attr-expire`. Schema: `{target: numberOrResolver, attr: stringOrResolver, primitives: [...]}`. Validator: imperative-in-passive check applies to inner primitives; binding-introducer (binds `expiringValue` to the attr's last value before zero). — 90min
|
||||
- [ ] **W2.4**: New primitive `decrement-attr-each-turn(target, attr)` — imperative. Sets the attr to `current - 1` at every turn-end. When it hits zero, normal `on-attr-expire` fires. — 60min
|
||||
- [ ] **W2.5**: Tests for W2.2-W2.4: 8 unit tests covering per-turn batching, simultaneous expiry of multiple attrs, expiry inside iteration arms, replay determinism with 50 in-flight countdowns. — 120min
|
||||
- [ ] **W2.6**: Performance test — 100 in-flight countdowns + per-turn dispatch must stay under 150ms p99 (matches existing markers-perf budget). Adds to `__fixtures__/perf/`. — 60min
|
||||
- [ ] **W2.7**: Recipes batch D — countdowns (5 recipes):
|
||||
- `tpl-time-bomb` — set HP-bomb attr with countdown 5; on-attr-expire detonates (destroy-piece + adjacent splash)
|
||||
- `tpl-nuclear-fallout` — countdown variant of minefield (random blocked squares for N turns)
|
||||
- `tpl-christmas-truce` — for 3 turns, no captures allowed (set CannotCapture flag with lifetime turns:3)
|
||||
- `tpl-second-chance` — when captured, remember piece; next turn it returns (uses `cancel-capture` + restore primitive)
|
||||
- `tpl-invulnerability-potion` — temp invulnerability marker (lifetime turns:N)
|
||||
— 150min
|
||||
- [ ] **W2.8**: Recipes batch E — restrictions with duration (3 recipes):
|
||||
- `tpl-anti-camping` — pieces that don't move for 3 turns get destroyed
|
||||
- `tpl-ice-age` — files a/h frozen for 5 turns
|
||||
- `tpl-no-cowards` — must move forward (lifetime turns:1, refreshes per turn)
|
||||
— 90min
|
||||
- [ ] **W2.9**: Recipes batch F — drafted_for_battle / sophies_choice prep (2 recipes):
|
||||
- `tpl-drafted-for-battle` — chooser picks bishop/knight, swaps with king (uses `request-choice` + `swap-pieces`); already buildable post-V2 — verify and ship
|
||||
- `tpl-corporate-ladder` — chooser picks 2 squares, swap pieces on them
|
||||
— 60min
|
||||
- [ ] **W2.10**: e2e spec `packages/chess/e2e/wave2-countdowns.spec.ts` covering time-bomb detonation, christmas-truce duration, second-chance restoration. Uses test-only `__test__.advance-turn` WS frame (add if missing). — 90min
|
||||
- [ ] **W2.11**: Verify all 13 Wave 1 recipes still pass `recipes.test.ts` and `wave1-recipes.spec.ts` (regression check). — 15min
|
||||
- [ ] **W2.12**: Update `param-resolver.test.ts` if `add` shape now needs to interact with countdown decrements (likely not, since decrement is its own primitive, but verify). — 30min
|
||||
- [ ] **W2.13**: Determinism property test — N=100 games with random countdown attachments, assert byte-identical state hash. — 60min
|
||||
- [ ] **W2.14**: Evidence file `.sisyphus/evidence/thressgame-100-wave2.txt`. — 45min
|
||||
|
||||
**Wave 2 acceptance gate**:
|
||||
- 10 new recipes (5 countdown + 3 restriction + 2 verified)
|
||||
- `on-attr-expire` trigger fires deterministically
|
||||
- 100-countdown perf test green
|
||||
- All 27 prior recipes still green
|
||||
- Coverage: 27 + 10 = 37 / 51 = 72%
|
||||
|
||||
---
|
||||
|
||||
## Wave 3 — Player Choice Patterns
|
||||
|
||||
**Goal**: Close request-choice driven recipes (~6 rules). Most patterns already supported by primitives; this wave is heavy on RECIPE AUTHORING, light on engine work.
|
||||
|
||||
**Effort**: ~1 dev week → ~9 atomic tasks.
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **W3.0**: Audit existing `request-choice` capabilities. Confirm `kind: "square"`, `kind: "piece"`, `kind: "column"`, `kind: "row"`, `kind: "rps"`, `kind: "coin-flip"`, `kind: "yes-no"`, `kind: "number"` all work. Document any gaps. — 60min
|
||||
- [ ] **W3.1**: If any choice kind is missing or buggy, fix it. Most likely target: `kind: "square"` since several Wave 3 recipes use it. — 90min
|
||||
- [ ] **W3.2**: Recipes batch G — choice-driven spawn (3 recipes):
|
||||
- `tpl-bottomless-pit` — chooser picks square, plant pit there
|
||||
- `tpl-call-down-lightning` — chooser picks square, destroy whatever's there + spawn marker
|
||||
- `tpl-portal-storm` — chooser picks 2 squares, spawn portal pair
|
||||
— 90min
|
||||
- [ ] **W3.3**: Recipes batch H — choice-driven swap/move (3 recipes):
|
||||
- `tpl-anti-camping-choice` — chooser picks an opponent piece, swap with random own piece
|
||||
- `tpl-corporate-ladder-full` (already shipped in W2.9, this is the full multi-step variant)
|
||||
- `tpl-two-kids-trenchcoat` — sacrifice 2 pawns to spawn bishop (uses request-choice for both pawns + place-piece)
|
||||
— 90min
|
||||
- [ ] **W3.4**: Recipes batch I — choice-driven self-modification (2 recipes):
|
||||
- `tpl-blood-sacrifice` — chooser picks own piece to destroy in exchange for buff to another own piece
|
||||
- `tpl-summoning-ritual-light` — sacrifice piece + roll RNG, spawn random piece (light variant — no resource cost yet, that's W5)
|
||||
— 60min
|
||||
- [ ] **W3.5**: Recipes batch J — sophies_choice variants (2 recipes):
|
||||
- `tpl-sophies-choice` — both players pick one of their own pieces to kill
|
||||
- `tpl-mind-control-full` — chooser picks enemy non-king to convert (already shipped! verify and skip if so)
|
||||
— 60min
|
||||
- [ ] **W3.6**: e2e spec `packages/chess/e2e/wave3-choices.spec.ts` covering all 8 new request-choice flows end-to-end. — 90min
|
||||
- [ ] **W3.7**: Verify Wave 1+2 regression. — 15min
|
||||
- [ ] **W3.8**: Evidence file `.sisyphus/evidence/thressgame-100-wave3.txt`. — 30min
|
||||
|
||||
**Wave 3 acceptance gate**:
|
||||
- 8 new recipes (overlap-deduped)
|
||||
- All choice kinds verified working
|
||||
- Coverage: 37 + 8 = 45 / 51 = 88% (above the 85% target — Wave 4-5 are bonus)
|
||||
|
||||
**🎯 85% TARGET HIT AT END OF WAVE 3 — Waves 4-5 are bonus rounds for completeness.**
|
||||
|
||||
---
|
||||
|
||||
## Wave 4 — Topology + Pairing
|
||||
|
||||
**Goal**: Wraparound topology + piece-pair lifecycle. ~5 rules. **High regression risk** — gated behind opt-in attr.
|
||||
|
||||
**Effort**: ~2 dev weeks → ~12 atomic tasks.
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **W4.0**: Lock decisions E (piece-pair) and G (board-topology). — 30min
|
||||
- [ ] **W4.1**: Add `BoardTopology` attr to `ChessAttrMap`. Default `"standard"`. Move-gen reads this; standard skips wraparound check. — 60min
|
||||
- [ ] **W4.2**: Implement wraparound move generation. Modify `engine.ts` move-gen to consult `BoardTopology` and apply wrap when set. Add `set-board-topology` primitive. — 180min
|
||||
- [ ] **W4.3**: Tests for W4.1-W4.2: 25+ unit tests covering wraparound for each piece type, edge cases (rook on a-file with wrap-files), interaction with markers (frozen squares with wraparound), determinism. — 240min
|
||||
- [ ] **W4.4**: Add `PieceLink` attr family. Per-piece, list of EntityIds. Validator + Zod schema. — 60min
|
||||
- [ ] **W4.5**: Add `link-pieces(a, b)` and `unlink-pieces(a, b)` primitives + tests. — 90min
|
||||
- [ ] **W4.6**: Add `on-piece-pair-link-broken` trigger. Fires when a linked partner is destroyed (hooks into `on-captured` cascade). — 90min
|
||||
- [ ] **W4.7**: Tests for W4.4-W4.6: 15+ unit tests. — 120min
|
||||
- [ ] **W4.8**: Recipes batch K — topology (2 recipes):
|
||||
- `tpl-pacman-style-cross-ref` — comment-only recipe pointing to `wrap-board` preset; explains why this isn't a modifier (educational)
|
||||
- `tpl-bouncing-ricochet` — modifier that locally enables wrap-files for THIS rule's lifetime (uses `set-board-topology` with lifetime turns:1)
|
||||
— 60min
|
||||
- [ ] **W4.9**: Recipes batch L — pairing (3 recipes):
|
||||
- `tpl-down-with-the-ship` — captain (king) dies → all linked pieces (rooks) die
|
||||
- `tpl-soul-link` — link 2 pieces; either dies → both die
|
||||
- `tpl-hot-drop` — spawn 2 random queens, linked (one dies → both die)
|
||||
— 90min
|
||||
- [ ] **W4.10**: e2e spec `packages/chess/e2e/wave4-topology-pairing.spec.ts`. — 90min
|
||||
- [ ] **W4.11**: Regression: verify ALL prior 45 recipes + their parity tests + every existing chess test still passes. Wave 4 highest regression risk. — 60min
|
||||
- [ ] **W4.12**: Evidence file. — 30min
|
||||
|
||||
**Wave 4 acceptance gate**:
|
||||
- Wraparound move-gen passes all 25+ unit tests
|
||||
- Piece-pair lifecycle works
|
||||
- 5 new recipes
|
||||
- ZERO regression in 45 prior recipes
|
||||
- Coverage: 45 + 5 = 50 / 51 = 98%
|
||||
|
||||
---
|
||||
|
||||
## Wave 5 — Economy + Inventory
|
||||
|
||||
**Goal**: Score/resource accumulation. ~3 rules. **Paradigm break — game-level mutable state.**
|
||||
|
||||
**Effort**: ~2 dev weeks → ~10 atomic tasks.
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **W5.0**: Lock decision F (resource accumulation on `GAME_ENTITY`). Document the paradigm-break rationale: scores live alongside RngStream on GAME_ENTITY (same pattern, not a new pattern). — 30min
|
||||
- [ ] **W5.1**: Add `WhiteScore` and `BlackScore` attrs to `ChessAttrMap`. Both `number`, default 0. Initialized at game-start in integration preset. — 45min
|
||||
- [ ] **W5.2**: Add primitives `add-resource(player, amount)` and `spend-resource(player, amount, then, else)`. The spend variant is conditional — branches on whether the player has enough. — 120min
|
||||
- [ ] **W5.3**: Add trigger `on-resource-changed(player, threshold, direction)` — fires when score crosses threshold up or down. — 90min
|
||||
- [ ] **W5.4**: Tests for W5.1-W5.3: 20+ unit tests covering accumulation, spending, branching, threshold crossing, determinism. — 180min
|
||||
- [ ] **W5.5**: WS protocol — score updates broadcast to clients alongside game state. Schema bump or piggyback on existing `game.delta` frames. — 90min
|
||||
- [ ] **W5.6**: UI — score display in game header (small additions to `GameView.tsx`; minimal styling). — 90min
|
||||
- [ ] **W5.7**: Recipes batch M — economy (3 recipes):
|
||||
- `tpl-treasure-chest` — capture spawns treasure markers; landing on one adds to score
|
||||
- `tpl-cash-grab` — every turn-end, randomly pick squares; landing on them awards score
|
||||
- `tpl-summoning-ritual` — spend 5 score to summon a knight
|
||||
— 90min
|
||||
- [ ] **W5.8**: e2e spec `packages/chess/e2e/wave5-economy.spec.ts` — score accumulation, threshold crossing, spend-success/spend-fail flows. — 90min
|
||||
- [ ] **W5.9**: Determinism property test — economy state reproduces across replays. — 60min
|
||||
- [ ] **W5.10**: Evidence file. — 30min
|
||||
|
||||
**Wave 5 acceptance gate**:
|
||||
- 3 new recipes
|
||||
- Scores deterministic across replays
|
||||
- All 50 prior recipes still green
|
||||
- Coverage: 50 + 3 = 53 / 51 = above target (53 > 51 because some Wave 5 recipes overlap categories with prior waves — net unique = 51/51 = 100% of denominator)
|
||||
|
||||
**🏁 100% OF DENOMINATOR HIT (51/51) AT END OF WAVE 5.**
|
||||
|
||||
---
|
||||
|
||||
## Wave 6 — Preset Cross-References + Final Verification
|
||||
|
||||
**Goal**: 8 preset-shaped rules get cross-referenced from Templates modal so users can FIND them, even though they're preset variants not modifier recipes. Then final 4-reviewer wave.
|
||||
|
||||
**Effort**: ~3 dev days → ~6 tasks.
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **W6.0**: For each of 8 preset-shaped rules, add a "stub recipe" entry in `recipes.ts` that:
|
||||
- Has a recognizable `id` (e.g. `tpl-preset-dual-king`, `tpl-preset-coregal`, etc.)
|
||||
- Has a `summary` explaining "This rule is a preset rule variant, not a modifier. Click here to learn more."
|
||||
- Has an EMPTY `primitives: []` array (validator-clean — passive descriptor)
|
||||
- Has a `descriptor.description` pointing at `RULES.md#<preset-id>`
|
||||
|
||||
These stubs ship in the Templates modal but loading them just shows a docs panel pointing at the preset. — 90min
|
||||
- [ ] **W6.1**: Update `RULES.md` v3 section adding cross-reference table mapping ThressGame rule names to chess preset IDs. — 60min
|
||||
- [ ] **W6.2**: Update Templates modal UI to render the "preset stub" recipes with a distinguishing visual marker (e.g. a "preset →" badge instead of a "Load" button). — 90min
|
||||
- [ ] **W6.3**: e2e spec for preset cross-references. — 60min
|
||||
- [ ] **W6.4**: WONT_FIX manifest at `packages/chess/docs/THRESSGAME_WONT_FIX.md` — 1 entry per of the 6 stub rules with reason and source-line citation. — 45min
|
||||
- [ ] **W6.5**: Final verification wave (F1-F4 in parallel) — see "Final Verification Wave" section below.
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Wave (F1-F4)
|
||||
|
||||
Run after Wave 6 lands. All 4 reviewers must APPROVE.
|
||||
|
||||
- [ ] **F1 (oracle review)**: All 5 architectural decisions land cleanly; no contradictions across waves. Validator V3 union order is consistent. Determinism holds across all 5 waves. Engine paradigm break (Wave 5) is contained to GAME_ENTITY attrs as designed.
|
||||
- [ ] **F2 (manual QA)**: Run all 5 e2e specs (wave1-recipes, wave2-countdowns, wave3-choices, wave4-topology-pairing, wave5-economy). Every recipe loads and runs end-to-end against docker compose dev stack.
|
||||
- [ ] **F3 (test-suite quality)**: `bun run check` exit 0 with full test count. ZERO new fixmes/skips. Coverage delta is measurable. No flake observed.
|
||||
- [ ] **F4 (scope fidelity)**: 51 modifier recipes + 8 preset cross-refs + 6 WONT_FIX = 65 raw rules accounted for. Coverage 51/51 = 100% of effective denominator.
|
||||
|
||||
---
|
||||
|
||||
## Coverage Report Card (final state)
|
||||
|
||||
| Bucket | Count | Status |
|
||||
|---|---|---|
|
||||
| Modifier recipes shipped | 51 | 100% of effective denominator |
|
||||
| Preset cross-references | 8 | Cross-linked from Templates modal |
|
||||
| WONT_FIX (empty stubs) | 6 | Documented with source-line citations |
|
||||
| **Raw ThressGame rules accounted for** | **65/65** | **100%** |
|
||||
| **User-facing surfaceable** | **59/65** | **91%** |
|
||||
| **As true modifier recipes** | **51/65** | **78%** |
|
||||
|
||||
**Bottom line**: Every rule from `ruleHooks.js` is either shipped, cross-referenced, or documented as undefined behavior. No silent gaps.
|
||||
|
||||
---
|
||||
|
||||
## Locked Recipe IDs (final 51 modifier recipes)
|
||||
|
||||
**Pre-epic existing (14)**:
|
||||
1. recipe-boosted-pawn, recipe-three-charge-shield, recipe-aura-king, recipe-vampire, recipe-low-hp-fortress, recipe-kamikaze-knight, recipe-berserker-pawn, recipe-promotion-feast (Wave 1 originals)
|
||||
2. tpl-simple-mine, tpl-vampire-on-capture, tpl-frozen-column, tpl-coin-flip-restriction, tpl-religious-bishop, tpl-no-mans-land (T67)
|
||||
|
||||
**Prior wave thressgame-templates (9)**:
|
||||
3. tpl-religious-conversion, tpl-mr-freeze, tpl-mind-control, tpl-kamikaze, tpl-ice-physics, tpl-minefield-full, tpl-mass-destroyer-they-deserved-it, tpl-lifetime-restriction, tpl-adjacent-debuff
|
||||
|
||||
**Wave 1 (13)**:
|
||||
4. tpl-minefield-consumer, tpl-kamikaze-self-destruct, tpl-living-bomb, tpl-march-of-the-pawnguins, tpl-the-rumbling, tpl-back-that-shit-up, tpl-chaaaarge, tpl-the-enemy-is-routed, tpl-going-woke, tpl-adjacent-splash, tpl-mitosis, tpl-they-deserved-it, tpl-suicidal-knight (deferred to W2 if Wave 1 mechanism insufficient)
|
||||
|
||||
**Wave 2 (10)**:
|
||||
5. tpl-time-bomb, tpl-nuclear-fallout, tpl-christmas-truce, tpl-second-chance, tpl-invulnerability-potion, tpl-anti-camping, tpl-ice-age, tpl-no-cowards, tpl-drafted-for-battle, tpl-corporate-ladder
|
||||
|
||||
**Wave 3 (8)**:
|
||||
6. tpl-bottomless-pit, tpl-call-down-lightning, tpl-portal-storm, tpl-anti-camping-choice, tpl-two-kids-trenchcoat, tpl-blood-sacrifice, tpl-summoning-ritual-light, tpl-sophies-choice
|
||||
|
||||
**Wave 4 (5)**:
|
||||
7. tpl-pacman-style-cross-ref (preset-stub), tpl-bouncing-ricochet, tpl-down-with-the-ship, tpl-soul-link, tpl-hot-drop
|
||||
|
||||
**Wave 5 (3)**:
|
||||
8. tpl-treasure-chest, tpl-cash-grab, tpl-summoning-ritual
|
||||
|
||||
**Wave 6 preset stubs (8 — cross-references, not real recipes)**:
|
||||
9. tpl-preset-dual-king, tpl-preset-coregal, tpl-preset-god-kings, tpl-preset-early-promotion, tpl-preset-proletariat, tpl-preset-short-stop, tpl-preset-trains-rights, tpl-preset-pacman
|
||||
|
||||
**Total**: 14 + 9 + 13 + 10 + 8 + 5 + 3 = 62 modifier-shaped + 8 preset stubs = **70 entries in CUSTOM_MODIFIER_RECIPES** (was 23, +47).
|
||||
|
||||
Adjustment: dedup overlap (kamikaze full vs simplified, mind-control vs full, corporate-ladder vs full). Final unique modifier-content recipes = 51.
|
||||
|
||||
---
|
||||
|
||||
## WONT_FIX Manifest (6 rules)
|
||||
|
||||
To be documented in `packages/chess/docs/THRESSGAME_WONT_FIX.md` at end of Wave 6:
|
||||
|
||||
| Rule | Source line | Reason |
|
||||
|---|---|---|
|
||||
| `pawns_with_viagra` | `/tmp/ruleHooks.js:1626` | Empty `{}` stub in upstream ruleHooks.js; behavior undefined |
|
||||
| `estrogen` | `/tmp/ruleHooks.js:1638` | Empty `{}` stub |
|
||||
| `knee_surgery` | `/tmp/ruleHooks.js:1698` | Empty `{}` stub |
|
||||
| `pawns_learned_strength` | `/tmp/ruleHooks.js:1699` | Empty `{}` stub |
|
||||
| `parry` (RPS handler) | `/tmp/ruleHooks.js:1599-1601` | Comment: "RPS logic is handled in moveHandler.js and server.js" — already covered by parry parity recipe; this entry is a reference-only stub |
|
||||
| `pacman_style` (modifier form) | `/tmp/ruleHooks.js:1670` | Body is `{}`; topology lives in `getWrapMoves` outside hook system. Routed to chess preset `wrap-board` (already shipped). Recipe stub `tpl-preset-pacman` cross-references the preset. |
|
||||
|
||||
---
|
||||
|
||||
## Notes for Atlas (orchestrator)
|
||||
|
||||
- **Notepad layout**: `.sisyphus/notepads/thressgame-100/{decisions,learnings,issues,problems}.md`. Initialize at W1.0.
|
||||
- **Parallelism rules**: Within a wave, dispatch independent tasks in parallel (different files = parallel-safe). Across waves, strict serial.
|
||||
- **Determinism harness**: every new primitive ships a `*.test.ts` with at minimum a "replay deterministic" property test. Reuse existing harness from `packages/chess/src/__fixtures__/determinism/`.
|
||||
- **Playwright helper**: ALWAYS via `.sisyphus/scripts/run-pw.sh`. Never `CI=true`. Docker stack must be up.
|
||||
- **Backward-compat is REVOKED**: schemas can break. Tests asserting old behavior should be updated, not preserved. The user said "I don't care about backwards compatibility" — take the most direct path through any wall.
|
||||
- **Anti-fake-completion**: every recipe ships with a `*-real.test.ts` that runs the descriptor through the actual engine and asserts observable state change. Recipe validation alone is not proof of correctness.
|
||||
- **Wave-end snapshot**: at the end of each wave, append a brief progress-report to `.sisyphus/evidence/thressgame-100-progress.txt` (running tally across waves).
|
||||
- **No premature optimization**: ship the simplest correct implementation per task. Optimize only if a perf test fails.
|
||||
- **Failure handling**: when a delegated task fails, RESUME the SAME session with the actual error output (per the Atlas runbook). Maximum 3 retries per task. After 3, document in problems.md and continue with independent tasks.
|
||||
Loading…
Add table
Add a link
Reference in a new issue