@bitmagic/cli 0.1.52-dev.6 → 0.1.52-dev.8

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 (56) hide show
  1. package/README.md +40 -0
  2. package/dist/assets/add.js +14 -12
  3. package/dist/assets/add.js.map +1 -1
  4. package/dist/assets/file-kind.d.ts +8 -0
  5. package/dist/assets/file-kind.js +17 -2
  6. package/dist/assets/file-kind.js.map +1 -1
  7. package/dist/assets/vxl-summary.d.ts +9 -0
  8. package/dist/assets/vxl-summary.js +15 -0
  9. package/dist/assets/vxl-summary.js.map +1 -1
  10. package/dist/cli.d.ts +1 -0
  11. package/dist/cli.js +2 -0
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/assets.d.ts +3 -10
  14. package/dist/commands/assets.js +11 -18
  15. package/dist/commands/assets.js.map +1 -1
  16. package/dist/commands/cover.js +2 -3
  17. package/dist/commands/cover.js.map +1 -1
  18. package/dist/commands/forge.d.ts +2 -9
  19. package/dist/commands/forge.js +2 -13
  20. package/dist/commands/forge.js.map +1 -1
  21. package/dist/commands/generate.d.ts +3 -10
  22. package/dist/commands/generate.js +3 -36
  23. package/dist/commands/generate.js.map +1 -1
  24. package/dist/commands/judge.d.ts +2 -7
  25. package/dist/commands/judge.js +2 -3
  26. package/dist/commands/judge.js.map +1 -1
  27. package/dist/commands/levels.d.ts +33 -0
  28. package/dist/commands/levels.js +241 -0
  29. package/dist/commands/levels.js.map +1 -0
  30. package/dist/commands/run-deps.d.ts +27 -0
  31. package/dist/commands/run-deps.js +43 -0
  32. package/dist/commands/run-deps.js.map +1 -0
  33. package/dist/editor/journal.d.ts +13 -0
  34. package/dist/editor/journal.js +2 -0
  35. package/dist/editor/journal.js.map +1 -1
  36. package/dist/editor/server.js +72 -91
  37. package/dist/editor/server.js.map +1 -1
  38. package/dist/editor/shell-page.js +119 -114
  39. package/dist/editor/shell-page.js.map +1 -1
  40. package/dist/editor/voxel-save.js +8 -12
  41. package/dist/editor/voxel-save.js.map +1 -1
  42. package/dist/errors.d.ts +10 -0
  43. package/dist/errors.js +16 -0
  44. package/dist/errors.js.map +1 -1
  45. package/dist/forge/apply-modifications.d.ts +9 -0
  46. package/dist/forge/apply-modifications.js +12 -18
  47. package/dist/forge/apply-modifications.js.map +1 -1
  48. package/dist/forge/voxelize-session.d.ts +37 -0
  49. package/dist/forge/voxelize-session.js +2 -2
  50. package/dist/forge/voxelize-session.js.map +1 -1
  51. package/dist/levels/registry.d.ts +88 -0
  52. package/dist/levels/registry.js +309 -0
  53. package/dist/levels/registry.js.map +1 -0
  54. package/dist/scaffold/project-files.js +3 -1
  55. package/dist/scaffold/project-files.js.map +1 -1
  56. package/package.json +4 -4
@@ -3,6 +3,15 @@ export interface ApplyModificationsOutcome {
3
3
  applied: number;
4
4
  /** One human-readable line per modification, for the command to print. */
5
5
  summary: string[];
6
+ /**
7
+ * How many entries each modification actually changed, in the same order.
8
+ *
9
+ * Reported from the write itself rather than counted off an earlier read. A caller that counts
10
+ * beforehand — "removing this asset will take 3 placed instances with it" — is describing a
11
+ * document that may not be the one written: `bitmagic dev`'s whole premise is that the creator's
12
+ * agent edits world.json concurrently, and the applier re-reads it under its own atomic write.
13
+ */
14
+ changed: number[];
6
15
  }
