diff --git a/packages/tabletop/src/state.test.ts b/packages/tabletop/src/state.test.ts index 741686a..d06e6b0 100644 --- a/packages/tabletop/src/state.test.ts +++ b/packages/tabletop/src/state.test.ts @@ -97,6 +97,27 @@ describe('computeSurfacePlacements', () => { const placements = computeSurfacePlacements(surface, { '/dock/0': ['harbor:boat#fleet'] }); expect(placements[0]!.candidate).toEqual({ seat: '0', x: 40, y: 5, rotation: 1 }); }); + + it('keeps the same piece on two paths as distinct placements', () => { + const surface = makeSurface({ + layout: [ + { route: '/deck', x: 0, y: 0, rotation: 0 }, + { route: '/community/:slot', x: 0, y: 0, rotation: 0 }, + ], + }); + // The deck expands to every card (including `as`); the flop also places `as`. + const placements = computeSurfacePlacements(surface, { + '/deck': ['poker:card#as', 'poker:card#kh'], + '/community/0': ['poker:card#as'], + }); + expect(placements).toHaveLength(3); + const deck = placements.filter((p) => p.path === '/deck'); + const flop = placements.filter((p) => p.path === '/community/0'); + expect(deck).toHaveLength(2); + expect(flop).toHaveLength(1); + // The same piece on two paths yields distinct placement keys. + expect(placementKey(deck[0]!)).not.toBe(placementKey(flop[0]!)); + }); }); describe('computeRenderState', () => { @@ -116,11 +137,14 @@ describe('computeRenderState', () => { }); describe('placementKey', () => { - it('is unique per surface and piece', () => { - const a = { surface: 'board#harbor', piece: 'harbor:card#a' } as never; - const b = { surface: 'board#harbor', piece: 'harbor:card#b' } as never; - const c = { surface: 'hud#hand', piece: 'harbor:card#a' } as never; + it('is unique per surface, path, and piece', () => { + const a = { surface: 'board#harbor', path: '/deck', piece: 'harbor:card#a' } as never; + const b = { surface: 'board#harbor', path: '/deck', piece: 'harbor:card#b' } as never; + const c = { surface: 'hud#hand', path: '/deck', piece: 'harbor:card#a' } as never; + // The same piece on two paths of the same surface is a distinct placement. + const d = { surface: 'board#harbor', path: '/community/0', piece: 'harbor:card#a' } as never; expect(placementKey(a)).not.toBe(placementKey(b)); expect(placementKey(a)).not.toBe(placementKey(c)); + expect(placementKey(a)).not.toBe(placementKey(d)); }); }); \ No newline at end of file diff --git a/packages/tabletop/src/state.ts b/packages/tabletop/src/state.ts index fa84326..17ca2c9 100644 --- a/packages/tabletop/src/state.ts +++ b/packages/tabletop/src/state.ts @@ -22,6 +22,8 @@ export interface GameState { export interface Placement { /** Surface id (`type#id`). */ surface: string; + /** The path key this placement came from. */ + path: string; /** The matched route. */ route: Route; /** The matched candidate, when the route has `:param`s. */ @@ -97,6 +99,7 @@ export function computeSurfacePlacements(surface: Surface, paths: Record {own.map((p) => ( - + ))} {node.children.map((child) => ( diff --git a/packages/tabletop/src/surfaces/WorldSurfaceView.tsx b/packages/tabletop/src/surfaces/WorldSurfaceView.tsx index 631664e..e2b6dd0 100644 --- a/packages/tabletop/src/surfaces/WorldSurfaceView.tsx +++ b/packages/tabletop/src/surfaces/WorldSurfaceView.tsx @@ -7,7 +7,7 @@ * it's never rendered. */ import type { Package } from '@tts/bgm'; -import { useRenderState } from '../state.js'; +import { useRenderState, placementKey } from '../state.js'; import { PartPlacement } from '../placement.js'; import type { MountNode } from '../mount.js'; @@ -23,7 +23,7 @@ export function SurfaceNode({ pkg, node }: { pkg: Package; node: MountNode }) { return ( {own.map((p) => ( - + ))} {node.children.map((child) => (