diff --git a/packages/tabletop/src/__fixtures__/vite-build/games/harbor/harbor.md b/packages/tabletop/src/__fixtures__/vite-build/games/harbor/harbor.md index edc74b8..6056add 100644 --- a/packages/tabletop/src/__fixtures__/vite-build/games/harbor/harbor.md +++ b/packages/tabletop/src/__fixtures__/vite-build/games/harbor/harbor.md @@ -36,5 +36,6 @@ role: setup type: game id: main setup: - /deck: harbor:token + - path: /deck + parts: harbor:token ``` \ No newline at end of file diff --git a/packages/tabletop/src/__fixtures__/vite-build/src/main.ts b/packages/tabletop/src/__fixtures__/vite-build/src/main.ts index 54c6709..cb349d3 100644 --- a/packages/tabletop/src/__fixtures__/vite-build/src/main.ts +++ b/packages/tabletop/src/__fixtures__/vite-build/src/main.ts @@ -24,7 +24,7 @@ const tree = resolveMountTree( new Set(Object.keys(seeded.surfaces)), ); -export const seededPaths = Object.keys(seeded.paths); +export const seededPaths = Object.keys(seeded.parts); export const expanded = expandSetupValue(pkg as never, 'harbor:token'); export const placementCount = placements.length; export const matched = matchRoute({ route: '/dock/:seat', x: 0, y: 0, rotation: 0 }, '/dock/0'); diff --git a/packages/tabletop/src/setup.test.ts b/packages/tabletop/src/setup.test.ts index a908ed3..e119b71 100644 --- a/packages/tabletop/src/setup.test.ts +++ b/packages/tabletop/src/setup.test.ts @@ -40,7 +40,7 @@ describe('seedFromSetup', () => { type: 'game', id: 'main', surfaces: ['board#harbor'], - setup: { '/deck': 'harbor:card#fleet' }, + setup: [{ path: '/deck', parts: 'harbor:card#fleet' }], }; const state = seedFromSetup(pkg, setup); expect(state.surfaces).toEqual({ 'board#harbor': true }); @@ -48,8 +48,24 @@ describe('seedFromSetup', () => { }); it('enables all surfaces when omitted', () => { - const setup = { type: 'game', id: 'main', setup: {} }; + const setup = { type: 'game', id: 'main', setup: [] }; const state = seedFromSetup(pkg, setup); expect(state.surfaces).toEqual({ 'board#harbor': true, 'hud#hand': true }); }); + + it('applies placements in order, last path wins', () => { + // `harbor:card#fleet` is placed on /deck first, then moved to /hand. Each + // path's indices stay contiguous 0..n-1 so stacking stays valid. + const setup = { + type: 'game', + id: 'main', + setup: [ + { path: '/deck', parts: ['harbor:card#fleet', 'harbor:token#wood'] }, + { path: '/hand', parts: 'harbor:card#fleet' }, + ], + }; + const state = seedFromSetup(pkg, setup); + expect(state.parts['harbor:card#fleet']).toEqual({ path: '/hand', index: 0, face: true }); + expect(state.parts['harbor:token#wood']).toEqual({ path: '/deck', index: 0, face: true }); + }); }); \ No newline at end of file diff --git a/packages/tabletop/src/setup.ts b/packages/tabletop/src/setup.ts index 4574e92..3fdef1e 100644 --- a/packages/tabletop/src/setup.ts +++ b/packages/tabletop/src/setup.ts @@ -6,7 +6,7 @@ * `type` (no id) expands to all parts of that type during initialization. */ import { useEffect } from 'react'; -import type { Package, Setup } from '@tts/bgm'; +import type { Package, Setup, SetupValue } from '@tts/bgm'; import { useTabletopStore, type PartState } from './state.js'; /** @@ -15,7 +15,7 @@ import { useTabletopStore, type PartState } from './state.js'; */ export function expandSetupValue( pkg: Package, - value: string | string[], + value: SetupValue, ): string[] { const values = Array.isArray(value) ? value : [value]; const out: string[] = []; @@ -48,11 +48,23 @@ export function seedFromSetup(pkg: Package, setup: Setup): { for (const key of pkg.surfaces.keys()) surfaces[key] = true; } + // Apply placements in order: each moves its parts to its path. A part listed + // in a later placement ends up on that placement's path. Then index each + // path's parts contiguously, in placement order, so `index` is always a + // valid position within its path's stack. const parts: Record = {}; - for (const [path, value] of Object.entries(setup.setup)) { - const ids = expandSetupValue(pkg, value); + for (const placement of setup.setup) { + for (const id of expandSetupValue(pkg, placement.parts)) { + parts[id] = { path: placement.path, index: 0, face: true }; + } + } + const byPath: Record = {}; + for (const [id, ps] of Object.entries(parts)) { + (byPath[ps.path] ??= []).push(id); + } + for (const [path, ids] of Object.entries(byPath)) { ids.forEach((id, index) => { - parts[id] = { path, index, face: true }; + parts[id] = { ...parts[id]!, index }; }); } return { surfaces, parts };