7
16
  /**
8
17
  * Read world.json, apply every modification in order, write once.
@@ -47,13 +47,6 @@ function ensureArray(parent, key) {
47
47
  }
48
48
  return parent[key];
49
49
  }
50
- /**
51
- * Apply one modification in place, returning the path it touched for the summary line.
52
- *
53
- * `*Root` types address the document root; the rest address paths inside `worldProfileData`.
54
- * Writing a `set` at the root instead would put it where the engine never looks, which reads as
55
- * success — the same trap `generate/apply-patches.ts` documents for `WorldPatch`.
56
- */
57
50
  function applyOne(world, modification) {
58
51
  const isRoot = modification.type.endsWith('Root');
59
52
  let target;
@@ -85,35 +78,35 @@ function applyOne(world, modification) {
85
78
  case 'set':
86
79
  case 'setRoot':
87
80
  parent[lastKey] = modification.value;
88
- return `set ${where}`;
81
+ return { summary: `set ${where}`, changed: 1 };
89
82
  case 'push':
90
83
  case 'pushRoot':
91
84
  ensureArray(parent, lastKey).push(modification.value);
92
- return `added to ${where}`;
85
+ return { summary: `added to ${where}`, changed: 1 };
93
86
  case 'update':
94
87
  case 'updateRoot': {
95
88
  const { predicate, value } = modification;
96
89
  const array = parent[lastKey];
97
90
  if (!Array.isArray(array))
98
- return `skipped ${where} (not an array)`;
91
+ return { summary: `skipped ${where} (not an array)`, changed: 0 };
99
92
  const index = array.findIndex(predicate);
100
93
  if (index === -1)
101
- return `no match to update in ${where}`;
94
+ return { summary: `no match to update in ${where}`, changed: 0 };
102
95
  array[index] = typeof value === 'function'
103
96
  ? value(array[index])
104
97
  : value;
105
- return `updated ${where}[${index}]`;
98
+ return { summary: `updated ${where}[${index}]`, changed: 1 };
106
99
  }
107
100
  case 'remove':
108
101
  case 'removeRoot': {
109
102
  const { predicate } = modification;
110
103
  const array = parent[lastKey];
111
104
  if (!Array.isArray(array))
112
- return `skipped ${where} (not an array)`;
105
+ return { summary: `skipped ${where} (not an array)`, changed: 0 };
113
106
  const kept = array.filter((item) => !predicate(item));
114
107
  const removed = array.length - kept.length;
115
108
  parent[lastKey] = kept;
116
- return `removed ${removed} from ${where}`;
109
+ return { summary: `removed ${removed} from ${where}`, changed: removed };
117
110
  }
118
111
  case 'upsert':
119
112
  case 'upsertRoot': {
@@ -122,10 +115,10 @@ function applyOne(world, modification) {
122
115
  const index = array.findIndex(predicate);
123
116
  if (index === -1) {
124
117
  array.push(value);
125
- return `added to ${where}`;
118
+ return { summary: `added to ${where}`, changed: 1 };
126
119
  }
127
120
  array[index] = value;
128
- return `updated ${where}[${index}]`;
121
+ return { summary: `updated ${where}[${index}]`, changed: 1 };
129
122
  }
130
123
  default: {
131
124
  // Not a degradation to swallow: an unknown type means this applier and the shared package's
@@ -144,7 +137,8 @@ export function applyModificationsToWorld(worldPath, modifications, options = {}
144
137
  // Baseline BEFORE applying: a violation already present in a legacy or imported world must not
145
138
  // block an unrelated write. Only violations this batch INTRODUCES are refused.
146
139
  const preExisting = new Set(validateWorldData(world));
147
- const summary = modifications.map((modification) => applyOne(world, modification));
140
+ const results = modifications.map((modification) => applyOne(world, modification));
141
+ const summary = results.map((result) => result.summary);
148
142
  // The engine statically casts world.json to Partial<GameData> (game/src/bundle/WorldDataLoader.ts),
149
143
  // so a non-conforming document breaks `tsc` in a file the creator cannot edit. The gate matters
150
144
  // more in this lane than in the web one: by the time it runs the design and geometry are already
@@ -159,6 +153,6 @@ export function applyModificationsToWorld(worldPath, modifications, options = {}
159
153
  + `schema, so nothing was written. Violations: ${introduced.join('; ')}`);
160
154
  }
161
155
  writeWorldJsonAtomic(worldPath, world);
162
- return { applied: modifications.length, summary };
156
+ return { applied: modifications.length, summary, changed: results.map((result) => result.changed) };
163
157
  }
164
158
  //# sourceMappingURL=apply-modifications.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"apply-modifications.js","sourceRoot":"","sources":["../../src/forge/apply-modifications.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"apply-modifications.js","sourceRoot":"","sources":["../../src/forge/apply-modifications.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,29 @@
1
+ /**
2
+ * One headless engine, ready to voxelize — the arrangement `bitmagic generate prop`, `generate
3
+ * vehicle` and `assets add <file.glb>` all need and none should own.
4
+ *
5
+ * Only the engine can voxelize, so each of those runs the project's own game in a headless Chrome
6
+ * and talks to it over the forge transport. Getting there is five steps in a fixed order, and the
7
+ * teardown has to run in the reverse order whatever happened:
8
+ *
9
+ * 1. reserve a port in 3000–3199 — the only origins the asset CDN allows, so the page can fetch
10
+ * the project's assets and the GLB it is about to bake;
11
+ * 2. spawn the project's vendored `vite` on it, and wait for it;
12
+ * 3. start the upload proxy — the engine uploads the baked `.vxl` itself, to a presigned URL it
13
+ * asks for at a hardcoded path with no credentials, and the proxy turns that into the
14
+ * authenticated api-server route while keeping the token out of the page
15
+ * (see `upload-proxy.ts`);
16
+ * 4. launch Chrome on the page and wait for the game to load;
17
+ * 5. run the caller's body against `host.transport`, then close the page (it may be mid-upload),
18
+ * then the proxy it was uploading through, then the server underneath both.
19
+ *
20
+ * Three commands carrying three copies of this is how a fix to one of them fails to reach the
21
+ * other two, so it lives here once. The two setup failures a creator can actually hit — no free
22
+ * port, vite not coming up — are reported with the caller's own `retryHint`, because what is kept
23
+ * and how to retry differs per command (a paid mesh, a paid vehicle design, an already-uploaded
24
+ * file) and that sentence is the useful part of the message.
25
+ */
26
+ import { type ChildProcess } from 'child_process';
1
27
  import type { Environment } from '../config/environments.js';
2
28
  import { ForgeBrowserHost } from './browser-host.js';
3
29
  import { type ProjectContext } from '../project/context.js';
@@ -22,3 +48,14 @@ export interface VoxelizeSession {
22
48
  port: number;
23
49
  }
24
50
  export declare function withVoxelizeSession<T>(options: VoxelizeSessionOptions, body: (session: VoxelizeSession) => Promise<T>): Promise<T>;
51
+ /**
52
+ * Run one teardown step, reporting a failure rather than propagating it. Cleanup must never mask
53
+ * the error that caused it, and must never stop the steps that follow.
54
+ */
55
+ export declare function closeQuietly(what: string, close: () => Promise<void> | undefined, log: (message: string) => void): Promise<void>;
56
+ /**
57
+ * Stop vite, then stop waiting for it. `unref` matters as much as the signal: the child was
58
+ * spawned with a piped stderr, so its handle keeps the parent's event loop alive and a vite that
59
+ * ignores SIGTERM left the CLI hanging after it had already printed its result.
60
+ */
61
+ export declare function killQuietly(server: ChildProcess, log: (message: string) => void): void;
@@ -111,7 +111,7 @@ export async function withVoxelizeSession(options, body) {
111
111
  * Run one teardown step, reporting a failure rather than propagating it. Cleanup must never mask
112
112
  * the error that caused it, and must never stop the steps that follow.
113
113
  */
114
- async function closeQuietly(what, close, log) {
114
+ export async function closeQuietly(what, close, log) {
115
115
  try {
116
116
  await close();
117
117
  }
@@ -124,7 +124,7 @@ async function closeQuietly(what, close, log) {
124
124
  * spawned with a piped stderr, so its handle keeps the parent's event loop alive and a vite that
125
125
  * ignores SIGTERM left the CLI hanging after it had already printed its result.
126
126
  */
127
- function killQuietly(server, log) {
127
+ export function killQuietly(server, log) {
128
128
  try {
129
129
  server.kill('SIGTERM');
130
130
  server.stderr?.removeAllListeners('data');
@@ -1 +1 @@
1
- {"version":3,"file":"voxelize-session.js","sourceRoot":"","sources":["../../src/forge/voxelize-session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"voxelize-session.js","sourceRoot":"","sources":["../../src/forge/voxelize-session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * `bitmagic levels` — the level registry in `worldProfileData.levels`, without the web Creator.
3
+ *
4
+ * `forge` can CREATE levels but never could manage them: only a game's first forge becomes the
5
+ * start level (or `forge --make-start` at creation), so fixing a wrong start, renaming, or deleting
6
+ * a level meant opening the Creator.
7
+ *
8
+ * ── The invariant this file exists to respect ────────────────────────────────────────────────
9
+ *
10
+ * `worldProfileData` keeps three LEGACY MIRRORS — `voxelUrl`, `spawnPoints` and
11
+ * `playerSpawnPosition` — describing the START level, because frozen genre and work-template code
12
+ * boots from those rather than from the registry (see `WorldLevel` in game/src/types/game.ts).
13
+ *
14
+ * Nothing self-heals on the normal boot path. `applyBootLevelOverride` does recompute them, but
15
+ * only when the main screen's level chooser passes a `bootLevelId`. So a `startLevelId` moved
16
+ * without its mirrors boots the NEW level's objects, lighting and navmesh over the OLD level's
17
+ * terrain, and nothing throws. That is the failure this module exists to prevent.
18
+ *
19
+ * The mirrors come from `buildStartLevelMirrors` in `@bitmagic/world-forger`, deliberately rather
20
+ * than reimplemented — the same function the forge uses, carrying a subtlety worth not
21
+ * rediscovering: spawn mirrors are written ONLY when the level owns spawn points, because a level
22
+ * without its own inherits the globals, which are then already correct.
23
+ *
24
+ * Pure: it computes modifications and never applies them.
25
+ */
26
+ import { type LevelEntry, type WorldJsonModification } from '@bitmagic/world-forger/pipeline/index.js';
27
+ import { type JsonObject } from '../project/world-json.js';
28
+ export interface LevelRow {
29
+ id: string;
30
+ name: string;
31
+ vwldAssetId: string;
32
+ /** The `.vwld` asset is absent from `assets[]`, so this level has nothing to load. */
33
+ missingAsset: boolean;
34
+ start: boolean;
35
+ spawnPoints: number;
36
+ /** Placed objects tagged to this level. Untagged ones are global and belong to none. */
37
+ placed: number;
38
+ }
39
+ export declare function listLevels(world: JsonObject): LevelRow[];
40
+ export interface LevelPlan {
41
+ modifications: WorldJsonModification[];
42
+ /** Said after the write. Never a reason to refuse. */
43
+ notes: string[];
44
+ }
45
+ /**
46
+ * Make `levelId` the level the game boots into — registry AND mirrors, together.
47
+ *
48
+ * Refused when the level's `.vwld` asset is missing or has no url: the mirrors are what actually
49
+ * boot the game, so writing `startLevelId` alone would leave `voxelUrl` describing the previous
50
+ * level, and the registry and the thing that loads would disagree with no error anywhere.
51
+ */
52
+ export declare function planSetStart(world: JsonObject, levelId: string): LevelPlan;
53
+ export declare function planRename(world: JsonObject, levelId: string, name: string): LevelPlan;
54
+ export interface AddPlan extends LevelPlan {
55
+ level: LevelEntry;
56
+ /** True when this made the game multi-level for the first time. */
57
+ converted: boolean;
58
+ }
59
+ /**
60
+ * Register an existing `.vwld` asset as a level.
61
+ *
62
+ * Start-level policy matches `forge`'s: a game's FIRST level becomes the start, later ones do not,
63
+ * and `--make-start` overrides. Stealing the start from a working game because someone registered
64
+ * a second level would be the surprising outcome.
65
+ */
66
+ export declare function planAdd(world: JsonObject, vwldAssetId: string, options: {
67
+ name?: string;
68
+ makeStart: boolean;
69
+ }): AddPlan;
70
+ export interface RemovePlan extends LevelPlan {
71
+ level: LevelEntry;
72
+ /** Placed instances tagged to this level, which go with it. */
73
+ placedRemoved: number;
74
+ /** Doors and key items scoped to it, which also go. */
75
+ scopedRemoved: string[];
76
+ }
77
+ /**
78
+ * Remove a level from the registry.
79
+ *
80
+ * Two refusals, matching the agent's `manage-levels delete` so the two lanes cannot disagree:
81
+ * the last level, and the START level. The second matters most — `startLevelId` naming nothing
82
+ * falls back to `levels[0]` at boot while the mirrors still describe the deleted level, which is
83
+ * the silent stale-mirror failure. Move the start first, deliberately.
84
+ *
85
+ * The `.vwld` asset is left alone: it may be large, nothing here deletes uploaded bytes, and a
86
+ * re-add is then free.
87
+ */
88
+ export declare function planRemove(world: JsonObject, levelId: string): RemovePlan;
@@ -0,0 +1,309 @@
1
+ /**
2
+ * `bitmagic levels` — the level registry in `worldProfileData.levels`, without the web Creator.
3
+ *
4
+ * `forge` can CREATE levels but never could manage them: only a game's first forge becomes the
5
+ * start level (or `forge --make-start` at creation), so fixing a wrong start, renaming, or deleting
6
+ * a level meant opening the Creator.
7
+ *
8
+ * ── The invariant this file exists to respect ────────────────────────────────────────────────
9
+ *
10
+ * `worldProfileData` keeps three LEGACY MIRRORS — `voxelUrl`, `spawnPoints` and
11
+ * `playerSpawnPosition` — describing the START level, because frozen genre and work-template code
12
+ * boots from those rather than from the registry (see `WorldLevel` in game/src/types/game.ts).
13
+ *
14
+ * Nothing self-heals on the normal boot path. `applyBootLevelOverride` does recompute them, but
15
+ * only when the main screen's level chooser passes a `bootLevelId`. So a `startLevelId` moved
16
+ * without its mirrors boots the NEW level's objects, lighting and navmesh over the OLD level's
17
+ * terrain, and nothing throws. That is the failure this module exists to prevent.
18
+ *
19
+ * The mirrors come from `buildStartLevelMirrors` in `@bitmagic/world-forger`, deliberately rather
20
+ * than reimplemented — the same function the forge uses, carrying a subtlety worth not
21
+ * rediscovering: spawn mirrors are written ONLY when the level owns spawn points, because a level
22
+ * without its own inherits the globals, which are then already correct.
23
+ *
24
+ * Pure: it computes modifications and never applies them.
25
+ */
26
+ import { buildConversionModifications, buildStartLevelMirrors, getLevels, getStartLevel, mintLevelId, } from '@bitmagic/world-forger/pipeline/index.js';
27
+ import { CliError } from '../errors.js';
28
+ import { isJsonObject } from '../project/world-json.js';
29
+ /** The same document, seen through the shared helpers' narrower view. */
30
+ function asWorldJson(world) {
31
+ return world;
32
+ }
33
+ function assetsOf(world) {
34
+ return (Array.isArray(world.assets) ? world.assets : []).filter(isJsonObject);
35
+ }
36
+ export function listLevels(world) {
37
+ const start = getStartLevel(asWorldJson(world));
38
+ const assets = assetsOf(world);
39
+ const objects = (Array.isArray(world.environmentObjects) ? world.environmentObjects : [])
40
+ .filter(isJsonObject);
41
+ return getLevels(asWorldJson(world)).map((level) => ({
42
+ id: level.id,
43
+ name: level.name,
44
+ vwldAssetId: level.vwldAssetId,
45
+ missingAsset: !assets.some((asset) => asset.id === level.vwldAssetId),
46
+ start: start !== null && start.id === level.id,
47
+ spawnPoints: level.spawnPoints?.length ?? 0,
48
+ placed: objects.filter((object) => object.levelId === level.id).length,
49
+ }));
50
+ }
51
+ function requireLevel(world, levelId) {
52
+ const levels = getLevels(asWorldJson(world));
53
+ const level = levels.find((entry) => entry.id === levelId);
54
+ if (level === undefined) {
55
+ throw new CliError(`No level with id "${levelId}" in this project. `
56
+ + (levels.length > 0
57
+ ? `This game has: ${levels.map((entry) => `${entry.id} ("${entry.name}")`).join(', ')}. `
58
+ : 'This game has no levels — `bitmagic forge` creates one. ')
59
+ + 'world.json is unchanged.');
60
+ }
61
+ return level;
62
+ }
63
+ /** The url a level's `.vwld` asset serves, or null when it is missing or not a level bake. */
64
+ function vwldUrlFor(world, level) {
65
+ const asset = assetsOf(world).find((item) => item.id === level.vwldAssetId);
66
+ if (asset === undefined || asset.type !== 'vwld')
67
+ return null;
68
+ return typeof asset.url === 'string' && asset.url !== '' ? asset.url : null;
69
+ }
70
+ /**
71
+ * Make `levelId` the level the game boots into — registry AND mirrors, together.
72
+ *
73
+ * Refused when the level's `.vwld` asset is missing or has no url: the mirrors are what actually
74
+ * boot the game, so writing `startLevelId` alone would leave `voxelUrl` describing the previous
75
+ * level, and the registry and the thing that loads would disagree with no error anywhere.
76
+ */
77
+ export function planSetStart(world, levelId) {
78
+ const level = requireLevel(world, levelId);
79
+ const current = getStartLevel(asWorldJson(world));
80
+ if (current !== null && current.id === levelId) {
81
+ throw new CliError(`"${level.name}" is already the start level. world.json is unchanged.`);
82
+ }
83
+ const vwldUrl = vwldUrlFor(world, level);
84
+ if (vwldUrl === null) {
85
+ throw new CliError(`Level "${level.name}" points at asset "${level.vwldAssetId}", which this project has no `
86
+ + 'loadable .vwld for — so it has nothing to boot. Fix the level first (`bitmagic assets '
87
+ + 'list` shows what is there). world.json is unchanged.');
88
+ }
89
+ return {
90
+ modifications: [
91
+ { type: 'set', path: ['startLevelId'], value: levelId },
92
+ // Without these the game boots the OLD level's terrain. See the file header.
93
+ ...buildStartLevelMirrors(level, vwldUrl),
94
+ ],
95
+ notes: (level.spawnPoints?.length ?? 0) === 0
96
+ ? [`"${level.name}" has no spawn points of its own, so it keeps the game's global ones.`]
97
+ : [],
98
+ };
99
+ }
100
+ export function planRename(world, levelId, name) {
101
+ const level = requireLevel(world, levelId);
102
+ const trimmed = name.trim();
103
+ if (trimmed === '')
104
+ throw new CliError('A level name cannot be empty. world.json is unchanged.');
105
+ if (trimmed === level.name) {
106
+ throw new CliError(`"${level.name}" already has that name. world.json is unchanged.`);
107
+ }
108
+ const clash = getLevels(asWorldJson(world)).some((e) => e.id !== levelId && e.name === trimmed);
109
+ return {
110
+ modifications: [{
111
+ type: 'update',
112
+ path: ['levels'],
113
+ predicate: (item) => isJsonObject(item) && item.id === levelId,
114
+ // An updater, not a `{ ...level, name }` snapshot (which is what the agent's tool builds):
115
+ // the snapshot was read before the write and reverts anything another writer changed on this
116
+ // level in between. Same reason editor/save.ts gives for its own level write.
117
+ value: (item) => ({ ...(isJsonObject(item) ? item : {}), name: trimmed }),
118
+ }],
119
+ notes: clash
120
+ ? [`Another level is also called "${trimmed}" — names are for humans, ids are what code uses.`]
121
+ : [],
122
+ };
123
+ }
124
+ /**
125
+ * Register an existing `.vwld` asset as a level.
126
+ *
127
+ * Start-level policy matches `forge`'s: a game's FIRST level becomes the start, later ones do not,
128
+ * and `--make-start` overrides. Stealing the start from a working game because someone registered
129
+ * a second level would be the surprising outcome.
130
+ */
131
+ export function planAdd(world, vwldAssetId, options) {
132
+ const asset = assetsOf(world).find((item) => item.id === vwldAssetId);
133
+ if (asset === undefined) {
134
+ const available = assetsOf(world).filter((item) => item.type === 'vwld');
135
+ throw new CliError(`No asset with id "${vwldAssetId}" in this project. `
136
+ + (available.length > 0
137
+ ? `Baked levels here: ${available.map((a) => `${String(a.id)} ("${String(a.name)}")`).join(', ')}. `
138
+ : 'This project has no baked levels — `bitmagic forge` makes one. ')
139
+ + 'world.json is unchanged.');
140
+ }
141
+ if (asset.type !== 'vwld') {
142
+ throw new CliError(`Asset "${vwldAssetId}" is ${typeof asset.type === 'string' ? `a ${asset.type}` : 'an unknown'} `
143
+ + 'asset, not a baked level (.vwld). Only a level bake can become a level. '
144
+ + 'world.json is unchanged.');
145
+ }
146
+ const url = typeof asset.url === 'string' ? asset.url : '';
147
+ if (url === '') {
148
+ throw new CliError(`Asset "${vwldAssetId}" has no url, so a level made from it would have nothing to load. `
149
+ + 'world.json is unchanged.');
150
+ }
151
+ // Two levels on one bake is the same place twice, and the forge's re-bake path resolves a level
152
+ // by `vwldAssetId` — with duplicates it picks the first arbitrarily.
153
+ const sharing = getLevels(asWorldJson(world)).find((entry) => entry.vwldAssetId === vwldAssetId);
154
+ if (sharing !== undefined) {
155
+ throw new CliError(`Level "${sharing.name}" (${sharing.id}) already uses that bake. world.json is unchanged.`);
156
+ }
157
+ const assetName = typeof asset.name === 'string' && asset.name !== '' ? asset.name : vwldAssetId;
158
+ const level = {
159
+ id: mintLevelId(),
160
+ name: (options.name ?? assetName).trim() || assetName,
161
+ vwldAssetId,
162
+ };
163
+ const existing = getLevels(asWorldJson(world));
164
+ if (existing.length > 0) {
165
+ const makeStart = options.makeStart;
166
+ return {
167
+ level,
168
+ converted: false,
169
+ modifications: [
170
+ { type: 'push', path: ['levels'], value: level },
171
+ ...(makeStart
172
+ ? [
173
+ { type: 'set', path: ['startLevelId'], value: level.id },
174
+ ...buildStartLevelMirrors(level, url),
175
+ ]
176
+ : []),
177
+ ],
178
+ notes: makeStart
179
+ ? [`"${level.name}" is now the start level.`]
180
+ : ['The start level is unchanged — pass --make-start, or use `bitmagic levels set-start`.'],
181
+ };
182
+ }
183
+ // No registry yet. If the game already HAS a world (a `voxelUrl` naming a registered bake), that
184
+ // world must become a level too — otherwise turning on multi-level would strand the place the
185
+ // game currently loads, with no entry naming it.
186
+ const conversion = safeConversion(world);
187
+ if (conversion !== null) {
188
+ return {
189
+ level,
190
+ converted: true,
191
+ modifications: [
192
+ // Everything the conversion does EXCEPT its own `levels` write, which named only level 1.
193
+ ...conversion.modifications.filter((mod) => !('path' in mod) || mod.path[0] !== 'levels'),
194
+ { type: 'set', path: ['levels'], value: [conversion.level1, level] },
195
+ ],
196
+ notes: [
197
+ `This game's existing world became level "${conversion.level1.name}" and remains the start `
198
+ + 'level; every object that was not already tagged now belongs to it.',
199
+ ],
200
+ };
201
+ }
202
+ // A procedural game with no world of its own: the new level is the only one, so it must boot.
203
+ return {
204
+ level,
205
+ converted: false,
206
+ modifications: [
207
+ { type: 'set', path: ['levels'], value: [level] },
208
+ { type: 'set', path: ['startLevelId'], value: level.id },
209
+ ...buildStartLevelMirrors(level, url),
210
+ ],
211
+ notes: [`This is the game's first level, so it is the start level.`],
212
+ };
213
+ }
214
+ /**
215
+ * The legacy→levels conversion, or null when there is nothing to convert.
216
+ *
217
+ * Throws inside the shared helper when the current world is not a registered bake (a legacy map or
218
+ * an unregistered one); that is a refusal for the caller, not a crash.
219
+ */
220
+ function safeConversion(world) {
221
+ try {
222
+ return buildConversionModifications(asWorldJson(world));
223
+ }
224
+ catch (error) {
225
+ throw new CliError('This game\'s current world is not a registered level bake, so it cannot be turned into a '
226
+ + `level alongside the new one: ${error instanceof Error ? error.message : String(error)} `
227
+ + 'Re-bake it with `bitmagic forge` first. world.json is unchanged.');
228
+ }
229
+ }
230
+ /**
231
+ * Remove a level from the registry.
232
+ *
233
+ * Two refusals, matching the agent's `manage-levels delete` so the two lanes cannot disagree:
234
+ * the last level, and the START level. The second matters most — `startLevelId` naming nothing
235
+ * falls back to `levels[0]` at boot while the mirrors still describe the deleted level, which is
236
+ * the silent stale-mirror failure. Move the start first, deliberately.
237
+ *
238
+ * The `.vwld` asset is left alone: it may be large, nothing here deletes uploaded bytes, and a
239
+ * re-add is then free.
240
+ */
241
+ export function planRemove(world, levelId) {
242
+ const level = requireLevel(world, levelId);
243
+ const levels = getLevels(asWorldJson(world));
244
+ if (levels.length <= 1) {
245
+ throw new CliError('This is the game\'s only level, and a levels-mode game needs one. world.json is unchanged.');
246
+ }
247
+ const start = getStartLevel(asWorldJson(world));
248
+ if (start !== null && start.id === levelId) {
249
+ const other = levels.find((entry) => entry.id !== levelId);
250
+ throw new CliError(`"${level.name}" is the start level. Removing it would leave the game booting one level's `
251
+ + 'terrain under another\'s objects. Move the start first:\n'
252
+ + ` bitmagic levels set-start ${other?.id ?? '<levelId>'}\n`
253
+ + 'world.json is unchanged.');
254
+ }
255
+ const profile = isJsonObject(world.worldProfileData) ? world.worldProfileData : {};
256
+ const objects = (Array.isArray(world.environmentObjects) ? world.environmentObjects : [])
257
+ .filter(isJsonObject);
258
+ const placedRemoved = objects.filter((object) => object.levelId === levelId).length;
259
+ const modifications = [
260
+ {
261
+ type: 'remove',
262
+ path: ['levels'],
263
+ predicate: (item) => isJsonObject(item) && item.id === levelId,
264
+ },
265
+ // Instances belonging to this level go with it. Left behind they would load in no level at
266
+ // all — invisible, and still counted as references that pin their assets against a purge.
267
+ {
268
+ type: 'removeRoot',
269
+ path: ['environmentObjects'],
270
+ predicate: (item) => isJsonObject(item) && item.levelId === levelId,
271
+ },
272
+ ];
273
+ // Doors and key items are level-scoped the same way, and the agent's tool leaves them — which
274
+ // makes them permanent dead weight: they never spawn, yet `persist-level` still counts their
275
+ // assetIds as referenced, so the placeholder assets can never be purged either.
276
+ const scopedRemoved = [];
277
+ for (const key of ['doors', 'keyItems']) {
278
+ const entries = Array.isArray(profile[key]) ? profile[key].filter(isJsonObject) : [];
279
+ const doomed = entries.filter((entry) => entry.levelId === levelId).length;
280
+ if (doomed === 0)
281
+ continue;
282
+ scopedRemoved.push(`${doomed} ${key}`);
283
+ modifications.push({
284
+ type: 'remove',
285
+ path: [key],
286
+ predicate: (item) => isJsonObject(item) && item.levelId === levelId,
287
+ });
288
+ }
289
+ const notes = [];
290
+ if (placedRemoved > 0) {
291
+ notes.push(`${placedRemoved} placed object(s) belonged to it and were removed with it.`);
292
+ }
293
+ if (scopedRemoved.length > 0)
294
+ notes.push(`Also removed: ${scopedRemoved.join(', ')}.`);
295
+ // The start-screen chooser filters by id and is hand-authored; it warns and skips unknown ids on
296
+ // its own, so this is reported rather than rewritten.
297
+ if (choosersNaming(profile, levelId) > 0) {
298
+ notes.push('The main screen\'s level chooser still lists this level; it will skip it, but tidy '
299
+ + '`hud.startScreen.selections[].levelIds` when you get a chance.');
300
+ }
301
+ return { level, modifications, notes, placedRemoved, scopedRemoved };
302
+ }
303
+ function choosersNaming(profile, levelId) {
304
+ const hud = isJsonObject(profile.hud) ? profile.hud : {};
305
+ const startScreen = isJsonObject(hud.startScreen) ? hud.startScreen : {};
306
+ const selections = Array.isArray(startScreen.selections) ? startScreen.selections : [];
307
+ return selections.filter((step) => isJsonObject(step) && Array.isArray(step.levelIds) && step.levelIds.includes(levelId)).length;
308
+ }
309
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/levels/registry.ts"],"names":[],"mappings":"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"}
@@ -854,7 +854,9 @@ bitmagic forge --prompt "a ruined desert temple with a central courtyard"
854
854
  \`\`\`
855
855
 
856
856
  This designs a scene, builds its geometry, bakes it to voxels and writes the finished level —
857
- terrain, props, spawn points — into \`src/work/world.json\`. Add \`--city\`, \`--dungeon\`,
857
+ terrain, props, spawn points — into \`src/work/world.json\`. \`bitmagic levels list | add | rename
858
+ | set-start | remove\` manages them afterwards (free, local); \`set-start\` is how you change which
859
+ level the game boots into, and it moves the legacy mirror fields with it. Add \`--city\`, \`--dungeon\`,
858
860
  \`--platformer\` or \`--freeform\` (at most one) for a different kind of place; omit them all for
859
861
  natural terrain.
860
862