Files
tts-workshop/docs/bgm-tabletop.md
T
hypercross 634a99dd25 docs: design command execution for tabletop scripts
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.
2026-08-10 15:41:43 +08:00

3.5 KiB

bgm-tabletop

a r3f based interactive component library to work with bgm 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:

{
  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 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.