Compare commits

...
2 Commits
Author SHA1 Message Date
hypercross 211c151971 docs: add bgm state model design
Describe the format's state model: components as constants, setup as the shape of states, path-to-stack by facing (part affordance x zone restriction), and the stacks-on-paths anchoring scope cut.
2026-08-16 12:38:58 +08:00
hypercross 345832e389 docs: reorganize docs into bgm and status folders
Group the bgm spec cluster under docs/bgm and move dev logs and plans under docs/status, add an overview index, and update cross-references in the docs, README, and source comments.
2026-08-16 11:57:19 +08:00
20 changed files with 217 additions and 43 deletions
+3 -2
View File
@@ -14,8 +14,9 @@ analyze their contents. A lightweight, client-only pnpm monorepo.
| `packages/mesh` | 2D shapes + extrusion into 3D mesh geometry | Isomorphic | | `packages/mesh` | 2D shapes + extrusion into 3D mesh geometry | Isomorphic |
| `packages/shared` | Shared types + zod schemas | Isomorphic | | `packages/shared` | Shared types + zod schemas | Isomorphic |
See [`docs/architecture.md`](docs/architecture.md) for the architecture and See [`docs/overview.md`](docs/overview.md) for the docs index,
[`docs/implementation-plan.md`](docs/implementation-plan.md) for the plan. [`docs/architecture.md`](docs/architecture.md) for the architecture, and
[`docs/status/implementation-plan.md`](docs/status/implementation-plan.md) for the plan.
## Setup ## Setup
@@ -30,7 +30,7 @@ export function tintedColor(base: THREE.Color, tint: THREE.Color): THREE.Color {
* the same image. * the same image.
* *
* These caches live for the session (like drei's global texture cache) and are * These caches live for the session (like drei's global texture cache) and are
* not disposed on unmount; see `docs/full-setup-view.md`. * not disposed on unmount; see `docs/status/full-setup-view.md`.
*/ */
const geometryCache = new Map<string, THREE.BufferGeometry>(); const geometryCache = new Map<string, THREE.BufferGeometry>();
+2 -2
View File
@@ -2,8 +2,8 @@
> **Scope:** The system's architecture and dependency graph. For implementation > **Scope:** The system's architecture and dependency graph. For implementation
> details (files, endpoints, build order), see > details (files, endpoints, build order), see
> [`implementation-plan.md`](./implementation-plan.md). For the rationale behind > [`status/implementation-plan.md`](./status/implementation-plan.md). For the
> key decisions, see [`decisions.md`](./decisions.md). > rationale behind key decisions, see [`decisions.md`](./decisions.md).
## Overview ## Overview
@@ -1,14 +1,14 @@
# bgm-commands # bgm-commands
Command execution for [bgm](./bgm-format.md) board games, built into Command execution for [bgm](./format.md) board games, built into
[`@tts/tabletop`](./bgm-tabletop.md). A command is a unit of scripted [`@tts/tabletop`](./tabletop.md). A command is a unit of scripted
interaction — focus the camera, wait for a tap, move a part, show a caption — interaction — focus the camera, wait for a tap, move a part, show a caption —
that runs against the tabletop state store and render layer. that runs against the tabletop state store and render layer.
This doc covers **command execution**: the async lifecycle, run contexts, and This doc covers **command execution**: the async lifecycle, run contexts, and
tap interaction. The message layer above this — how commands are *declared* tap interaction. The message layer above this — how commands are *declared*
and *fired* (triggers, orchestrators, the message queue) — is specified in and *fired* (triggers, orchestrators, the message queue) — is specified in
[`bgm-engine.md`](./bgm-engine.md). Commands are async functions registered [`engine.md`](./engine.md). Commands are async functions registered
with the engine's handler registry; `@tts/tabletop` provides the concrete with the engine's handler registry; `@tts/tabletop` provides the concrete
commands that mutate the tabletop store and render layer. commands that mutate the tabletop store and render layer.
@@ -16,7 +16,7 @@ commands that mutate the tabletop store and render layer.
A command is an async function that returns a result. Every command ends in A command is an async function that returns a result. Every command ends in
one of three states, emitted as a message discriminated on the type suffix one of three states, emitted as a message discriminated on the type suffix
(see `bgm-engine.md` §3): (see `engine.md` §3):
- `:done` — completed normally. - `:done` — completed normally.
- `:cancel` — interrupted (a newer command superseded it, the user skipped, - `:cancel` — interrupted (a newer command superseded it, the user skipped,
@@ -124,7 +124,7 @@ cancel, so a cancelled `wait: tap` never leaks a handler.
## 5. run context ## 5. run context
The context a command receives is the handle to everything it can affect. The The context a command receives is the handle to everything it can affect. The
engine defines the base `RunContext` (see `bgm-engine.md` §5): `signal` engine defines the base `RunContext` (see `engine.md` §5): `signal`
(cancellation), `emit`, `wait`, and `enableTrigger`/`disableTrigger`. Tabletop (cancellation), `emit`, `wait`, and `enableTrigger`/`disableTrigger`. Tabletop
extends it with the handles commands need to mutate the board: extends it with the handles commands need to mutate the board:
@@ -142,12 +142,12 @@ interface TabletopRunContext extends RunContext {
The tap detector reads trigger points from a runtime map keyed by part id; it The tap detector reads trigger points from a runtime map keyed by part id; it
does not care where they are declared. Declaration (on the part definition, in does not care where they are declared. Declaration (on the part definition, in
a setup, or in a script) is the deferred "how to declare" half and lives in a setup, or in a script) is the deferred "how to declare" half and lives in
`bgm-format.md`. `format.md`.
## Open decisions ## Open decisions
- **Where commands are declared** — the `script` role and its schema - **Where commands are declared** — the `script` role and its schema
(`bgm-format.md`), deferred. (`format.md`), deferred.
- **Animation** — a general "ease toward target placement" layer (preferred) - **Animation** — a general "ease toward target placement" layer (preferred)
vs explicit per-move tweens. vs explicit per-move tweens.
- **Camera**`CameraControls` (drei) vs hand-rolled. - **Camera**`CameraControls` (drei) vs hand-rolled.
+5 -5
View File
@@ -1,7 +1,7 @@
# bgm-engine # bgm-engine
The message layer that drives [bgm](./bgm-format.md) board games, built into The message layer that drives [bgm](./format.md) board games, built into
[`@tts/engine`](./architecture.md). It unifies the two halves of scripted [`@tts/engine`](../architecture.md). It unifies the two halves of scripted
interaction — *declaring* what should happen and *executing* it — into a single interaction — *declaring* what should happen and *executing* it — into a single
reactive loop: **messages** flow through a **queue**, and **handlers** react to reactive loop: **messages** flow through a **queue**, and **handlers** react to
them. them.
@@ -9,7 +9,7 @@ them.
This doc covers the message model (what flows), the queue and its tick (how it This doc covers the message model (what flows), the queue and its tick (how it
flows), and the handlers (who reacts). Command *execution* — the async flows), and the handlers (who reacts). Command *execution* — the async
lifecycle, run contexts, and tap interaction — is specified in lifecycle, run contexts, and tap interaction — is specified in
[`bgm-commands.md`](./bgm-commands.md); this doc is the layer above it. [`commands.md`](./commands.md); this doc is the layer above it.
## package split ## package split
@@ -19,7 +19,7 @@ is node-testable in isolation (mirroring `@tts/extract`'s isomorphic, zero-dep
style). It defines the contract — `Message`, the handler registry, `Trigger`, style). It defines the contract — `Message`, the handler registry, `Trigger`,
`Orchestrator`, and `RunContext`. `Orchestrator`, and `RunContext`.
[`@tts/tabletop`](./bgm-tabletop.md) is one consumer of that contract: it [`@tts/tabletop`](./tabletop.md) is one consumer of that contract: it
registers the built-in commands (`move`, `focus`, `caption`, `enableSurface`, registers the built-in commands (`move`, `focus`, `caption`, `enableSurface`,
...) that mutate the tabletop store and drive the render layer. The engine ...) that mutate the tabletop store and drive the render layer. The engine
never imports tabletop; tabletop depends on the engine for the message types never imports tabletop; tabletop depends on the engine for the message types
@@ -94,7 +94,7 @@ it cannot re-enter itself.
### interaction messages ### interaction messages
Interaction is the player's input, reported to the engine as messages. Only Interaction is the player's input, reported to the engine as messages. Only
tap interaction is supported (see `bgm-commands.md` §4). tap interaction is supported (see `commands.md` §4).
```ts ```ts
interface TapMessage { interface TapMessage {
+137
View File
@@ -0,0 +1,137 @@
# bgm State Model
The format's model of what a board game *is*, and the shape of the states it can
be in. This is the organizing model behind the concrete specs:
[`format.md`](./format.md) (the manifest), [`tabletop.md`](./tabletop.md) (the
render library), and [`engine.md`](./engine.md) / [`commands.md`](./commands.md)
(scripted interaction).
> **Status:** Design. The loader and render layer implement components and a
> static setup today; the state-shape framing, zone-facing, and the anchoring
> scope cut below are proposals to guide the next phase.
## 1. Components are constants; setup is the shape of states
A bgm game is authored two ways, and they answer different questions:
- **Components** (`part`, `surface`) describe the **constants** — the physical
pieces and the board geometry that do not change during play. A tile is a
45×45 mm square with a meadow/road/city face; the play board has a draw pile
and an 11×11 grid. These are timeless facts about the game, not state.
- **setup** describes the **shape of the game's states** — which parts can be
where, how many, and with what facing. It is a *schema* over the state space,
not a concrete snapshot of one run.
The runtime store is then an **instance** of the setup's shape: the current
state of play, a point in the state space the setup describes. This buys three
things:
- **Validation** — a state is legal iff it matches the setup's shape (on the
right paths, right counts, right facing).
- **A contract for the rule engine** — legal play is a *transition between
shapes*; the rule layer (layer 4 in the layering vision) reasons over them.
- **A clean boundary** — components are timeless; setup is the state space; the
store is the current point.
### Open: does setup carry the initial state?
A game needs a concrete starting position, not just a schema. The default is
that a `setup` is **shape + initial instance** — one role that both describes
the legal state space and seeds the store. The alternative (schema-only, with
the initial state derived from the shape) is explored but not preferred.
## 2. The state: path → stack × facing
The state space is made of two axes.
### Path → stack
Every part lives on a **path** (a URL-style key like `/grid/5/5` or `/draw`),
and multiple parts on a path form an ordered **stack** (a deck, a pile of meeples,
a tile with a meeple on it). Placement is one axis. This is already in use.
### facing — part affordance × zone restriction
Facing is the second axis, and it has two distinct sources of constraint:
- **The part** declares the **physical affordance** — the facings the piece
physically supports. A card supports face/back/standing; a tile supports
face/back but not "tapped".
- **The path (zone)** declares the **legal facing** — what's allowed on that
zone. An MTG discard pile requires face-up; a play area allows tapped; a
facedown deck requires face-down. The same card is face-up in the discard,
tapped in play, face-up in exile — the piece's affordance doesn't change, only
the zone's rule does.
A state's facing is legal iff it is **both physically possible (part) and
zone-legal (path)** — the effective set is the intersection.
```yaml
role: part
type: card
id: basic
facing: [face, back, standing] # physical affordance
role: surface
type: board
id: main
layout:
- route: /discard
facing: [face] # zone restriction
- route: /play
facing: [face, tap] # zone restriction
```
Deck / the current facing lives in the store and must be in the intersection.
### Open questions
- **Default when a path declares no `facing`** — unrestricted (only the part's
affordance bounds it), or a sensible default like `face`? Lean unrestricted.
- **Is "tapped" a facing or a rotation?** In MTG it's a 90° in-plane rotation.
Default: fold common rotations into the facing enum (`face` / `back` /
`standing` / `tap`) for schema simplicity; arbitrary rotation is a later
extension.
- **Naming** — the part-side and path-side are different constraints wearing the
same word. Worth distinct terms (capability/typ) so they don't collide.
## 3. Anchoring scope: stacks-on-paths, not part-to-part networks
Real TTS mods anchor components to each other — a meeple on a tile, a fanned
hand, tokens scattered on a board. We are **not** modeling part-aligned-to-part
relative placement networks. Instead:
- **Stacks absorb part-on-part.** "Meeple on tile" is a stack `[tile, meeple]`
on a path. Much of TTS's anchoring collapses into the path→stack model.
- **Free relative placement is out of scope** — a meeple at an offset on a tile,
a fanned hand, arbitrary token scatter are not modeled.
This is a deliberate scope cut. It keeps the state model closed and simple, but
it is a **fidelity loss for converted games** and part of the "with some fixing"
cost of the TTS→bgm conversion. Components (layer 1), not the state model, are
the place to extend later if needed.
### The grid compromise
Because we don't model parts-aligned-to-parts, common layouts are expressed
explicitly — and grid layouts become verbose (the Carcassonne board is 121
hand-written row coordinates in `grid.csv`). The mitigations:
- **Grid shorthand** — a declarative `grid` (cols/rows, cell size, origin) that
expands to routes, instead of authored coordinates.
- **Free-placement shorthand** — a `free`/scatter mode for loose collections,
when the exact positions don't matter to play.
But there is **no general part-to-part network** on the roadmap. If a converted
game needs it, that is a format extension to design deliberately, not an
implicit assumption.
## Summary
| Concept | Role | Where |
| --- | --- | --- |
| Components | Constants — the physical pieces & board geometry | `part`, `surface` |
| Setup | The shape of the state space (+ initial instance) | `setup` |
| Store | The current state, an instance of the setup | `@tts/tabletop` |
| Facing | Part affordance × zone restriction | part + path/zone restriction |
| Anchoring | Stacks-on-paths only; no part networks | — |
@@ -1,6 +1,6 @@
# bgm-tabletop # 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. a r3f based interactive component library to work with [bgm](./format.md) board games. will be used somewhere in the `web` app's bgm inspector routes.
## 1. stack ## 1. stack
@@ -47,14 +47,14 @@ the render map is per enabled surface: a piece may appear on more than one enabl
## 4. stacking ## 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. 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 ## 5. commands
Scripted interaction — focus, tap-to-advance, move, caption — is built on an Scripted interaction — focus, tap-to-advance, move, caption — is built on an
async command layer. See [`bgm-commands.md`](./bgm-commands.md) for command async command layer. See [`commands.md`](./commands.md) for command
execution (lifecycle, run contexts, tap interaction), and execution (lifecycle, run contexts, tap interaction), and
[`bgm-engine.md`](./bgm-engine.md) for the message layer above it (the queue, [`engine.md`](./engine.md) for the message layer above it (the queue,
triggers, and orchestrators that declare and fire commands). triggers, and orchestrators that declare and fire commands).
## 6. usage ## 6. usage
+2 -2
View File
@@ -2,7 +2,7 @@
> **Scope:** The rationale behind key design decisions. For the system's > **Scope:** The rationale behind key design decisions. For the system's
> architecture, see [`architecture.md`](./architecture.md). For the concrete > architecture, see [`architecture.md`](./architecture.md). For the concrete
> build plan, see [`implementation-plan.md`](./implementation-plan.md). > build plan, see [`status/implementation-plan.md`](./status/implementation-plan.md).
> >
> Each entry records the decision, the context, and the alternatives considered. > Each entry records the decision, the context, and the alternatives considered.
> New entries are appended; existing entries are updated only to correct facts, > New entries are appended; existing entries are updated only to correct facts,
@@ -275,7 +275,7 @@ group starts (e.g. a `camera` group so a second focus cancels the first).
**Context:** The user wants to script interaction sequences — focus, caption, **Context:** The user wants to script interaction sequences — focus, caption,
title, highlight, tap-to-advance, move, camera away. The state store and title, highlight, tap-to-advance, move, camera away. The state store and
render layer already exist; what's missing is a way to drive them over time render layer already exist; what's missing is a way to drive them over time
and react to input. Design: [`bgm-commands.md`](./bgm-commands.md). and react to input. Design: [`bgm/commands.md`](./bgm/commands.md).
**Alternatives considered:** A single monolithic script interpreter. Rejected **Alternatives considered:** A single monolithic script interpreter. Rejected
— commands as self-contained async units are testable in isolation and let — commands as self-contained async units are testable in isolation and let
+26
View File
@@ -0,0 +1,26 @@
# Docs
The documentation is split into the living specs, which describe how the
system works today, and the status/plans, which track development iterations
and go stale as work lands.
## Specs — how the system works
| Doc | Covers |
| --- | --- |
| [`architecture.md`](./architecture.md) | System architecture and the package dependency graph |
| [`decisions.md`](./decisions.md) | Key design decisions and the rationale behind them |
| [`bgm/format.md`](./bgm/format.md) | The board game manifest (bgm) format spec |
| [`bgm/engine.md`](./bgm/engine.md) | The bgm message layer: queue, triggers, orchestrators |
| [`bgm/commands.md`](./bgm/commands.md) | bgm command execution: lifecycle, run contexts, tap interaction |
| [`bgm/tabletop.md`](./bgm/tabletop.md) | The r3f tabletop component library |
| [`bgm/state-model.md`](./bgm/state-model.md) | The format's state model: components vs setup, facing, anchoring scope |
## Status & plans (dev logs)
| Doc | Covers |
| --- | --- |
| [`status/implementation-plan.md`](./status/implementation-plan.md) | Original build plan |
| [`status/bgm-loader.md`](./status/bgm-loader.md) | bgm loader — what's built, works, missing |
| [`status/bgm-tabletop.md`](./status/bgm-tabletop.md) | bgm tabletop — implementation plan / status |
| [`status/full-setup-view.md`](./status/full-setup-view.md) | Full-setup view plan |
@@ -1,7 +1,7 @@
# bgm Loader — Status # bgm Loader — Status
> WIP. What's built, what works, what's missing, and the known issues. > WIP. What's built, what works, what's missing, and the known issues.
> Spec: [`bgm-format.md`](./bgm-format.md). > Spec: [`../bgm/format.md`](../bgm/format.md).
## What's built ## What's built
@@ -30,7 +30,7 @@ A real 52-card deck: a single `card` part expanded by `$variants` into 52 cards,
### `packages/tabletop` — rendering library (new) ### `packages/tabletop` — rendering library (new)
A standalone r3f library that renders bgm parts. `PartView`/`PartMesh` build a mesh from a `Part` definition via `@tts/mesh` (size/fillet, face/back sprite UVs, traced `shape` or rect fallback). Proxy calls (`/asset`, `/trace`) default to `@tts/http` handlers and are overridable via `TabletopProvider`. Plan: [`bgm-tabletop-plan.md`](./bgm-tabletop-plan.md). A standalone r3f library that renders bgm parts. `PartView`/`PartMesh` build a mesh from a `Part` definition via `@tts/mesh` (size/fillet, face/back sprite UVs, traced `shape` or rect fallback). Proxy calls (`/asset`, `/trace`) default to `@tts/http` handlers and are overridable via `TabletopProvider`. Plan: [`bgm-tabletop.md`](./bgm-tabletop.md).
### `packages/http` — shared proxy HTTP (new) ### `packages/http` — shared proxy HTTP (new)
@@ -71,5 +71,5 @@ The vite plugin itself lives in `packages/bgm/src/vite.ts` (exported from `@tts/
- **`$variants` URL paths** — spec mentions file/URL; URLs deferred. - **`$variants` URL paths** — spec mentions file/URL; URLs deferred.
- **zod `SerializedPackage` shape for the emitted JSON** — the plugin emits `SerializedPackage` objects; a zod schema for the emitted module would give runtime validation beyond the ambient `declare module`. - **zod `SerializedPackage` shape for the emitted JSON** — the plugin emits `SerializedPackage` objects; a zod schema for the emitted module would give runtime validation beyond the ambient `declare module`.
- **`setup` value expansion** — `type` without `id` → all parts of that type is documented but not implemented in the loader (it's a game-state init concern; noted as future). - **`setup` value expansion** — `type` without `id` → all parts of that type is documented but not implemented in the loader (it's a game-state init concern; noted as future).
- **Surface mounting is validated but not resolved**`mount`/`children`/`surfaces` are parsed and validated, but the loader doesn't resolve child→parent relationships or enforce that a setup's `surfaces`/a surface's `children` reference existing surfaces. That's a game-state/rendering concern (see `docs/bgm-tabletop.md`). - **Surface mounting is validated but not resolved**`mount`/`children`/`surfaces` are parsed and validated, but the loader doesn't resolve child→parent relationships or enforce that a setup's `surfaces`/a surface's `children` reference existing surfaces. That's a game-state/rendering concern (see `../bgm/tabletop.md`).
- Docs for the loader itself (this file is the start). - Docs for the loader itself (this file is the start).
@@ -1,8 +1,9 @@
# bgm-tabletop — Implementation Plan / Status # bgm-tabletop — Implementation Plan / Status
> **Scope:** A standalone r3f component library that renders [bgm](./bgm-format.md) > **Scope:** A standalone r3f component library that renders
> board games: a state store, surface mounting, part placement with stacking, > [bgm](../bgm/format.md) board games: a state store, surface mounting, part
> and per-part meshes. Design: [`bgm-tabletop.md`](./bgm-tabletop.md). > placement with stacking, and per-part meshes. Design:
> [`../bgm/tabletop.md`](../bgm/tabletop.md).
> **Status:** items 18 implemented and the full tabletop scene is wired into > **Status:** items 18 implemented and the full tabletop scene is wired into
> the web app's setup detail route (`/bgm/:id/setups/:type/:setup`). The > the web app's setup detail route (`/bgm/:id/setups/:type/:setup`). The
> part-inspection route renders `PartView` from the library. > part-inspection route renders `PartView` from the library.
@@ -75,7 +76,7 @@ useful slice and unblocks the web app's part inspection route immediately.
### 3. State store (`state.ts`) ✅ ### 3. State store (`state.ts`) ✅
Source-of-truth game state per `bgm-tabletop.md` §2: Source-of-truth game state per `../bgm/tabletop.md` §2:
```ts ```ts
interface GameState { interface GameState {
@@ -96,7 +97,7 @@ interface PartState {
Computed with a selector/memo so the render list is stable. A path's ordered 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`. children (for stacking) are derived from the parts map by sorting on `index`.
- **Assumption**: each piece id is unique on the board (documented in - **Assumption**: each piece id is unique on the board (documented in
`bgm-tabletop.md`); the render map is keyed by piece id. `../bgm/tabletop.md`); the render map is keyed by piece id.
### 4. Setup seeding (`setup.ts`) ✅ ### 4. Setup seeding (`setup.ts`) ✅
@@ -104,7 +105,7 @@ interface PartState {
`Setup` — enables its `surfaces` (or all when omitted) and applies its `Setup` — enables its `surfaces` (or all when omitted) and applies its
ordered `setup` placements (each moves its `parts` to a `path`). ordered `setup` placements (each moves its `parts` to a `path`).
- `setup` value expansion: a bare `type` (no id) expands to all parts of that - `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 type (documented in `../bgm/format.md` §3; the loader doesn't do this — it's a
game-state init concern, so it lives here). game-state init concern, so it lives here).
### 5. Surface mounting (`mount.ts`) ✅ ### 5. Surface mounting (`mount.ts`) ✅
@@ -126,7 +127,7 @@ interface PartState {
### 7. Stacking (`stacking.ts`) ✅ ### 7. Stacking (`stacking.ts`) ✅
- `useStacking(route.stacking, index, stackSize)``{ x, y, rotation, z, tilt }`. - `useStacking(route.stacking, index, stackSize)``{ x, y, rotation, z, tilt }`.
- Implements the format's positioning process (`bgm-format.md` §4): step - Implements the format's positioning process (`../bgm/format.md` §4): step
length from curve length / `max(steps, count-1)`, alignment (`start`/`end`/ length from curve length / `max(steps, count-1)`, alignment (`start`/`end`/
`center`), and `limit` (`0` all, `n` first n, `-n` last n). `center`), and `limit` (`0` all, `n` first n, `-n` last n).
- `z` ramps linearly from `zStart` to `zEnd` across the curve's span; `tilt` - `z` ramps linearly from `zStart` to `zEnd` across the curve's span; `tilt`
@@ -174,7 +175,7 @@ consumers share them (see Open decisions).
## Commands (not yet implemented) ## Commands (not yet implemented)
Scripted interaction is designed in [`bgm-commands.md`](./bgm-commands.md): Scripted interaction is designed in [`../bgm/commands.md`](../bgm/commands.md):
async commands with `ok`/`cancel`/`error` results, per-invocation run async commands with `ok`/`cancel`/`error` results, per-invocation run
contexts, fire-and-forget vs self-managed waiting, and tap interaction with contexts, fire-and-forget vs self-managed waiting, and tap interaction with
part-local trigger points. Implementation order: types + run-context manager, part-local trigger points. Implementation order: types + run-context manager,
@@ -2,8 +2,8 @@
> **Scope:** The concrete build plan — files, endpoints, dependencies, build > **Scope:** The concrete build plan — files, endpoints, dependencies, build
> order. For the system's architecture and dependency graph, see > order. For the system's architecture and dependency graph, see
> [`architecture.md`](./architecture.md). For the rationale behind key decisions, > [`../architecture.md`](../architecture.md). For the rationale behind key decisions,
> see [`decisions.md`](./decisions.md). > see [`../decisions.md`](../decisions.md).
A lightweight, client-only pnpm monorepo for searching the Tabletop Simulator A lightweight, client-only pnpm monorepo for searching the Tabletop Simulator
Steam Workshop, fetching full TTS save files, and analyzing their contents. Steam Workshop, fetching full TTS save files, and analyzing their contents.
@@ -55,6 +55,15 @@ tts-workshop/
├── .npmrc ├── .npmrc
├── .env.example # STEAM_API_KEY, PORT ├── .env.example # STEAM_API_KEY, PORT
├── docs/ ├── docs/
│ ├── overview.md
│ ├── architecture.md
│ ├── decisions.md
│ ├── bgm/
│ │ ├── format.md
│ │ ├── engine.md
│ │ ├── commands.md
│ │ └── tabletop.md
│ └── status/
│ └── implementation-plan.md # this file │ └── implementation-plan.md # this file
├── apps/ ├── apps/
│ ├── proxy/ │ ├── proxy/
+1 -1
View File
@@ -11,7 +11,7 @@
* their `include` patterns, and assembles the package's parts, surfaces, * their `include` patterns, and assembles the package's parts, surfaces,
* and setups. * and setups.
* *
* See docs/bgm-format.md for the format's concrete behavior. * See docs/bgm/format.md for the format's concrete behavior.
*/ */
import * as path from 'node:path'; import * as path from 'node:path';
import picomatch from 'picomatch'; import picomatch from 'picomatch';
+1 -1
View File
@@ -2,7 +2,7 @@
* Parse raw definition files (yaml/json/toml text) into JSON objects. * Parse raw definition files (yaml/json/toml text) into JSON objects.
* *
* A def file's document can be either a single JSON object (the root) or a * A def file's document can be either a single JSON object (the root) or a
* list of objects; both are handled per docs/bgm-format.md §3. In list mode, * list of objects; both are handled per docs/bgm/format.md §3. In list mode,
* each object is a separate definition. * each object is a separate definition.
*/ */
import * as fs from 'node:fs'; import * as fs from 'node:fs';
+1 -1
View File
@@ -3,7 +3,7 @@
* *
* These validate the raw definition objects (after `$variants` expansion) * These validate the raw definition objects (after `$variants` expansion)
* and produce the typed `Part` / `Surface` / `Setup` / `PackageDef` values. * and produce the typed `Part` / `Surface` / `Setup` / `PackageDef` values.
* See docs/bgm-format.md for the format's concrete behavior. * See docs/bgm/format.md for the format's concrete behavior.
*/ */
import { z } from 'zod'; import { z } from 'zod';
import type { PackageDef, Part, Setup, Surface } from './types.js'; import type { PackageDef, Part, Setup, Surface } from './types.js';
+1 -1
View File
@@ -5,7 +5,7 @@
* discovered as JSON objects from yaml/json/toml files and from markdown * discovered as JSON objects from yaml/json/toml files and from markdown
* code blocks, then assembled into a `Package` (see `collect.ts` / `emit.ts`). * code blocks, then assembled into a `Package` (see `collect.ts` / `emit.ts`).
* *
* The concrete behavior of the format is described in `docs/bgm-format.md`. * The concrete behavior of the format is described in `docs/bgm/format.md`.
*/ */
/** Part value types. */ /** Part value types. */
+1 -1
View File
@@ -2,7 +2,7 @@
* The `$variants` directive: parse a CSV into a typed object array and * The `$variants` directive: parse a CSV into a typed object array and
* extend the original object with each row. * extend the original object with each row.
* *
* Per docs/bgm-format.md §1: * Per docs/bgm/format.md §1:
* - The CSV's first row is the header, the second row is the type declaration * - The CSV's first row is the header, the second row is the type declaration
* (`string`, `number`, `string[]`, `[number;number;number;number]`, ...), * (`string`, `number`, `string[]`, `[number;number;number;number]`, ...),
* the remaining rows are data. * the remaining rows are data.
+1 -1
View File
@@ -1,5 +1,5 @@
/** /**
* Stacking the format's positioning process (`bgm-format.md` §4). * Stacking the format's positioning process (`docs/bgm/format.md` §4).
* *
* Given a route's `stacking` strategy and a piece's position in its path's * Given a route's `stacking` strategy and a piece's position in its path's
* stack, compute the offset/rotation to apply. Parts are spread along an SVG * stack, compute the offset/rotation to apply. Parts are spread along an SVG