Files
hypercross 211c151971 docs: add bgm state model design
Describe the format's state model: components as constants, setup as the shape of states, path-to-stack by facing (part affordance x zone restriction), and the stacks-on-paths anchoring scope cut.
2026-08-16 12:38:58 +08:00

137 lines
6.0 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.
# 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 | — |