houserules/packages
Joey Yakimowich-Payne 8220f1507e
feat(engine): PlayerAction + transferable-royalty preset (solo)
Feature 4a of post-epic-deferrals. Introduces the PlayerAction
surface — a turn-consuming event orthogonal to LegalMove — and one
preset that uses it to transfer royalty between friendly pieces
once per game per color. Solo-only for v1 (F4b will add the WS
protocol; F4c will add the UI).

Engine surface:
  - New module packages/chess/src/actions.ts exports PlayerAction
    (discriminated union; starts with 'transfer-royalty' kind),
    PlayerActionKind, and ActionResult {ok,error,reason}.
  - ChessEngine.performAction(action): ActionResult runs a parallel
    pipeline to applyMove: terminal-state guard → poll every
    active preset's performAction hook (first non-undefined wins)
    → handler returns ok=false => no turn consumption → handler
    returns ok=true => advanceTurnAfterMutation shared helper
    (factored out of applyMove) which handles HalfMovesThisTurn
    increment, shouldAdvanceTurn poll, onTurnStart fire, etc.
  - ActionResult error codes: NO_HANDLER, REJECTED, INVALID_TARGET,
    NOT_YOUR_TURN, GAME_OVER. Stable for future UI / protocol.

PresetDef additions:
  - performAction(ctx): ActionResult | undefined — first non-
    undefined wins. Handlers validate + mutate state + return.
  - transformRoyalPieces(ctx, current): EntityId[] — a POST-union
    transform on the accumulated royal set, letting a preset
    reassign rather than append. Used by transferable-royalty to
    swap transferredFrom -> transferredTo.

Preset: transferable-royalty
  - category 'king'; incompat with suicide-chess + capture-all
    (both empty the royal set).
  - State: transferredFrom/To keyed by color — one-shot per color.
  - performAction validates: fromPiece alive, currently royal,
    toPiece alive, same color, not already royal, not already
    transferred. Returns INVALID_TARGET / REJECTED on failure,
    ok:true on success.
  - transformRoyalPieces swaps old royal for new in the engine's
    royal resolution; defensively drops dead ids so a transferred
    royal that later died doesn't linger.

Tests:
  - transferable-royalty.test.ts: 20 tests covering registration,
    happy path, once-per-game cap, all INVALID_TARGET paths,
    turn consumption, composition with knightmate-rules and
    piece-hp.
  - engine.performAction.test.ts: 7 tests covering NO_HANDLER,
    GAME_OVER guard, first-match-wins, shouldAdvanceTurn veto,
    performAction + applyMove interleaving.
  - presets.test.ts: EXPECTED_IDS bumped; symmetry + dangling-ref
    audits still green.
  - capture-all.ts: reciprocated incompat with transferable-royalty
    (symmetry audit).

Verification: 1699 tests passing (was 1671, +28). Typecheck + lint
clean. No regressions.

Plan: .sisyphus/plans/post-epic-deferrals.md F4a complete.
F4b (WS protocol) and F4c (UI) remain.

(Agent hit 200-tool-cap near the end; orchestrator reconciled
a missing defaultKingRoyals helper + 2 test setups that triggered
insufficient-material draws + symmetric incompat declaration.)
2026-04-21 11:30:24 -06:00
..
chess feat(engine): PlayerAction + transferable-royalty preset (solo) 2026-04-21 11:30:24 -06:00
rete refactor(rete): use asEntityId for AGG_FACT sentinel id 2026-04-19 16:49:50 -06:00
server feat(multiplayer): host color preference (white/black/random) 2026-04-21 10:51:45 -06:00