@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.
- package/dist/artifact-types.d.ts +295 -0
- package/dist/artifact-types.d.ts.map +1 -0
- package/dist/artifact-types.js +17 -0
- package/dist/artifact-types.js.map +1 -0
- package/dist/forge-constants.d.ts +106 -0
- package/dist/forge-constants.d.ts.map +1 -0
- package/dist/forge-constants.js +111 -0
- package/dist/forge-constants.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +26 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +14 -0
- package/dist/log.js.map +1 -0
- package/dist/pipeline/archetype-skips.d.ts +57 -0
- package/dist/pipeline/archetype-skips.d.ts.map +1 -0
- package/dist/pipeline/archetype-skips.js +51 -0
- package/dist/pipeline/archetype-skips.js.map +1 -0
- package/dist/pipeline/artifact-store.d.ts +4 -0
- package/dist/pipeline/artifact-store.d.ts.map +1 -0
- package/dist/pipeline/artifact-store.js +10 -0
- package/dist/pipeline/artifact-store.js.map +1 -0
- package/dist/pipeline/asset-catalog.d.ts +27 -0
- package/dist/pipeline/asset-catalog.d.ts.map +1 -0
- package/dist/pipeline/asset-catalog.js +47 -0
- package/dist/pipeline/asset-catalog.js.map +1 -0
- package/dist/pipeline/create-level-archetypes.d.ts +45 -0
- package/dist/pipeline/create-level-archetypes.d.ts.map +1 -0
- package/dist/pipeline/create-level-archetypes.js +197 -0
- package/dist/pipeline/create-level-archetypes.js.map +1 -0
- package/dist/pipeline/deps.d.ts +72 -0
- package/dist/pipeline/deps.d.ts.map +1 -0
- package/dist/pipeline/deps.js +2 -0
- package/dist/pipeline/deps.js.map +1 -0
- package/dist/pipeline/errors.d.ts +10 -0
- package/dist/pipeline/errors.d.ts.map +1 -0
- package/dist/pipeline/errors.js +13 -0
- package/dist/pipeline/errors.js.map +1 -0
- package/dist/pipeline/forged-work-store.d.ts +46 -0
- package/dist/pipeline/forged-work-store.d.ts.map +1 -0
- package/dist/pipeline/forged-work-store.js +69 -0
- package/dist/pipeline/forged-work-store.js.map +1 -0
- package/dist/pipeline/index.d.ts +36 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +36 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/job-types.d.ts +145 -0
- package/dist/pipeline/job-types.d.ts.map +1 -0
- package/dist/pipeline/job-types.js +2 -0
- package/dist/pipeline/job-types.js.map +1 -0
- package/dist/pipeline/level-registry.d.ts +57 -0
- package/dist/pipeline/level-registry.d.ts.map +1 -0
- package/dist/pipeline/level-registry.js +108 -0
- package/dist/pipeline/level-registry.js.map +1 -0
- package/dist/pipeline/model-types.d.ts +51 -0
- package/dist/pipeline/model-types.d.ts.map +1 -0
- package/dist/pipeline/model-types.js +2 -0
- package/dist/pipeline/model-types.js.map +1 -0
- package/dist/pipeline/persist-level.d.ts +82 -0
- package/dist/pipeline/persist-level.d.ts.map +1 -0
- package/dist/pipeline/persist-level.js +201 -0
- package/dist/pipeline/persist-level.js.map +1 -0
- package/dist/pipeline/place-and-persist-level.d.ts +83 -0
- package/dist/pipeline/place-and-persist-level.d.ts.map +1 -0
- package/dist/pipeline/place-and-persist-level.js +405 -0
- package/dist/pipeline/place-and-persist-level.js.map +1 -0
- package/dist/pipeline/request-id.d.ts +7 -0
- package/dist/pipeline/request-id.d.ts.map +1 -0
- package/dist/pipeline/request-id.js +10 -0
- package/dist/pipeline/request-id.js.map +1 -0
- package/dist/pipeline/spawn-points.d.ts +47 -0
- package/dist/pipeline/spawn-points.d.ts.map +1 -0
- package/dist/pipeline/spawn-points.js +100 -0
- package/dist/pipeline/spawn-points.js.map +1 -0
- package/dist/pipeline/starter-scatter.d.ts +34 -0
- package/dist/pipeline/starter-scatter.d.ts.map +1 -0
- package/dist/pipeline/starter-scatter.js +49 -0
- package/dist/pipeline/starter-scatter.js.map +1 -0
- package/dist/pipeline/storage-types.d.ts +48 -0
- package/dist/pipeline/storage-types.d.ts.map +1 -0
- package/dist/pipeline/storage-types.js +19 -0
- package/dist/pipeline/storage-types.js.map +1 -0
- package/dist/pipeline/transport-types.d.ts +35 -0
- package/dist/pipeline/transport-types.d.ts.map +1 -0
- package/dist/pipeline/transport-types.js +2 -0
- package/dist/pipeline/transport-types.js.map +1 -0
- package/dist/pipeline/voxelize-level.d.ts +24 -0
- package/dist/pipeline/voxelize-level.d.ts.map +1 -0
- package/dist/pipeline/voxelize-level.js +63 -0
- package/dist/pipeline/voxelize-level.js.map +1 -0
- package/dist/pipeline/world-json-source.d.ts +78 -0
- package/dist/pipeline/world-json-source.d.ts.map +1 -0
- package/dist/pipeline/world-json-source.js +2 -0
- package/dist/pipeline/world-json-source.js.map +1 -0
- package/dist/pipeline/world-json-types.d.ts +104 -0
- package/dist/pipeline/world-json-types.d.ts.map +1 -0
- package/dist/pipeline/world-json-types.js +15 -0
- package/dist/pipeline/world-json-types.js.map +1 -0
- package/dist/terrain-height-grid.d.ts +45 -0
- package/dist/terrain-height-grid.d.ts.map +1 -0
- package/dist/terrain-height-grid.js +50 -0
- package/dist/terrain-height-grid.js.map +1 -0
- 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"}
|