Add bgm-commands.md covering async commands with ok/cancel/error results, per-invocation run contexts, fire-and-forget vs self-managed waiting, and tap interaction with part-local trigger points. Link it from the tabletop design and plan docs, and record the decisions in decisions.md.
62 lines
3.5 KiB
Markdown
62 lines
3.5 KiB
Markdown
# bgm-tabletop
|
|
|
|
a r3f based interactive component library to work with [bgm](./bgm-format.md) board games. will be used somewhere in the `web` app's bgm inspector routes.
|
|
|
|
## 1. stack
|
|
|
|
`react` - react, react router, tailwindv4
|
|
`r3f` - r3f, drei, postprocessing
|
|
`zustand` - for state management
|
|
|
|
## 2. states
|
|
|
|
source-of-truth game state:
|
|
|
|
```ts
|
|
{
|
|
surfaces: Record<string, boolean>, // enabled per surface id
|
|
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
|
|
facing: 'face' | 'back' | 'standing', // how the part is oriented on the board
|
|
}
|
|
```
|
|
|
|
**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, facing }` 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.
|
|
- `facing` - how the piece is oriented on the board (`face` / `back` / `standing`).
|
|
|
|
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.
|
|
|
|
## 3. components
|
|
|
|
- `SetupLoader` side effect only component that seeds the game state with setup (enabled surfaces + part placement).
|
|
- `WorldSurfaceView` mounts a surface to world space.
|
|
- `HudSurfaceView` mounts a surface to hud space.
|
|
- `PartPlacement` a stable per-part component that positions a part on a surface location. uses the stacking hook (below) to apply the route's stacking strategy.
|
|
- `PartView` used in `PartPlacement`, creates a mesh from part definition. a standalone component library, so it reuses geometry/shape code from `@tts/mesh` rather than the web app's viewers.
|
|
|
|
## 4. stacking
|
|
|
|
the format's stacking strategy (`curve` / `limit` / `align` / `steps` / `tilt` / `zStart` / `zEnd`, see `bgm-format.md` §4) is implemented as a hook, e.g. `useStacking(route.stacking, index, stackSize)`, returning the offset/rotation to apply to a piece: `{ x, y, rotation, z, tilt }`. `x`/`y`/`rotation` come from the `curve`; `z` is the surface-normal height ramped from `zStart` to `zEnd`; `tilt` is the rotation about the card's local Y (long) axis, applied to every part. `PartPlacement` consumes it.
|
|
|
|
## 5. commands
|
|
|
|
Scripted interaction — focus, tap-to-advance, move, caption — is built on an
|
|
async command layer. See [`bgm-commands.md`](./bgm-commands.md) for command
|
|
execution (lifecycle, run contexts, tap interaction).
|
|
|
|
## 6. usage
|
|
|
|
- we will inspect individual parts with `PartView` in the web app's part inspection route.
|
|
- as a library, the public surface is the components above: mount a surface with `WorldSurfaceView`/`HudSurfaceView`, seed state with `SetupLoader`, and let `PartPlacement`/`PartView` render the pieces. the web app is one consumer; the library should not assume the web app's routes or store.
|
|
- a surface is mounted only when enabled; a disabled surface is not rendered. |