diff --git a/docs/bgm-tabletop-plan.md b/docs/bgm-tabletop-plan.md new file mode 100644 index 0000000..ff761b6 --- /dev/null +++ b/docs/bgm-tabletop-plan.md @@ -0,0 +1,172 @@ +# bgm-tabletop — Implementation Plan / Status + +> **Scope:** A standalone r3f component library that renders [bgm](./bgm-format.md) +> board games: a state store, surface mounting, part placement with stacking, +> and per-part meshes. Design: [`bgm-tabletop.md`](./bgm-tabletop.md). +> **Status:** planning — no code yet. + +## Goal + +A library (new `packages/tabletop`) that takes a bgm package and renders it as +an interactive 3D table: enabled surfaces mounted in world/HUD space, parts +placed on their routes, stacked per the format's stacking strategy. The web +app's bgm inspector routes are one consumer; the library must not depend on the +web app. + +## Stack + +`react`, `react-router` (types only), `tailwind` (styles only), `r3f` +(`@react-three/fiber`), `drei`, `postprocessing`, `zustand`, `three`, +`@tts/bgm` (types), `@tts/mesh` (geometry). + +## Package layout + +``` +packages/tabletop/ + package.json # @tts/tabletop + tsconfig.json + vitest.config.ts + src/ + index.ts # public exports + state.ts # zustand store + derived render state + setup.ts # SetupLoader: seed state from a setup + mount.ts # resolve surface mount tree (table/hud/child) + stacking.ts # useStacking hook + placement.ts # PartPlacement + partView.tsx # PartView: mesh from a part definition + surfaces/ + WorldSurfaceView.tsx + HudSurfaceView.tsx + *.test.ts # colocated unit tests +``` + +## Work items + +### 1. Package scaffold + +- New `packages/tabletop` workspace package (`pnpm-workspace.yaml` already + globs `packages/*`). +- Deps: `@tts/bgm`, `@tts/mesh`, `three`, `@react-three/fiber`, `@react-three/drei`, + `@react-three/postprocessing`, `zustand`. Dev: `vitest`, `typescript`, + `@types/three`. +- `tsconfig.json` mirroring `packages/bgm`'s (strict, ESM, `dist` output). + +### 2. Part meshes + export + web integration + +First deliverable: `PartView` renders a single part's mesh from its definition, +reusing `@tts/mesh` geometry (not the web app's viewers). This is the smallest +useful slice and unblocks the web app's part inspection route immediately. + +- `PartView` (`partView.tsx`): creates a mesh from a `Part` definition: + - `size` → world dimensions; `fillet` → corner radius. + - `face`/`faceCrop`/`back`/`backCrop` → textures (drei `useTexture`), sprite + UVs from `faceCrop`/`backCrop` (a `[col,row,cols,rows]` grid cell). + - `shape` → traced silhouette (via the proxy `/trace`, like the web token + viewer) or a fallback rect/rounded-rect. + - `extrudeShapeParts` from `@tts/mesh` for the mesh. +- Shared geometry/material caching (module-level `Map`s) so repeated parts + reuse buffers, mirroring the web viewers' `sharedResources`. +- Export `PartView` from `index.ts`. +- **Web integration**: replace the web app's part inspection route + (`/bgm/:id/parts/:type/:part`) to render `PartView` from the library, proving + it end-to-end. + +### 3. State store (`state.ts`) + +Source-of-truth game state per `bgm-tabletop.md` §2: + +```ts +interface GameState { + surfaces: Record; // enabled per surface id + paths: Record; // path -> part list +} +``` + +- 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 + `bgm-tabletop.md`); the render map is keyed by piece id. + +### 4. Setup seeding (`setup.ts`) + +- `SetupLoader`: side-effect-only component that seeds the store from a + `Setup` — enables its `surfaces` (or all when omitted) and places parts on + `setup` paths. +- `setup` value expansion: a bare `type` (no id) expands to all parts of that + type (documented in `bgm-format.md` §3; the loader doesn't do this — it's a + game-state init concern, so it lives here). + +### 5. Surface mounting (`mount.ts`) + +- Resolve the surface mount tree from `Surface.mount` + `Surface.children`: + - `kind: table` — root, world space. + - `kind: hud` — HUD area (`mount.area`). + - `kind: child` — mounted relative to a parent that lists it in `children`. +- `WorldSurfaceView` / `HudSurfaceView` mount an enabled surface; a disabled + surface isn't rendered. Child surfaces mount relative to their parent's + anchor (`x`/`y`/`rotation`). + +### 6. Part placement (`placement.ts`) + +- `PartPlacement`: stable per-part component that positions a part on a surface + location from the derived render state (route anchor + candidate anchor). +- Applies the route's stacking strategy via `useStacking`. + +### 7. Stacking (`stacking.ts`) + +- `useStacking(route.stacking, index, stackSize)` → `{ offset, rotation }`. +- Implements the format's positioning process (`bgm-format.md` §4): step + length from curve length / `max(steps, count-1)`, alignment (`start`/`end`/ + `center`), and `limit` (`0` all, `n` first n, `-n` last n). +- Curve length from an SVG path string (small helper; no new dep). + +### 8. Public API (`index.ts`) + +Export `SetupLoader`, `WorldSurfaceView`, `HudSurfaceView`, `PartPlacement`, +`PartView`, `useStacking`, and the store hooks. The web app consumes these; the +library never imports from `apps/*`. + +## Reuse from `@tts/mesh` + +- `extrudeShapeParts` / `extrudeShape` — front/back/walls geometry. +- `rectShape`, `roundedRectShape`, `circleShape`, `polygonShape`, `hexShape`, + `frameShape`, `scaleShape` — shape generators for parts without a `shape` + sprite. +- `shapeFromThree` — author shapes with the three.js path API. +- `ExtrudedGeometry` / `UVBounds` — raw typed arrays + UV framing. + +The web viewers (`TokenViewer`/`CardViewer`) contain logic we'll mirror rather +than import: trace-to-shape conversion, sprite UV math, texture flipping. These +are candidates to lift into `@tts/mesh` or `@tts/tabletop` later so both +consumers share them (see Open decisions). + +## Testing + +- `state.ts` — derived render state: enabled surfaces, route matching, + candidate selection, stacking index/stackSize. +- `stacking.ts` — positioning process: step length, alignment, limit. +- `setup.ts` — seeding + bare-type expansion. +- `mount.ts` — mount tree resolution (table/hud/child, children refs). +- `partView.tsx` — geometry from a part def (size/fillet/crop), sprite UVs. +- A real `vite build` integration test (mirroring `packages/bgm/src/vite.test.ts`) + proving the library bundles against a fixture package. + +## Validation + +- `pnpm --filter @tts/tabletop build` / `typecheck` / `test`. +- Root `pnpm test` stays green. +- `pnpm --filter @tts/web build` — the part inspection route renders `PartView` + from the library (work item 2), proving it end-to-end. + +## Open decisions (defaults in bold) + +- **Where the trace/sprite helpers live** — **lift into `@tts/mesh`** (shared + by web viewers + tabletop) vs duplicate in `@tts/tabletop`. Lifting is + cleaner but touches the web viewers; decide when `PartView` (work item 2) + needs them. +- **HUD rendering** — **drei `Html`/orthographic overlay** vs a second + `Canvas`. Default to an overlay so world + HUD share one scene. +- **Curve length** — **small internal SVG-path length helper** vs a dependency + (e.g. `svg-path-properties`). Prefer the helper to avoid a dep. \ No newline at end of file