From 8d0e3931009e45f18f11f93478532b009a17194b Mon Sep 17 00:00:00 2001 From: hypercross Date: Mon, 10 Aug 2026 00:27:01 +0800 Subject: [PATCH] feat(tabletop): let candidates inherit and override route stacking Candidates now carry their own stacking strategy, inheriting the route's when absent, so the default tilt applies consistently. Drop the now redundant explicit tilt from the poker deck. --- docs/bgm-format.md | 2 +- games/poker/poker.md | 1 - packages/bgm/src/types.ts | 2 ++ packages/tabletop/src/placement.tsx | 4 +++- packages/tabletop/src/state.test.ts | 16 ++++++++++++++++ 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/bgm-format.md b/docs/bgm-format.md index 2718671..35a9f08 100644 --- a/docs/bgm-format.md +++ b/docs/bgm-format.md @@ -341,7 +341,7 @@ string,number,number,number The router should select only the first candidate with all params matched against its props — the fields in the candidate's CSV row (e.g. `:seat` matches the candidate's `seat` value). -When no candidates match, the whole route fails to match. +A candidate inherits the route's `x`, `y`, `rotation`, and `stacking`, and may override any of them with its own values. When no candidates match, the whole route fails to match. ### Stacking diff --git a/games/poker/poker.md b/games/poker/poker.md index 11fbf66..d65c7b8 100644 --- a/games/poker/poker.md +++ b/games/poker/poker.md @@ -106,7 +106,6 @@ layout: align: center zStart: 0 #zEnd: 100 - tilt: 1 curve: M -50 -200 C 50 -150 450 -150 550 -200 - route: /community/:slot candidates: diff --git a/packages/bgm/src/types.ts b/packages/bgm/src/types.ts index 832b40c..e7bd2ac 100644 --- a/packages/bgm/src/types.ts +++ b/packages/bgm/src/types.ts @@ -84,6 +84,8 @@ export interface Candidate { x?: number; y?: number; rotation?: number; + /** Stacking strategy; overrides the route's when set, else inherits it. */ + stacking?: Stacking; } export interface Route { diff --git a/packages/tabletop/src/placement.tsx b/packages/tabletop/src/placement.tsx index 59e6b0a..859cd15 100644 --- a/packages/tabletop/src/placement.tsx +++ b/packages/tabletop/src/placement.tsx @@ -17,7 +17,9 @@ export function PartPlacement({ pkg, placement }: { pkg: Package; placement: Pla const part = pkg.parts.get(piece.split(':').slice(1).join(':')); if (!part) return null; - const { x, y, rotation, z, tilt } = useStacking(route.stacking, index, stackSize); + // A candidate inherits the route's stacking unless it overrides it. + const stacking = candidate?.stacking ?? route.stacking; + const { x, y, rotation, z, tilt } = useStacking(stacking, index, stackSize); // Route anchors and stacking offsets are in mm; convert to world units so // parts land on the (world-scaled) surface. `z` raises the part along the diff --git a/packages/tabletop/src/state.test.ts b/packages/tabletop/src/state.test.ts index d06e6b0..04da27a 100644 --- a/packages/tabletop/src/state.test.ts +++ b/packages/tabletop/src/state.test.ts @@ -98,6 +98,22 @@ describe('computeSurfacePlacements', () => { expect(placements[0]!.candidate).toEqual({ seat: '0', x: 40, y: 5, rotation: 1 }); }); + it('keeps the candidate stacking alongside its anchor', () => { + const surface = makeSurface({ + layout: [ + { + route: '/dock/:seat', + x: 0, + y: 0, + rotation: 0, + candidates: [{ seat: '0', x: 40, y: 5, stacking: { tilt: 2 } }], + }, + ], + }); + const placements = computeSurfacePlacements(surface, { '/dock/0': ['harbor:boat#fleet'] }); + expect(placements[0]!.candidate).toEqual({ seat: '0', x: 40, y: 5, stacking: { tilt: 2 } }); + }); + it('keeps the same piece on two paths as distinct placements', () => { const surface = makeSurface({ layout: [