# bgm State Model The format's model of what a board game *is*, and the shape of the states it can be in. This is the organizing model behind the concrete specs: [`format.md`](./format.md) (the manifest), [`tabletop.md`](./tabletop.md) (the render library), and [`engine.md`](./engine.md) / [`commands.md`](./commands.md) (scripted interaction). > **Status:** Design. The loader and render layer implement components and a > static setup today; the state-shape framing, zone-facing, and the anchoring > scope cut below are proposals to guide the next phase. ## 1. Components are constants; setup is the shape of states A bgm game is authored two ways, and they answer different questions: - **Components** (`part`, `surface`) describe the **constants** — the physical pieces and the board geometry that do not change during play. A tile is a 45×45 mm square with a meadow/road/city face; the play board has a draw pile and an 11×11 grid. These are timeless facts about the game, not state. - **setup** describes the **shape of the game's states** — which parts can be where, how many, and with what facing. It is a *schema* over the state space, not a concrete snapshot of one run. The runtime store is then an **instance** of the setup's shape: the current state of play, a point in the state space the setup describes. This buys three things: - **Validation** — a state is legal iff it matches the setup's shape (on the right paths, right counts, right facing). - **A contract for the rule engine** — legal play is a *transition between shapes*; the rule layer (layer 4 in the layering vision) reasons over them. - **A clean boundary** — components are timeless; setup is the state space; the store is the current point. ### Open: does setup carry the initial state? A game needs a concrete starting position, not just a schema. The default is that a `setup` is **shape + initial instance** — one role that both describes the legal state space and seeds the store. The alternative (schema-only, with the initial state derived from the shape) is explored but not preferred. ## 2. The state: path → stack × facing The state space is made of two axes. ### Path → stack Every part lives on a **path** (a URL-style key like `/grid/5/5` or `/draw`), and multiple parts on a path form an ordered **stack** (a deck, a pile of meeples, a tile with a meeple on it). Placement is one axis. This is already in use. ### facing — part affordance × zone restriction Facing is the second axis, and it has two distinct sources of constraint: - **The part** declares the **physical affordance** — the facings the piece physically supports. A card supports face/back/standing; a tile supports face/back but not "tapped". - **The path (zone)** declares the **legal facing** — what's allowed on that zone. An MTG discard pile requires face-up; a play area allows tapped; a facedown deck requires face-down. The same card is face-up in the discard, tapped in play, face-up in exile — the piece's affordance doesn't change, only the zone's rule does. A state's facing is legal iff it is **both physically possible (part) and zone-legal (path)** — the effective set is the intersection. ```yaml role: part type: card id: basic facing: [face, back, standing] # physical affordance role: surface type: board id: main layout: - route: /discard facing: [face] # zone restriction - route: /play facing: [face, tap] # zone restriction ``` Deck / the current facing lives in the store and must be in the intersection. ### Open questions - **Default when a path declares no `facing`** — unrestricted (only the part's affordance bounds it), or a sensible default like `face`? Lean unrestricted. - **Is "tapped" a facing or a rotation?** In MTG it's a 90° in-plane rotation. Default: fold common rotations into the facing enum (`face` / `back` / `standing` / `tap`) for schema simplicity; arbitrary rotation is a later extension. - **Naming** — the part-side and path-side are different constraints wearing the same word. Worth distinct terms (capability/typ) so they don't collide. ## 3. Anchoring scope: stacks-on-paths, not part-to-part networks Real TTS mods anchor components to each other — a meeple on a tile, a fanned hand, tokens scattered on a board. We are **not** modeling part-aligned-to-part relative placement networks. Instead: - **Stacks absorb part-on-part.** "Meeple on tile" is a stack `[tile, meeple]` on a path. Much of TTS's anchoring collapses into the path→stack model. - **Free relative placement is out of scope** — a meeple at an offset on a tile, a fanned hand, arbitrary token scatter are not modeled. This is a deliberate scope cut. It keeps the state model closed and simple, but it is a **fidelity loss for converted games** and part of the "with some fixing" cost of the TTS→bgm conversion. Components (layer 1), not the state model, are the place to extend later if needed. ### The grid compromise Because we don't model parts-aligned-to-parts, common layouts are expressed explicitly — and grid layouts become verbose (the Carcassonne board is 121 hand-written row coordinates in `grid.csv`). The mitigations: - **Grid shorthand** — a declarative `grid` (cols/rows, cell size, origin) that expands to routes, instead of authored coordinates. - **Free-placement shorthand** — a `free`/scatter mode for loose collections, when the exact positions don't matter to play. But there is **no general part-to-part network** on the roadmap. If a converted game needs it, that is a format extension to design deliberately, not an implicit assumption. ## Summary | Concept | Role | Where | | --- | --- | --- | | Components | Constants — the physical pieces & board geometry | `part`, `surface` | | Setup | The shape of the state space (+ initial instance) | `setup` | | Store | The current state, an instance of the setup | `@tts/tabletop` | | Facing | Part affordance × zone restriction | part + path/zone restriction | | Anchoring | Stacks-on-paths only; no part networks | — |