Model part face in tabletop state

Replace the path->part-list store with a per-part map keyed by part id, so each placed part carries its path, stack index, and face. Derive each path's ordered children for stacking, and flip face-down parts in PartPlacement.
This commit is contained in:
2026-08-10 11:10:16 +08:00
parent 6502fdae4f
commit bc418b2c73
8 changed files with 170 additions and 69 deletions
+11 -4
View File
@@ -80,15 +80,22 @@ Source-of-truth game state per `bgm-tabletop.md` §2:
```ts
interface GameState {
surfaces: Record<string, boolean>; // enabled per surface id
paths: Record<string, string[]>; // path -> part list
parts: Record<string, PartState>; // part id -> placement state
}
interface PartState {
path: string; // the path key this part is on
index: number; // the part's position in its path's stack
face: boolean; // whether the part's face is up
}
```
- A zustand store holding `GameState`.
- **Derived render state**: `game state + surface routes => map of piece id to
`{ surface, route, candidate, index, stackSize }``, per enabled surface.
Computed with a selector/memo so the render list is stable.
- **Assumption**: each piece id is unique within a path (documented in
`{ surface, route, candidate, index, stackSize, face }``, per enabled surface.
Computed with a selector/memo so the render list is stable. A path's ordered
children (for stacking) are derived from the parts map by sorting on `index`.
- **Assumption**: each piece id is unique on the board (documented in
`bgm-tabletop.md`); the render map is keyed by piece id.
### 4. Setup seeding (`setup.ts`) ✅
+10 -3
View File
@@ -15,18 +15,25 @@ source-of-truth game state:
```ts
{
surfaces: Record<string, boolean>, // enabled per surface id
paths: Record<string, string[]>, // path -> part list
parts: Record<string, PartState>, // part id -> placement state
}
interface PartState {
path: string, // the path key this part is on
index: number, // the part's position in its path's stack
face: boolean, // whether the part's face is up
}
```
**assumption:** each piece on the board has a unique id, even tokens of the same type. so each entry in a path's list is a unique piece id, and a piece id never appears twice in the same path. this makes the render list keyed by piece id stable and unambiguous.
**assumption:** each piece on the board has a unique id, even tokens of the same type. so a part id appears at most once, and a path's ordered children (for stacking) are derived from the map by sorting on `index`. this makes the render list keyed by piece id stable and unambiguous.
derived surface render state: game state + surface routes => map of piece id to `{ surface, route, candidate, index, stackSize }` for rendering on a surface. keys of this map makes a stable render list.
derived surface render state: game state + surface routes => map of piece id to `{ surface, route, candidate, index, stackSize, face }` for rendering on a surface. keys of this map makes a stable render list.
- `route` - the matched route.
- `candidate` - the matched candidate for a `:param` route, carrying its anchor `x`/`y`/`rotation`. absent for routes without candidates.
- `index` - the piece's position in its path's stack.
- `stackSize` - the number of pieces on the path.
- `face` - whether the piece's face is up.
the render map is per enabled surface: a piece may appear on more than one enabled surface (e.g. an expansion path and the main board), and each is rendered independently.