Add the dialog role and the interaction affordances the free-interaction
layer depends on: Setup.interactions, Package.dialogs, and the Interaction,
DialogAction, and Dialog types. Wire the dialog role through the zod
schemas, package collection (with duplicate checking), and vite
serialization, and update the tabletop serializedToPackage and test
fixtures to carry dialogs.
Add the interaction half of the sandbox: a held part and dialog stack as
UI state outside the game store, with pure helpers (interactionsFor,
dropPaths, pickPath, partFacings, nextFacing). PartPlacement is now
interactive (pick up, drag to move, click to cycle facing), and a drop on
a path with a stack-dialog interaction opens the deck dialog instead of
moving directly. DialogLayer renders the stack-inspector dialog whose
insert button issues a move. Add the dialog role, Setup.interactions, and
Package.dialogs to @tts/bgm, and declare the deck insert dialog in
games/poker.
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.
Add the command/dialog split to the state-model and interactions docs, declare role dialog and zone facing in the format spec, and let setups declare which dialogs power open interactions.
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.
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.
Replace the single dominant top light with evenly-spaced key lightformers
so a laid-flat card's top face isn't blown out and its underside is lifted
by a warm bounce.
Lay flat card/tile/token meshes so the scene-level contact shadow has a
surface to project onto, and size the shadow plane to the measured content
bounds instead of a fixed 22-unit default that left small objects shadowless.
Give deck carousel cards their own soft radial shadow so the fanned arc reads
as floating over a floor.
Replace the hand-placed light rig with an Environment built from Lightformers, giving MeshStandardMaterial objects consistent PBR lighting and richer color. Keep ContactShadows, which needs no light.
TextureLoader leaves colorSpace as NoColorSpace, so card, tile, token, model, and part art is uploaded as linear and then double-decoded in the shader, washing out contrast. Mark each texture sRGB so the GPU decodes it once, correctly.
The EffectComposer disables three's tone mapping, leaving colors flat and washed out. Add ACES filmic tone mapping plus a small brightness lift so cards match their source images.
Replace the counter with a sliding-dot pagination: dots slide and fade
as the active card changes, a counter above the bar shows the current
index, and round first/last and prev/next buttons flank the strip.
Scrolling over the dots steps through the deck. Also fix the camera fit
so it frames the active card front-on instead of the carousel center.
Fill the viewport with a fixed sidebar of highlightable type-filter
icons over an independently scrolling tree, and let the viewer expand
into a full-height scene.
Fit the camera in a frame callback so the active card has settled into
its arc slot first, and aim it straight at the card's front face instead
of keeping the initial side angle. Measure the card's width and widen the
arc radius so wide cards no longer clip their neighbors.
Replace the global TTS Workshop header on mod routes with a ModHeader showing the thumbnail, game name, and download/setup links styled like the Search/BGM nav.
Include the Workshop title and preview image in the items response so the mod header survives a page refresh, not just navigation from search. Resolve both from the same Steam API call.
three.js does not auto-declare uniforms added via onBeforeCompile, so the
injected uMapRepeat/uMapOffset were undeclared and the card shader failed
to compile, rendering cards transparent. Prepend the declarations to the
vertex shader.
Show the game name and thumbnail in the header, float the action buttons and object GUID to the right, and keep the viewer icon, label, and GUID on one line.
Move the card sprite repeat/offset out of per-card texture clones and
into a uniform injected into the material shader. Cards now share the
deck sheet (one GPU upload), the shader, and the geometry, with only
per-card material uniforms differing, so navigating a deck no longer
re-uploads the sheet on every step.
Add a DeckViewer that frames the active card in a deck carousel. Scene gains a fit prop so the shared bounds behavior can be disabled and refit manually.
Chips highlight object types to show only those in the tree; with none highlighted, everything is shown. Filtered nodes keep their original index path so selection stays stable.
Add a /pdf proxy route that forces Content-Disposition inline so PDFs render instead of downloading, and a web viewer that embeds the document in an iframe with a standalone open-in-new-tab link.
Replace the CommandHost with a handler registry keyed by message type.
Message is a permissive base type the host extends; CommandResult is a
discriminated union on the type suffix. Commands and orchestrators are
the same async shape taking a RunContext with an AbortSignal; runCommand
emits :done/:cancel/:error.
Messages are a discriminated union with generic command/result types;
the command host is dropped for a handler registry; commands and
orchestrators are the same async shape taking a cancellable RunContext.
New @tts/engine package: the pure message layer that drives bgm games.
MessageQueue snapshots and drains on tick; TriggerRegistry matches by
type/id with runtime enable/disable; runOrchestrator suspends on wait
until a matching message is processed. Engine ties them together behind
the CommandHost contract that @tts/tabletop will implement.
Define the message/queue/tick model, the three handler kinds (trigger,
orchestrator, command host), and the @tts/engine package split. Cross-link
from bgm-commands and bgm-tabletop, and add the engine to the architecture
package table.
A definition is identified by role.type and named role.type.lang
(package.lang for packages). Code blocks declare it via role= on the
info string; real files by their filename. Blocks and files without a
matching role.type are ignored. file= still overrides the auto-name.
Relative asset and $variants paths now resolve against the markdown
file's directory (baseDir) rather than the virtual name. Same role.type
with different ids groups under one name; duplicate type#id still
errors. Migrate fixtures and example games to role=.
Parse role.type#id from the fence info string and merge it into each
parsed object, erroring on conflicts with the content. id on the info
string can't combine with $variants. Auto-named blocks now hash their
content instead of the info string, so identical blocks dedupe.
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.
Expand $variants to accept an array of csv paths, concatenating their
rows. Detect a path by a .csv suffix on the first line instead of a
newline, so inline csv and paths are self-documenting and the rule
applies uniformly to single values and array elements.
Replace the face/back boolean with a three-state facing (face, back,
standing) seeded from setup placements. Parts now orient about the
anchor at the resting face/edge, so back-down parts sit on the table
instead of below it and standing parts rest on their bottom edge.
Convert poker and carcassonne setups to the ordered placement list. Deal the deck/draw first, then move the flop cards and opening tiles out onto their paths.
Replace the path->parts setup map with an ordered list of placements, each moving its parts to a path. Entries apply in order, so a part listed in a later placement ends up on that placement's path.
Replace the path->part-list store with a per-part map keyed by part id, so each placed part carries its path, stack index, and face. Derive each path's ordered children for stacking, and flip face-down parts in PartPlacement.