@bitmagic/cli 0.1.18 → 0.1.20
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/README.md +58 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/edit.d.ts +10 -0
- package/dist/commands/edit.js +147 -0
- package/dist/commands/edit.js.map +1 -0
- package/dist/commands/generate.d.ts +15 -0
- package/dist/commands/generate.js +87 -0
- package/dist/commands/generate.js.map +1 -1
- package/dist/editor/journal.d.ts +108 -0
- package/dist/editor/journal.js +214 -0
- package/dist/editor/journal.js.map +1 -0
- package/dist/editor/save.d.ts +57 -0
- package/dist/editor/save.js +144 -0
- package/dist/editor/save.js.map +1 -0
- package/dist/editor/server.d.ts +24 -0
- package/dist/editor/server.js +306 -0
- package/dist/editor/server.js.map +1 -0
- package/dist/editor/shell-page.d.ts +38 -0
- package/dist/editor/shell-page.js +455 -0
- package/dist/editor/shell-page.js.map +1 -0
- package/dist/generate/prop.d.ts +47 -0
- package/dist/generate/prop.js +208 -0
- package/dist/generate/prop.js.map +1 -0
- package/dist/generate/stream.d.ts +6 -0
- package/dist/generate/stream.js +7 -2
- package/dist/generate/stream.js.map +1 -1
- package/dist/scaffold/project-files.js +42 -0
- package/dist/scaffold/project-files.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor's event journal — how the agent that launched `bitmagic edit` finds out what the
|
|
3
|
+
* human did in the browser.
|
|
4
|
+
*
|
|
5
|
+
* For a drag, `git diff src/work/world.json` is already a complete record: the change is
|
|
6
|
+
* synchronous and self-describing. That stops being true the moment an editor action is
|
|
7
|
+
* asynchronous, costs money, or expresses an intention the editor cannot carry out itself — the
|
|
8
|
+
* "Generate high-quality version" button is all three. An agent watching only the file would see
|
|
9
|
+
* an asset mutate minutes later with no idea who asked, what it cost, or that anything is pending.
|
|
10
|
+
*
|
|
11
|
+
* So every editor action is appended here as one JSON object per line, and mirrored to stdout for
|
|
12
|
+
* whatever launched the command. The journal lives under `.bitmagic/`, which the scaffold
|
|
13
|
+
* gitignores: world.json is the tracked record of WHAT the scene is, and this is the untracked
|
|
14
|
+
* account of WHO changed it and WHY. Append-only and never rewritten, so an agent can read it
|
|
15
|
+
* incrementally (by byte offset or line count) without racing the writer.
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from 'fs';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
/** Journal location, relative to the project root. */
|
|
20
|
+
export const JOURNAL_DIR = path.join('.bitmagic', 'edit');
|
|
21
|
+
export const JOURNAL_FILE = 'events.jsonl';
|
|
22
|
+
function isRecord(value) {
|
|
23
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function asVec3(value) {
|
|
26
|
+
if (!isRecord(value))
|
|
27
|
+
return null;
|
|
28
|
+
const { x, y, z } = value;
|
|
29
|
+
if (typeof x !== 'number' || typeof y !== 'number' || typeof z !== 'number')
|
|
30
|
+
return null;
|
|
31
|
+
return { x, y, z };
|
|
32
|
+
}
|
|
33
|
+
/** A 1e-15 float wobble is not an edit worth a journal line. */
|
|
34
|
+
function near(a, b) {
|
|
35
|
+
return Math.abs(a - b) < 1e-6;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Euler angles compare modulo a full turn.
|
|
39
|
+
*
|
|
40
|
+
* Not a nicety: world.json stores rotations in radians, and the engine hands back the value its
|
|
41
|
+
* own quaternion round-trip produced — so a `y` of 4.712 comes back as -1.571, the identical
|
|
42
|
+
* heading. Compared naively that is a rotation on every object the engine re-serializes, and the
|
|
43
|
+
* journal fills with turns nobody made.
|
|
44
|
+
*/
|
|
45
|
+
function sameAngle(a, b) {
|
|
46
|
+
const TWO_PI = Math.PI * 2;
|
|
47
|
+
const delta = Math.abs(a - b) % TWO_PI;
|
|
48
|
+
return near(delta, 0) || near(delta, TWO_PI);
|
|
49
|
+
}
|
|
50
|
+
function sameVec3(a, b, angular) {
|
|
51
|
+
if (a === null || b === null)
|
|
52
|
+
return a === b;
|
|
53
|
+
const same = angular ? sameAngle : near;
|
|
54
|
+
return same(a.x, b.x) && same(a.y, b.y) && same(a.z, b.z);
|
|
55
|
+
}
|
|
56
|
+
function transformDelta(before, after) {
|
|
57
|
+
const delta = {};
|
|
58
|
+
for (const field of ['position', 'rotation', 'scale']) {
|
|
59
|
+
const to = asVec3(after[field]);
|
|
60
|
+
if (to === null)
|
|
61
|
+
continue;
|
|
62
|
+
const from = before ? asVec3(before[field]) : null;
|
|
63
|
+
if (sameVec3(from, to, field === 'rotation'))
|
|
64
|
+
continue;
|
|
65
|
+
delta[field] = { from, to };
|
|
66
|
+
}
|
|
67
|
+
return delta;
|
|
68
|
+
}
|
|
69
|
+
function stringField(source, key) {
|
|
70
|
+
const value = source[key];
|
|
71
|
+
return typeof value === 'string' ? value : undefined;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* What changed between the world on disk and what the engine is reporting.
|
|
75
|
+
*
|
|
76
|
+
* Pure, and given the BEFORE document rather than reading it — so the caller reads world.json once
|
|
77
|
+
* for both this and the modification builder, and the whole thing is testable without a filesystem
|
|
78
|
+
* or a browser.
|
|
79
|
+
*
|
|
80
|
+
* Silence is meaningful: an upserted object whose transform is unchanged produces no line. The
|
|
81
|
+
* engine re-serializes every object it was told is modified, including ones whose only "change"
|
|
82
|
+
* was being marked, and a journal that logged those would drown the real edits.
|
|
83
|
+
*/
|
|
84
|
+
export function deriveSceneEvents(before, payload) {
|
|
85
|
+
const previous = new Map();
|
|
86
|
+
for (const object of before.environmentObjects ?? []) {
|
|
87
|
+
const id = object.id;
|
|
88
|
+
if (typeof id === 'string')
|
|
89
|
+
previous.set(id, object);
|
|
90
|
+
}
|
|
91
|
+
const events = [];
|
|
92
|
+
for (const id of payload.changes.deletedObjectIds ?? []) {
|
|
93
|
+
const gone = previous.get(id);
|
|
94
|
+
events.push({
|
|
95
|
+
event: 'object.deleted',
|
|
96
|
+
objectId: id,
|
|
97
|
+
...(gone ? { type: stringField(gone, 'type'), assetId: stringField(gone, 'assetId') } : {}),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const deleted = new Set(payload.changes.deletedObjectIds ?? []);
|
|
101
|
+
const fullSave = payload.changes.fullSaveNeeded === true;
|
|
102
|
+
const modified = new Set((payload.changes.modifiedObjectIds ?? []).filter((id) => id !== '__FULL_SAVE__' && !id.startsWith('spawn_')));
|
|
103
|
+
for (const object of payload.status.environmentObjects ?? []) {
|
|
104
|
+
const id = object.id;
|
|
105
|
+
if (typeof id !== 'string' || deleted.has(id))
|
|
106
|
+
continue;
|
|
107
|
+
if (!fullSave && !modified.has(id))
|
|
108
|
+
continue;
|
|
109
|
+
const was = previous.get(id);
|
|
110
|
+
if (was === undefined) {
|
|
111
|
+
events.push({
|
|
112
|
+
event: 'object.added',
|
|
113
|
+
objectId: id,
|
|
114
|
+
type: stringField(object, 'type'),
|
|
115
|
+
assetId: stringField(object, 'assetId'),
|
|
116
|
+
...(asVec3(object.position) ? { position: asVec3(object.position) } : {}),
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const delta = transformDelta(was, object);
|
|
121
|
+
if (Object.keys(delta).length === 0)
|
|
122
|
+
continue;
|
|
123
|
+
events.push({
|
|
124
|
+
event: 'object.moved',
|
|
125
|
+
objectId: id,
|
|
126
|
+
type: stringField(object, 'type'),
|
|
127
|
+
assetId: stringField(object, 'assetId'),
|
|
128
|
+
...delta,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const spawnPoints = payload.changes.pendingSpawnPoints;
|
|
132
|
+
if (spawnPoints && spawnPoints.length > 0) {
|
|
133
|
+
const player = spawnPoints.find((point) => point.type === 'player');
|
|
134
|
+
const at = player ? asVec3(player.position) : null;
|
|
135
|
+
events.push({ event: 'spawn.changed', count: spawnPoints.length, ...(at ? { playerSpawn: at } : {}) });
|
|
136
|
+
}
|
|
137
|
+
if (payload.markersDirty === true) {
|
|
138
|
+
events.push({ event: 'markers.changed', count: (payload.status.markers ?? []).length });
|
|
139
|
+
}
|
|
140
|
+
const worldConfig = payload.changes.pendingWorldConfig;
|
|
141
|
+
if (worldConfig && Array.isArray(worldConfig.path) && worldConfig.path.length > 0) {
|
|
142
|
+
events.push({ event: 'worldconfig.changed', path: worldConfig.path });
|
|
143
|
+
}
|
|
144
|
+
return events;
|
|
145
|
+
}
|
|
146
|
+
function short(id) {
|
|
147
|
+
return id.length > 22 ? `${id.slice(0, 22)}…` : id;
|
|
148
|
+
}
|
|
149
|
+
function vec(value) {
|
|
150
|
+
if (value === null)
|
|
151
|
+
return '(new)';
|
|
152
|
+
const round = (n) => (Math.round(n * 100) / 100).toString();
|
|
153
|
+
return `(${round(value.x)}, ${round(value.y)}, ${round(value.z)})`;
|
|
154
|
+
}
|
|
155
|
+
/** The one-line human form mirrored to stdout. The JSONL line stays the machine contract. */
|
|
156
|
+
export function formatEvent(event) {
|
|
157
|
+
switch (event.event) {
|
|
158
|
+
case 'session.started':
|
|
159
|
+
return `[edit] session.started ${event.gameId} → ${event.editorUrl}`;
|
|
160
|
+
case 'object.moved': {
|
|
161
|
+
const parts = ['position', 'rotation', 'scale']
|
|
162
|
+
.filter((field) => event[field] !== undefined)
|
|
163
|
+
.map((field) => `${field} ${vec(event[field].from)} → ${vec(event[field].to)}`);
|
|
164
|
+
return `[edit] object.moved ${short(event.objectId)}${event.type ? ` (${event.type})` : ''} ${parts.join(', ')}`;
|
|
165
|
+
}
|
|
166
|
+
case 'object.added':
|
|
167
|
+
return `[edit] object.added ${short(event.objectId)}${event.type ? ` (${event.type})` : ''} at ${vec(event.position ?? null)}`;
|
|
168
|
+
case 'object.deleted':
|
|
169
|
+
return `[edit] object.deleted ${short(event.objectId)}${event.type ? ` (${event.type})` : ''}`;
|
|
170
|
+
case 'spawn.changed':
|
|
171
|
+
return `[edit] spawn.changed ${event.count} spawn point(s)${event.playerSpawn ? `, player at ${vec(event.playerSpawn)}` : ''}`;
|
|
172
|
+
case 'markers.changed':
|
|
173
|
+
return `[edit] markers.changed ${event.count} marker(s)`;
|
|
174
|
+
case 'worldconfig.changed':
|
|
175
|
+
return `[edit] worldconfig.changed worldProfileData.${event.path.join('.')}`;
|
|
176
|
+
case 'hq.requested':
|
|
177
|
+
return `[edit] hq.requested "${event.assetName}" — run: ${event.command}`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Append-only writer. Failures to write are reported through `log` and never thrown: the journal
|
|
182
|
+
* is a record OF the edit, and losing the record must not cost the creator the edit itself.
|
|
183
|
+
*/
|
|
184
|
+
export class EditorJournal {
|
|
185
|
+
file;
|
|
186
|
+
log;
|
|
187
|
+
now;
|
|
188
|
+
constructor(options) {
|
|
189
|
+
this.file = path.join(options.root, JOURNAL_DIR, JOURNAL_FILE);
|
|
190
|
+
this.log = options.log ?? (() => { });
|
|
191
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
192
|
+
}
|
|
193
|
+
/** Absolute path of the journal, for the startup banner that tells an agent where to look. */
|
|
194
|
+
get path() {
|
|
195
|
+
return this.file;
|
|
196
|
+
}
|
|
197
|
+
append(...events) {
|
|
198
|
+
if (events.length === 0)
|
|
199
|
+
return;
|
|
200
|
+
const lines = events.map((event) => {
|
|
201
|
+
this.log(formatEvent(event));
|
|
202
|
+
return JSON.stringify({ at: this.now(), ...event });
|
|
203
|
+
});
|
|
204
|
+
try {
|
|
205
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true });
|
|
206
|
+
fs.appendFileSync(this.file, `${lines.join('\n')}\n`);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
210
|
+
this.log(`[edit] could not write ${this.file}: ${message}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=journal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/editor/journal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn the game engine's own scene-editing replies into a `WorldJsonModification[]`.
|
|
3
|
+
*
|
|
4
|
+
* This is the CLI lane's counterpart to the Creator's `useSave.ts saveSceneModifications` and the
|
|
5
|
+
* spawn-point/world-config half of `useTabSwitching.ts deactivateScene`. The web lane posts its
|
|
6
|
+
* modifications to `POST /api/edit-world-config`, which rebuilds `predicateField`/`predicateValue`
|
|
7
|
+
* into real predicates server-side because functions cannot cross HTTP. Here there is no server:
|
|
8
|
+
* the shell page posts the engine's replies VERBATIM to the local sidecar, and this module — which
|
|
9
|
+
* runs in Node, in the same process that writes the file — builds the predicates directly.
|
|
10
|
+
*
|
|
11
|
+
* That split is deliberate. Everything that knows what world.json means lives on this side, so it
|
|
12
|
+
* is plain, synchronous, dependency-free code that can be unit-tested without a browser; the shell
|
|
13
|
+
* stays a dumb relay of `SCENE_HAS_CHANGES` and `SCENE_EDITING_STATUS`.
|
|
14
|
+
*/
|
|
15
|
+
import type { WorldJsonModification, WorldJsonShape } from '@bitmagic/world-forger/pipeline/index.js';
|
|
16
|
+
/** The engine's `SCENE_HAS_CHANGES` reply (`CreatorMessageHandler.ts`, `CHECK_SCENE_CHANGES`). */
|
|
17
|
+
export interface SceneChangeReport {
|
|
18
|
+
hasChanges?: boolean;
|
|
19
|
+
modifiedObjectIds?: string[];
|
|
20
|
+
deletedObjectIds?: string[];
|
|
21
|
+
fullSaveNeeded?: boolean;
|
|
22
|
+
pendingWorldConfig?: {
|
|
23
|
+
settings: unknown;
|
|
24
|
+
path: string[];
|
|
25
|
+
title?: string;
|
|
26
|
+
} | null;
|
|
27
|
+
pendingSpawnPoints?: Array<Record<string, unknown>> | null;
|
|
28
|
+
}
|
|
29
|
+
/** The engine's `SCENE_EDITING_STATUS` reply (`CreatorMessageHandler.ts`, line ~1463). */
|
|
30
|
+
export interface SceneEditingStatus {
|
|
31
|
+
unlocked?: boolean;
|
|
32
|
+
environmentObjects?: Array<Record<string, unknown>>;
|
|
33
|
+
/**
|
|
34
|
+
* WHICH level `environmentObjects` covers. `serializeEnvironmentObjects()` walks the LIVE scene
|
|
35
|
+
* and a multi-level game only instantiates one level, so a non-null id means the array is this
|
|
36
|
+
* level plus untagged globals — never the whole world. `null` (single-level or legacy) means it
|
|
37
|
+
* genuinely is the whole world.
|
|
38
|
+
*/
|
|
39
|
+
activeLevelId?: string | null;
|
|
40
|
+
markers?: Array<Record<string, unknown>>;
|
|
41
|
+
}
|
|
42
|
+
/** What the shell POSTs to `/api/scene/save`. Just the two engine replies, plus one flag. */
|
|
43
|
+
export interface ScenePayload {
|
|
44
|
+
changes: SceneChangeReport;
|
|
45
|
+
status: SceneEditingStatus;
|
|
46
|
+
/** Set by the shell when the engine posted `ADD_MARKER` / `UPDATE_MARKER` since the last save. */
|
|
47
|
+
markersDirty?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build the write for one scene-editing flush. Returns `[]` when there is nothing to persist, so
|
|
51
|
+
* the caller can skip the file write entirely.
|
|
52
|
+
*
|
|
53
|
+
* `world` is the CURRENT parsed world.json, needed only to resolve the levels registry. Everything
|
|
54
|
+
* else comes from the engine, which is the source of truth for scene state — this never invents a
|
|
55
|
+
* position, rotation or scale.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildSceneModifications(payload: ScenePayload, world: WorldJsonShape): WorldJsonModification[];
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `EditorManager.markFullSaveNeeded()` pushes this sentinel into the same `Set` that holds real
|
|
3
|
+
* object ids, so it arrives in `modifiedObjectIds` and must be filtered out before any id is
|
|
4
|
+
* matched against an object. `fullSaveNeeded` carries the actual signal.
|
|
5
|
+
*/
|
|
6
|
+
const FULL_SAVE_SENTINEL = '__FULL_SAVE__';
|
|
7
|
+
/** Spawn markers are tracked in the same modified-id set but persist through `pendingSpawnPoints`. */
|
|
8
|
+
const SPAWN_ID_PREFIX = 'spawn_';
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
/** Match an array item by its `id`, the predicate every environmentObjects write uses. */
|
|
13
|
+
function byId(id) {
|
|
14
|
+
return (item) => isRecord(item) && item.id === id;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The spawn-point half, mirroring `useTabSwitching.ts deactivateScene` lines 234-303.
|
|
18
|
+
*
|
|
19
|
+
* The rule that matters: in a levels game the spawns belong to the ACTIVE level's entry, and the
|
|
20
|
+
* flat `spawnPoints` / `playerSpawn*` fields are only mirrored when that level is the start level.
|
|
21
|
+
* Published games carry frozen template code that reads only the flat fields, so the mirror cannot
|
|
22
|
+
* be dropped — but writing it from a non-start level would make the wrong level's spawn the one
|
|
23
|
+
* every published build uses.
|
|
24
|
+
*/
|
|
25
|
+
function spawnPointModifications(spawnPoints, activeLevelId, world) {
|
|
26
|
+
const player = spawnPoints.find((point) => point.type === 'player');
|
|
27
|
+
const globals = [
|
|
28
|
+
{ type: 'set', path: ['spawnPoints'], value: spawnPoints },
|
|
29
|
+
];
|
|
30
|
+
if (player?.position !== undefined) {
|
|
31
|
+
globals.push({ type: 'set', path: ['playerSpawnPosition'], value: player.position });
|
|
32
|
+
}
|
|
33
|
+
// Only when present: writing `undefined` would drop the key on stringify for a game that had a
|
|
34
|
+
// rotation before, silently resetting the player's facing.
|
|
35
|
+
if (player?.rotationY !== undefined) {
|
|
36
|
+
globals.push({ type: 'set', path: ['playerSpawnRotationY'], value: player.rotationY });
|
|
37
|
+
}
|
|
38
|
+
const levels = world.worldProfileData?.levels ?? [];
|
|
39
|
+
if (levels.length === 0)
|
|
40
|
+
return globals;
|
|
41
|
+
const firstLevel = levels[0];
|
|
42
|
+
if (firstLevel === undefined)
|
|
43
|
+
return globals;
|
|
44
|
+
const startLevelId = world.worldProfileData?.startLevelId ?? firstLevel.id;
|
|
45
|
+
const targetId = activeLevelId ?? startLevelId;
|
|
46
|
+
const level = levels.find((entry) => entry.id === targetId);
|
|
47
|
+
// No matching entry means the engine reported a level this world.json does not know about.
|
|
48
|
+
// Falling back to the globals keeps the edit rather than dropping it on the floor.
|
|
49
|
+
if (level === undefined)
|
|
50
|
+
return globals;
|
|
51
|
+
const levelModification = {
|
|
52
|
+
type: 'update',
|
|
53
|
+
path: ['levels'],
|
|
54
|
+
predicate: (item) => isRecord(item) && item.id === level.id,
|
|
55
|
+
// An updater rather than a `{ ...level, spawnPoints }` snapshot (which is what the Creator
|
|
56
|
+
// builds): the snapshot was read before the write and would silently revert any other field
|
|
57
|
+
// the agent changed on this level in between.
|
|
58
|
+
value: (item) => ({ ...(isRecord(item) ? item : {}), spawnPoints }),
|
|
59
|
+
};
|
|
60
|
+
return level.id === startLevelId ? [levelModification, ...globals] : [levelModification];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the write for one scene-editing flush. Returns `[]` when there is nothing to persist, so
|
|
64
|
+
* the caller can skip the file write entirely.
|
|
65
|
+
*
|
|
66
|
+
* `world` is the CURRENT parsed world.json, needed only to resolve the levels registry. Everything
|
|
67
|
+
* else comes from the engine, which is the source of truth for scene state — this never invents a
|
|
68
|
+
* position, rotation or scale.
|
|
69
|
+
*/
|
|
70
|
+
export function buildSceneModifications(payload, world) {
|
|
71
|
+
const { changes, status } = payload;
|
|
72
|
+
const environmentObjects = status.environmentObjects ?? [];
|
|
73
|
+
const activeLevelId = status.activeLevelId ?? null;
|
|
74
|
+
const deletedObjectIds = changes.deletedObjectIds ?? [];
|
|
75
|
+
const modifiedObjectIds = (changes.modifiedObjectIds ?? []).filter((id) => id !== FULL_SAVE_SENTINEL && !id.startsWith(SPAWN_ID_PREFIX));
|
|
76
|
+
const modifications = [];
|
|
77
|
+
// Deletions first: order is the contract for `applyModificationsToWorld`, and an id that is both
|
|
78
|
+
// deleted and re-added in the same flush must end up added.
|
|
79
|
+
for (const id of deletedObjectIds) {
|
|
80
|
+
modifications.push({
|
|
81
|
+
type: 'removeRoot',
|
|
82
|
+
path: ['environmentObjects'],
|
|
83
|
+
predicate: byId(id),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (changes.fullSaveNeeded === true) {
|
|
87
|
+
if (activeLevelId === null) {
|
|
88
|
+
// The array genuinely is the whole world, so replacing it wholesale is safe — and it is the
|
|
89
|
+
// only form that can DROP an object the engine no longer has but never reported deleting.
|
|
90
|
+
modifications.push({
|
|
91
|
+
type: 'setRoot',
|
|
92
|
+
path: ['environmentObjects'],
|
|
93
|
+
value: environmentObjects,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Deliberate divergence from the Creator, which issues a `setRoot` here. In a levels game the
|
|
98
|
+
// array holds only the active level plus globals, so a `setRoot` deletes every other level's
|
|
99
|
+
// scenery. Upserting each object writes exactly what the engine reported and touches nothing
|
|
100
|
+
// else.
|
|
101
|
+
for (const object of environmentObjects) {
|
|
102
|
+
const id = object.id;
|
|
103
|
+
if (typeof id !== 'string')
|
|
104
|
+
continue;
|
|
105
|
+
modifications.push({
|
|
106
|
+
type: 'upsertRoot',
|
|
107
|
+
path: ['environmentObjects'],
|
|
108
|
+
predicate: byId(id),
|
|
109
|
+
value: object,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const modifiedIds = new Set(modifiedObjectIds);
|
|
116
|
+
for (const object of environmentObjects) {
|
|
117
|
+
const id = object.id;
|
|
118
|
+
if (typeof id !== 'string' || !modifiedIds.has(id))
|
|
119
|
+
continue;
|
|
120
|
+
modifications.push({
|
|
121
|
+
type: 'upsertRoot',
|
|
122
|
+
path: ['environmentObjects'],
|
|
123
|
+
predicate: byId(id),
|
|
124
|
+
value: object,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (changes.pendingWorldConfig) {
|
|
129
|
+
const { path, settings } = changes.pendingWorldConfig;
|
|
130
|
+
if (Array.isArray(path) && path.length > 0) {
|
|
131
|
+
modifications.push({ type: 'set', path, value: settings });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (changes.pendingSpawnPoints && changes.pendingSpawnPoints.length > 0) {
|
|
135
|
+
modifications.push(...spawnPointModifications(changes.pendingSpawnPoints, activeLevelId, world));
|
|
136
|
+
}
|
|
137
|
+
if (payload.markersDirty === true) {
|
|
138
|
+
// Whole-array `set` rather than push/update: `SCENE_EDITING_STATUS` returns the live
|
|
139
|
+
// `markerSystem.serializeMarkers()` output, which already reflects adds, edits and removals.
|
|
140
|
+
modifications.push({ type: 'set', path: ['markers'], value: status.markers ?? [] });
|
|
141
|
+
}
|
|
142
|
+
return modifications;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=save.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"save.js","sourceRoot":"","sources":["../../src/editor/save.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface EditorServerOptions {
|
|
2
|
+
/** The project root — the directory holding `bitmagic.json`. */
|
|
3
|
+
root: string;
|
|
4
|
+
/** The game this project owns, from `bitmagic.json`. */
|
|
5
|
+
gameId: string;
|
|
6
|
+
/** Port vite serves the game on; the shell iframes it. */
|
|
7
|
+
gamePort: number;
|
|
8
|
+
/** Port to bind. Fails loudly if taken — the URL is meant to be stable and bookmarkable. */
|
|
9
|
+
port: number;
|
|
10
|
+
log?: (message: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface EditorServer {
|
|
13
|
+
/** The URL to open, e.g. `http://localhost:3011/`. */
|
|
14
|
+
readonly url: string;
|
|
15
|
+
/** Where editor actions are journalled, so the startup banner can point an agent at it. */
|
|
16
|
+
readonly journalPath: string;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The command the agent should run to fulfil an HQ request. One definition, quoted for a shell,
|
|
21
|
+
* so the journal line, the HTTP reply and the editor's own confirmation cannot drift apart.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hqCommand(assetId: string, prompt: string): string;
|
|
24
|
+
export declare function startEditorServer(options: EditorServerOptions): Promise<EditorServer>;
|