From c2693277ac270c813fb671cbacf98f56d2e44d30 Mon Sep 17 00:00:00 2001 From: hypercross Date: Mon, 10 Aug 2026 11:27:46 +0800 Subject: [PATCH] feat(bgm): model setup as ordered placements 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. --- packages/bgm/src/__fixtures__/harbor/harbor.md | 6 ++++-- .../__fixtures__/vite-build/games/azul/azul.md | 3 ++- .../vite-build/games/harbor/harbor.md | 6 ++++-- packages/bgm/src/collect.test.ts | 8 ++++---- packages/bgm/src/schemas.ts | 9 ++++++++- packages/bgm/src/types.ts | 16 ++++++++++++++-- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/packages/bgm/src/__fixtures__/harbor/harbor.md b/packages/bgm/src/__fixtures__/harbor/harbor.md index dff3c0c..44a97bf 100644 --- a/packages/bgm/src/__fixtures__/harbor/harbor.md +++ b/packages/bgm/src/__fixtures__/harbor/harbor.md @@ -103,6 +103,8 @@ surfaces: - board#harbor - board#player setup: - /dock/0: harbor:token#wood - /deck: harbor:token#grain + - path: /dock/0 + parts: harbor:token#wood + - path: /deck + parts: harbor:token#grain ``` \ No newline at end of file diff --git a/packages/bgm/src/__fixtures__/vite-build/games/azul/azul.md b/packages/bgm/src/__fixtures__/vite-build/games/azul/azul.md index bc6edd1..3cf658c 100644 --- a/packages/bgm/src/__fixtures__/vite-build/games/azul/azul.md +++ b/packages/bgm/src/__fixtures__/vite-build/games/azul/azul.md @@ -48,5 +48,6 @@ role: setup type: game id: main setup: - /factory/0: azul:tile#blue + - path: /factory/0 + parts: azul:tile#blue ``` \ No newline at end of file diff --git a/packages/bgm/src/__fixtures__/vite-build/games/harbor/harbor.md b/packages/bgm/src/__fixtures__/vite-build/games/harbor/harbor.md index 3099ca0..1385895 100644 --- a/packages/bgm/src/__fixtures__/vite-build/games/harbor/harbor.md +++ b/packages/bgm/src/__fixtures__/vite-build/games/harbor/harbor.md @@ -53,6 +53,8 @@ role: setup type: game id: main setup: - /dock/0: harbor:token#wood - /deck: harbor:token#grain + - path: /dock/0 + parts: harbor:token#wood + - path: /deck + parts: harbor:token#grain ``` diff --git a/packages/bgm/src/collect.test.ts b/packages/bgm/src/collect.test.ts index b7e5ec6..1239461 100644 --- a/packages/bgm/src/collect.test.ts +++ b/packages/bgm/src/collect.test.ts @@ -55,10 +55,10 @@ describe('collectPackages', () => { expect([...harbor.setups.keys()]).toEqual(['game#main']); const setup = harbor.setups.get('game#main')!; expect(setup.surfaces).toEqual(['board#harbor', 'board#player']); - expect(setup.setup).toEqual({ - '/dock/0': 'harbor:token#wood', - '/deck': 'harbor:token#grain', - }); + expect(setup.setup).toEqual([ + { path: '/dock/0', parts: 'harbor:token#wood' }, + { path: '/deck', parts: 'harbor:token#grain' }, + ]); }); it('scopes include patterns to the package declaration directory', () => { diff --git a/packages/bgm/src/schemas.ts b/packages/bgm/src/schemas.ts index 934cdf8..7c49860 100644 --- a/packages/bgm/src/schemas.ts +++ b/packages/bgm/src/schemas.ts @@ -60,11 +60,18 @@ const surfaceSchema = z.object({ layout: z.array(route), }); +const setupValue = z.union([z.string(), z.array(z.string())]); + +const setupPlacement = z.object({ + path: z.string(), + parts: setupValue, +}); + const setupSchema = z.object({ type: z.string().min(1), id: z.string().min(1), surfaces: z.array(z.string()).optional(), - setup: z.record(z.string(), z.union([z.string(), z.array(z.string())])), + setup: z.array(setupPlacement), }); const packageSchema = z.object({ diff --git a/packages/bgm/src/types.ts b/packages/bgm/src/types.ts index e7bd2ac..abec3dd 100644 --- a/packages/bgm/src/types.ts +++ b/packages/bgm/src/types.ts @@ -157,13 +157,25 @@ export interface Surface { export type SetupValue = string | string[]; -/** Seeds the state store: the enabled surfaces and a map from path to parts. */ +/** + * One setup placement: move `parts` to `path`. Entries are applied in order, + * so a part listed in a later entry ends up on that entry's path. + */ +export interface SetupPlacement { + /** The path key to place the parts on. */ + path: string; + /** Parts to place: a part id, a bare type (expands to all of that type), or a list of either. */ + parts: SetupValue; +} + +/** Seeds the state store: the enabled surfaces and an ordered list of placements. */ export interface Setup { type: string; id: string; /** Surfaces (`type#id` refs) enabled at the start; omitted = all enabled. */ surfaces?: string[]; - setup: Record; + /** Ordered placements; each moves its parts to its path. */ + setup: SetupPlacement[]; } export type Role = 'package' | 'part' | 'surface' | 'setup';