@bitmagic/world-forger 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/artifact-types.d.ts +295 -0
  2. package/dist/artifact-types.d.ts.map +1 -0
  3. package/dist/artifact-types.js +17 -0
  4. package/dist/artifact-types.js.map +1 -0
  5. package/dist/forge-constants.d.ts +106 -0
  6. package/dist/forge-constants.d.ts.map +1 -0
  7. package/dist/forge-constants.js +111 -0
  8. package/dist/forge-constants.js.map +1 -0
  9. package/dist/index.d.ts +28 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +28 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/log.d.ts +26 -0
  14. package/dist/log.d.ts.map +1 -0
  15. package/dist/log.js +14 -0
  16. package/dist/log.js.map +1 -0
  17. package/dist/pipeline/archetype-skips.d.ts +57 -0
  18. package/dist/pipeline/archetype-skips.d.ts.map +1 -0
  19. package/dist/pipeline/archetype-skips.js +51 -0
  20. package/dist/pipeline/archetype-skips.js.map +1 -0
  21. package/dist/pipeline/artifact-store.d.ts +4 -0
  22. package/dist/pipeline/artifact-store.d.ts.map +1 -0
  23. package/dist/pipeline/artifact-store.js +10 -0
  24. package/dist/pipeline/artifact-store.js.map +1 -0
  25. package/dist/pipeline/asset-catalog.d.ts +27 -0
  26. package/dist/pipeline/asset-catalog.d.ts.map +1 -0
  27. package/dist/pipeline/asset-catalog.js +47 -0
  28. package/dist/pipeline/asset-catalog.js.map +1 -0
  29. package/dist/pipeline/create-level-archetypes.d.ts +45 -0
  30. package/dist/pipeline/create-level-archetypes.d.ts.map +1 -0
  31. package/dist/pipeline/create-level-archetypes.js +197 -0
  32. package/dist/pipeline/create-level-archetypes.js.map +1 -0
  33. package/dist/pipeline/deps.d.ts +72 -0
  34. package/dist/pipeline/deps.d.ts.map +1 -0
  35. package/dist/pipeline/deps.js +2 -0
  36. package/dist/pipeline/deps.js.map +1 -0
  37. package/dist/pipeline/errors.d.ts +10 -0
  38. package/dist/pipeline/errors.d.ts.map +1 -0
  39. package/dist/pipeline/errors.js +13 -0
  40. package/dist/pipeline/errors.js.map +1 -0
  41. package/dist/pipeline/forged-work-store.d.ts +46 -0
  42. package/dist/pipeline/forged-work-store.d.ts.map +1 -0
  43. package/dist/pipeline/forged-work-store.js +69 -0
  44. package/dist/pipeline/forged-work-store.js.map +1 -0
  45. package/dist/pipeline/index.d.ts +36 -0
  46. package/dist/pipeline/index.d.ts.map +1 -0
  47. package/dist/pipeline/index.js +36 -0
  48. package/dist/pipeline/index.js.map +1 -0
  49. package/dist/pipeline/job-types.d.ts +145 -0
  50. package/dist/pipeline/job-types.d.ts.map +1 -0
  51. package/dist/pipeline/job-types.js +2 -0
  52. package/dist/pipeline/job-types.js.map +1 -0
  53. package/dist/pipeline/level-registry.d.ts +57 -0
  54. package/dist/pipeline/level-registry.d.ts.map +1 -0
  55. package/dist/pipeline/level-registry.js +108 -0
  56. package/dist/pipeline/level-registry.js.map +1 -0
  57. package/dist/pipeline/model-types.d.ts +51 -0
  58. package/dist/pipeline/model-types.d.ts.map +1 -0
  59. package/dist/pipeline/model-types.js +2 -0
  60. package/dist/pipeline/model-types.js.map +1 -0
  61. package/dist/pipeline/persist-level.d.ts +82 -0
  62. package/dist/pipeline/persist-level.d.ts.map +1 -0
  63. package/dist/pipeline/persist-level.js +201 -0
  64. package/dist/pipeline/persist-level.js.map +1 -0
  65. package/dist/pipeline/place-and-persist-level.d.ts +83 -0
  66. package/dist/pipeline/place-and-persist-level.d.ts.map +1 -0
  67. package/dist/pipeline/place-and-persist-level.js +405 -0
  68. package/dist/pipeline/place-and-persist-level.js.map +1 -0
  69. package/dist/pipeline/request-id.d.ts +7 -0
  70. package/dist/pipeline/request-id.d.ts.map +1 -0
  71. package/dist/pipeline/request-id.js +10 -0
  72. package/dist/pipeline/request-id.js.map +1 -0
  73. package/dist/pipeline/spawn-points.d.ts +47 -0
  74. package/dist/pipeline/spawn-points.d.ts.map +1 -0
  75. package/dist/pipeline/spawn-points.js +100 -0
  76. package/dist/pipeline/spawn-points.js.map +1 -0
  77. package/dist/pipeline/starter-scatter.d.ts +34 -0
  78. package/dist/pipeline/starter-scatter.d.ts.map +1 -0
  79. package/dist/pipeline/starter-scatter.js +49 -0
  80. package/dist/pipeline/starter-scatter.js.map +1 -0
  81. package/dist/pipeline/storage-types.d.ts +48 -0
  82. package/dist/pipeline/storage-types.d.ts.map +1 -0
  83. package/dist/pipeline/storage-types.js +19 -0
  84. package/dist/pipeline/storage-types.js.map +1 -0
  85. package/dist/pipeline/transport-types.d.ts +35 -0
  86. package/dist/pipeline/transport-types.d.ts.map +1 -0
  87. package/dist/pipeline/transport-types.js +2 -0
  88. package/dist/pipeline/transport-types.js.map +1 -0
  89. package/dist/pipeline/voxelize-level.d.ts +24 -0
  90. package/dist/pipeline/voxelize-level.d.ts.map +1 -0
  91. package/dist/pipeline/voxelize-level.js +63 -0
  92. package/dist/pipeline/voxelize-level.js.map +1 -0
  93. package/dist/pipeline/world-json-source.d.ts +78 -0
  94. package/dist/pipeline/world-json-source.d.ts.map +1 -0
  95. package/dist/pipeline/world-json-source.js +2 -0
  96. package/dist/pipeline/world-json-source.js.map +1 -0
  97. package/dist/pipeline/world-json-types.d.ts +104 -0
  98. package/dist/pipeline/world-json-types.d.ts.map +1 -0
  99. package/dist/pipeline/world-json-types.js +15 -0
  100. package/dist/pipeline/world-json-types.js.map +1 -0
  101. package/dist/terrain-height-grid.d.ts +45 -0
  102. package/dist/terrain-height-grid.d.ts.map +1 -0
  103. package/dist/terrain-height-grid.js +50 -0
  104. package/dist/terrain-height-grid.js.map +1 -0
  105. package/package.json +72 -0
