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.
This commit is contained in:
2026-08-10 11:27:46 +08:00
parent bc418b2c73
commit c2693277ac
6 changed files with 36 additions and 12 deletions
@@ -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
```
@@ -48,5 +48,6 @@ role: setup
type: game
id: main
setup:
/factory/0: azul:tile#blue
- path: /factory/0
parts: azul:tile#blue
```
@@ -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
```
+4 -4
View File
@@ -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', () => {
+8 -1
View File
@@ -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({
+14 -2
View File
@@ -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<string, SetupValue>;
/** Ordered placements; each moves its parts to its path. */
setup: SetupPlacement[];
}
export type Role = 'package' | 'part' | 'surface' | 'setup';