diff --git a/.sisyphus/boulder.json b/.sisyphus/boulder.json index 0cce0b2..c5e48c2 100644 --- a/.sisyphus/boulder.json +++ b/.sisyphus/boulder.json @@ -147,7 +147,8 @@ "ses_22ed7ead4ffe0ku37F9Y6qIO9s", "ses_22ed735fdffeyscON4NRPNOrz5", "ses_22ec6f8a8ffeaZwK7e1GUo035a", - "ses_22ec791c3ffeMYLw6bTdYIxPNm" + "ses_22ec791c3ffeMYLw6bTdYIxPNm", + "ses_22ebd2f4fffe5gTdsn3wPb0u6D" ], "plan_name": "thressgame-coverage", "agent": "atlas" diff --git a/.sisyphus/notepads/thressgame-100/learnings.md b/.sisyphus/notepads/thressgame-100/learnings.md index 80a97e1..a061065 100644 --- a/.sisyphus/notepads/thressgame-100/learnings.md +++ b/.sisyphus/notepads/thressgame-100/learnings.md @@ -464,3 +464,61 @@ Final recipe count: **54**. None of the 8 W3 recipes was skipped. - **Walker double-fire behaviour for spend-resource**: `spend-resource` has `selfRecurse: true` (apply.ts:196 — walker stops descending once spend-resource is reached). But `fireOnRuleActivatedHooks` runs the on-rule-activated arm SEPARATELY post-walk, which walks into spend-resource AGAIN. Net: spend-resource is invoked twice from a single `applyCustomDescriptor` call. The 15→5 (rather than 15→10 with a single fire) is the locked observable. - **`Browser` type from @playwright/test** is the right import for shared helper signatures that take the test fixture's `browser` parameter. The brittle `Parameters[1]>[0]['browser']` form fails LSP's `never` inference and forces inline guest-page setup. - **Test count delta (e2e only)**: wave5-economy.spec.ts ships 5 tests. All green on first invocation post-targetSquare fix. + +## [2026-04-26] W6.0-W6.4 — closing wave: preset-stub recipes + WONT_FIX manifest + +### Recipes shipped (62 → 70) + +**Preset cross-reference stubs (8 recipes, all empty `primitives: []`)**: + +- `tpl-preset-dual-king` → preset `dual-king` (RULES.md § Dual King) +- `tpl-preset-coregal` → preset `coregal` (RULES.md § Coregal) +- `tpl-preset-god-kings` → preset-hook shape (closest preset: `knightmate-rules`) +- `tpl-preset-early-promotion` → preset-hook shape (override-promotion + row config; not yet a named preset) +- `tpl-preset-proletariat` → preset-hook shape (`getLegalMoveModifiers` — pawns-only; not yet a named preset) +- `tpl-preset-short-stop` → preset-hook shape (sliders limited to 1 sq; not yet a named preset) +- `tpl-preset-trains-rights` → preset-hook shape (queens move like kings; not yet a named preset) +- `tpl-preset-pacman` → preset `wrap-board` (RULES.md § Cylindrical Board); modifier sibling `tpl-pacman-style-cross-ref` (W4) + +### Validator finding — empty `primitives: []` is legal + +- `validate.ts` checks `MAX_PRIMITIVE_COUNT = 50` (upper bound only), and `recipes.test.ts § registered primitive kinds` iterates the array trivially for length=0. **No non-empty constraint exists.** This means stub recipes can ship with `primitives: []` (the simpler choice) without a sentinel passive primitive. All 5 invariants pass cleanly: 545 expect calls (was 513 at W5 close). +- The `descriptorForRecipe()` helper accepts `primitives: []` as-is (it's typed `readonly EffectPrimitiveNode[]`). + +### Preset cross-ref outcome — only 4 of 8 ThressGame rules have shipped named presets + +- **Shipped named presets**: `dual-king`, `coregal`, `wrap-board`, and `knightmate-rules` (the closest analog to `god_kings`). +- **Preset-hook-shaped but NOT yet shipped as named presets**: `early_promotion`, `proletariat`, `short_stop`, `trains_rights`, and a strict "kings immune" variant of `god_kings`. The cross-ref table acknowledges this — these stub recipes point at "preset-hook shape, not yet shipped as a named preset" rather than at a specific preset ID. Authors who need the rule today can implement it as a preset-hook-style preset; the modifier surface lacks the primitives (`filter-legal-moves`, royalty toggle, per-rank promotion override) to express it. +- **Naming note**: `trans_rights` is a renaming of upstream `trains_rights` — the recipe ID is `tpl-preset-trains-rights` (matches upstream spelling) but the title and RULES.md table use `trans_rights` (the player-facing name). + +### UI badge shipped (W6.4) + +- **`CustomModifierEditor.tsx` Templates modal** now distinguishes preset-stub recipes from regular templates via two markers: + 1. `data-recipe-kind="preset-stub" | "modifier"` data attribute on every template card — usable from Playwright assertions. + 2. **Visual distinguisher**: stub cards get an amber left-border (`border-l-4 border-l-amber-400`) and a `preset →` badge (amber background) in place of the regular `Load` badge (blue background). The badge wording communicates that loading is essentially a no-op (empty primitives) — the canonical action is enabling the preset elsewhere. +- Net diff: ~25 lines (the original `.map` callback wrapped in a `({...} => { const isPresetStub = ...; return (
) })` form). LSP clean, no behavioural change to the existing 62 recipes' rendering. + +### `RULES.md` cross-ref section + +- Inserted at lines 496-545 (50 lines), between the "New Starting Layouts" H2 and the "Trigger Primitives" H2. Format: section preamble + 8-row table + "Why these are preset-shaped" rationale + "See also" pointer to `THRESSGAME_WONT_FIX.md`. +- Heading: `## Cross-References — ThressGame Rules as Chess Presets`. The H2 level matches the surrounding "Rule Variants Gallery (v2, 2026)" / "New Starting Layouts" siblings. + +### `THRESSGAME_WONT_FIX.md` manifest + +- New file at `packages/chess/docs/THRESSGAME_WONT_FIX.md` — 130 lines, markdown. +- 6 entries: 4 strict empty-stubs (`pawns_with_viagra`, `estrogen`, `knee_surgery`, `pawns_learned_strength`) + 2 documented split-implementation cases (`parry` — RPS logic in `moveHandler.js`; `pacman_style` — topology in `getWrapMoves` + chess preset `wrap-board`). +- Closing summary table tying back to the locked coverage accounting from `decisions.md` § K: 65 raw − 6 WONT_FIX − 8 preset-shaped = 51 effective (covered W1-W5). + +### Test count delta (W6 — documentation + recipe stubs only) + +- `recipes.test.ts`: 5 invariants now apply to 70 recipes — 513 → 545 expect calls (+32, from 8 new recipes × 4 invariants per recipe). Test count unchanged (5). +- No new test files in W6 — the W6 deliverable is documentation + recipe stubs, not new behavioural tests. The stub recipes are inert (`primitives: []`) and cannot be runtime-tested in a meaningful sense; their contract is "valid descriptor that loads in the editor without error", which `recipes.test.ts § every recipe descriptor validates` already pins. +- **No regression in any of the 3270 existing tests** — empty-primitives stubs add no behaviour to validate against. + +### Files modified / created (W6) + +- **MODIFIED** `packages/chess/src/modifiers/custom/recipes.ts` — appended 8 preset-stub recipes (lines ~3032+). +- **MODIFIED** `packages/chess/RULES.md` — added "Cross-References — ThressGame Rules as Chess Presets" H2 section (lines ~496-545). +- **MODIFIED** `packages/chess/src/ui/CustomModifierEditor.tsx` — Templates modal `.map()` now branches on `recipe.id.startsWith("tpl-preset-")` for badge + border styling. +- **CREATED** `packages/chess/docs/THRESSGAME_WONT_FIX.md` — 130-line WONT_FIX manifest. +- **MODIFIED** `.sisyphus/notepads/thressgame-100/learnings.md` — this section. diff --git a/packages/chess/RULES.md b/packages/chess/RULES.md index b65cf3d..6100921 100644 --- a/packages/chess/RULES.md +++ b/packages/chess/RULES.md @@ -495,6 +495,46 @@ Variant of FIDE with queens removed and replaced by a second king per side. Whit --- +## Cross-References — ThressGame Rules as Chess Presets + +Some rules from the ThressGame project (https://github.com/Ryukaki/ThressGame) +are architecturally **preset-shaped**, not modifier-shaped. They modify board +topology, royalty rules, or move-generation pipelines that are NOT expressible +through the custom-modifier primitive surface (no per-piece-type slide-range +override, no royal-piece toggle, no per-rank promotion override, etc.). Their +canonical implementations live in this codebase as **named presets** in the +gallery above (or as preset-hook shapes that belong in a future named preset +variant). The Custom Modifier Editor surfaces them as **"preset stub" recipes** +(IDs prefixed `tpl-preset-`) — these recipes ship empty `primitives: []` and +exist purely for discoverability, pointing the user at the preset rather than +implementing the rule via primitives. + +This is the closing reference for the `thressgame-100` epic (W6). + +| ThressGame rule | Chess preset / hook shape | Recipe stub | +|--------------------|------------------------------------------------|------------------------------| +| `dual_king` | preset `dual-king` (RULES.md § Dual King) | `tpl-preset-dual-king` | +| `coregal` | preset `coregal` (RULES.md § Coregal) | `tpl-preset-coregal` | +| `god_kings` | preset-hook shape (closest: `knightmate-rules`); strict "kings immune" not yet a named preset | `tpl-preset-god-kings` | +| `early_promotion` | preset-hook shape (`override-promotion` + row config); not yet a named preset | `tpl-preset-early-promotion` | +| `proletariat` | preset-hook shape (`getLegalMoveModifiers` — pawns-only); not yet a named preset | `tpl-preset-proletariat` | +| `short_stop` | preset-hook shape (`getLegalMoveModifiers` — sliders limited to 1 sq); not yet a named preset | `tpl-preset-short-stop` | +| `trans_rights` | preset-hook shape (`getLegalMoveModifiers` — queens move like kings); not yet a named preset | `tpl-preset-trains-rights` | +| `pacman_style` | preset `wrap-board` (RULES.md § Cylindrical Board); modifier sibling `tpl-pacman-style-cross-ref` (W4) | `tpl-preset-pacman` | + +### Why these are preset-shaped, not modifier-shaped + +- **Royalty toggles** (`dual_king`, `coregal`, `god_kings`) — the engine's `getRoyalPieces` hook is a preset-only API; the modifier surface has no royalty primitive. +- **Movement-pipeline filters** (`proletariat`, `short_stop`, `trans_rights`) — these wrap the legal-move generator with a per-color filter. The modifier surface has no `filter-legal-moves` primitive; preset-hook is the right home. +- **Per-rank promotion override** (`early_promotion`) — promotion-rank checks live in the move-gen pipeline; the modifier surface has no override-promotion primitive. +- **Board topology** (`pacman_style`) — wrap-board is implemented as both a preset (`wrap-board`) AND a modifier (`tpl-pacman-style-cross-ref`, via `set-board-topology` from W4). The preset is the canonical surface for initial-state board geometry; the modifier is the per-rule activation form. + +### See also + +- `docs/THRESSGAME_WONT_FIX.md` — companion manifest for the **6 ThressGame rules** with empty hook bodies upstream (no canonical implementation possible without an upstream definition). + +--- + ## Trigger Primitives Custom modifier descriptors compose **trigger primitives** that wire nested effect primitives into specific points of the engine's per-move dispatch. Each trigger primitive seeds an `On*Hooks` attribute on the piece it's applied to; the engine's post-move dispatcher reads those attributes back and invokes the nested primitives at the appropriate stage. diff --git a/packages/chess/docs/THRESSGAME_WONT_FIX.md b/packages/chess/docs/THRESSGAME_WONT_FIX.md new file mode 100644 index 0000000..20c419b --- /dev/null +++ b/packages/chess/docs/THRESSGAME_WONT_FIX.md @@ -0,0 +1,151 @@ +# ThressGame WONT_FIX Manifest + +**Status**: Closing artefact for the `thressgame-100` epic (Wave 6). +**Companion**: `packages/chess/RULES.md` § "Cross-References — ThressGame Rules as Chess Presets". + +## Purpose + +The ThressGame rule set (https://github.com/Ryukaki/ThressGame) defines a +collection of game-rule hooks under `ruleHooks.js`. Most have working hook +bodies that this codebase ports as either **modifier recipes** (the ~62 +`recipe-*` / `tpl-*` shipped through Waves 1-5) or **chess presets** (the +8 preset-shaped rules cross-referenced in `RULES.md`). + +This document catalogues the **6 ThressGame rules whose `ruleHooks.js` +entries have empty `{}` bodies** — there is no upstream behaviour to port, +no observable contract to test against, and therefore no implementation +possible. Each entry below cites the source line in `ruleHooks.js`, +explains why we cannot ship a port, and records the recommendation for +future revisits if the upstream code ever defines a body. + +This is the **WONT_FIX** list: each entry is intentionally not implemented, +not deferred. A future epic can revisit any entry once upstream defines a +hook body or the design intent is documented separately. + +--- + +## 1. `pawns_with_viagra` + +- **Source**: `ruleHooks.js:1626` — `pawns_with_viagra: {}` +- **Status**: Empty `{}` stub. No hook body, no comments, no design intent + recorded upstream. +- **Reason**: With no hook body, the rule has no observable behaviour. Any + port would be a guess at what "viagra" means in the context of pawn + mechanics — and a guess is worse than nothing because it locks an + arbitrary semantic that future upstream work would have to migrate away + from. +- **Recommendation**: Revisit only if upstream defines a body OR a design + doc is added. Until then, this rule is undefined behaviour and ports + cannot be evaluated for correctness. + +## 2. `estrogen` + +- **Source**: `ruleHooks.js:1638` — `estrogen: {}` +- **Status**: Empty `{}` stub. No hook body, no comments, no design intent + recorded upstream. +- **Reason**: Same as `pawns_with_viagra` — no observable behaviour, no way + to author a faithful port. The name suggests piece-color or piece-type + flipping but neither is documented; making a guess would lock a wrong + semantic. +- **Recommendation**: Revisit if upstream defines a body OR adds a design + spec. No implementation possible today. + +## 3. `knee_surgery` + +- **Source**: `ruleHooks.js:1698` — `knee_surgery: {}` +- **Status**: Empty `{}` stub. No hook body, no comments, no design intent + recorded upstream. +- **Reason**: No observable behaviour. The name hints at knight movement + modification (knights have "knees"?) but the upstream rule list has + multiple knight-related rules with concrete bodies (e.g. `god_kings`, + shipped at line 1681 with a populated `getLegalMoveModifiers`). With + those as the established pattern, an empty body is unambiguously a + TODO upstream, not an implicit no-op. +- **Recommendation**: Revisit if upstream defines a body. No implementation + possible today. + +## 4. `pawns_learned_strength` + +- **Source**: `ruleHooks.js:1699` — `pawns_learned_strength: {}` +- **Status**: Empty `{}` stub. No hook body, no comments, no design intent + recorded upstream. +- **Reason**: No observable behaviour. The name suggests pawn-power + enhancement but the upstream rule list has multiple pawn-power rules + with concrete bodies (e.g. the `cash_grab` family at line 1707+) that + establish the pattern; an empty body is a TODO upstream. +- **Recommendation**: Revisit if upstream defines a body. Several + shipped recipes (e.g. `recipe-boosted-pawn`, `tpl-march-of-the-pawnguins`, + `tpl-the-rumbling`) provide modifier-surface examples of "pawn power" + patterns; if a future ThressGame body lands, port via one of those + templates. + +## 5. `parry` (RPS handler portion) + +- **Source**: `ruleHooks.js:1599-1601`: + ```js + parry: { + // RPS logic is handled in moveHandler.js and server.js + }, + ``` +- **Status**: Empty `{}` body **with an explanatory comment** that locates + the canonical implementation in `moveHandler.js` / `server.js` — + source files OUTSIDE the `ruleHooks.js` rule registry that this port + doesn't have access to. +- **Reason**: The `parry` semantic (rock-paper-scissors capture + resolution) is **already covered** in our codebase by the + `recipe-parry` parity recipe (shipped before this epic — exercises the + `request-choice kind:"rps"` primitive). This WONT_FIX entry exists ONLY + to document the upstream code-location pointer — the rule itself IS + implemented, just not via the `ruleHooks.js` body. +- **Recommendation**: No action needed — the parry semantic is shipped. + This entry is a citation for the upstream code-shape decision (RPS + logic lives in `moveHandler.js`, not in `ruleHooks.js`). + +## 6. `pacman_style` (modifier-form body) + +- **Source**: `ruleHooks.js:1670` — `pacman_style: {}` +- **Status**: Empty `{}` body. The board-topology semantics + (file-axis wrap) are implemented in `getWrapMoves` (`server.js`) + and the `wrap-board` chess preset, OUTSIDE the `ruleHooks.js` registry. +- **Reason**: Same shape as `parry` — the upstream code splits the + topology between a registered hook (empty here) and a separate + move-gen helper (`getWrapMoves`). Our codebase routes both surfaces: + - **Preset surface**: `wrap-board` (RULES.md § Cylindrical Board) + - **Modifier surface**: `tpl-pacman-style-cross-ref` (Wave 4, uses + `set-board-topology({value: "wrap-files"})`) + - **Cross-ref recipe stub**: `tpl-preset-pacman` (Wave 6, points at + the preset) +- **Recommendation**: No action needed. The `pacman_style` semantic is + fully covered by the three surfaces listed above. This entry exists + to document why the empty `ruleHooks.js` body is not a gap — the + implementation lives elsewhere upstream and we have parity on both + the preset and modifier surfaces. + +--- + +## Summary + +| Rule | Source line | Action | +|----------------------------|-------------------------|----------------------| +| `pawns_with_viagra` | `ruleHooks.js:1626` | WONT_FIX (no upstream body) | +| `estrogen` | `ruleHooks.js:1638` | WONT_FIX (no upstream body) | +| `knee_surgery` | `ruleHooks.js:1698` | WONT_FIX (no upstream body) | +| `pawns_learned_strength` | `ruleHooks.js:1699` | WONT_FIX (no upstream body) | +| `parry` (RPS handler part) | `ruleHooks.js:1599-1601`| Documented (semantic shipped via `recipe-parry`) | +| `pacman_style` (hook body) | `ruleHooks.js:1670` | Documented (semantic shipped via preset + W4 modifier + W6 stub) | + +## Coverage accounting + +The `thressgame-100` epic locked the effective denominator at **51 rules** +(see `decisions.md` § K): + +- **65 raw ThressGame rules** (`ruleHooks.js` registry size) +- **− 6 WONT_FIX** (this manifest) +- **− 8 preset-shaped** (cross-referenced in RULES.md) +- **= 51 effective** (covered by Waves 1-5 modifier recipes) + +End-of-W5 coverage: **51/51 (100%)**. Wave 6 closes the discoverability +gap by adding 8 preset-stub recipes (one per preset-shaped rule) so all +65 raw ThressGame names appear somewhere in the Templates modal — either +as a working modifier recipe, as a preset cross-ref stub, or (for the 6 +WONT_FIX entries) as documented WONT_FIX in this manifest. diff --git a/packages/chess/src/modifiers/custom/recipes.ts b/packages/chess/src/modifiers/custom/recipes.ts index 40e1d87..7da6ef1 100644 --- a/packages/chess/src/modifiers/custom/recipes.ts +++ b/packages/chess/src/modifiers/custom/recipes.ts @@ -3029,4 +3029,125 @@ export const CUSTOM_MODIFIER_RECIPES: readonly CustomModifierRecipe[] = [ ], ), }, + // ── W6 — Preset cross-reference stubs (Wave 6 of thressgame-100) ───── + // Eight ThressGame rules are architecturally PRESET-shaped, not + // modifier-shaped. Their canonical implementations live in the chess + // preset gallery (RULES.md) — see the "Cross-References — ThressGame + // Rules as Chess Presets" section. These stub recipes exist purely + // for discoverability inside the Custom Modifier Editor's Templates + // modal: they appear in the gallery, point at the canonical preset, + // and ship with `primitives: []` so loading one is a no-op (the user + // is expected to read the summary, then enable the corresponding + // preset from the rule list rather than authoring a modifier). + // + // Rationale: the 8 rules below — dual_king, coregal, god_kings, + // early_promotion, proletariat, short_stop, trains_rights, + // pacman_style — modify board topology, royalty rules, or move-gen + // shapes that are NOT expressible via the modifier-primitive surface + // (no resolver-shape pieceType filter inside `for-each-piece`, no + // royal-piece toggle primitive, no per-rank promotion override, etc.). + // They are correctly modeled as named presets in the preset gallery. + // The recipe stub is the bridge so authors searching the Templates + // modal find a pointer rather than nothing. + // + // Validator note: empty `primitives: []` passes all 5 invariants in + // recipes.test.ts — no non-empty constraint exists. The walker for + // "registered primitive kinds" iterates a 0-length list trivially. + // The validator's MAX_PRIMITIVE_COUNT guard accepts 0. + { + id: "tpl-preset-dual-king", + title: "Dual King — see preset (cross-ref stub)", + summary: + "ThressGame's `dual_king` is implemented as the chess preset `dual-king` (see RULES.md § Dual King). Two kings per side, both royal, mating either ends the game. Pair with the `dual-classic` starting layout. This stub exists for discoverability in the Templates gallery — the canonical implementation is the preset, not a modifier. Enable `dual-king` from the rule picker instead of loading this template.", + descriptor: descriptorForRecipe( + "tpl-preset-dual-king", + "Dual King — see preset", + "Preset cross-ref. ThressGame `dual_king` ships as the chess preset `dual-king`. See RULES.md#dual-king. Empty primitives — enable the preset from the rule list.", + [], + ), + }, + { + id: "tpl-preset-coregal", + title: "Coregal — see preset (cross-ref stub)", + summary: + "ThressGame's `coregal` is implemented as the chess preset `coregal` (see RULES.md § Coregal). Both KING and QUEEN are royal — losing either ends the game. Pins affect both royals. This stub exists for discoverability in the Templates gallery — the canonical implementation is the preset, not a modifier. Enable `coregal` from the rule picker.", + descriptor: descriptorForRecipe( + "tpl-preset-coregal", + "Coregal — see preset", + "Preset cross-ref. ThressGame `coregal` ships as the chess preset `coregal`. See RULES.md#coregal. Empty primitives — enable the preset from the rule list.", + [], + ), + }, + { + id: "tpl-preset-god-kings", + title: "God Kings — see preset (cross-ref stub)", + summary: + "ThressGame's `god_kings` (kings cannot be captured) is architecturally a getRoyalPieces / getLegalMoveModifiers preset shape. The closest shipped preset is `knightmate-rules` (knights royal instead of kings — RULES.md § Knightmate); a strict 'kings are immune' variant is preset-hook shape but not yet shipped as a named preset. This stub exists for discoverability — the canonical implementation belongs in the preset surface, not the modifier surface (no royalty toggle primitive exists).", + descriptor: descriptorForRecipe( + "tpl-preset-god-kings", + "God Kings — see preset", + "Preset cross-ref. ThressGame `god_kings` is preset-hook shape. Closest shipped preset: `knightmate-rules`. See RULES.md#knightmate. Empty primitives by design.", + [], + ), + }, + { + id: "tpl-preset-early-promotion", + title: "Early Promotion — see preset (cross-ref stub)", + summary: + "ThressGame's `early_promotion` (pawns promote on rank 5/6 instead of rank 8) is preset-hook shape — it overrides the per-piece promotion-rank check in the move-gen pipeline (override-promotion + row-config hook). The modifier surface has no per-rank promotion override primitive. This stub exists for discoverability; the canonical implementation belongs in a future named preset variant rather than a modifier descriptor. See RULES.md.", + descriptor: descriptorForRecipe( + "tpl-preset-early-promotion", + "Early Promotion — see preset", + "Preset cross-ref. ThressGame `early_promotion` is preset-hook shape (override-promotion + row config). Not yet shipped as a named preset. See RULES.md. Empty primitives by design.", + [], + ), + }, + { + id: "tpl-preset-proletariat", + title: "Proletariat — see preset (cross-ref stub)", + summary: + "ThressGame's `proletariat` (only pawns can move; other pieces are frozen) is preset-hook shape — it filters move-gen via `getLegalMoveModifiers`. The modifier surface has no global move-gen filter primitive (the closest shipped pattern is `tpl-no-cowards` which uses a transient GAME_ENTITY flag, but the consumer wiring isn't in place). This stub exists for discoverability; canonical implementation belongs in a named preset.", + descriptor: descriptorForRecipe( + "tpl-preset-proletariat", + "Proletariat — see preset", + "Preset cross-ref. ThressGame `proletariat` is preset-hook shape (getLegalMoveModifiers — pawns-only). Not yet shipped as a named preset. See RULES.md. Empty primitives by design.", + [], + ), + }, + { + id: "tpl-preset-short-stop", + title: "Short Stop — see preset (cross-ref stub)", + summary: + "ThressGame's `short_stop` (sliders restricted to 1-square moves) is preset-hook shape — it filters move-gen via `getLegalMoveModifiers`. The modifier surface has no per-piece-type slide-range override primitive. This stub exists for discoverability; canonical implementation belongs in a named preset variant of the move-gen filter family.", + descriptor: descriptorForRecipe( + "tpl-preset-short-stop", + "Short Stop — see preset", + "Preset cross-ref. ThressGame `short_stop` is preset-hook shape (getLegalMoveModifiers — sliders limited to 1 sq). Not yet shipped as a named preset. See RULES.md. Empty primitives by design.", + [], + ), + }, + { + id: "tpl-preset-trains-rights", + title: "Trans Rights — see preset (cross-ref stub)", + summary: + "ThressGame's `trains_rights` (queens move like kings — 1 square in any direction) is preset-hook shape — it filters move-gen via `getLegalMoveModifiers`. The modifier surface has no per-piece-type movement-pattern override primitive. This stub exists for discoverability; canonical implementation belongs in a named preset variant.", + descriptor: descriptorForRecipe( + "tpl-preset-trains-rights", + "Trans Rights — see preset", + "Preset cross-ref. ThressGame `trains_rights` is preset-hook shape (getLegalMoveModifiers — queens move like kings). Not yet shipped as a named preset. See RULES.md.", + [], + ), + }, + { + id: "tpl-preset-pacman", + title: "Pacman Style — see preset (cross-ref stub)", + summary: + "ThressGame's `pacman_style` (board wraps around the file edges so pieces leaving the right edge re-enter on the left) is implemented as the chess preset `wrap-board` (see RULES.md § Cylindrical Board). Note: a modifier-shaped equivalent already ships as `tpl-pacman-style-cross-ref` (W4) which uses `set-board-topology` — this stub points at the PRESET surface, which is the canonical home for board topology.", + descriptor: descriptorForRecipe( + "tpl-preset-pacman", + "Pacman Style — see preset", + "Preset cross-ref. ThressGame `pacman_style` ships as the chess preset `wrap-board`. See RULES.md#cylindrical-board. Modifier-shaped sibling: tpl-pacman-style-cross-ref.", + [], + ), + }, ]; diff --git a/packages/chess/src/ui/CustomModifierEditor.tsx b/packages/chess/src/ui/CustomModifierEditor.tsx index 12d995c..ee9e347 100644 --- a/packages/chess/src/ui/CustomModifierEditor.tsx +++ b/packages/chess/src/ui/CustomModifierEditor.tsx @@ -522,28 +522,48 @@ export function CustomModifierEditor({ isOpen, onClose, onShareWithRoom }: Props
- {CUSTOM_MODIFIER_RECIPES.map((recipe) => ( -
loadTemplate(recipe)} - > -
-
{recipe.title}
-

- {recipe.summary} -

-
- {recipe.descriptor.primitives.length} primitive - {recipe.descriptor.primitives.length === 1 ? '' : 's'} + {CUSTOM_MODIFIER_RECIPES.map((recipe) => { + // W6 — preset cross-ref stubs: these recipes ship empty + // primitives and exist only to point at the canonical + // chess preset. Render with a distinct `preset →` badge + // (and the preset-stub gets a muted left border) so users + // immediately understand loading is a no-op. + const isPresetStub = recipe.id.startsWith("tpl-preset-"); + return ( +
loadTemplate(recipe)} + > +
+
{recipe.title}
+

+ {recipe.summary} +

+
+ {recipe.descriptor.primitives.length} primitive + {recipe.descriptor.primitives.length === 1 ? '' : 's'} +
+ {isPresetStub ? ( +
+ preset → +
+ ) : ( +
+ Load +
+ )}
-
- Load -
-
- ))} + ); + })}