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.
This commit is contained in:
parent
abc5c863fd
commit
a159299818
1 changed files with 978 additions and 0 deletions
978
.sisyphus/plans/rule-variants-v2.md
Normal file
978
.sisyphus/plans/rule-variants-v2.md
Normal file
|
|
@ -0,0 +1,978 @@
|
|||
# Rule Variants — Greenchess cat=4 Preset Suite (execution plan)
|
||||
|
||||
> **For the agent picking this up:** This is the execution-ready successor to
|
||||
> `.sisyphus/plans/rule-variants.md` (design doc, Momus-approved). The design
|
||||
> doc has the full rationale + risks + hook architecture sketch. THIS file is
|
||||
> the step-by-step to-do list with commit discipline and verification gates,
|
||||
> formatted like the recently-shipped `modifier-profiles-t3.md`. Read both
|
||||
> — this one for the work, the design doc for the WHY.
|
||||
|
||||
## TL;DR
|
||||
|
||||
> Ship 4 new preset-API hooks (`getRoyalPieces`, `filterLegalMoves`,
|
||||
> `shouldAdvanceTurn`, `overridePieceMoves`) plus `HalfMovesThisTurn` game
|
||||
> fact, then 14 rule-variant presets built on them. Tier 1 (4 presets)
|
||||
> closes deferrals from the starting-layouts plan. Tier 2 (10 presets)
|
||||
> rounds out greenchess cat=4 coverage. Tier 3 (`royalty-transfer`) is
|
||||
> explicitly deferred.
|
||||
>
|
||||
> **Deliverables**:
|
||||
> - 4 additive hook signatures on `PresetDef`
|
||||
> - `HalfMovesThisTurn` game fact + turn-advance gating
|
||||
> - 14 preset implementations with 8-15 behavioral tests each
|
||||
> - `dual-classic` layout (2 kings per side) — needed by `dual-king` preset
|
||||
> - Rule-variant Playwright e2e smoke (3 representative variants)
|
||||
> - `PRESET-API.md` hook-ordering diagram + preset gallery
|
||||
> - Lobby RulesDrawer preset grouping (King / Objective / Movement / Multimove)
|
||||
>
|
||||
> **Estimated Effort**: Large (31 tasks, 6 phases)
|
||||
> **Parallel Execution**: YES within each phase; phases are hard-gated
|
||||
> **Critical Path**: Phase A hooks → Phase B T1 presets → Phase C/D/E T2 presets → Phase F integration + e2e
|
||||
> **Explicit Deferral**: `royalty-transfer` — needs mid-game "which royal is active" state toggle; UX outside "one move at a time".
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
### What Shipped Last (commits `abc5c86` back to `cbe4a4b`)
|
||||
|
||||
- **modifier-profiles-t3** complete (32 tasks + 4 reviewers all APPROVE)
|
||||
plus audit follow-ups (gaps 1-7 + Q3.2/Q4.2/Q4.4/Q4.6/Q4.7).
|
||||
Codebase state: 1417 unit tests + 80/80 Playwright, all green. Master
|
||||
at `abc5c86`.
|
||||
- **starting-layouts** shipped: added Dunsany, Monster, Pawns-Only, Horde,
|
||||
Knightmate, Chess960 as layout-only presets. **Each of those layouts
|
||||
declared `suggestedPresets` pointing at presets that DON'T EXIST YET.**
|
||||
Tier 1 of this plan ships those presets.
|
||||
- **preset-flexibility-architecture** shipped: `PIECE_TYPE_REGISTRY`,
|
||||
`engine.presetState<T>(id)`, `engine.spawnPiece`, `engine.emitEffect`,
|
||||
scope-aware hook dispatch, `MoveRecord`/`engine.moveLog`,
|
||||
`onBeforeMove`/`onTurnStart` hooks all live. Everything this plan
|
||||
builds on is in place.
|
||||
|
||||
### What T3's Trip Taught Us
|
||||
|
||||
Load-time consumer integrity check pattern (T3 Q3.2, in
|
||||
`packages/chess/src/modifiers/primitives/manifest.ts`) converts
|
||||
silent-inert features into loud load-time failures. This plan uses the
|
||||
same pattern for the new hooks: every preset declaring a new hook is
|
||||
structural; we don't need a manifest for hooks themselves, but document
|
||||
hook-ordering semantics clearly in `PRESET-API.md` so future contributors
|
||||
can't accidentally compose a first-wins hook in a commutative way.
|
||||
|
||||
### Key File Anchors (verified live)
|
||||
|
||||
- `packages/chess/src/rules/check.ts:83` — `isInCheck(session, color)`:
|
||||
hardcoded to `"king"` via `findKingPosition(session, color)`.
|
||||
- `packages/chess/src/rules/checkmate.ts:80`,
|
||||
`packages/chess/src/rules/stalemate.ts:92,105` — call `isInCheck` and
|
||||
iterate "every king" implicitly.
|
||||
- `packages/chess/src/engine.ts:855` — `getAllLegalMoves()`: aggregation
|
||||
point where `filterLegalMoves` will hook in (POST self-check filter).
|
||||
- `packages/chess/src/engine.ts:964` — `applyMove`: turn-flip lives here;
|
||||
`shouldAdvanceTurn` intercepts before the flip.
|
||||
- `packages/chess/src/engine.ts` — `getAllLegalMoves` dispatches per-piece
|
||||
via `PIECE_TYPE_REGISTRY.get(type).generateMoves`; `overridePieceMoves`
|
||||
runs BEFORE this dispatch.
|
||||
- `packages/chess/src/presets/registry.ts:297-500` — `PresetDef` hook
|
||||
surface. New hooks slot in here.
|
||||
|
||||
### Out of Scope (deferred)
|
||||
|
||||
- **Royalty Transfer Chess** (T3 in the design doc). Needs mid-game
|
||||
"which king is active" flip triggered by a non-move action. UX outside
|
||||
"one move at a time". Post-v1.
|
||||
- **4+ player variants**. Engine assumes exactly two sides.
|
||||
- **Non-8×8 variants** from other greenchess categories (Capablanca,
|
||||
Janus). Needs board-dimensions refactor.
|
||||
- **PGN notation with non-king royalty** (e.g., `N#` in Knightmate).
|
||||
Rabbit hole; notation stays king-centric; move log displays via
|
||||
`describeMoveEffect` instead.
|
||||
- **Check-banner multi-royal indicator UX**. Risk 4 in the design doc;
|
||||
deferred to a small follow-up UI task after this plan lands.
|
||||
- **Extinction Chess target-type UI chip** (D.3 of the design doc).
|
||||
Preset ships with configurable `presetState({targetType})`; a
|
||||
drawer-UI chip for cycling targets is a follow-up.
|
||||
|
||||
---
|
||||
|
||||
## Conventions for the Executing Agent
|
||||
|
||||
### Repo Discipline
|
||||
|
||||
- Strict TypeScript: no `as any`, no `@ts-ignore`, no `as unknown as X`.
|
||||
One judicious `as X` boundary cast is OK with JSDoc explaining why.
|
||||
Audit with `grep -rn "as unknown as" packages/chess/src packages/server/src --include='*.ts' --include='*.tsx' | grep -v '\.test\.'`.
|
||||
- Pre-commit hook runs `bun run check` (typecheck + lint + vitest). Must
|
||||
be green before every commit. NEVER bypass with `--no-verify`.
|
||||
- Tests: `bun run test` (vitest), NOT `bun test`. Playwright from repo
|
||||
root: `bunx playwright test`. For multiplayer scenarios the server
|
||||
must be running:
|
||||
`tmux new-session -d -s ws-server -c /home/joey/Projects/rules 'bun run packages/server/src/index.ts'`
|
||||
(the dev server does NOT hot-reload protocol changes — restart after
|
||||
any `packages/server/src/protocol.ts` edit).
|
||||
- Atomic commits per task. Conventional-commit messages
|
||||
(`feat(engine): ...`, `test(e2e): ...`, `docs(adr): ...`,
|
||||
`chore(sisyphus): ...`).
|
||||
- Notepad at `.sisyphus/notepads/rule-variants/` — create on plan start.
|
||||
APPEND (never overwrite) under `## [YYYY-MM-DD HH:MM] Task: X.Y`
|
||||
headings.
|
||||
|
||||
### T3-Learned Tactics (don't repeat the pain)
|
||||
|
||||
- **Tool-cap fragility**: delegating a large phase to a single
|
||||
deep/visual agent reliably hits the 200-tool-call limit. Prefer
|
||||
shipping each preset as its OWN delegation OR doing them inline.
|
||||
Never batch 15+ file creations under one agent.
|
||||
- **Parallel waves**: run at most 3-5 agents per wave, each scoped to a
|
||||
disjoint file set. A preset + its own test file is one good unit.
|
||||
- **Commit discipline for delegated work**: if an agent is at risk of
|
||||
hitting the tool cap, INSTRUCT IT to commit incrementally every 2-3
|
||||
files. Uncommitted work at cancellation requires orchestrator cleanup.
|
||||
- **Schema v3 ↔ v4 mirror**: if a task requires server protocol changes
|
||||
(this plan has none — all hooks are chess-package-internal), be
|
||||
vigilant. Cross-package parity test in
|
||||
`packages/server/src/custom-modifier-wire-parity.test.ts` is the
|
||||
template for how to prove agreement.
|
||||
- **`test.fixme` policy**: only use for scenarios that genuinely need
|
||||
engine wiring not yet in place. Every `fixme` in this plan's e2e
|
||||
task MUST be marked with a `// blocked on: TASK_ID` comment so
|
||||
cleanup knows what unblocks it.
|
||||
|
||||
### `MUST` / `SHOULD` / `MAY`
|
||||
|
||||
- `MUST` = blocks phase completion.
|
||||
- `SHOULD` = expected but permissible to defer with written rationale
|
||||
in the notepad.
|
||||
- `MAY` = optional; do it if tool budget allows.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Sketch (recap — full version in design doc)
|
||||
|
||||
### Hook signatures (verbatim)
|
||||
|
||||
```ts
|
||||
// rules/check.ts + registry.ts
|
||||
interface RoyalContext {
|
||||
readonly engine: ChessEngine;
|
||||
readonly color: "white" | "black";
|
||||
}
|
||||
interface PresetDef {
|
||||
// Phase A.1 — return the set of piece ENTITY IDs that count as
|
||||
// royal for this color. Undefined = "don't contribute" (engine falls
|
||||
// back to union of other presets' returns; empty union → default
|
||||
// {PieceType === "king"}).
|
||||
readonly getRoyalPieces?: (ctx: RoyalContext) => readonly EntityId[] | undefined;
|
||||
|
||||
// Phase A.2 — post-aggregation filter on the per-color legal-move
|
||||
// list. Applied in preset-registration order AFTER the self-check
|
||||
// filter. Returning a subset is the only sanctioned way to express
|
||||
// compulsory-capture-style rules.
|
||||
readonly filterLegalMoves?: (
|
||||
ctx: FilterLegalMovesContext,
|
||||
) => readonly LegalMove[];
|
||||
|
||||
// Phase A.3 — returning false cancels the turn flip for this
|
||||
// applyMove. First false wins. Undefined = no opinion. Engine
|
||||
// increments HalfMovesThisTurn BEFORE polling; presets read it via
|
||||
// ctx.halfMovesThisTurn.
|
||||
readonly shouldAdvanceTurn?: (
|
||||
ctx: TurnAdvanceContext,
|
||||
) => boolean | undefined;
|
||||
|
||||
// Phase A.4 — REPLACE the default move generator for this piece.
|
||||
// First non-undefined return wins (documented as a guardrail, not
|
||||
// an opt-in; conflicts should be caught via incompatibleWith).
|
||||
readonly overridePieceMoves?: (
|
||||
engine: ChessEngine,
|
||||
pieceId: EntityId,
|
||||
) => readonly LegalMove[] | undefined;
|
||||
}
|
||||
|
||||
// engine.ts
|
||||
// The move-generation dispatch order (documented in PRESET-API.md):
|
||||
// 1) overridePieceMoves — first match wins, skips default
|
||||
// 2) PIECE_TYPE_REGISTRY — default per-type generator
|
||||
// 3) getExtraMoves — each preset appends
|
||||
// 4) filterMoves — each preset filters per-piece
|
||||
// 5) aggregate all pieces
|
||||
// 6) self-check filter — suppressible via shouldFilterSelfCheck
|
||||
// 7) filterLegalMoves — each preset runs in registration order
|
||||
```
|
||||
|
||||
### New game fact: `HalfMovesThisTurn`
|
||||
|
||||
```ts
|
||||
// packages/chess/src/schema.ts — ChessAttrMap additions
|
||||
HalfMovesThisTurn: number; // on GAME_ENTITY. Resets to 0 on turn flip.
|
||||
```
|
||||
|
||||
Seeded in `starting-position.ts` alongside `Turn`. Incremented in
|
||||
`applyMove` BEFORE the turn-flip decision. Reset to 0 when the flip
|
||||
happens.
|
||||
|
||||
### Royal-piece dispatch (pseudo)
|
||||
|
||||
```ts
|
||||
// engine.ts, private
|
||||
getActiveRoyalEntityIds(color): EntityId[] {
|
||||
const acc = new Set<EntityId>();
|
||||
let anyPresetContributed = false;
|
||||
for (const entry of this.activePresets.list()) {
|
||||
const def = PRESET_REGISTRY.get(entry.id);
|
||||
const result = def?.getRoyalPieces?.({ engine: this, color });
|
||||
if (result === undefined) continue;
|
||||
anyPresetContributed = true;
|
||||
for (const id of result) acc.add(id);
|
||||
}
|
||||
if (!anyPresetContributed) {
|
||||
// Default: every piece with PieceType === "king" of this color.
|
||||
return defaultKingIds(this.session, color);
|
||||
}
|
||||
return [...acc];
|
||||
}
|
||||
```
|
||||
|
||||
Empty royal set = "no royalty" (Suicide, Capture-all interpretation).
|
||||
`isInCheck` / `isCheckmate` / `isStalemate` handle this degenerate case
|
||||
by returning `false` always when the set is empty.
|
||||
|
||||
### Tier 1 → Tier 2 dependency graph
|
||||
|
||||
Tier 1 presets CAN ship without Tier 2. Tier 2 presets MAY depend on
|
||||
Tier 1 hooks but NOT on Tier 1 presets behaviorally.
|
||||
|
||||
- `knightmate-rules` → getRoyalPieces only
|
||||
- `double-move` → shouldAdvanceTurn + HalfMovesThisTurn
|
||||
- `monster-rules` → shouldAdvanceTurn + scope-aware dispatch
|
||||
- `first-promotion-wins` → onAfterMove (existing) + onCheckGameResult
|
||||
(existing) + shouldFilterSelfCheck (existing). No new hooks needed
|
||||
but grouped in T1 because it closes a layouts deferral.
|
||||
- `coregal`, `dual-king`, `weak-dual-king` → getRoyalPieces only
|
||||
- `suicide-chess` → filterLegalMoves + getRoyalPieces (empty)
|
||||
- `capture-all`, `extinction-chess` → existing hooks only
|
||||
- `berolina-pawns`, `berolina-pawns-2` → overridePieceMoves
|
||||
- `bouncing-pieces`, `bouncing-pieces-2` → existing `getExtraMoves`
|
||||
|
||||
---
|
||||
|
||||
## Phase A — Hook API Extensions (architecture)
|
||||
|
||||
**Goal**: Add 4 hooks + `HalfMovesThisTurn` fact; zero behavioral change
|
||||
to any existing preset/game.
|
||||
|
||||
- [ ] A.1. **`getRoyalPieces` hook + engine royal-dispatch + isInCheck refactor**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/registry.ts`: add `RoyalContext` type
|
||||
and `getRoyalPieces` hook signature on `PresetDef`.
|
||||
- `packages/chess/src/rules/check.ts`: `isInCheck(session, color)`
|
||||
gains an optional 3rd param `royalEntityIds?: readonly EntityId[]`.
|
||||
When provided, iterate those entities' positions instead of calling
|
||||
`findKingPosition`. When absent, keep current behaviour (back-compat
|
||||
for callers that don't pass the param).
|
||||
- `packages/chess/src/rules/checkmate.ts` + `stalemate.ts`: same
|
||||
optional-param widening.
|
||||
- `packages/chess/src/engine.ts`: add private
|
||||
`getActiveRoyalEntityIds(color): EntityId[]` that unions every
|
||||
active preset's `getRoyalPieces` returns. Empty-union fallback =
|
||||
default king entities via `findKingPosition`-equivalent logic.
|
||||
Use this in every internal call to `isInCheck` /
|
||||
`isCheckmate` / `isStalemate` / `filterSelfCheckMoves`.
|
||||
- Handle empty-royal case: `isInCheck` / `isCheckmate` /
|
||||
`isStalemate` MUST return `false` when the royal set is empty
|
||||
(document + test).
|
||||
|
||||
**Files touched**: `packages/chess/src/presets/registry.ts`,
|
||||
`packages/chess/src/rules/check.ts`,
|
||||
`packages/chess/src/rules/checkmate.ts`,
|
||||
`packages/chess/src/rules/stalemate.ts`,
|
||||
`packages/chess/src/engine.ts`,
|
||||
`packages/chess/src/presets/royal-pieces.test.ts` (new).
|
||||
|
||||
**Verification**: `bun run check` green. All existing 1417 tests pass
|
||||
unchanged. The new `royal-pieces.test.ts` must include:
|
||||
- Prototype "all-kings-royal" preset returning `[king1, king2]`
|
||||
when a piece spawned as a king exists — isInCheck behaves
|
||||
correctly.
|
||||
- Empty-royal preset (`getRoyalPieces → []`) — isInCheck returns
|
||||
false, no crash.
|
||||
- Two presets contributing overlapping sets — union dedupes.
|
||||
- No preset contributes — default king detection path unchanged.
|
||||
|
||||
**Recommended Agent Profile**: `deep` OR orchestrator-direct.
|
||||
|
||||
**Parallelization**: Phase A. Blocks: B.1, C.1, C.2, C.3, D.1, D.2.
|
||||
Blocked by: none.
|
||||
|
||||
**Commit**: `feat(engine): getRoyalPieces hook + engine royal dispatch`
|
||||
|
||||
- [ ] A.2. **`filterLegalMoves` hook**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/registry.ts`: add
|
||||
`FilterLegalMovesContext { readonly engine: ChessEngine; readonly color: "white" | "black"; readonly moves: readonly LegalMove[] }`
|
||||
+ `filterLegalMoves` hook.
|
||||
- `packages/chess/src/engine.ts` `getAllLegalMoves`: after the
|
||||
existing self-check filter (step 6), iterate active presets'
|
||||
`filterLegalMoves` in registration order, passing the
|
||||
accumulated list through each. Document "order matters; use
|
||||
incompatibleWith when composition would be lossy".
|
||||
|
||||
**Files touched**: `packages/chess/src/presets/registry.ts`,
|
||||
`packages/chess/src/engine.ts`,
|
||||
`packages/chess/src/presets/filter-legal-moves.test.ts` (new).
|
||||
|
||||
**Verification**:
|
||||
- Prototype "no-a-file" preset dropping every a-file source/target
|
||||
move; assert across multiple pieces.
|
||||
- No active preset with hook = getAllLegalMoves unchanged.
|
||||
|
||||
**Parallelization**: Phase A. Blocks: D.1. Blocked by: none.
|
||||
|
||||
**Commit**: `feat(engine): filterLegalMoves hook`
|
||||
|
||||
- [ ] A.3. **`shouldAdvanceTurn` hook + `HalfMovesThisTurn` tracking**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/schema.ts`: add `HalfMovesThisTurn: number`
|
||||
to `ChessAttrMap`.
|
||||
- `packages/chess/src/starting-position.ts`: seed
|
||||
`HalfMovesThisTurn = 0` on `GAME_ENTITY` wherever `Turn` is seeded.
|
||||
- `packages/chess/src/engine.ts` `applyMove`:
|
||||
1. BEFORE the turn-flip decision, increment `HalfMovesThisTurn`.
|
||||
2. Build `TurnAdvanceContext { mover, halfMovesThisTurn }`.
|
||||
3. Iterate active presets' `shouldAdvanceTurn`. First `false` wins
|
||||
→ skip the flip. Otherwise flip Turn and reset
|
||||
`HalfMovesThisTurn = 0`.
|
||||
- `packages/chess/src/presets/registry.ts`: new
|
||||
`TurnAdvanceContext` + hook.
|
||||
- Snapshot/restore: verify `HalfMovesThisTurn` round-trips via
|
||||
`session.allFacts` (save/load, game.state snapshot).
|
||||
|
||||
**Files touched**: `packages/chess/src/schema.ts`,
|
||||
`packages/chess/src/starting-position.ts`,
|
||||
`packages/chess/src/engine.ts`,
|
||||
`packages/chess/src/presets/registry.ts`,
|
||||
`packages/chess/src/presets/turn-advance.test.ts` (new).
|
||||
|
||||
**Verification**:
|
||||
- Prototype "never-flip" preset; white keeps moving forever.
|
||||
- Fact resets on flip; increments on each non-cancelled move.
|
||||
- Existing 1417 tests unchanged (no preset implements the hook).
|
||||
|
||||
**Parallelization**: Phase A. Blocks: B.2, B.3. Blocked by: none.
|
||||
|
||||
**Commit**: `feat(engine): shouldAdvanceTurn hook + HalfMovesThisTurn fact`
|
||||
|
||||
- [ ] A.4. **`overridePieceMoves` hook**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/registry.ts`: new `overridePieceMoves`
|
||||
hook on `PresetDef`.
|
||||
- `packages/chess/src/engine.ts`: in per-piece move generation,
|
||||
BEFORE `PIECE_TYPE_REGISTRY.get(type).generateMoves` runs, iterate
|
||||
presets' `overridePieceMoves(engine, pieceId)`. First non-undefined
|
||||
→ replace the generator output for this piece and skip steps 2-3
|
||||
(default generator + getExtraMoves). Still apply steps 4 (filterMoves)
|
||||
and 7 (filterLegalMoves).
|
||||
- Collision handling: when a second preset also returns non-undefined
|
||||
for the same piece, log a dev-mode console warning with both preset
|
||||
ids. Don't throw (let `incompatibleWith` handle this at config
|
||||
time).
|
||||
|
||||
**Files touched**: `packages/chess/src/presets/registry.ts`,
|
||||
`packages/chess/src/engine.ts`,
|
||||
`packages/chess/src/presets/override-piece-moves.test.ts` (new).
|
||||
|
||||
**Verification**:
|
||||
- Prototype "lame-knight" preset replacing knight moves with
|
||||
single-square orthogonal; knight behaves differently only with
|
||||
preset active.
|
||||
- getExtraMoves still contributes when no override is declared.
|
||||
- Collision warning fires in dev mode (verify via console spy).
|
||||
|
||||
**Parallelization**: Phase A. Blocks: E.1, E.2. Blocked by: none.
|
||||
|
||||
**Commit**: `feat(engine): overridePieceMoves hook`
|
||||
|
||||
- [ ] A.5. **Phase A verification gate + PRESET-API.md hook-ordering doc**
|
||||
|
||||
**What to do**:
|
||||
- Run `bun run check` — expect 1417 + new prototype tests green.
|
||||
- Run full Playwright suite — expect 80/80 (no behavioral change).
|
||||
- `packages/chess/docs/PRESET-API.md`: add "Hook ordering" section
|
||||
with the 7-step dispatch diagram from the design doc. Worked
|
||||
example for each of the 4 new hooks.
|
||||
- Append phase summary to `.sisyphus/notepads/rule-variants/learnings.md`.
|
||||
|
||||
**Parallelization**: Phase A finale. Blocks: Phase B onward.
|
||||
|
||||
**Commit**: `docs(preset-api): hook ordering + Phase A summary`
|
||||
|
||||
---
|
||||
|
||||
## Phase B — Tier 1 Presets (close layouts-plan deferrals)
|
||||
|
||||
**Goal**: 4 T1 presets, each unblocks its matching layout's
|
||||
`suggestedPresets` chip.
|
||||
|
||||
- [ ] B.1. **`knightmate-rules` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/knightmate-rules.ts`: implements
|
||||
`getRoyalPieces` returning every Knight entity of `color`.
|
||||
Declares `requires: []` (works with any layout, best with
|
||||
Knightmate). `incompatibleWith: ["coregal", "dual-king",
|
||||
"weak-dual-king"]`.
|
||||
- `packages/chess/src/presets/knightmate-rules.test.ts`: 10+ tests:
|
||||
(a) king moves into attacked square legally, (b) knight in check
|
||||
restricts moves to resolving check, (c) checkmating the last
|
||||
knight ends the game, (d) losing all knights-but-one is not
|
||||
game-over if any knight survives but IS game-over if ALL royals
|
||||
captured, (e) composition with piece-hp — attacking a knight
|
||||
drops HP instead of killing.
|
||||
- `packages/chess/src/layouts/knightmate.ts`: populate
|
||||
`suggestedPresets: ["knightmate-rules"]`.
|
||||
|
||||
**Parallelization**: Phase B, runs with B.2/B.3/B.4 concurrently
|
||||
(disjoint files). Blocks: F.2, F.4. Blocked by: A.1.
|
||||
|
||||
**Commit**: `feat(presets): knightmate-rules`
|
||||
|
||||
- [ ] B.2. **`double-move` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/double-move.ts`: implements
|
||||
`shouldAdvanceTurn` returning `false` when
|
||||
`halfMovesThisTurn < 2`. No royal override — uses default king
|
||||
logic. `incompatibleWith: ["monster-rules"]`.
|
||||
- `packages/chess/src/presets/double-move.test.ts`: 12+ tests:
|
||||
(a) white plays two moves before black, (b) halfMovesThisTurn
|
||||
resets on flip, (c) check on first half-move — opponent must
|
||||
still respond on their first half-move (design call: check is
|
||||
allowed, opponent is NOT forced to block on move 1 — they can
|
||||
play any legal move including an intermediate move, and check
|
||||
filtering applies on each half-move separately; document this
|
||||
interpretation), (d) composition with piece-hp, (e) composition
|
||||
with king-heals (heal fires on full-turn-flip only).
|
||||
|
||||
**Parallelization**: Phase B concurrent. Blocks: F.4. Blocked by: A.3.
|
||||
|
||||
**Commit**: `feat(presets): double-move`
|
||||
|
||||
- [ ] B.3. **`monster-rules` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/monster-rules.ts`: scope-aware.
|
||||
For `scope: "white"`, `shouldAdvanceTurn` returns `false` when
|
||||
`mover === "white"` and `halfMovesThisTurn < 2`. For
|
||||
`scope: "black"`, behaves as default. `incompatibleWith:
|
||||
["double-move", "suicide-chess", "capture-all"]`.
|
||||
- `packages/chess/src/presets/monster-rules.test.ts`: 10+ tests.
|
||||
White moves twice, black moves once. Combined with Monster
|
||||
layout → white plays king + 4 pawns; verify both moves
|
||||
resolve before black responds.
|
||||
- `packages/chess/src/layouts/monster.ts`: populate
|
||||
`suggestedPresets: ["monster-rules"]`.
|
||||
|
||||
**Parallelization**: Phase B concurrent. Blocks: F.2, F.4. Blocked by: A.3.
|
||||
|
||||
**Commit**: `feat(presets): monster-rules`
|
||||
|
||||
- [ ] B.4. **`first-promotion-wins` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/first-promotion-wins.ts`:
|
||||
`onAfterMove` inspects `engine.moveLog[last].promotion`; if
|
||||
non-null, records winner via `engine.presetState`.
|
||||
`onCheckGameResult` returns winner if recorded, else `undefined`.
|
||||
Also implements `shouldFilterSelfCheck → false` so a pawn under
|
||||
check can still promote. `incompatibleWith: ["capture-to-win",
|
||||
"last-piece-standing", "extinction-chess", "suicide-chess",
|
||||
"capture-all"]`.
|
||||
- `packages/chess/src/presets/first-promotion-wins.test.ts`: 8+
|
||||
tests: (a) first pawn promotion wins, (b) works with Pawns-Only
|
||||
layout, (c) composition with double-move (second half-move
|
||||
promotion also wins), (d) composition with piece-hp.
|
||||
- `packages/chess/src/layouts/pawns-only.ts`: populate
|
||||
`suggestedPresets: ["first-promotion-wins"]`.
|
||||
|
||||
**Parallelization**: Phase B concurrent. Blocks: F.2, F.4. Blocked by: A.1, A.3.
|
||||
|
||||
**Commit**: `feat(presets): first-promotion-wins`
|
||||
|
||||
- [ ] B.5. **Phase B verification gate**
|
||||
|
||||
**What to do**: `bun run check` green. Manual Playwright smoke:
|
||||
open lobby, pick Knightmate layout → chip for knightmate-rules
|
||||
appears → enabling it + Play Solo starts a game with knight as
|
||||
the royal.
|
||||
|
||||
**Commit**: `chore(sisyphus): Phase B (Tier 1 presets) complete`
|
||||
|
||||
---
|
||||
|
||||
## Phase C — Tier 2 Royal-Variant Presets
|
||||
|
||||
**Goal**: 3 multi-royal presets exercising `getRoyalPieces` in
|
||||
broader ways.
|
||||
|
||||
- [ ] C.1. **`coregal` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/coregal.ts`: `getRoyalPieces`
|
||||
returns union of king + queen entities for `color`.
|
||||
`incompatibleWith: ["knightmate-rules", "dual-king",
|
||||
"weak-dual-king", "suicide-chess"]`.
|
||||
- `packages/chess/src/presets/coregal.test.ts`: 10+ tests:
|
||||
(a) mating only the king ends the game (queen can't save),
|
||||
(b) mating only the queen ends the game, (c) no queen on board
|
||||
→ behaves as FIDE, (d) pins affect the queen (moving pinned queen
|
||||
leaves king in check → illegal), (e) composition with piece-hp —
|
||||
queen loses HP on capture attempts.
|
||||
|
||||
**Parallelization**: Phase C concurrent. Blocks: F.1. Blocked by: A.1.
|
||||
|
||||
**Commit**: `feat(presets): coregal`
|
||||
|
||||
- [ ] C.2. **`dual-classic` layout + `dual-king` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/layouts/dual-classic.ts`: new layout based
|
||||
on FIDE but with 2 kings per side (e.g., kings on e1 + d1 for
|
||||
white; queens moved to a file or omitted; document the choice).
|
||||
Register in layouts barrel.
|
||||
- `packages/chess/src/presets/dual-king.ts`: `getRoyalPieces`
|
||||
returns ALL king entities of `color`. Works with any layout but
|
||||
designed for dual-classic. `incompatibleWith: ["weak-dual-king",
|
||||
"coregal", "knightmate-rules"]`.
|
||||
- `packages/chess/src/presets/dual-king.test.ts`: 8+ tests. Mating
|
||||
EITHER king ends the game (strong variant). Pawn
|
||||
underpromotion-to-king increases the royal count — integration
|
||||
test with 3 kings.
|
||||
|
||||
**Parallelization**: Phase C concurrent. Blocks: F.1, F.2.
|
||||
Blocked by: A.1.
|
||||
|
||||
**Commit**: `feat(presets): dual-king + dual-classic layout`
|
||||
|
||||
- [ ] C.3. **`weak-dual-king` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/weak-dual-king.ts`: `getRoyalPieces`
|
||||
returns ALL king entities of `color`. Adds
|
||||
`onCheckGameResult` override: game-over only when EVERY royal
|
||||
of one side is captured OR all remaining royals are mated
|
||||
simultaneously. First mate of ONE king does NOT end the game
|
||||
— the other king plays on. `incompatibleWith: ["dual-king",
|
||||
"coregal", "knightmate-rules"]`.
|
||||
- `packages/chess/src/presets/weak-dual-king.test.ts`: 8+ tests:
|
||||
(a) one king lost → game continues, (b) both lost → game ends,
|
||||
(c) last king mated → game ends.
|
||||
|
||||
**Parallelization**: Phase C concurrent. Blocks: F.1. Blocked by: A.1.
|
||||
|
||||
**Commit**: `feat(presets): weak-dual-king`
|
||||
|
||||
- [ ] C.4. **Phase C verification gate**
|
||||
|
||||
**What to do**: `bun run check` green. Integration test: coregal
|
||||
+ piece-hp — queen loses HP but king is still the primary royal;
|
||||
attacker must checkmate AT LEAST ONE royal to end.
|
||||
|
||||
**Commit**: `chore(sisyphus): Phase C (royal variants) complete`
|
||||
|
||||
---
|
||||
|
||||
## Phase D — Tier 2 Objective-Variant Presets
|
||||
|
||||
**Goal**: 3 win-condition-override presets.
|
||||
|
||||
- [ ] D.1. **`suicide-chess` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/suicide-chess.ts`:
|
||||
- `filterLegalMoves`: if any move in the aggregated list has
|
||||
`isCapture === true`, return only captures.
|
||||
- `shouldFilterSelfCheck → false` (king is not royal).
|
||||
- `getRoyalPieces → []` (explicit empty).
|
||||
- `onCheckGameResult`: if one side has 0 pieces on the board,
|
||||
that side WINS (suicide inverts).
|
||||
- `incompatibleWith: ["capture-to-win", "last-piece-standing",
|
||||
"monster-rules", "knightmate-rules", "coregal", "dual-king",
|
||||
"weak-dual-king", "capture-all", "extinction-chess",
|
||||
"first-promotion-wins"]`.
|
||||
- `packages/chess/src/presets/suicide-chess.test.ts`: 14+ tests:
|
||||
(a) captures compulsory when any capture available, (b)
|
||||
non-captures legal otherwise, (c) losing all pieces wins,
|
||||
(d) pawn promotion still legal, (e) composition with piece-hp —
|
||||
an HP-tagged capture that doesn't kill still satisfies the
|
||||
compulsion (design call documented in-file).
|
||||
|
||||
**Parallelization**: Phase D concurrent. Blocks: F.1, F.4.
|
||||
Blocked by: A.1, A.2.
|
||||
|
||||
**Commit**: `feat(presets): suicide-chess`
|
||||
|
||||
- [ ] D.2. **`capture-all` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/capture-all.ts`:
|
||||
- `shouldFilterSelfCheck → false`.
|
||||
- `getRoyalPieces → []`.
|
||||
- `onCheckGameResult`: winner = side whose opponent has 0
|
||||
pieces.
|
||||
- `incompatibleWith: ["suicide-chess", "capture-to-win",
|
||||
"last-piece-standing", "extinction-chess",
|
||||
"first-promotion-wins"]`.
|
||||
- `packages/chess/src/presets/capture-all.test.ts`: 8+ tests.
|
||||
|
||||
**Parallelization**: Phase D concurrent. Blocks: F.1. Blocked by: A.1.
|
||||
|
||||
**Commit**: `feat(presets): capture-all`
|
||||
|
||||
- [ ] D.3. **`extinction-chess` preset (configurable target type)**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/extinction-chess.ts`:
|
||||
Configurable target type via
|
||||
`engine.presetState<{ targetType: PieceType }>("extinction-chess")`.
|
||||
Default: `"pawn"` (common, exercises attrition).
|
||||
`onCheckGameResult` returns winner when opposite side has 0
|
||||
pieces of `targetType`. No royal override; king can still be
|
||||
captured freely. `incompatibleWith: ["suicide-chess",
|
||||
"capture-all", "first-promotion-wins"]`.
|
||||
- `packages/chess/src/presets/extinction-chess.test.ts`: 10+
|
||||
tests covering each target type (pawn, knight, bishop, rook,
|
||||
queen, king).
|
||||
- **UI follow-up note**: Cycling target via drawer chip is out
|
||||
of scope; document as a post-landing follow-up.
|
||||
|
||||
**Parallelization**: Phase D concurrent. Blocks: F.1. Blocked by: none.
|
||||
|
||||
**Commit**: `feat(presets): extinction-chess`
|
||||
|
||||
- [ ] D.4. **Phase D verification gate**
|
||||
|
||||
**What to do**: `bun run check` green.
|
||||
|
||||
**Commit**: `chore(sisyphus): Phase D (objective variants) complete`
|
||||
|
||||
---
|
||||
|
||||
## Phase E — Tier 2 Movement-Variant Presets
|
||||
|
||||
**Goal**: 4 move-generation-override presets.
|
||||
|
||||
- [ ] E.1. **`berolina-pawns` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/berolina-pawns.ts`:
|
||||
`overridePieceMoves` for pawn pieces. Generates:
|
||||
- Forward diagonals → non-capture push (one square; two from
|
||||
home rank)
|
||||
- Forward orthogonal → capture only
|
||||
- En passant reinterpreted: if opponent just double-diagonal-
|
||||
pushed adjacent to yours, capture orthogonally to the
|
||||
skipped square (document the rule choice in-file — several
|
||||
variants exist).
|
||||
- Promotion: diagonal push to promotion rank still promotes.
|
||||
`incompatibleWith: ["pawn-diagonal-no-capture",
|
||||
"berolina-pawns-2", "pawns-move-backward"]`.
|
||||
- `packages/chess/src/presets/berolina-pawns.test.ts`: 15+ tests
|
||||
covering double-push, capture, en-passant, promotion, block
|
||||
detection, composition with piece-hp.
|
||||
|
||||
**Parallelization**: Phase E concurrent. Blocks: E.2, F.4.
|
||||
Blocked by: A.4.
|
||||
|
||||
**Commit**: `feat(presets): berolina-pawns`
|
||||
|
||||
- [ ] E.2. **`berolina-pawns-2` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/berolina-pawns-2.ts`: identical to
|
||||
`berolina-pawns` but also allows sideways captures (same-rank,
|
||||
adjacent file).
|
||||
`incompatibleWith: ["berolina-pawns", ...same list]`.
|
||||
- `packages/chess/src/presets/berolina-pawns-2.test.ts`: 6+ tests
|
||||
targeting the DIFF from berolina-pawns.
|
||||
|
||||
**Parallelization**: Phase E concurrent. Blocks: F.4.
|
||||
Blocked by: E.1 (for shared helpers).
|
||||
|
||||
**Commit**: `feat(presets): berolina-pawns-2`
|
||||
|
||||
- [ ] E.3. **`bouncing-pieces` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/bouncing-pieces.ts`: `getExtraMoves`
|
||||
for bishop + queen. Computes diagonal rays that reflect off
|
||||
left/right file edges. Stops on capture or friendly block.
|
||||
Respects the existing move-generator blocking logic. Stays
|
||||
within rank bounds.
|
||||
`incompatibleWith: ["bouncing-pieces-2", "wrap-board"]`
|
||||
(cylinder already wraps; bouncing is redundant).
|
||||
- `packages/chess/src/presets/bouncing-pieces.test.ts`: 10+ tests:
|
||||
bishop on e4 reaches squares via right-edge bounce, queen rays
|
||||
reflect and stop on block, capture on reflected ray works,
|
||||
friendly block stops before the reflection.
|
||||
|
||||
**Parallelization**: Phase E concurrent. Blocks: E.4. Blocked by: none.
|
||||
|
||||
**Commit**: `feat(presets): bouncing-pieces`
|
||||
|
||||
- [ ] E.4. **`bouncing-pieces-2` preset**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/presets/bouncing-pieces-2.ts`: like
|
||||
bouncing-pieces but reflects off ALL four edges. **Max
|
||||
reflection count capped at 2** (prevents infinite-loop ray
|
||||
computation on empty board; document). Extends/replaces
|
||||
bouncing-pieces; both shouldn't be active simultaneously.
|
||||
- `packages/chess/src/presets/bouncing-pieces-2.test.ts`: 8+
|
||||
tests.
|
||||
|
||||
**Parallelization**: Phase E concurrent. Blocks: F.1.
|
||||
Blocked by: E.3.
|
||||
|
||||
**Commit**: `feat(presets): bouncing-pieces-2`
|
||||
|
||||
- [ ] E.5. **Phase E verification gate**
|
||||
|
||||
**What to do**: `bun run check` green.
|
||||
|
||||
**Commit**: `chore(sisyphus): Phase E (movement variants) complete`
|
||||
|
||||
---
|
||||
|
||||
## Phase F — Lobby Integration + E2E
|
||||
|
||||
- [ ] F.1. **Audit `incompatibleWith` graph**
|
||||
|
||||
**What to do**: Add `packages/chess/src/presets/presets.test.ts`
|
||||
(if missing) or extend the existing preset-registry test. Reflexive
|
||||
+ symmetric check: if A declares B incompatible, B MUST declare A
|
||||
incompatible (or explicitly opt out via an `allowedOneWay: ["B"]`
|
||||
field with a comment — prefer mutual). Runs across every registered
|
||||
preset. Failure message names the pair.
|
||||
|
||||
**Parallelization**: Phase F. Blocks: F.5. Blocked by: B-E.
|
||||
|
||||
**Commit**: `test(presets): enforce incompatibleWith symmetry`
|
||||
|
||||
- [ ] F.2. **`suggestedPresets` wiring in premade layouts**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/layouts/knightmate.ts`:
|
||||
`suggestedPresets: ["knightmate-rules"]` (done in B.1 task; this
|
||||
step is the audit that every layout's suggestions point at real
|
||||
presets).
|
||||
- `packages/chess/src/layouts/monster.ts`: `["monster-rules"]`.
|
||||
- `packages/chess/src/layouts/pawns-only.ts`:
|
||||
`["first-promotion-wins"]`.
|
||||
- `packages/chess/src/layouts/dunsany.ts`: `[]` (no canonical rules).
|
||||
- `packages/chess/src/layouts/horde.ts`: `[]`.
|
||||
- `packages/chess/src/layouts/dual-classic.ts`: `["dual-king"]`.
|
||||
- `packages/chess/src/ui/LayoutPicker.tsx` (from layouts plan):
|
||||
render a small "Suggested rules" badge group next to each layout
|
||||
when `suggestedPresets.length > 0`. Tap to enable.
|
||||
- Add a unit test verifying every `suggestedPresets` entry
|
||||
references a registered preset id (prevents dangling links).
|
||||
|
||||
**Parallelization**: Phase F. Blocks: F.4, F.5. Blocked by: B.1-B.4,
|
||||
C.2.
|
||||
|
||||
**Commit**: `feat(layouts-ui): suggested-preset chips`
|
||||
|
||||
- [ ] F.3. **Lobby RulesDrawer preset grouping**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/src/ui/RulesDrawer.tsx`: group presets by category
|
||||
(King Variants, Objectives, Movement, Multi-move, Pieces, Misc).
|
||||
Each preset declares a `category: "king" | "objective" | "movement" | "multi-move" | "pieces" | "misc"`
|
||||
field (new, optional for legacy presets — default to `"misc"`).
|
||||
Render a section header per category; preserve existing filter/
|
||||
search UI.
|
||||
- Visual separator between categories (thin `border-neutral-200`).
|
||||
- Keep the existing enable/disable chip semantics; no functional
|
||||
change, purely organizational.
|
||||
|
||||
**Parallelization**: Phase F. Blocked by: B-E.
|
||||
|
||||
**Commit**: `feat(ui): group rules drawer presets by category`
|
||||
|
||||
- [ ] F.4. **Rule-variant e2e smoke**
|
||||
|
||||
**What to do**: `packages/chess/e2e/rule-variants.spec.ts` (new).
|
||||
Three representative scenarios end-to-end:
|
||||
1. **Knightmate**: open lobby → pick Knightmate layout → enable
|
||||
knightmate-rules chip → Play Solo → make any move → attack
|
||||
a knight → check banner appears → attack the king → no
|
||||
check banner (king not royal).
|
||||
2. **Double-move**: enable double-move → white plays two moves
|
||||
→ black plays one → turn indicator flips correctly.
|
||||
3. **Suicide-chess**: enable suicide-chess → play pieces until
|
||||
a capture is available → verify only captures are offered
|
||||
in the legal-move UI.
|
||||
|
||||
**Parallelization**: Phase F. Blocks: F.5.
|
||||
Blocked by: B.1, B.2, D.1, F.2.
|
||||
|
||||
**Commit**: `test(e2e): rule-variant vertical slice`
|
||||
|
||||
- [ ] F.5. **Docs + final regression**
|
||||
|
||||
**What to do**:
|
||||
- `packages/chess/docs/PRESET-API.md`: add a "Rule Variants
|
||||
Gallery" section with a one-paragraph description of each of
|
||||
the 14 new presets.
|
||||
- `packages/chess/RULES.md`: cross-reference each preset with the
|
||||
greenchess.net entry, link back to
|
||||
https://greenchess.net/variants.php?cat=4 for authority.
|
||||
- Run full regression: `bun run check` + full Playwright suite
|
||||
(server up for multiplayer scenarios).
|
||||
|
||||
**Parallelization**: Phase F finale. Blocks: Final Wave.
|
||||
Blocked by: F.1, F.2, F.3, F.4.
|
||||
|
||||
**Commit**: `docs(preset-api): rule variants gallery + RULES.md cross-refs`
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Wave
|
||||
|
||||
Run every reviewer AFTER Phase F lands. All must return APPROVE before
|
||||
marking the plan complete. Pattern mirrors T3's F1-F4.
|
||||
|
||||
- [ ] F1. **Plan compliance audit** — oracle
|
||||
- All 31 top-level tasks checked.
|
||||
- 4 new hooks declared in `packages/chess/src/presets/registry.ts`.
|
||||
- 14 preset `.ts` files + matching `.test.ts` exist.
|
||||
- `HalfMovesThisTurn` in `ChessAttrMap`.
|
||||
- `dual-classic.ts` layout file present and registered.
|
||||
- Tier 3 (`royalty-transfer`) explicitly NOT present.
|
||||
|
||||
- [ ] F2. **Code quality review** — unspecified-high
|
||||
- Zero `as any`, `@ts-ignore`, `@ts-expect-error` in non-test source.
|
||||
- `as unknown as X` count does not increase vs baseline.
|
||||
- Every new preset declares `incompatibleWith`.
|
||||
- `incompatibleWith` graph is symmetric (F.1 test proves).
|
||||
- Every new preset has ≥ 8 behavioral tests.
|
||||
- Hook dispatch in `engine.ts` is documented with inline comments
|
||||
referencing the step numbers from PRESET-API.md.
|
||||
|
||||
- [ ] F3. **Manual QA** — unspecified-high with playwright
|
||||
- Full Playwright regression green.
|
||||
- Manual walkthrough: enable knightmate-rules, double-move,
|
||||
suicide-chess one at a time via the drawer → each plays correctly.
|
||||
- Visual check: RulesDrawer category grouping renders cleanly.
|
||||
|
||||
- [ ] F4. **Scope fidelity** — deep
|
||||
- No hook added outside the 4 declared (getRoyalPieces,
|
||||
filterLegalMoves, shouldAdvanceTurn, overridePieceMoves).
|
||||
- No royalty-transfer preset smuggled in.
|
||||
- No PGN notation changes.
|
||||
- No ranking/ELO code.
|
||||
- Non-8×8 variants absent.
|
||||
|
||||
**Approval workflow**: Each reviewer returns APPROVE or REJECT. On
|
||||
reject, fix in the same session via `task(session_id=..., prompt=
|
||||
"REJECT reason: ... fix by ...")`. Re-run after fix. Do NOT mark the
|
||||
plan complete until all four report APPROVE.
|
||||
|
||||
**Commit**: `chore(sisyphus): Rule Variants Final Verification Wave — all reviewers APPROVE`
|
||||
|
||||
---
|
||||
|
||||
## Parallel Execution Map
|
||||
|
||||
```
|
||||
Phase A (architecture) — 5 tasks, A.1-A.4 parallel, A.5 gate
|
||||
A.1 getRoyalPieces ──┐
|
||||
A.2 filterLegalMoves ─┤
|
||||
A.3 shouldAdvanceTurn ┤ → A.5 docs + gate
|
||||
A.4 overridePieceMoves┘
|
||||
|
||||
Phase B (Tier 1) — 5 tasks, B.1-B.4 parallel, B.5 gate
|
||||
B.1 knightmate ──────┐
|
||||
B.2 double-move ─────┤
|
||||
B.3 monster ─────────┤ → B.5 gate
|
||||
B.4 first-promotion ─┘
|
||||
|
||||
Phase C (royal T2) — 4 tasks, C.1-C.3 parallel, C.4 gate
|
||||
Phase D (objective T2) — 4 tasks, D.1-D.3 parallel, D.4 gate
|
||||
Phase E (movement T2) — 5 tasks, E.1+E.3 parallel, E.2 after E.1,
|
||||
E.4 after E.3, E.5 gate
|
||||
|
||||
Phase F (integration) — 5 tasks, F.1-F.3 parallel, F.4 after F.2,
|
||||
F.5 finale
|
||||
|
||||
Final Wave — F1-F4 parallel
|
||||
```
|
||||
|
||||
Recommended wave size: 3-5 tasks per parallel batch. Never > 5.
|
||||
Every delegated agent runs with its own disjoint file set.
|
||||
|
||||
---
|
||||
|
||||
## Risks (recap — full rationale in design doc)
|
||||
|
||||
1. **Hook ordering**. `filterLegalMoves` is pipeline-composable (safe),
|
||||
but `shouldAdvanceTurn` and `overridePieceMoves` are first-match-wins.
|
||||
Mitigation: document in PRESET-API.md + `incompatibleWith` between
|
||||
any two presets that both override the same hook for the same
|
||||
subject.
|
||||
2. **Suicide + HP edge case**. Capture-compulsory + non-lethal HP
|
||||
capture: DOES the no-kill hit satisfy compulsion? YES (the move IS
|
||||
a capture). Documented in D.1.
|
||||
3. **Multi-royal performance**. Check detection iterates royals. With
|
||||
dual-king (2) + coregal (1 king + N queens), cost is linear in
|
||||
royals. Realistic max ~5 pieces. Profile only if it shows up.
|
||||
4. **Check banner UX with multi-royal**. Which royal is in check?
|
||||
Deferred to a small follow-up UI task (Risks 4 of design doc).
|
||||
5. **Promotion to king (dual-king underpromotion)**. 3+ kings per side
|
||||
possible. Rules stay sound; verify `getAllLegalMoves` doesn't explode
|
||||
via an integration test with 3 kings.
|
||||
6. **Berolina en passant interpretation**. Multiple valid reads. Pick
|
||||
one (reflected en passant to the skipped square) and document
|
||||
in-file.
|
||||
7. **Override-piece-moves collisions**. Two presets overriding pawns =
|
||||
undefined behavior. Mitigation: `incompatibleWith` declarations +
|
||||
dev-mode console warning (implemented in A.4).
|
||||
|
||||
---
|
||||
|
||||
## Glossary
|
||||
|
||||
- **Royal**: a piece whose capture/mate ends the game. FIDE default =
|
||||
PieceType king. Knightmate = knight. Coregal = king + queen.
|
||||
- **Half-move**: one player's single move. Turn = typically 1 half-move;
|
||||
Monster/Double-move turn = 2 half-moves.
|
||||
- **Compulsory capture**: Suicide Chess — if any capture is legal, the
|
||||
mover MUST choose a capture.
|
||||
- **Royal set**: the `EntityId[]` returned from `getRoyalPieces`. Can
|
||||
be empty (Suicide) for "no royalty".
|
||||
|
||||
---
|
||||
|
||||
## When to Stop Mid-Plan
|
||||
|
||||
If tool budget or context pressure forces an early stop:
|
||||
|
||||
- **After Phase A**: foundation lands; no new presets. Document the
|
||||
hooks as "available, no active users" — Tier 1 can ship in a
|
||||
follow-up.
|
||||
- **After Phase B**: T1 presets close the layouts deferrals. Ship.
|
||||
Tier 2 is a future epic.
|
||||
- **After Phase C or D**: Tier 2 royal/objective variants ship. The
|
||||
remaining tier (movement variants E.1-E.4) becomes its own follow-up
|
||||
mini-epic.
|
||||
- **After Phase E but before F**: feature-complete but UI integration
|
||||
(category grouping, suggestion chips) is a polish follow-up.
|
||||
|
||||
In every case: update this plan's checkboxes to reflect actual landed
|
||||
state, append a `## Early Stop` section with rationale, and do NOT run
|
||||
the Final Verification Wave — reserve that for the full plan.
|
||||
Loading…
Add table
Add a link
Reference in a new issue