You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every design decision for configurable game setup (zone/seat model, ownership & visibility, DSL semantics & surface, graphical editor paradigm, persistence, execution UX) is locked, and the riskiest bets — DSL semantics and graphical editor feel — have throwaway prototypes the user has reacted to. Implementation is a separate effort after this map closes.
Notes
Settled during charting (constraints, not open questions):
The DSL is designed for growth into full game rules (bots, simulation, rulebook generation) but only setup features are specced/built now.
Solo playtesting now — one designer plays all seats — but the zone/seat model (ownership, visibility) is designed so networked multiplayer doesn't require remodeling.
Setup configuration is a server-persisted entity belonging to the Project, edited in the graphical editor, loaded when the tabletop opens.
boardgame.io is the leading candidate for the execution foundation (state management, turns, multiplayer, bots); the DSL would transpile to it. Not locked — the first research ticket validates fit against the existing v2 tabletop engine.
Relevant code: src/Deckle.Web/src/lib/tabletop/ — v2 engine, pure reducer over serializable TabletopState (cards/piles/zones); zone types freeform | grid | spread | group; no server-side session persistence today.
Skills to consult per ticket: /grilling + /domain-modeling for decisions, /prototype for prototypes, /research for research.
Decisions so far
Survey existing board-game description DSLs — Design a new, small, JSON-serializable declarative DSL; nothing surveyed is adoptable wholesale (Ludii and CardStock license-blocked, boardgame.io/Boardzilla setup is imperative JS, GDL too low-level). Steal: Ludii ludeme-tree structure, RECYCLE zone/visibility ontology, boardgame.io setup(ctx) contract, TTS deal() params. Findings: agents/research/game-dsl-survey.md on research/game-dsl-survey.
Survey graphical programming editor paradigms and web libraries — Structured step-list / sentence-builder paradigm (Zapier/Notion-style sentences with dropdown slots and nested ifs), built as a custom Svelte 5 editor whose state IS the DSL AST (lossless round-trip); Blockly is the escape hatch if the language outgrows it; node/dataflow canvases rejected (wrong paradigm for sequential rules). Findings: agents/research/graphical-editor-survey.md on research/graphical-editor-survey.
Validate boardgame.io as the execution foundation — Adopt-with-caveats: bridge, not replace — operations.ts stays the shared kernel, moves wrap it; one perpetual phase + ActivePlayers.ALL preserves freeform play; vanilla JS client suits Svelte 5. Caveat: pin a commit / await the July 2026 revivals next release. Findings: agents/research/boardgameio-foundation.md on research/boardgameio-foundation.
Decide the zone/seat model for configurable game setups — Seat template stamped N times on a ring-ordered model (render strategy — radial vs BGA rows — is a swappable view concern); three zone scopes table | seat | edge with pure ring math (always N edges); seats compile to existing freeform-container zones carrying inert seatIndex/edgeIndex + role metadata; the setup script builds the table from a blank tabletop ("Play" → prompt count/prefs → animated execution), per-count structure via script conditionals; drawn blueprints hold geometry, the script instantiates them by name; explicit minPlayers/maxPlayers on the setup config.
Decide the ownership and visibility model for zones — Owner = the zone's seatIndex (no new field; table/edge zones unowned); two orthogonal zone axes layered over per-card physical isFlipped — faceVisibility all|owner|others|none (others = Hanabi) and presence visible|hidden-from-non-owners — with editor presets (hand, deck, tableau, Hanabi hand, secret stash); reveal = move the card somewhere visible (no per-card override); no interaction field now (multiplayer default derives from ownership; move legality belongs to the future boardgame.io moves layer); view is a pure function of (state, viewerSeat|omniscient) — solo gets a seat switcher + omniscient mode; multiplayer enforces hiding via sync-layer redaction of card identity fields (boardgame.io playerView).
Decide DSL core semantics and surface syntax — JSON AST only (no parsed text grammar; the sentence-builder editor is the surface); three node kinds (action / when-else / forEach-seat) with both implicit each fan-out and explicit loops; zone-based references (component ids only in spawn steps; seat zones by (seat, role)); eight verbs (placeSeats, placeZone, place, shuffle, deal, move, flip, roll); three-layer condition algebra (all/any/not over typed comparisons of get value-exprs: playerCount, option, count(zone), cardField) with options declared on the config and surfaced in the Play prompt; GameSetup = {version, min/maxPlayers, options, blueprints, setup} with reserved keys (phases, moves, triggers, end) for full rules; sequential + deterministic (seeded RNG) + fail-fast execution contract.
Prototype the graphical rules editor — Variant B “rulebook outline” wins outright: prose rulebook doc, inline dotted-underline token slots, collapsible nested blocks, drag-and-drop reordering + insert-between-steps (required affordances). Wavy-red-underline errors validated; fix red-text contrast on teal/red backgrounds in the real build. Future (not required): modal target-picker à la variant C's inspector for complex targeting. Asset: prototype/rules-editor branch.
Decide the setup execution UX — No mode (tabletop stays one freeform sandbox; "Play setup…" just populates it); one Play dialog (setup list → bounded count stepper + typed options, remembers last choices, no seat naming); compute-first execution with skippable animated replay via a per-verb animation registry (input locked during replay); re-run = pre-filled dialog, new seed by default + "replay same deal", confirm wipe only when non-empty, whole run one undo entry; failed runs apply nothing — error in dialog as rulebook sentence + "Open in editor"; toolbar seat switcher (omniscient default) only when seat zones exist; radial ring ships first behind the strategy seam.
Decide the DSL runtime architecture — Interpret the JSON AST in TS (no transpile): pure runSetup(doc, {playerCount, options, seed, templates}) → {state, trace} in a new $lib/gamerunner/ module (named for growth into full rules), emitting the linear step trace Decide the setup execution UX #107's replay consumes; boardgame.io setup() later just calls it; mulberry32-class seeded rng + counter ids via explicit RunContext threaded through ops (within-version determinism only); server stays semantically blind — structural guardrails in .NET, all semantic validation is TS validateGameSetup() sharing the interpreter's AST code.
Prototype seat stamping and ring layout at varying player counts — Model validated at 1–8 players under both strategies; render strategy becomes a user-facing option (both radial and BGA rows read well; more strategies anticipated behind the seam, e.g. two facing rows); author knobs: ring radius stays, seat-1 angle and seat scale rejected — components render at true physical scale (enables deriving table-space data). Asset: prototype/seat-ring branch.
Decide validation and failure semantics of setup scripts — Save never gated; full validateGameSetup(doc, projectContext) runs editor-live and pre-run (the hard gate); client-computed validity boolean persisted at save for Play-dialog badges (amends Decide persistence and data model for game setup config #106: summary = {Name, IsValid}); static scope = structure + referential integrity + condition type-checking, no flow analysis (count-specific breakage is a runtime failure); errors only, flat {docPath, message}; runtime failures = closed-union SetupRunError (insufficient-cards, zone-not-on-table, card-field-miss, stale-reference, internal) feeding Decide the setup execution UX #107's dialog rendering; re-run only, no resume-after-fix.
(empty — no open questions remain; every ticket on this map is resolved and the way to the destination is clear)
Out of scope
Representing full game rules (turn structure, legal moves, win conditions) — the DSL is designed not to preclude it, but nothing beyond setup is specced here.
Destination
Every design decision for configurable game setup (zone/seat model, ownership & visibility, DSL semantics & surface, graphical editor paradigm, persistence, execution UX) is locked, and the riskiest bets — DSL semantics and graphical editor feel — have throwaway prototypes the user has reacted to. Implementation is a separate effort after this map closes.
Notes
src/Deckle.Web/src/lib/tabletop/— v2 engine, pure reducer over serializableTabletopState(cards/piles/zones); zone typesfreeform | grid | spread | group; no server-side session persistence today./grilling+/domain-modelingfor decisions,/prototypefor prototypes,/researchfor research.Decisions so far
Survey existing board-game description DSLs — Design a new, small, JSON-serializable declarative DSL; nothing surveyed is adoptable wholesale (Ludii and CardStock license-blocked, boardgame.io/Boardzilla setup is imperative JS, GDL too low-level). Steal: Ludii ludeme-tree structure, RECYCLE zone/visibility ontology, boardgame.io setup(ctx) contract, TTS deal() params. Findings: agents/research/game-dsl-survey.md on research/game-dsl-survey.
Survey graphical programming editor paradigms and web libraries — Structured step-list / sentence-builder paradigm (Zapier/Notion-style sentences with dropdown slots and nested ifs), built as a custom Svelte 5 editor whose state IS the DSL AST (lossless round-trip); Blockly is the escape hatch if the language outgrows it; node/dataflow canvases rejected (wrong paradigm for sequential rules). Findings: agents/research/graphical-editor-survey.md on research/graphical-editor-survey.
Validate boardgame.io as the execution foundation — Adopt-with-caveats: bridge, not replace — operations.ts stays the shared kernel, moves wrap it; one perpetual phase + ActivePlayers.ALL preserves freeform play; vanilla JS client suits Svelte 5. Caveat: pin a commit / await the July 2026 revivals next release. Findings: agents/research/boardgameio-foundation.md on research/boardgameio-foundation.
Decide the zone/seat model for configurable game setups — Seat template stamped N times on a ring-ordered model (render strategy — radial vs BGA rows — is a swappable view concern); three zone scopes
table | seat | edgewith pure ring math (always N edges); seats compile to existing freeform-container zones carrying inertseatIndex/edgeIndex+rolemetadata; the setup script builds the table from a blank tabletop ("Play" → prompt count/prefs → animated execution), per-count structure via script conditionals; drawn blueprints hold geometry, the script instantiates them by name; explicitminPlayers/maxPlayerson the setup config.Decide the ownership and visibility model for zones — Owner = the zone's seatIndex (no new field; table/edge zones unowned); two orthogonal zone axes layered over per-card physical isFlipped — faceVisibility
all|owner|others|none(others = Hanabi) and presencevisible|hidden-from-non-owners— with editor presets (hand, deck, tableau, Hanabi hand, secret stash); reveal = move the card somewhere visible (no per-card override); no interaction field now (multiplayer default derives from ownership; move legality belongs to the future boardgame.io moves layer); view is a pure function of (state, viewerSeat|omniscient) — solo gets a seat switcher + omniscient mode; multiplayer enforces hiding via sync-layer redaction of card identity fields (boardgame.io playerView).Decide DSL core semantics and surface syntax — JSON AST only (no parsed text grammar; the sentence-builder editor is the surface); three node kinds (action / when-else / forEach-seat) with both implicit
eachfan-out and explicit loops; zone-based references (component ids only in spawn steps; seat zones by(seat, role)); eight verbs (placeSeats, placeZone, place, shuffle, deal, move, flip, roll); three-layer condition algebra (all/any/not over typed comparisons ofgetvalue-exprs: playerCount, option, count(zone), cardField) with options declared on the config and surfaced in the Play prompt;GameSetup = {version, min/maxPlayers, options, blueprints, setup}with reserved keys (phases,moves,triggers,end) for full rules; sequential + deterministic (seeded RNG) + fail-fast execution contract.Prototype the graphical rules editor — Variant B “rulebook outline” wins outright: prose rulebook doc, inline dotted-underline token slots, collapsible nested blocks, drag-and-drop reordering + insert-between-steps (required affordances). Wavy-red-underline errors validated; fix red-text contrast on teal/red backgrounds in the real build. Future (not required): modal target-picker à la variant C's inspector for complex targeting. Asset: prototype/rules-editor branch.
Decide persistence and data model for game setup config — Standalone
GameSetupentity on Project (not a component), many named setups per project; whole Decide DSL core semantics and surface syntax #104 document atomically in one jsonb column (promoted columns: Name, plus IsValid per Decide validation and failure semantics of setup scripts); client-side migrate-on-read (upgradeGameSetupTS chain, persist only on save, version ceiling);/projects/{id}/setupsCRUD withJsonElementpassthrough DTOs, summary list; instantiation is ephemeral client TabletopState — nothing written back.Decide the setup execution UX — No mode (tabletop stays one freeform sandbox; "Play setup…" just populates it); one Play dialog (setup list → bounded count stepper + typed options, remembers last choices, no seat naming); compute-first execution with skippable animated replay via a per-verb animation registry (input locked during replay); re-run = pre-filled dialog, new seed by default + "replay same deal", confirm wipe only when non-empty, whole run one undo entry; failed runs apply nothing — error in dialog as rulebook sentence + "Open in editor"; toolbar seat switcher (omniscient default) only when seat zones exist; radial ring ships first behind the strategy seam.
Decide the DSL runtime architecture — Interpret the JSON AST in TS (no transpile): pure
runSetup(doc, {playerCount, options, seed, templates}) → {state, trace}in a new$lib/gamerunner/module (named for growth into full rules), emitting the linear step trace Decide the setup execution UX #107's replay consumes; boardgame.iosetup()later just calls it; mulberry32-class seeded rng + counter ids via explicitRunContextthreaded through ops (within-version determinism only); server stays semantically blind — structural guardrails in .NET, all semantic validation is TSvalidateGameSetup()sharing the interpreter's AST code.Prototype seat stamping and ring layout at varying player counts — Model validated at 1–8 players under both strategies; render strategy becomes a user-facing option (both radial and BGA rows read well; more strategies anticipated behind the seam, e.g. two facing rows); author knobs: ring radius stays, seat-1 angle and seat scale rejected — components render at true physical scale (enables deriving table-space data). Asset: prototype/seat-ring branch.
Decide validation and failure semantics of setup scripts — Save never gated; full
validateGameSetup(doc, projectContext)runs editor-live and pre-run (the hard gate); client-computed validity boolean persisted at save for Play-dialog badges (amends Decide persistence and data model for game setup config #106: summary = {Name, IsValid}); static scope = structure + referential integrity + condition type-checking, no flow analysis (count-specific breakage is a runtime failure); errors only, flat {docPath, message}; runtime failures = closed-unionSetupRunError(insufficient-cards, zone-not-on-table, card-field-miss, stale-reference, internal) feeding Decide the setup execution UX #107's dialog rendering; re-run only, no resume-after-fix.Decide the blueprint authoring gesture — Dedicated editor (no capture-from-tabletop), a switchable view on the setup-editor page sharing one document/save state; blueprints are kind-fixed (seat|table|edge) zone bundles referenced by stable ids with unique display names; zone label IS the role + Decide the ownership and visibility model for zones #103 visibility presets; single-seat canvas with setup-time auto-fit ring from derived bounding boxes (amends Prototype seat stamping and ring layout at varying player counts #109: radius knob becomes optional roomier-override); delete-anytime with Decide validation and failure semantics of setup scripts #110 dangling-ref validation; duplicate at v1.
Not yet specified
(empty — no open questions remain; every ticket on this map is resolved and the way to the destination is clear)
Out of scope