@@ -0,0 +1,82 @@
1
+ import type { WorldJsonModification, WorldJsonShape } from './world-json-types.js';
2
+ /**
3
+ * Pure builder for the world-forger persist step's world.json modifications —
4
+ * level-native (multi-level registry) with a legacy fallback, plus the
5
+ * orphaned-placeholder purge. Extracted from placeAndPersistLevel so the
6
+ * invariants are unit-testable without the WS/browser machinery:
7
+ *
8
+ * - A forge targets a LEVEL. Re-forging the same level asset replaces THAT
9
+ * level's instances only; other levels and globals are untouched.
10
+ * - The registry is created on first forge (conversion moment: the game's
11
+ * current baked world becomes level 1 and keeps its content; a procedural
12
+ * game's forged level becomes levels[0]). An unconvertible legacy world
13
+ * (voxelUrl with no matching vwld asset) falls back to today's legacy
14
+ * semantics rather than failing the forge.
15
+ * - Legacy mirror invariant: voxelUrl/spawn/water globals are written ONLY
16
+ * when the forged level is the start level. Dungeon doors/key pickups are
17
+ * NOT part of that set — they stay level-native even for the start level
18
+ * (frozen game code has no door system, and a global copy would leak one
19
+ * level's doors into every other one). Atmosphere overrides ride the level
20
+ * entry, except in legacy-fallback mode, where they mirror onto the globals
21
+ * because there is no entry to carry them.
22
+ * - Reused assets (`reused: true` stubs) are never upserted — the existing
23
+ * record may be an HQ model.
24
+ * - Purge: forge placeholders (`placeholder === true`) with zero instances
25
+ * anywhere and no active HQ job are removed from assets[] (fixes the
26
+ * re-forge pile-up). HQ/user/vwld assets are never purged.
27
+ */
28
+ export interface PersistLevelInput {
29
+ existingWorldJson: WorldJsonShape;
30
+ /** The vwld level asset record (stable id when the orchestrator pre-mints). */
31
+ levelAsset: Record<string, unknown>;
32
+ levelName: string;
33
+ /** Baked archetype asset records; reused stubs carry `reused: true`. */
34
+ archetypeAssets: Array<Record<string, unknown>>;
35
+ /** Placed instance records (already reconciled to writable asset ids). */
36
+ instances: Array<Record<string, unknown>>;
37
+ spawn: {
38
+ x: number;
39
+ y: number;
40
+ z: number;
41
+ };
42
+ spawnPoints: Array<Record<string, unknown>>;
43
+ playerSpawnRotationY: number | null;
44
+ /** World-space sea level; null = no water zones. */
45
+ waterLevelY: number | null;
46
+ /**
47
+ * World-space `DoorDefinition` records for THIS level (`levelId` injected
48
+ * here, `assetId` already injected by the pipeline). Omitted = leave the
49
+ * world's doors untouched; an empty array clears this level's doors (a
50
+ * dungeon re-forged as plain terrain). Never mirrored globally.
51
+ */
52
+ doors?: Array<Record<string, unknown>>;
53
+ /** World-space `KeyItemDefinition` records for THIS level; same semantics as `doors`. */
54
+ keyItems?: Array<Record<string, unknown>>;
55
+ /**
56
+ * Atmosphere overrides merged onto the level entry's `overrides` alongside
57
+ * `waterLevelY` (a dungeon's dark lighting + close fog). Enumerated to match
58
+ * the engine's `WorldLevelOverrides`.
59
+ */
60
+ levelOverrides?: {
61
+ fogConfig?: unknown;
62
+ lightingConfig?: unknown;
63
+ skyboxUrl?: string;
64
+ };
65
+ /** Asset ids with an HQ job in flight — never purged. */
66
+ activeHqAssetIds: Set<string>;
67
+ /** Explicit user intent for the boot level: true = make the forged level the
68
+ * start, false = keep the current start. Omitted = default policy (steal
69
+ * only when there is nothing else to boot into — first level / conversion
70
+ * moment). */
71
+ makeStartLevel?: boolean;
72
+ }
73
+ export interface PersistLevelOutput {
74
+ modifications: WorldJsonModification[];
75
+ /** Null in legacy-fallback mode (no registry written). */
76
+ levelId: string | null;
77
+ isStartLevel: boolean;
78
+ purgedAssetIds: string[];
79
+ notes: string[];
80
+ }
81
+ export declare function buildLevelPersistModifications(input: PersistLevelInput): PersistLevelOutput;
82
+ //# sourceMappingURL=persist-level.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist-level.d.ts","sourceRoot":"","sources":["../../src/pipeline/persist-level.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,qBAAqB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAU/F;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,iBAAiB,EAAE,cAAc,CAAC;IAClC,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChD,0EAA0E;IAC1E,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1C,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5C,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,oDAAoD;IACpD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvC,yFAAyF;IACzF,QAAQ,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1C;;;;OAIG;IACH,cAAc,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvF,yDAAyD;IACzD,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B;;;mBAGe;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,qBAAqB,EAAE,CAAC;IACvC,0DAA0D;IAC1D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAuBD,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,iBAAiB,GAAG,kBAAkB,CA4L3F"}
@@ -0,0 +1,201 @@
1
+ import { getLevels, getStartLevel, isLevelsMode, mintLevelId, synthesizeLevel1, tagUntaggedInstances, } from './level-registry.js';
2
+ /**
3
+ * Replace one level's slice of a level-scoped definition array (doors, key
4
+ * pickups) — the same rule the instance replace uses. In levels mode the level
5
+ * tag is authoritative: every entry of THIS level goes, the incoming set
6
+ * arrives tagged, and other levels are untouched. In legacy-fallback mode
7
+ * there is no level to tag with, so entries are replaced by id.
8
+ */
9
+ function replaceLevelScoped(existing, incoming, levelId) {
10
+ const tagged = levelId === null
11
+ ? incoming
12
+ : incoming.map((e) => ({ ...e, levelId }));
13
+ const kept = levelId === null
14
+ ? existing.filter((e) => !tagged.some((n) => n.id === e.id))
15
+ : existing.filter((e) => e.levelId !== levelId);
16
+ return [...kept, ...tagged];
17
+ }
18
+ export function buildLevelPersistModifications(input) {
19
+ const notes = [];
20
+ const world = input.existingWorldJson;
21
+ const vwldUrl = String(input.levelAsset.url ?? '');
22
+ const levelAssetId = String(input.levelAsset.id);
23
+ // ── Resolve the target level entry (levels mode / conversion / legacy) ──
24
+ let levels = getLevels(world);
25
+ let baseInstances = world.environmentObjects ?? [];
26
+ let legacyFallback = false;
27
+ let converted = false;
28
+ if (!isLevelsMode(world)) {
29
+ try {
30
+ const level1 = synthesizeLevel1(world);
31
+ if (level1) {
32
+ levels = [level1];
33
+ converted = true;
34
+ baseInstances = tagUntaggedInstances(baseInstances, level1.id);
35
+ notes.push(`Converted existing world to level "${level1.name}" (${level1.id})`);
36
+ }
37
+ else {
38
+ levels = []; // procedural game — the forged level becomes levels[0]
39
+ }
40
+ }
41
+ catch (err) {
42
+ // Unconvertible legacy world: keep today's semantics (global voxelUrl
43
+ // replace, stale instances untouched) rather than failing the forge.
44
+ legacyFallback = true;
45
+ notes.push(`Levels registry not created: ${err instanceof Error ? err.message : String(err)}`);
46
+ }
47
+ }
48
+ const existingEntry = levels.find((l) => l.vwldAssetId === levelAssetId) ?? null;
49
+ const levelId = legacyFallback ? null : (existingEntry?.id ?? mintLevelId());
50
+ // ── Instances: replace ONLY this level's set (legacy: full replace-by-add) ──
51
+ const taggedNew = levelId === null
52
+ ? input.instances
53
+ : input.instances.map((o) => ({ ...o, levelId }));
54
+ const keptInstances = levelId === null
55
+ ? baseInstances.filter((o) => !taggedNew.some((n) => n.id === o.id))
56
+ : baseInstances.filter((o) => o.levelId !== levelId);
57
+ const finalInstances = [...keptInstances, ...taggedNew];
58
+ // ── Asset upserts: level asset + baked archetypes (never reused stubs) ──
59
+ const modifications = [
60
+ {
61
+ type: 'upsertRoot',
62
+ path: ['assets'],
63
+ predicate: (a) => a.id === levelAssetId,
64
+ value: input.levelAsset,
65
+ },
66
+ ...input.archetypeAssets
67
+ .filter((a) => a.reused !== true)
68
+ .map((asset) => ({
69
+ type: 'upsertRoot',
70
+ path: ['assets'],
71
+ predicate: (a) => a.id === asset.id,
72
+ value: asset,
73
+ })),
74
+ { type: 'setRoot', path: ['environmentObjects'], value: finalInstances },
75
+ ];
76
+ // ── Level-scoped door / key definitions (dungeon forges) ──
77
+ // Same replace-by-level rule as the instances above, and deliberately NOT
78
+ // part of the start-level mirror set below. The resulting arrays are computed
79
+ // even when this forge writes none, because the PURGE below reads them.
80
+ const finalDefinitions = { doors: [], keyItems: [] };
81
+ for (const key of ['doors', 'keyItems']) {
82
+ const existing = (world.worldProfileData?.[key] ?? []);
83
+ const incoming = input[key];
84
+ // `undefined` = this forge has none, so the array stays exactly as it is.
85
+ const next = incoming === undefined ? existing : replaceLevelScoped(existing, incoming, levelId);
86
+ finalDefinitions[key] = next;
87
+ if (incoming === undefined)
88
+ continue;
89
+ if (existing.length === 0 && next.length === 0)
90
+ continue; // never introduce an empty array
91
+ modifications.push({ type: 'set', path: [key], value: next });
92
+ if (incoming.length > 0)
93
+ notes.push(`Wrote ${incoming.length} ${key} entr${incoming.length === 1 ? 'y' : 'ies'} scoped to this level`);
94
+ }
95
+ // ── Registry + mirrors ──
96
+ let isStartLevel;
97
+ if (legacyFallback) {
98
+ isStartLevel = true; // legacy world IS the (only) world — mirror everything
99
+ // Atmosphere has no level entry to ride on here, and no other level to
100
+ // pollute — so it goes onto the globals. Skipping it is how a dungeon
101
+ // bakes correctly and then renders in full daylight with invisible
102
+ // fixture lights. (Non-atmosphere forges pass nothing and touch nothing.)
103
+ for (const key of ['skyboxUrl', 'fogConfig', 'lightingConfig']) {
104
+ const value = input.levelOverrides?.[key];
105
+ if (value !== undefined)
106
+ modifications.push({ type: 'set', path: [key], value });
107
+ }
108
+ }
109
+ else {
110
+ // Level atmosphere: the forge OWNS this entry's overrides, so a re-forge
111
+ // replaces them wholesale rather than accumulating stale ones.
112
+ const overrides = {
113
+ ...(input.waterLevelY !== null ? { waterLevelY: input.waterLevelY } : {}),
114
+ ...(input.levelOverrides?.skyboxUrl !== undefined ? { skyboxUrl: input.levelOverrides.skyboxUrl } : {}),
115
+ ...(input.levelOverrides?.fogConfig !== undefined ? { fogConfig: input.levelOverrides.fogConfig } : {}),
116
+ ...(input.levelOverrides?.lightingConfig !== undefined ? { lightingConfig: input.levelOverrides.lightingConfig } : {}),
117
+ };
118
+ const entry = {
119
+ id: levelId,
120
+ name: input.levelName,
121
+ vwldAssetId: levelAssetId,
122
+ spawnPoints: input.spawnPoints,
123
+ ...(Object.keys(overrides).length > 0 ? { overrides } : {}),
124
+ };
125
+ const newLevels = existingEntry
126
+ ? levels.map((l) => (l.id === levelId ? entry : l))
127
+ : [...levels, entry];
128
+ modifications.push({ type: 'set', path: ['levels'], value: newLevels });
129
+ if (existingEntry) {
130
+ // Re-forge of an existing level keeps the current start — unless the
131
+ // user explicitly asked for this level to become the boot level.
132
+ const startId = world.worldProfileData?.startLevelId ?? getStartLevel(world)?.id ?? newLevels[0].id;
133
+ isStartLevel = startId === levelId || newLevels.length === 1 || input.makeStartLevel === true;
134
+ if (isStartLevel && startId !== levelId) {
135
+ modifications.push({ type: 'set', path: ['startLevelId'], value: levelId });
136
+ }
137
+ }
138
+ else {
139
+ // Start-level policy for a NEW level: explicit user intent wins; by
140
+ // default steal start only when there is nothing else to boot into —
141
+ // the game's first level, or the conversion moment (pre-levels forges
142
+ // replaced the world outright, so landing in the fresh forge preserves
143
+ // that expectation). Adding a level to a game that already has a
144
+ // levels registry NEVER moves the start on its own — the creator owns
145
+ // that via manage-levels setStart / the Levels panel.
146
+ const onlyLevel = levels.length === 0;
147
+ isStartLevel = onlyLevel || (input.makeStartLevel ?? converted);
148
+ if (isStartLevel) {
149
+ modifications.push({ type: 'set', path: ['startLevelId'], value: levelId });
150
+ }
151
+ else if (converted) {
152
+ // Conversion without stealing start: pin the boot level to the
153
+ // synthesized level 1 explicitly (matches manage-levels' conversion).
154
+ modifications.push({ type: 'set', path: ['startLevelId'], value: levels[0].id });
155
+ }
156
+ }
157
+ }
158
+ if (isStartLevel) {
159
+ // Exactly the pre-levels write set — frozen boot code reads only these.
160
+ modifications.push({ type: 'set', path: ['voxelUrl'], value: vwldUrl }, { type: 'set', path: ['playerSpawnPosition'], value: input.spawn }, { type: 'set', path: ['spawnPoints'], value: input.spawnPoints });
161
+ if (input.playerSpawnRotationY !== null) {
162
+ modifications.push({ type: 'set', path: ['playerSpawnRotationY'], value: input.playerSpawnRotationY });
163
+ }
164
+ if (input.waterLevelY !== null) {
165
+ modifications.push({ type: 'set', path: ['waterLevelY'], value: input.waterLevelY });
166
+ }
167
+ }
168
+ // ── Purge orphaned forge placeholders (the re-forge pile-up fix) ──
169
+ // Door leaves and key pickups are ZERO-INSTANCE assets: nothing in
170
+ // environmentObjects references them, only the door/key definitions do. Purging
171
+ // on instance count alone stripped them on the NEXT forge and left
172
+ // `doors[].assetId` / `keyItems[].assetId` dangling — the door silently
173
+ // degrading to the engine's box fallback. Every SURVIVING definition counts,
174
+ // across all levels, not just the one being forged.
175
+ const referencedAssetIds = new Set([
176
+ ...finalInstances.map((o) => String(o.assetId)),
177
+ ...finalDefinitions.doors.map((d) => String(d.assetId ?? '')),
178
+ ...finalDefinitions.keyItems.map((k) => String(k.assetId ?? '')),
179
+ ]);
180
+ const upsertedIds = new Set([
181
+ levelAssetId,
182
+ ...input.archetypeAssets.map((a) => String(a.id)),
183
+ ]);
184
+ const purgedAssetIds = (world.assets ?? [])
185
+ .filter((a) => a.placeholder === true &&
186
+ !upsertedIds.has(String(a.id)) &&
187
+ !referencedAssetIds.has(String(a.id)) &&
188
+ !input.activeHqAssetIds.has(String(a.id)))
189
+ .map((a) => String(a.id));
190
+ if (purgedAssetIds.length > 0) {
191
+ const purgedSet = new Set(purgedAssetIds);
192
+ modifications.push({
193
+ type: 'removeRoot',
194
+ path: ['assets'],
195
+ predicate: (a) => purgedSet.has(String(a.id)),
196
+ });
197
+ notes.push(`Purged ${purgedAssetIds.length} orphaned placeholder asset(s)`);
198
+ }
199
+ return { modifications, levelId, isStartLevel, purgedAssetIds, notes };
200
+ }
201
+ //# sourceMappingURL=persist-level.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist-level.js","sourceRoot":"","sources":["../../src/pipeline/persist-level.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Step 5 — place the archetype instances in the browser and PLAN the world.json
3
+ * write (level asset record, archetype assets, instances, level URL, spawn).
4
+ *
5
+ * Moved from game-play-agent's `pipeline.ts` verbatim, with three host couplings
6
+ * turned into seams:
7
+ * - the `BATCH_PLACE_VOXEL_OBJECTS` calls go through `deps.transport`,
8
+ * - the two world.json READS go through `deps.worldJson` (see
9
+ * world-json-source.ts for why there are two of them),
10
+ * - the world.json WRITE moves OUT: this step returns the modifications and
11
+ * the heightfield sidecar payload, and the HOST applies both. Same boundary
12
+ * the earlier phases established, and what lets a CLI project apply the
13
+ * result to its own single world.json.
14
+ *
15
+ * The `persisted` artifact is written by the HOST too, after the write lands —
16
+ * it is the job-complete marker a resumed run returns directly, so recording it
17
+ * before the world.json write would let a failed write resume into "done".
18
+ */
19
+ import type { ForgedWorkStore } from './forged-work-store.js';
20
+ import type { ForgeDeps } from './deps.js';
21
+ import type { ForgeLevelSummary } from './job-types.js';
22
+ import type { WorldJsonModification } from './world-json-types.js';
23
+ import type { ForgedDungeonView } from '../artifact-types.js';
24
+ import type { ForgedHeightGrid } from '../terrain-height-grid.js';
25
+ /** What step 5 produces for the host to apply. */
26
+ export interface PlaceAndPersistResult {
27
+ /** The rich tool summary — also the `persisted` artifact the host records. */
28
+ summary: ForgeLevelSummary;
29
+ /**
30
+ * world.json modifications IN ORDER (starter-scatter removals first, then the
31
+ * level's own upserts/sets). The host applies them in exactly this order.
32
+ */
33
+ modifications: WorldJsonModification[];
34
+ /**
35
+ * World-space terrain heightfield to persist as a sidecar NEXT TO world.json
36
+ * (`FORGED_HEIGHTFIELD_FILE`) so post-forge placements can resolve
37
+ * `placeOnTerrain` Y on the forged surface — a placement raycast can't, since
38
+ * the new terrain only loads after the session reload. Best-effort for the
39
+ * host: a missing sidecar just falls back to runtime snapping. Absent on
40
+ * resumed runs whose `forge` artifact predates the field.
41
+ */
42
+ terrainHeightGrid?: ForgedHeightGrid;
43
+ }
44
+ /**
45
+ * Reconcile placed instances against the assets actually being written, so a dangling `assetId`
46
+ * can never make the strict validator discard the WHOLE level at session cleanup (which surfaces
47
+ * as a reported "success" over an empty world). An instance whose `assetId` is not among the
48
+ * written assets is recovered by archetype name (carried on the instance's `type`) when a
49
+ * same-named asset exists; otherwise it is dropped. Pure + exported for unit testing.
50
+ */
51
+ export declare function reconcileInstancesToAssets(placedInstances: Array<Record<string, unknown>>, writableAssetIds: Set<string>, assetIdByName: Map<string, string>): {
52
+ instances: Array<Record<string, unknown>>;
53
+ remapped: number;
54
+ dropped: number;
55
+ };
56
+ /**
57
+ * The per-level atmosphere a dungeon needs to read as one: without it the
58
+ * engine keeps every scene at full daylight and the torch lights are invisible.
59
+ * Fog colour is DERIVED from the plan's own palette (the primary wall material,
60
+ * darkened) rather than a fixed grey, so an ice crypt and a lava mine recede
61
+ * into their own stone; a plan whose every material glows falls back to its
62
+ * first material, which still tracks the theme. Exported for testing.
63
+ */
64
+ export declare function buildDungeonAtmosphere(plan: ForgedDungeonView): {
65
+ fogConfig: {
66
+ enabled: boolean;
67
+ color: string;
68
+ near: number;
69
+ far: number;
70
+ };
71
+ lightingConfig: {
72
+ sunIntensity: number;
73
+ environmentIntensity: number;
74
+ skyboxIntensity: number;
75
+ };
76
+ };
77
+ /**
78
+ * Place the archetype instances and build the world.json modifications that
79
+ * install the level (level asset record, archetype assets, instances, level
80
+ * URL, spawn). The host applies what this returns.
81
+ */
82
+ export declare function placeAndPersistLevel(store: ForgedWorkStore, deps: ForgeDeps): Promise<PlaceAndPersistResult>;
83
+ //# sourceMappingURL=place-and-persist-level.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"place-and-persist-level.d.ts","sourceRoot":"","sources":["../../src/pipeline/place-and-persist-level.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAgC,iBAAiB,EAAqB,MAAM,gBAAgB,CAAC;AACzG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AASnE,OAAO,KAAK,EAAE,iBAAiB,EAAmB,MAAM,sBAAsB,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAOlE,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,OAAO,EAAE,iBAAiB,CAAC;IAC3B;;;OAGG;IACH,aAAa,EAAE,qBAAqB,EAAE,CAAC;IACvC;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,CAAC;CACtC;AA8DD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC/C,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,EAC7B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC;IAAE,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAgBlF;AA+BD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,iBAAiB,GAAG;IAC/D,SAAS,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,cAAc,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,oBAAoB,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;CACjG,CAgBA;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,qBAAqB,CAAC,CAuNhC"}