Files
hypercross 71ae91960f docs: mark stale bgm engine/loader docs as in progress
Call out that the tabletop and engine package split is not yet landed, add a status note to tabletop.md, and drop the retired WIP banner in bgm-loader.md.
2026-08-16 13:04:14 +08:00

69 lines
3.8 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-tabletop
> **Status:** Implemented (items 18 of the plan). A few features below are
> designed but not yet wired (commands, HUD rendering); see §5 and
> [`../status/bgm-tabletop.md`](../status/bgm-tabletop.md).
An r3f-based interactive component library for working with [bgm](./format.md)
board games. It is used in the `web` app's bgm inspector routes.
## 1. stack
`react` - react, react router, tailwind v4
`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 `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 [`commands.md`](./commands.md) for command
execution (lifecycle, run contexts, tap interaction), and
[`engine.md`](./engine.md) for the message layer above it (the queue,
triggers, and orchestrators that declare and fire commands).
## 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.