@bitmagic/cli 0.1.52-dev.0 → 0.1.52-dev.10

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 (139) hide show
  1. package/README.md +178 -6
  2. package/dist/assets/add.d.ts +40 -0
  3. package/dist/assets/add.js +229 -0
  4. package/dist/assets/add.js.map +1 -0
  5. package/dist/assets/entry.d.ts +44 -0
  6. package/dist/assets/entry.js +71 -0
  7. package/dist/assets/entry.js.map +1 -0
  8. package/dist/assets/file-kind.d.ts +61 -0
  9. package/dist/assets/file-kind.js +195 -0
  10. package/dist/assets/file-kind.js.map +1 -0
  11. package/dist/assets/list.d.ts +27 -0
  12. package/dist/assets/list.js +50 -0
  13. package/dist/assets/list.js.map +1 -0
  14. package/dist/assets/remove.d.ts +50 -0
  15. package/dist/assets/remove.js +91 -0
  16. package/dist/assets/remove.js.map +1 -0
  17. package/dist/assets/voxelize-glb.d.ts +37 -0
  18. package/dist/assets/voxelize-glb.js +65 -0
  19. package/dist/assets/voxelize-glb.js.map +1 -0
  20. package/dist/assets/vxl-summary.d.ts +32 -0
  21. package/dist/assets/vxl-summary.js +118 -0
  22. package/dist/assets/vxl-summary.js.map +1 -0
  23. package/dist/bin.js +25 -0
  24. package/dist/bin.js.map +1 -1
  25. package/dist/browser-gl.d.ts +46 -8
  26. package/dist/browser-gl.js +54 -26
  27. package/dist/browser-gl.js.map +1 -1
  28. package/dist/cli.d.ts +22 -0
  29. package/dist/cli.js +17 -1
  30. package/dist/cli.js.map +1 -1
  31. package/dist/commands/assets.d.ts +56 -0
  32. package/dist/commands/assets.js +328 -0
  33. package/dist/commands/assets.js.map +1 -0
  34. package/dist/commands/cover.js +2 -3
  35. package/dist/commands/cover.js.map +1 -1
  36. package/dist/commands/forge.d.ts +7 -9
  37. package/dist/commands/forge.js +12 -14
  38. package/dist/commands/forge.js.map +1 -1
  39. package/dist/commands/generate.d.ts +41 -9
  40. package/dist/commands/generate.js +234 -54
  41. package/dist/commands/generate.js.map +1 -1
  42. package/dist/commands/init.js +19 -12
  43. package/dist/commands/init.js.map +1 -1
  44. package/dist/commands/judge.d.ts +36 -0
  45. package/dist/commands/judge.js +216 -0
  46. package/dist/commands/judge.js.map +1 -0
  47. package/dist/commands/levels.d.ts +33 -0
  48. package/dist/commands/levels.js +241 -0
  49. package/dist/commands/levels.js.map +1 -0
  50. package/dist/commands/number-flag.d.ts +7 -0
  51. package/dist/commands/number-flag.js +38 -0
  52. package/dist/commands/number-flag.js.map +1 -0
  53. package/dist/commands/publish.js +13 -0
  54. package/dist/commands/publish.js.map +1 -1
  55. package/dist/commands/reset-account.js +2 -8
  56. package/dist/commands/reset-account.js.map +1 -1
  57. package/dist/commands/run-deps.d.ts +27 -0
  58. package/dist/commands/run-deps.js +43 -0
  59. package/dist/commands/run-deps.js.map +1 -0
  60. package/dist/commands/verify.js +3 -3
  61. package/dist/commands/verify.js.map +1 -1
  62. package/dist/editor/journal.d.ts +48 -1
  63. package/dist/editor/journal.js +44 -0
  64. package/dist/editor/journal.js.map +1 -1
  65. package/dist/editor/previews.d.ts +6 -0
  66. package/dist/editor/previews.js +16 -0
  67. package/dist/editor/previews.js.map +1 -1
  68. package/dist/editor/server.js +228 -53
  69. package/dist/editor/server.js.map +1 -1
  70. package/dist/editor/shell-page.js +221 -116
  71. package/dist/editor/shell-page.js.map +1 -1
  72. package/dist/editor/voxel-save.d.ts +47 -0
  73. package/dist/editor/voxel-save.js +49 -0
  74. package/dist/editor/voxel-save.js.map +1 -0
  75. package/dist/errors.d.ts +10 -0
  76. package/dist/errors.js +16 -0
  77. package/dist/errors.js.map +1 -1
  78. package/dist/forge/apply-modifications.d.ts +18 -1
  79. package/dist/forge/apply-modifications.js +15 -32
  80. package/dist/forge/apply-modifications.js.map +1 -1
  81. package/dist/forge/browser-host.js +9 -4
  82. package/dist/forge/browser-host.js.map +1 -1
  83. package/dist/forge/voxelize-session.d.ts +61 -0
  84. package/dist/forge/voxelize-session.js +137 -0
  85. package/dist/forge/voxelize-session.js.map +1 -0
  86. package/dist/generate/model.d.ts +36 -0
  87. package/dist/generate/model.js +90 -0
  88. package/dist/generate/model.js.map +1 -0
  89. package/dist/generate/prop.d.ts +52 -4
  90. package/dist/generate/prop.js +169 -102
  91. package/dist/generate/prop.js.map +1 -1
  92. package/dist/generate/stream.d.ts +7 -0
  93. package/dist/generate/stream.js +2 -1
  94. package/dist/generate/stream.js.map +1 -1
  95. package/dist/generate/vehicle.js +5 -85
  96. package/dist/generate/vehicle.js.map +1 -1
  97. package/dist/http/signed-upload.d.ts +55 -0
  98. package/dist/http/signed-upload.js +168 -0
  99. package/dist/http/signed-upload.js.map +1 -0
  100. package/dist/judge/facts.d.ts +18 -0
  101. package/dist/judge/facts.js +180 -0
  102. package/dist/judge/facts.js.map +1 -0
  103. package/dist/judge/record.d.ts +16 -0
  104. package/dist/judge/record.js +55 -0
  105. package/dist/judge/record.js.map +1 -0
  106. package/dist/judge/stream.d.ts +36 -0
  107. package/dist/judge/stream.js +122 -0
  108. package/dist/judge/stream.js.map +1 -0
  109. package/dist/levels/registry.d.ts +88 -0
  110. package/dist/levels/registry.js +309 -0
  111. package/dist/levels/registry.js.map +1 -0
  112. package/dist/messages/check.d.ts +30 -0
  113. package/dist/messages/check.js +119 -0
  114. package/dist/messages/check.js.map +1 -0
  115. package/dist/messages/notice.d.ts +58 -0
  116. package/dist/messages/notice.js +54 -0
  117. package/dist/messages/notice.js.map +1 -0
  118. package/dist/messages/refresh.d.ts +19 -0
  119. package/dist/messages/refresh.js +42 -0
  120. package/dist/messages/refresh.js.map +1 -0
  121. package/dist/project/jobs.d.ts +1 -1
  122. package/dist/project/jobs.js.map +1 -1
  123. package/dist/publish/client.d.ts +13 -0
  124. package/dist/publish/client.js +13 -34
  125. package/dist/publish/client.js.map +1 -1
  126. package/dist/publish/platform.d.ts +46 -0
  127. package/dist/publish/platform.js +92 -0
  128. package/dist/publish/platform.js.map +1 -0
  129. package/dist/scaffold/project-files.d.ts +8 -0
  130. package/dist/scaffold/project-files.js +192 -34
  131. package/dist/scaffold/project-files.js.map +1 -1
  132. package/dist/scaffold/project.js +2 -1
  133. package/dist/scaffold/project.js.map +1 -1
  134. package/dist/verify/browser.d.ts +11 -6
  135. package/dist/verify/browser.js +31 -15
  136. package/dist/verify/browser.js.map +1 -1
  137. package/dist/verify/iterate.js +3 -3
  138. package/dist/verify/iterate.js.map +1 -1
  139. package/package.json +4 -4
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The on-disk record of the last `bitmagic judge` run.
3
+ *
4
+ * Lives at `.bitmagic/judge.json` — inside `.bitmagic` so it is gitignored and
5
+ * excluded from the publish fingerprint (judging never invalidates a passing
6
+ * verify), and deliberately NOT inside `.bitmagic/verify/`: that directory has
7
+ * gating semantics of its own (`--fast` deletes files there, publish reads
8
+ * `result.json`), and a foreign file in it invites accidents.
9
+ *
10
+ * `fingerprint` is the same project fingerprint verify stamps, so a reader can
11
+ * tell "judged this exact state" from "judged, then edited". There is no
12
+ * designHash-style cache on purpose — the game changes between judges by
13
+ * design, and every run is a deliberate, cheap spend.
14
+ */
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import { judgeScorecardSchema } from '@bitmagic/asset-core';
18
+ const JUDGE_RECORD_FILE = path.join('.bitmagic', 'judge.json');
19
+ export function judgeRecordPath(root) {
20
+ return path.join(root, JUDGE_RECORD_FILE);
21
+ }
22
+ export function writeJudgeRecord(root, record) {
23
+ fs.mkdirSync(path.dirname(judgeRecordPath(root)), { recursive: true });
24
+ fs.writeFileSync(judgeRecordPath(root), `${JSON.stringify(record, null, 2)}\n`);
25
+ }
26
+ export function readJudgeRecord(root) {
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(fs.readFileSync(judgeRecordPath(root), 'utf-8'));
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ if (typeof parsed !== 'object' || parsed === null)
35
+ return null;
36
+ const record = parsed;
37
+ const scorecard = judgeScorecardSchema.safeParse(record.scorecard);
38
+ if (!scorecard.success)
39
+ return null;
40
+ if (typeof record.fingerprint !== 'string'
41
+ || typeof record.at !== 'string'
42
+ || typeof record.model !== 'string'
43
+ || typeof record.screenshotPath !== 'string') {
44
+ return null;
45
+ }
46
+ return {
47
+ scorecard: scorecard.data,
48
+ fingerprint: record.fingerprint,
49
+ at: record.at,
50
+ model: record.model,
51
+ screenshotPath: record.screenshotPath,
52
+ ...(typeof record.genre === 'string' ? { genre: record.genre } : {}),
53
+ };
54
+ }
55
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/judge/record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAuB,MAAM,sBAAsB,CAAC;AAEjF,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AAE/D,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;AAC5C,CAAC;AAeD,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,MAAmB;IAChE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvE,EAAE,CAAC,aAAa,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAiC,CAAC;IACjD,MAAM,SAAS,GAAG,oBAAoB,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACnE,IAAI,CAAC,SAAS,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IACpC,IACE,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ;WACnC,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ;WAC7B,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;WAChC,OAAO,MAAM,CAAC,cAAc,KAAK,QAAQ,EAC5C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,SAAS,EAAE,SAAS,CAAC,IAAI;QACzB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,GAAG,CAAC,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The client for `POST /api/cli/v1/judge/stream` — one vision call that grades
3
+ * the verify screenshot(s) against a rubric and returns a scorecard.
4
+ *
5
+ * Mirrors `forge/stream.ts` in shape (same SSE plumbing, same pre-stream
6
+ * status mapping, same "a truncated stream is not a confirmed failure"
7
+ * discipline), but far simpler: there is no job to resume — a judge run that
8
+ * dies is simply re-run — and the terminal payload is
9
+ * `{ success, scorecard, model, sparks }`.
10
+ */
11
+ import { type JudgeScorecard } from '@bitmagic/asset-core';
12
+ import type { Environment } from '../config/environments.js';
13
+ export interface JudgeSuccess {
14
+ success: true;
15
+ scorecard: JudgeScorecard;
16
+ model: string;
17
+ sparks: number;
18
+ }
19
+ export interface JudgeFailure {
20
+ success: false;
21
+ message: string;
22
+ sparks: number;
23
+ }
24
+ export type JudgeOutcome = JudgeSuccess | JudgeFailure;
25
+ export interface JudgeStreamDeps {
26
+ fetch: typeof globalThis.fetch;
27
+ /** Called for every progress frame the server sends (there are few). */
28
+ onProgress: (message: string) => void;
29
+ }
30
+ /**
31
+ * POSTs the rubric + screenshot(s) and consumes the SSE response. A stream
32
+ * that closes with no terminal frame is a truncated connection, not a
33
+ * confirmed failure — nothing is saved server-side, so the remedy is simply
34
+ * to run the command again.
35
+ */
36
+ export declare function requestJudge(environment: Environment, accessToken: string, body: Record<string, unknown>, deps: JudgeStreamDeps): Promise<JudgeOutcome>;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The client for `POST /api/cli/v1/judge/stream` — one vision call that grades
3
+ * the verify screenshot(s) against a rubric and returns a scorecard.
4
+ *
5
+ * Mirrors `forge/stream.ts` in shape (same SSE plumbing, same pre-stream
6
+ * status mapping, same "a truncated stream is not a confirmed failure"
7
+ * discipline), but far simpler: there is no job to resume — a judge run that
8
+ * dies is simply re-run — and the terminal payload is
9
+ * `{ success, scorecard, model, sparks }`.
10
+ */
11
+ import { judgeScorecardSchema } from '@bitmagic/asset-core';
12
+ import { CliError } from '../errors.js';
13
+ import { consumeSseStream } from '../http/sse.js';
14
+ import { isSpillConsentRefusal, sparkRefusalCode, spillConsentMessage } from '../http/spark-refusal.js';
15
+ import { apiServerHeaders } from '../telemetry/request-headers.js';
16
+ function isRecord(value) {
17
+ return typeof value === 'object' && value !== null;
18
+ }
19
+ /** Map a pre-stream error status to the message a creator can act on. */
20
+ async function toCliError(response, gameId) {
21
+ const body = await response
22
+ .json()
23
+ .then((parsed) => (isRecord(parsed) ? parsed : {}))
24
+ .catch(() => ({}));
25
+ const serverMessage = typeof body.error === 'string' && body.error.length > 0 ? body.error : null;
26
+ if (response.status === 403) {
27
+ return new CliError(serverMessage ?? `You cannot judge game "${gameId}".`);
28
+ }
29
+ if (response.status === 402) {
30
+ const code = sparkRefusalCode(body);
31
+ if (isSpillConsentRefusal(body)) {
32
+ return new CliError(spillConsentMessage('a judge run'), 1, code);
33
+ }
34
+ const { balance, required } = body;
35
+ const detail = typeof balance === 'number' && typeof required === 'number'
36
+ ? ` You have ${balance} sparks; a judge run requires at least ${required}.`
37
+ : '';
38
+ return new CliError(`Not enough sparks to run the judge.${detail}`, 1, code);
39
+ }
40
+ if (response.status === 400) {
41
+ return new CliError(serverMessage ?? 'The server rejected this judge request.');
42
+ }
43
+ return new CliError(`api-server returned HTTP ${response.status} starting the judge`
44
+ + (serverMessage ? `: ${serverMessage}.` : '.'));
45
+ }
46
+ /**
47
+ * POSTs the rubric + screenshot(s) and consumes the SSE response. A stream
48
+ * that closes with no terminal frame is a truncated connection, not a
49
+ * confirmed failure — nothing is saved server-side, so the remedy is simply
50
+ * to run the command again.
51
+ */
52
+ export async function requestJudge(environment, accessToken, body, deps) {
53
+ const gameId = typeof body.gameId === 'string' ? body.gameId : '(unknown game)';
54
+ const url = `${environment.apiUrl}/api/cli/v1/judge/stream`;
55
+ let response;
56
+ try {
57
+ response = await deps.fetch(url, {
58
+ method: 'POST',
59
+ headers: {
60
+ ...apiServerHeaders(),
61
+ Authorization: `Bearer ${accessToken}`,
62
+ Accept: 'text/event-stream',
63
+ 'Content-Type': 'application/json',
64
+ },
65
+ body: JSON.stringify(body),
66
+ });
67
+ }
68
+ catch {
69
+ throw new CliError(`Cannot reach api-server at ${environment.apiUrl}.`, 1, 'unreachable');
70
+ }
71
+ if (!response.ok) {
72
+ throw await toCliError(response, gameId);
73
+ }
74
+ if (!response.body) {
75
+ throw new CliError('The server accepted the judge request but returned no stream body.');
76
+ }
77
+ let outcome = null;
78
+ const onEvent = (event) => {
79
+ if (event.event === 'progress') {
80
+ const message = isRecord(event.data) && typeof event.data.message === 'string'
81
+ ? event.data.message
82
+ : null;
83
+ if (message !== null)
84
+ deps.onProgress(message);
85
+ return false;
86
+ }
87
+ if (event.event === 'result') {
88
+ const data = isRecord(event.data) ? event.data : {};
89
+ // Validated HERE, not trusted: `.bitmagic/judge.json` promises a
90
+ // schema-valid scorecard to whoever reads it, and the shared schema is
91
+ // exactly what makes that promise checkable on both sides of the wire.
92
+ const scorecard = judgeScorecardSchema.safeParse(data.scorecard);
93
+ if (data.success === true && scorecard.success) {
94
+ outcome = {
95
+ success: true,
96
+ scorecard: scorecard.data,
97
+ model: typeof data.model === 'string' ? data.model : '(unknown)',
98
+ sparks: typeof data.sparks === 'number' ? data.sparks : 0,
99
+ };
100
+ return true;
101
+ }
102
+ return false;
103
+ }
104
+ if (event.event === 'error') {
105
+ const data = isRecord(event.data) ? event.data : {};
106
+ outcome = {
107
+ success: false,
108
+ message: typeof data.message === 'string' ? data.message : 'The judge failed without reporting a reason.',
109
+ sparks: typeof data.sparks === 'number' ? data.sparks : 0,
110
+ };
111
+ return true;
112
+ }
113
+ return false;
114
+ };
115
+ const sawTerminal = await consumeSseStream(response.body, onEvent);
116
+ if (!sawTerminal || outcome === null) {
117
+ throw new CliError('The connection dropped before the judge finished — this is not a confirmed failure. '
118
+ + 'Nothing is saved server-side; run `bitmagic judge` again.');
119
+ }
120
+ return outcome;
121
+ }
122
+ //# sourceMappingURL=stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/judge/stream.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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"}