feat(tabletop): add free-interaction layer (layer 3)

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.
This commit is contained in:
hyper
2026-08-18 10:20:22 +08:00
parent 3813287489
commit 8eff44712f
6 changed files with 432 additions and 3 deletions
+4 -3
View File
@@ -5,9 +5,10 @@ with a bgm game that has no rules yet: a sandbox. It builds on the state model:
see [`state-model.md`](./state-model.md) for components-vs-setup, path→stack ×
facing, and the anchoring scope.
> **Status:** Design. Proposes the operation set, the command/dialog split, and
> the deck-pick-up dialog before the interaction half of `@tts/tabletop` is
> built.
> **Status:** Implemented (layer 3, sandbox). The operation set, the
> command/dialog split, and the deck-pick-up dialog are built in
> `@tts/tabletop` (`interactions.ts`, `dialog.tsx`). Rule-enforced play (layer
> 4) is not yet wired — the rule seam is a no-op filter over free interaction.
## 1. The operation set is closed and tiny
+22
View File
@@ -173,6 +173,28 @@ consumers share them (see Open decisions).
- `pnpm --filter @tts/web build` — the part inspection route renders `PartView`
from the library (work item 2), proving it end-to-end.
## Free interaction (layer 3) ✅
Sandbox interaction is built per [`../bgm/interactions.md`](../bgm/interactions.md):
- `interactions.ts` — the held part + dialog stack (UI state, outside the game
store), plus pure helpers (`interactionsFor`, `dropPaths`, `pickPath`,
`partFacings`, `nextFacing`).
- `state.ts` — `setFacing` alongside `movePart` (the `move(id, path, index?)`
primitive, defaulting to top of stack).
- `placement.tsx` — `PartPlacement` is now interactive: pick up a part (held,
lifted above the board), drag to a path anchor to `move`, click to cycle
facing. A drop on a path with a stack-dialog interaction opens the deck
dialog instead of moving directly.
- `dialog.tsx` — `DialogLayer`, the stack-inspector dialog: an alternate view
of a stack with an insertion cursor; its insert button issues a `move`.
- `@tts/bgm` — the `dialog` role, `Setup.interactions`, and `Package.dialogs`
(schema + collection + serialization).
- Demo: `games/poker` declares an `interactions:` + `role: dialog` (stack
insert) on the deck.
The rule seam (layer 4) is a no-op filter: sandbox applies intents directly.
## Commands (not yet implemented)
Scripted interaction is designed in [`../bgm/commands.md`](../bgm/commands.md):