houserules/.sisyphus/notepads/visual-modifier-builder/decisions.md
Joey Yakimowich-Payne 88581ff6a9
fix(thressgame-coverage): F1+F4 remediation (T58 RequestChoiceModal + coin-flip kind)
Final Verification Wave found two real blockers:
1. T58 RequestChoiceModal.tsx was marked complete but did NOT exist on disk.
2. request-choice locked 6-kind enum was shipped as 5 (missing 'coin-flip').

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

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

Tests: 2740 -> 2744 (+4). bun run check exit 0.
2026-04-26 14:09:28 -06:00

65 lines
3.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Architectural Decisions — Visual Modifier Builder
## DSL Extensions
### `PrimitiveApplyContext` extension (T1)
- Add `target?: TargetResolver` (default `'self'` if omitted)
- Add `event?: PrimitiveEvent` (carries trigger-specific metadata)
- TargetResolver = `'self' | 'attacker' | 'defender' | { squares: Square[] } | { relation: 'ally' | 'enemy', filter?: { pieceType?: PieceType } }`
- Centralized resolver: `resolveTargets(ctx, target): readonly EntityId[]` — returns `[ctx.pieceId]` for default 'self'
- Existing 14 primitives: keep using `ctx.pieceId` directly when target is 'self' (default) — byte-identical behavior
### Trigger semantic decisions (locked)
- `on-promotion`: fires AFTER PieceType flip. event = `{promotedFrom: 'pawn', promotedTo: PieceType}`
- `on-check-received`: edge-triggered (transition only); royal pieces only
- `on-check-delivered`: attributes to revealing piece in discovered check
- `on-move`: fires whenever Position WME changes (captures fire BOTH on-capture and on-move)
- `on-moved-onto-square`: discriminated union `{kind:'squares', squares[]} | {kind:'predicate', file?, rank?}`
- `on-captured`: fires BEFORE retraction; nested primitives have access to defender attrs via `ctx.event = {attackerId, defenderId}`
- `on-turn-end`: fires for mover at end of their turn, BEFORE opponent's on-turn-start
## UI Decisions
### Mode toggle (T22)
- localStorage key: `houserules:custom-modifier-editor-mode:v1`
- Default: 'form' (preserves existing behavior on fresh install)
- Toggle in editor header next to Templates/Load/Save
- Visual mode replaces center+right panels (palette stays in left sidebar)
### dnd-kit choice (T18)
- Use `@dnd-kit/core + sortable + utilities` (3 packages)
- `PointerSensor` + `KeyboardSensor` (a11y mandatory)
- `DragOverlay` for smooth nested drag preview
- Each trigger's children list = nested SortableContext
- Depth-4 drop attempt = no-op + toast (use existing Sonner)
### Preview pane (T17)
- 3 tabs: Narrative / JSON / Board
- All 3 views memoized on descriptor identity
- BoardDiagramView shows squares from on-moved-onto-square filters, aura radius, poisoned squares
- Empty descriptor = "No board effect" placeholder
- Plain pre/JSON formatting (no react-syntax-highlighter — bundle bloat)
### `narrate.ts` (T15) — pure module
- Zero session/engine access
- Static kind→narrator function map (no PRIMITIVE_REGISTRY lookups)
- Cycle guard via visited Set
- Length cap 4000 chars with truncation suffix
- Perf budget: <1ms on 50-node descriptor
## Test Strategy
### TDD per primitive (T5–T11)
- RED: write test first, runs but fails
- GREEN: minimal implementation to pass
- One commit per primitive (atomic)
### Pure refactor (T14 — ParamField)
- Snapshot test BEFORE extraction (golden DOM)
- Extract verbatim from CustomModifierEditor.tsx:625-872
- Snapshot test AFTER extraction must match byte-equal
- Existing e2e tests must pass unchanged
### Backward compat (T20)
- Fixture: descriptor using ONLY 15 legacy kinds, includes 2 nested triggers
- 4 round-trip assertions: parse, validate, apply, serialize → byte-equal