@bitmagic/cli 0.1.52-dev.0 → 0.1.52-dev.2
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 +66 -1
- package/dist/assets/add.d.ts +66 -0
- package/dist/assets/add.js +274 -0
- package/dist/assets/add.js.map +1 -0
- package/dist/assets/entry.d.ts +44 -0
- package/dist/assets/entry.js +71 -0
- package/dist/assets/entry.js.map +1 -0
- package/dist/assets/file-kind.d.ts +53 -0
- package/dist/assets/file-kind.js +180 -0
- package/dist/assets/file-kind.js.map +1 -0
- package/dist/assets/list.d.ts +27 -0
- package/dist/assets/list.js +50 -0
- package/dist/assets/list.js.map +1 -0
- package/dist/assets/remove.d.ts +50 -0
- package/dist/assets/remove.js +91 -0
- package/dist/assets/remove.js.map +1 -0
- package/dist/assets/vxl-summary.d.ts +23 -0
- package/dist/assets/vxl-summary.js +103 -0
- package/dist/assets/vxl-summary.js.map +1 -0
- package/dist/browser-gl.d.ts +46 -8
- package/dist/browser-gl.js +54 -26
- package/dist/browser-gl.js.map +1 -1
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/assets.d.ts +62 -0
- package/dist/commands/assets.js +359 -0
- package/dist/commands/assets.js.map +1 -0
- package/dist/commands/forge.d.ts +5 -0
- package/dist/commands/forge.js +10 -1
- package/dist/commands/forge.js.map +1 -1
- package/dist/commands/init.js +19 -12
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/judge.d.ts +41 -0
- package/dist/commands/judge.js +217 -0
- package/dist/commands/judge.js.map +1 -0
- package/dist/commands/reset-account.js +2 -8
- package/dist/commands/reset-account.js.map +1 -1
- package/dist/commands/verify.js +3 -3
- package/dist/commands/verify.js.map +1 -1
- package/dist/editor/previews.d.ts +6 -0
- package/dist/editor/previews.js +16 -0
- package/dist/editor/previews.js.map +1 -1
- package/dist/editor/server.js +2 -2
- package/dist/editor/server.js.map +1 -1
- package/dist/forge/apply-modifications.d.ts +9 -1
- package/dist/forge/apply-modifications.js +3 -14
- package/dist/forge/apply-modifications.js.map +1 -1
- package/dist/forge/voxelize-session.d.ts +24 -0
- package/dist/forge/voxelize-session.js +137 -0
- package/dist/forge/voxelize-session.js.map +1 -0
- package/dist/generate/prop.js +9 -63
- package/dist/generate/prop.js.map +1 -1
- package/dist/generate/vehicle.js +5 -85
- package/dist/generate/vehicle.js.map +1 -1
- package/dist/http/signed-upload.d.ts +55 -0
- package/dist/http/signed-upload.js +168 -0
- package/dist/http/signed-upload.js.map +1 -0
- package/dist/judge/facts.d.ts +18 -0
- package/dist/judge/facts.js +180 -0
- package/dist/judge/facts.js.map +1 -0
- package/dist/judge/record.d.ts +16 -0
- package/dist/judge/record.js +55 -0
- package/dist/judge/record.js.map +1 -0
- package/dist/judge/stream.d.ts +36 -0
- package/dist/judge/stream.js +122 -0
- package/dist/judge/stream.js.map +1 -0
- package/dist/project/jobs.d.ts +1 -1
- package/dist/project/jobs.js.map +1 -1
- package/dist/publish/client.js +13 -34
- package/dist/publish/client.js.map +1 -1
- package/dist/scaffold/project-files.d.ts +8 -0
- package/dist/scaffold/project-files.js +104 -21
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.js +2 -1
- package/dist/scaffold/project.js.map +1 -1
- package/dist/verify/browser.d.ts +11 -6
- package/dist/verify/browser.js +31 -15
- package/dist/verify/browser.js.map +1 -1
- package/dist/verify/iterate.js +3 -3
- package/dist/verify/iterate.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `bitmagic assets add <file>` makes of a filename, decided BEFORE anything is read, uploaded
|
|
3
|
+
* or written — so a file this lane cannot take costs nothing and leaves world.json untouched.
|
|
4
|
+
*
|
|
5
|
+
* The table mirrors the Creator's Assets-tab upload (`creator/src/vue/composables/useAssetsEditor.ts`
|
|
6
|
+
* `handleFileUpload` + `assetsUploadTypes.ts` `detectAssetType`) where the CLI can do the same
|
|
7
|
+
* thing, and refuses — by name, with where to go instead — where it cannot:
|
|
8
|
+
*
|
|
9
|
+
* - `.gltf` is refused rather than accepted-and-broken: the engine voxelizes ONE fetched URL, and
|
|
10
|
+
* a `.gltf` is a JSON index whose buffers and textures live in files beside it.
|
|
11
|
+
* - `.vox`/`.qb` (MagicaVoxel/Qubicle import), `.fbx` (animation import), `.spz`/`.ply`/`.sog`
|
|
12
|
+
* (splat transcode) and `.mp4` (video faststart) each need a conversion the Creator runs through
|
|
13
|
+
* game-play-agent, which a CLI project cannot reach.
|
|
14
|
+
*
|
|
15
|
+
* Audio and raster images are uploaded AS THEY ARE. The Creator transcodes them to `.opus`/`.webp`
|
|
16
|
+
* for size; the engine decodes mp3/ogg/m4a through Web Audio and draws png/jpg like any browser,
|
|
17
|
+
* so the difference is bytes, not whether it works. `audioTranscodeNote` says so once.
|
|
18
|
+
*/
|
|
19
|
+
import * as path from 'path';
|
|
20
|
+
import { CliError } from '../errors.js';
|
|
21
|
+
const CREATOR_ONLY = {
|
|
22
|
+
vox: 'MagicaVoxel import',
|
|
23
|
+
qb: 'Qubicle import',
|
|
24
|
+
fbx: 'FBX animation import',
|
|
25
|
+
spz: 'Gaussian-splat transcode',
|
|
26
|
+
ply: 'Gaussian-splat transcode',
|
|
27
|
+
sog: 'Gaussian-splat transcode',
|
|
28
|
+
mp4: 'video',
|
|
29
|
+
};
|
|
30
|
+
const IMAGE_TYPES = {
|
|
31
|
+
png: 'image/png',
|
|
32
|
+
jpg: 'image/jpeg',
|
|
33
|
+
jpeg: 'image/jpeg',
|
|
34
|
+
webp: 'image/webp',
|
|
35
|
+
};
|
|
36
|
+
const AUDIO_TYPES = {
|
|
37
|
+
opus: 'audio/ogg',
|
|
38
|
+
ogg: 'audio/ogg',
|
|
39
|
+
m4a: 'audio/mp4',
|
|
40
|
+
mp3: 'audio/mpeg',
|
|
41
|
+
};
|
|
42
|
+
/** Every extension `classifyAssetFile` accepts, for help text and the README. */
|
|
43
|
+
export const ACCEPTED_EXTENSIONS = ['glb', 'vxl', 'png', 'jpg', 'jpeg', 'webp', 'opus', 'ogg', 'm4a', 'mp3', 'json'];
|
|
44
|
+
export function fileExtension(filename) {
|
|
45
|
+
return path.extname(filename).slice(1).toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
export function classifyAssetFile(filename, options) {
|
|
48
|
+
const extension = fileExtension(filename);
|
|
49
|
+
const base = path.basename(filename);
|
|
50
|
+
if (options.keepGlb && extension !== 'glb') {
|
|
51
|
+
throw new CliError(`--keep-glb only applies to a .glb file, and ${base} is not one. world.json is unchanged.`);
|
|
52
|
+
}
|
|
53
|
+
if (extension === 'glb') {
|
|
54
|
+
return options.keepGlb
|
|
55
|
+
? { kind: 'glb-keep', assetType: 'glb', contentType: 'model/gltf-binary', extension }
|
|
56
|
+
: { kind: 'glb-voxelize', assetType: 'vxl', contentType: 'model/gltf-binary', extension };
|
|
57
|
+
}
|
|
58
|
+
if (extension === 'vxl') {
|
|
59
|
+
// The same content type the engine uploads its own bakes under (VoxelMessageHandlers.ts).
|
|
60
|
+
// api-server marks it compressible, so the PUT gzips.
|
|
61
|
+
return { kind: 'vxl', assetType: 'vxl', contentType: 'application/octet-stream', extension };
|
|
62
|
+
}
|
|
63
|
+
const imageType = IMAGE_TYPES[extension];
|
|
64
|
+
if (imageType !== undefined) {
|
|
65
|
+
return { kind: 'image', assetType: 'image', contentType: imageType, extension };
|
|
66
|
+
}
|
|
67
|
+
const audioType = AUDIO_TYPES[extension];
|
|
68
|
+
if (audioType !== undefined) {
|
|
69
|
+
return { kind: 'audio', assetType: 'audio', contentType: audioType, extension };
|
|
70
|
+
}
|
|
71
|
+
if (extension === 'json') {
|
|
72
|
+
return { kind: 'json', assetType: 'json', contentType: 'application/json', extension };
|
|
73
|
+
}
|
|
74
|
+
if (extension === 'gltf') {
|
|
75
|
+
throw new CliError(`${base} is a .gltf, which keeps its buffers and textures in files beside it — the game loads a `
|
|
76
|
+
+ 'single URL. Export it as a self-contained .glb and add that instead. world.json is unchanged.');
|
|
77
|
+
}
|
|
78
|
+
const creatorOnly = CREATOR_ONLY[extension];
|
|
79
|
+
if (creatorOnly !== undefined) {
|
|
80
|
+
throw new CliError(`.${extension} (${creatorOnly}) needs a conversion this CLI does not run. `
|
|
81
|
+
+ 'Upload it through the Creator\'s Assets tab instead. world.json is unchanged.');
|
|
82
|
+
}
|
|
83
|
+
throw new CliError(`Cannot add ${base}: this CLI takes ${ACCEPTED_EXTENSIONS.map((e) => `.${e}`).join(', ')}. `
|
|
84
|
+
+ 'world.json is unchanged.');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The asset's name when `--name` is not given: the file's basename without its extension, which
|
|
88
|
+
* is what the Creator pre-fills its name prompt with.
|
|
89
|
+
*/
|
|
90
|
+
export function defaultAssetName(filename) {
|
|
91
|
+
return path.basename(filename).replace(/\.[^/.]+$/, '');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Characters that survive a path check but break the URL the object is served from, or the storage
|
|
95
|
+
* key itself. api-server builds `publicUrl` by concatenating the key onto the portal origin with no
|
|
96
|
+
* percent-encoding, so a `#` truncates the URL at a fragment and a `?` at a query — the asset then
|
|
97
|
+
* 404s at game load, long after a successful-looking upload. GCS rejects CR/LF in object names
|
|
98
|
+
* outright. Refused up front rather than escaped, so the name in world.json is the name on disk.
|
|
99
|
+
*/
|
|
100
|
+
// eslint-disable-next-line no-control-regex -- control characters are exactly what this rejects
|
|
101
|
+
const URL_UNSAFE = /[#?%\u0000-\u001f\u007f]/;
|
|
102
|
+
/** GCS caps an object key at 1024 bytes; the engine prefixes and suffixes the name inside one. */
|
|
103
|
+
const MAX_NAME_LENGTH = 128;
|
|
104
|
+
function describeUnsafe(value) {
|
|
105
|
+
const bad = URL_UNSAFE.exec(value)?.[0] ?? '';
|
|
106
|
+
const code = bad.charCodeAt(0);
|
|
107
|
+
return code < 0x20 || code === 0x7f ? `a control character (0x${code.toString(16)})` : `"${bad}"`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A name the engine can build a filename from. `CREATE_ASSET_FROM_GLB_URL` uploads its bake as
|
|
111
|
+
* `<gameId>-<name>-<timestamp>.vxl`, and api-server refuses a filename carrying a separator or
|
|
112
|
+
* `..` — so a name that would trip that fails HERE, before the file has been uploaded and the
|
|
113
|
+
* browser launched, instead of as an opaque "Failed to upload VXL file" minutes later.
|
|
114
|
+
*
|
|
115
|
+
* The length cap and the URL-unsafe set are part of the same job: both produce a failure that only
|
|
116
|
+
* appears after the upload (or after a ten-minute bake), which is precisely what this is for.
|
|
117
|
+
*/
|
|
118
|
+
export function validateAssetName(name) {
|
|
119
|
+
const trimmed = name.trim();
|
|
120
|
+
if (trimmed === '')
|
|
121
|
+
throw new CliError('The asset name cannot be empty. world.json is unchanged.');
|
|
122
|
+
if (/[/\\]/.test(trimmed) || trimmed.includes('..')) {
|
|
123
|
+
throw new CliError(`The asset name ${JSON.stringify(name)} cannot contain a path separator or "..". world.json is unchanged.`);
|
|
124
|
+
}
|
|
125
|
+
if (URL_UNSAFE.test(trimmed)) {
|
|
126
|
+
throw new CliError(`The asset name ${JSON.stringify(name)} cannot contain ${describeUnsafe(trimmed)} — it ends up in `
|
|
127
|
+
+ 'the uploaded file\'s URL. world.json is unchanged.');
|
|
128
|
+
}
|
|
129
|
+
if (trimmed.length > MAX_NAME_LENGTH) {
|
|
130
|
+
throw new CliError(`The asset name is ${trimmed.length} characters; keep it under ${MAX_NAME_LENGTH}. world.json is unchanged.`);
|
|
131
|
+
}
|
|
132
|
+
return trimmed;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The upload filename: a bare basename, which is all api-server accepts.
|
|
136
|
+
*
|
|
137
|
+
* Mirrors `sanitizeUploadFilename` in full — separators AND `..`, as three independent checks.
|
|
138
|
+
* `path.basename` strips `/` on POSIX but leaves `\`, so a legal `my\model.glb` used to pass here
|
|
139
|
+
* and then eat a network round-trip to be refused by the server with a message naming nothing the
|
|
140
|
+
* creator could act on. The URL-unsafe and length checks are here for the same reason as on the
|
|
141
|
+
* name above.
|
|
142
|
+
*/
|
|
143
|
+
export function uploadFilename(filePath) {
|
|
144
|
+
const base = path.basename(filePath);
|
|
145
|
+
if (base.includes('/') || base.includes('\\')) {
|
|
146
|
+
throw new CliError(`Rename ${JSON.stringify(base)} first: an upload filename cannot contain a path separator. `
|
|
147
|
+
+ 'world.json is unchanged.');
|
|
148
|
+
}
|
|
149
|
+
if (base.includes('..')) {
|
|
150
|
+
throw new CliError(`Rename ${base} first: an upload filename cannot contain "..". world.json is unchanged.`);
|
|
151
|
+
}
|
|
152
|
+
if (URL_UNSAFE.test(base)) {
|
|
153
|
+
throw new CliError(`Rename ${JSON.stringify(base)} first: a filename cannot contain ${describeUnsafe(base)} — it ends up `
|
|
154
|
+
+ 'in the uploaded file\'s URL. world.json is unchanged.');
|
|
155
|
+
}
|
|
156
|
+
if (base.length > MAX_NAME_LENGTH) {
|
|
157
|
+
throw new CliError(`Rename ${JSON.stringify(base)} first: keep the filename under ${MAX_NAME_LENGTH} characters. `
|
|
158
|
+
+ 'world.json is unchanged.');
|
|
159
|
+
}
|
|
160
|
+
return base;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* `asset_<timestamp>_<random>` — byte-compatible with the engine's `ObjectIdService.generateId`
|
|
164
|
+
* and with what api-server mints for the generators (`cli-assets.ts`), so an id minted here is
|
|
165
|
+
* indistinguishable from one minted anywhere else in the system.
|
|
166
|
+
*/
|
|
167
|
+
export function mintAssetId(now = Date.now, random = Math.random) {
|
|
168
|
+
return `asset_${now()}_${random().toString(36).substring(2, 8)}`;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Said once per audio upload that the Creator would have transcoded. The engine plays the file
|
|
172
|
+
* as-is; this is about download size, and it names the exact command rather than a vague "consider".
|
|
173
|
+
*/
|
|
174
|
+
export function audioTranscodeNote(extension) {
|
|
175
|
+
if (extension === 'opus')
|
|
176
|
+
return null;
|
|
177
|
+
return `Uploaded as .${extension}. The game plays it as-is; for a smaller download convert it first: `
|
|
178
|
+
+ `ffmpeg -i in.${extension} -c:a libopus -b:a 96k out.opus`;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=file-kind.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-kind.js","sourceRoot":"","sources":["../../src/assets/file-kind.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bitmagic assets list` — the project's `assets[]`, as the dev view's assets panel sees it
|
|
3
|
+
* (`editor/server.ts` `GET /api/assets` reads the same array and nothing else).
|
|
4
|
+
*/
|
|
5
|
+
import { type JsonObject } from '../project/world-json.js';
|
|
6
|
+
export interface AssetRow {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
type: string;
|
|
10
|
+
size?: number;
|
|
11
|
+
/** Placed instances in `environmentObjects[]`. */
|
|
12
|
+
placed: number;
|
|
13
|
+
/**
|
|
14
|
+
* Every reference anywhere in world.json — placed instances plus levels, maps, doors, key items,
|
|
15
|
+
* village rosters.
|
|
16
|
+
*
|
|
17
|
+
* Reported alongside {@link placed} rather than folded into it because the two answer different
|
|
18
|
+
* questions, and counting only placed instances was actively misleading: a level asset showed
|
|
19
|
+
* `0`, an agent read that as "safe to delete", and `assets remove` then refused — or, with
|
|
20
|
+
* `--force`, removed the asset while leaving the level pointing at nothing.
|
|
21
|
+
*/
|
|
22
|
+
references: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function summarizeAssets(world: JsonObject): AssetRow[];
|
|
25
|
+
export declare function formatSize(bytes: number | undefined): string;
|
|
26
|
+
/** Fixed-width columns, no dependency, readable in a terminal and greppable by an agent. */
|
|
27
|
+
export declare function formatAssetTable(rows: AssetRow[]): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bitmagic assets list` — the project's `assets[]`, as the dev view's assets panel sees it
|
|
3
|
+
* (`editor/server.ts` `GET /api/assets` reads the same array and nothing else).
|
|
4
|
+
*/
|
|
5
|
+
import { isJsonObject } from '../project/world-json.js';
|
|
6
|
+
import { findAssetReferences } from './remove.js';
|
|
7
|
+
export function summarizeAssets(world) {
|
|
8
|
+
const assets = Array.isArray(world.assets) ? world.assets : [];
|
|
9
|
+
return assets.filter(isJsonObject).map((asset) => {
|
|
10
|
+
const id = typeof asset.id === 'string' ? asset.id : '';
|
|
11
|
+
// The SAME scan `assets remove` refuses on, so the two commands cannot disagree about what
|
|
12
|
+
// counts as a reference.
|
|
13
|
+
const found = id === '' ? [] : findAssetReferences(world, id);
|
|
14
|
+
return {
|
|
15
|
+
id,
|
|
16
|
+
name: typeof asset.name === 'string' ? asset.name : '',
|
|
17
|
+
type: typeof asset.type === 'string' ? asset.type : '',
|
|
18
|
+
...(typeof asset.size === 'number' ? { size: asset.size } : {}),
|
|
19
|
+
placed: found.filter((ref) => ref.placed).length,
|
|
20
|
+
references: found.length,
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function formatSize(bytes) {
|
|
25
|
+
if (bytes === undefined || !Number.isFinite(bytes) || bytes < 0)
|
|
26
|
+
return '';
|
|
27
|
+
if (bytes < 1024)
|
|
28
|
+
return `${bytes} B`;
|
|
29
|
+
if (bytes < 1024 * 1024)
|
|
30
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
31
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
32
|
+
}
|
|
33
|
+
/** Fixed-width columns, no dependency, readable in a terminal and greppable by an agent. */
|
|
34
|
+
export function formatAssetTable(rows) {
|
|
35
|
+
if (rows.length === 0)
|
|
36
|
+
return 'No assets in world.json yet. `bitmagic assets add <file>` adds one.';
|
|
37
|
+
const header = { id: 'ID', name: 'NAME', type: 'TYPE', size: 'SIZE', placed: 'PLACED', references: 'REFS' };
|
|
38
|
+
const cells = rows.map((row) => ({
|
|
39
|
+
id: row.id, name: row.name, type: row.type, size: formatSize(row.size),
|
|
40
|
+
placed: String(row.placed), references: String(row.references),
|
|
41
|
+
}));
|
|
42
|
+
const width = (key) => Math.max(header[key].length, ...cells.map((cell) => cell[key].length));
|
|
43
|
+
const w = {
|
|
44
|
+
id: width('id'), name: width('name'), type: width('type'), size: width('size'), placed: width('placed'),
|
|
45
|
+
};
|
|
46
|
+
const line = (cell) => `${cell.id.padEnd(w.id)} ${cell.name.padEnd(w.name)} ${cell.type.padEnd(w.type)} `
|
|
47
|
+
+ `${cell.size.padStart(w.size)} ${cell.placed.padStart(w.placed)} ${cell.references}`;
|
|
48
|
+
return [line(header), ...cells.map(line)].join('\n');
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/assets/list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `bitmagic assets remove <id>` does to world.json, decided before anything is written.
|
|
3
|
+
*
|
|
4
|
+
* Removal is world.json only. The uploaded bytes stay in storage — that is what the Creator's
|
|
5
|
+
* Remove and game-play-agent's `/api/remove-asset` do too (there is no delete endpoint on either
|
|
6
|
+
* server, and every upload is immutable-cached), so a published game that still points at the file
|
|
7
|
+
* keeps working.
|
|
8
|
+
*
|
|
9
|
+
* The one thing this does that the Creator's button does not: it looks for what still POINTS at
|
|
10
|
+
* the asset first. world.json references an asset by id under a handful of keys — placed objects'
|
|
11
|
+
* `assetId`, a level's `vwldAssetId`, a map's `mapAssetId`, doors, key items, towers — and a
|
|
12
|
+
* removed asset that any of them still names is a load error the creator finds later, in the
|
|
13
|
+
* browser, with nothing connecting it back to this command. So the scan is a generic walk for
|
|
14
|
+
* those keys rather than a list of known containers: a container added to the schema tomorrow is
|
|
15
|
+
* still caught. What it cannot see is game CODE (`engine.playSound(assetId)`), and the refusal
|
|
16
|
+
* says so.
|
|
17
|
+
*
|
|
18
|
+
* `--force` removes the asset and every placed instance in `environmentObjects[]` in the same
|
|
19
|
+
* atomic write, and reports the rest as warnings.
|
|
20
|
+
*/
|
|
21
|
+
import type { WorldJsonModification } from '@bitmagic/world-forger/pipeline/index.js';
|
|
22
|
+
import { type JsonObject } from '../project/world-json.js';
|
|
23
|
+
export interface AssetReference {
|
|
24
|
+
/** JSON-pointer-ish path to the referencing value, e.g. `environmentObjects[3].assetId`. */
|
|
25
|
+
path: string;
|
|
26
|
+
/**
|
|
27
|
+
* True when this is a placed instance in root `environmentObjects[]` — the one kind of reference
|
|
28
|
+
* `--force` can clean up by removing it alongside the asset.
|
|
29
|
+
*
|
|
30
|
+
* Recorded during the walk rather than recovered by re-parsing {@link path}: the path is a
|
|
31
|
+
* human-readable rendering, and deriving structure back out of it silently returns "no placed
|
|
32
|
+
* instances" the moment that rendering changes.
|
|
33
|
+
*/
|
|
34
|
+
placed: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface RemovalPlan {
|
|
37
|
+
asset: JsonObject;
|
|
38
|
+
modifications: WorldJsonModification[];
|
|
39
|
+
/** Placed instances in `environmentObjects[]` — removed with the asset under `--force`. */
|
|
40
|
+
placedInstances: number;
|
|
41
|
+
/** References elsewhere in the document, which `--force` leaves in place and reports. */
|
|
42
|
+
otherReferences: AssetReference[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Every place in the document where one of the reference keys holds `assetId`. The asset's own
|
|
46
|
+
* `assets[]` entry is skipped — its `id` is not a reference key, but a vehicle asset's own
|
|
47
|
+
* `vehicleFitment` might one day carry one, so the whole `assets[]` subtree is excluded outright.
|
|
48
|
+
*/
|
|
49
|
+
export declare function findAssetReferences(world: JsonObject, assetId: string): AssetReference[];
|
|
50
|
+
export declare function planAssetRemoval(world: JsonObject, assetId: string, force: boolean): RemovalPlan;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { CliError } from '../errors.js';
|
|
2
|
+
import { isJsonObject } from '../project/world-json.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether a key holds an asset id. Matched by SHAPE rather than by an enumerated list, because an
|
|
5
|
+
* enumerated list is exactly what went wrong: `buildingAssetIds` (required on every village
|
|
6
|
+
* hotspot) was missing, so removing a village's building asset reported zero references and left
|
|
7
|
+
* the world throwing `references unknown assetId(s)` at load.
|
|
8
|
+
*
|
|
9
|
+
* The shape covers `assetId` and every `<thing>AssetId` / `<thing>AssetIds` in the GameData types
|
|
10
|
+
* (`vwldAssetId`, `mapAssetId`, `characterAssetId`, `buildingAssetIds`, `audioAssetIds`, …), so a
|
|
11
|
+
* container OR a key added to the schema tomorrow is caught without editing this file.
|
|
12
|
+
*/
|
|
13
|
+
function isReferenceKey(key) {
|
|
14
|
+
return key === 'assetId' || /^[A-Za-z]\w*AssetIds?$/.test(key);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Does this value name the asset? Plural keys hold an ARRAY of ids, which a bare `===` could never
|
|
18
|
+
* match — the second half of the same bug.
|
|
19
|
+
*/
|
|
20
|
+
function namesAsset(value, assetId) {
|
|
21
|
+
if (typeof value === 'string')
|
|
22
|
+
return value === assetId;
|
|
23
|
+
return Array.isArray(value) && value.some((item) => item === assetId);
|
|
24
|
+
}
|
|
25
|
+
/** Root array of placed instances — the only references `--force` removes rather than reports. */
|
|
26
|
+
const PLACED_OBJECTS_KEY = 'environmentObjects';
|
|
27
|
+
/**
|
|
28
|
+
* Every place in the document where one of the reference keys holds `assetId`. The asset's own
|
|
29
|
+
* `assets[]` entry is skipped — its `id` is not a reference key, but a vehicle asset's own
|
|
30
|
+
* `vehicleFitment` might one day carry one, so the whole `assets[]` subtree is excluded outright.
|
|
31
|
+
*/
|
|
32
|
+
export function findAssetReferences(world, assetId) {
|
|
33
|
+
const found = [];
|
|
34
|
+
const walk = (value, path, inPlacedObjects) => {
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
value.forEach((item, index) => walk(item, `${path}[${index}]`, inPlacedObjects));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!isJsonObject(value))
|
|
40
|
+
return;
|
|
41
|
+
for (const [key, child] of Object.entries(value)) {
|
|
42
|
+
const childPath = path === '' ? key : `${path}.${key}`;
|
|
43
|
+
if (path === '' && key === 'assets')
|
|
44
|
+
continue;
|
|
45
|
+
if (isReferenceKey(key) && namesAsset(child, assetId)) {
|
|
46
|
+
found.push({ path: childPath, placed: inPlacedObjects && key === 'assetId' });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
walk(child, childPath, inPlacedObjects || (path === '' && key === PLACED_OBJECTS_KEY));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
walk(world, '', false);
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
export function planAssetRemoval(world, assetId, force) {
|
|
56
|
+
const assets = Array.isArray(world.assets) ? world.assets : [];
|
|
57
|
+
const asset = assets.find((item) => isJsonObject(item) && item.id === assetId);
|
|
58
|
+
if (asset === undefined) {
|
|
59
|
+
throw new CliError(`No asset with id "${assetId}" in this project's world.json. `
|
|
60
|
+
+ '`bitmagic assets list` shows the ids. world.json is unchanged.');
|
|
61
|
+
}
|
|
62
|
+
const references = findAssetReferences(world, assetId);
|
|
63
|
+
const placed = references.filter((ref) => ref.placed);
|
|
64
|
+
const others = references.filter((ref) => !ref.placed);
|
|
65
|
+
if (references.length > 0 && !force) {
|
|
66
|
+
const name = typeof asset.name === 'string' ? asset.name : assetId;
|
|
67
|
+
const lines = references.slice(0, 8).map((ref) => ` - ${ref.path}`);
|
|
68
|
+
if (references.length > 8)
|
|
69
|
+
lines.push(` - …and ${references.length - 8} more`);
|
|
70
|
+
throw new CliError(`"${name}" is still referenced ${references.length === 1 ? 'once' : `${references.length} times`} in world.json:\n`
|
|
71
|
+
+ `${lines.join('\n')}\n`
|
|
72
|
+
+ 'Removing it would leave those pointing at nothing. Re-run with --force to remove the asset '
|
|
73
|
+
+ `and its ${placed.length} placed instance${placed.length === 1 ? '' : 's'} together`
|
|
74
|
+
+ (others.length > 0 ? ' (the other references stay and are reported)' : '')
|
|
75
|
+
+ '. Game code that names the id (playSound, spawnFromAsset, …) is not checked. world.json is unchanged.');
|
|
76
|
+
}
|
|
77
|
+
const modifications = [{
|
|
78
|
+
type: 'removeRoot',
|
|
79
|
+
path: ['assets'],
|
|
80
|
+
predicate: (item) => isJsonObject(item) && item.id === assetId,
|
|
81
|
+
}];
|
|
82
|
+
if (placed.length > 0) {
|
|
83
|
+
modifications.push({
|
|
84
|
+
type: 'removeRoot',
|
|
85
|
+
path: ['environmentObjects'],
|
|
86
|
+
predicate: (item) => isJsonObject(item) && item.assetId === assetId,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return { asset, modifications, placedInstances: placed.length, otherReferences: others };
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=remove.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove.js","sourceRoot":"","sources":["../../src/assets/remove.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface VxlSummary {
|
|
2
|
+
version: number;
|
|
3
|
+
minVoxelSize: number;
|
|
4
|
+
boundingBox: {
|
|
5
|
+
minX: number;
|
|
6
|
+
minY: number;
|
|
7
|
+
minZ: number;
|
|
8
|
+
maxX: number;
|
|
9
|
+
maxY: number;
|
|
10
|
+
maxZ: number;
|
|
11
|
+
};
|
|
12
|
+
fragmentCount: number;
|
|
13
|
+
/** Absent when the fragment table could not be walked to its end. */
|
|
14
|
+
voxelCount?: number;
|
|
15
|
+
}
|
|
16
|
+
/** True when the first four bytes are the VXL3 magic — cheap, no copy, no decompression. */
|
|
17
|
+
export declare function isVxl3(bytes: Uint8Array): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Read a VXL3 file's header. Returns `null` for anything that is not a well-formed VXL3 — a legacy
|
|
20
|
+
* JSON `.vxl`, a truncated file, an unknown compression id — so the caller can name the problem
|
|
21
|
+
* instead of writing an asset entry the engine would then fail to load.
|
|
22
|
+
*/
|
|
23
|
+
export declare function summarizeVxl(bytes: Uint8Array): VxlSummary | null;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The metadata a `.vxl` file carries in its own header, read here so `bitmagic assets add x.vxl`
|
|
3
|
+
* can record `boundingBox` / `voxelSize` / `voxelCount` the way the Creator's upload does — the
|
|
4
|
+
* Creator gets them from game-play-agent's `/api/analyze-asset` (`AssetsController.parseVxlV3Summary`),
|
|
5
|
+
* which a CLI project cannot reach, and this is the same read of the same bytes.
|
|
6
|
+
*
|
|
7
|
+
* Only what the fixed 48-byte body header states outright is trusted unconditionally: bounds,
|
|
8
|
+
* LOD-0 voxel size and fragment count. `voxelCount` is the sum of the LOD-0 fragments' leaf counts,
|
|
9
|
+
* found by walking the fragment table (its stride is `16 + leafCount × (7 + paletteIdxBytes)` for
|
|
10
|
+
* every version through v8 — the columnar and emissive/slot additions changed what is INSIDE a
|
|
11
|
+
* fragment and what follows the table, not its size). Should a future version change that, the
|
|
12
|
+
* walk runs off the end and the count is simply omitted rather than reported wrong.
|
|
13
|
+
*
|
|
14
|
+
* Layout reference: game/src/engine/VxlV3Format.ts (the wire-format doc comment at its top).
|
|
15
|
+
*/
|
|
16
|
+
import * as zlib from 'zlib';
|
|
17
|
+
/** "VXL3" little-endian, as `VxlV3Format.ts` writes it. */
|
|
18
|
+
const VXL3_MAGIC = 0x33_4c_58_56;
|
|
19
|
+
const WRAPPER_BYTES = 5;
|
|
20
|
+
const BODY_HEADER_BYTES = 48;
|
|
21
|
+
/**
|
|
22
|
+
* The versions the engine's own decoder accepts (`VxlV3Format.ts`: 3 no-LOD, 4 LOD trailer,
|
|
23
|
+
* 5 columnar, 6 emissive, 7 slots, 8 per-LOD material columns, 9 block types).
|
|
24
|
+
*
|
|
25
|
+
* Checked rather than merely recorded: this header parse only reads the fixed 48-byte prologue,
|
|
26
|
+
* which a FUTURE version could keep byte-compatible while changing what follows. Accepting such a
|
|
27
|
+
* file would write an `assets[]` entry the vendored engine then throws on at load — the exact
|
|
28
|
+
* outcome returning `null` exists to prevent.
|
|
29
|
+
*/
|
|
30
|
+
const MIN_SUPPORTED_VERSION = 3;
|
|
31
|
+
const MAX_SUPPORTED_VERSION = 9;
|
|
32
|
+
/** True when the first four bytes are the VXL3 magic — cheap, no copy, no decompression. */
|
|
33
|
+
export function isVxl3(bytes) {
|
|
34
|
+
if (bytes.byteLength < 4)
|
|
35
|
+
return false;
|
|
36
|
+
return new DataView(bytes.buffer, bytes.byteOffset, 4).getUint32(0, true) === VXL3_MAGIC;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Read a VXL3 file's header. Returns `null` for anything that is not a well-formed VXL3 — a legacy
|
|
40
|
+
* JSON `.vxl`, a truncated file, an unknown compression id — so the caller can name the problem
|
|
41
|
+
* instead of writing an asset entry the engine would then fail to load.
|
|
42
|
+
*/
|
|
43
|
+
export function summarizeVxl(bytes) {
|
|
44
|
+
// Only the WRAPPER is guaranteed to be on disk at full size — the body header's 48 bytes are
|
|
45
|
+
// measured after decompression below. A gzipped body compresses well under 48 bytes for a
|
|
46
|
+
// near-empty asset, and gating on the raw length here rejected such files as malformed.
|
|
47
|
+
if (!isVxl3(bytes) || bytes.byteLength < WRAPPER_BYTES)
|
|
48
|
+
return null;
|
|
49
|
+
const compressionId = bytes[4];
|
|
50
|
+
const payload = bytes.subarray(WRAPPER_BYTES);
|
|
51
|
+
let body;
|
|
52
|
+
if (compressionId === 1) {
|
|
53
|
+
try {
|
|
54
|
+
body = new Uint8Array(zlib.gunzipSync(payload));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (compressionId === 0) {
|
|
61
|
+
body = payload;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (body.byteLength < BODY_HEADER_BYTES)
|
|
67
|
+
return null;
|
|
68
|
+
const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
69
|
+
const version = view.getUint32(0, true);
|
|
70
|
+
if (version < MIN_SUPPORTED_VERSION || version > MAX_SUPPORTED_VERSION)
|
|
71
|
+
return null;
|
|
72
|
+
const minVoxelSize = view.getFloat32(4, true);
|
|
73
|
+
const boundingBox = {
|
|
74
|
+
minX: view.getFloat32(16, true),
|
|
75
|
+
minY: view.getFloat32(20, true),
|
|
76
|
+
minZ: view.getFloat32(24, true),
|
|
77
|
+
maxX: view.getFloat32(28, true),
|
|
78
|
+
maxY: view.getFloat32(32, true),
|
|
79
|
+
maxZ: view.getFloat32(36, true),
|
|
80
|
+
};
|
|
81
|
+
const paletteIdxBytes = body[41];
|
|
82
|
+
if (paletteIdxBytes !== 1 && paletteIdxBytes !== 2)
|
|
83
|
+
return null;
|
|
84
|
+
const paletteCount = view.getUint16(42, true);
|
|
85
|
+
const fragmentCount = view.getUint16(44, true);
|
|
86
|
+
const summary = { version, minVoxelSize, boundingBox, fragmentCount };
|
|
87
|
+
// Best-effort leaf total. Every fragment header is 16 bytes (leafCount + aabb) followed by
|
|
88
|
+
// leafCount records of 7 + paletteIdxBytes bytes, however those bytes are arranged inside.
|
|
89
|
+
const leafBytes = 7 + paletteIdxBytes;
|
|
90
|
+
let cursor = BODY_HEADER_BYTES + paletteCount * 2;
|
|
91
|
+
let voxelCount = 0;
|
|
92
|
+
for (let f = 0; f < fragmentCount; f++) {
|
|
93
|
+
if (cursor + 16 > body.byteLength)
|
|
94
|
+
return summary;
|
|
95
|
+
const leafCount = view.getUint32(cursor, true);
|
|
96
|
+
voxelCount += leafCount;
|
|
97
|
+
cursor += 16 + leafCount * leafBytes;
|
|
98
|
+
if (cursor > body.byteLength)
|
|
99
|
+
return summary;
|
|
100
|
+
}
|
|
101
|
+
return { ...summary, voxelCount };
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=vxl-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vxl-summary.js","sourceRoot":"","sources":["../../src/assets/vxl-summary.ts"],"names":[],"mappings":"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"}
|
package/dist/browser-gl.d.ts
CHANGED
|
@@ -43,13 +43,10 @@ export type RendererChoice = 'webgpu' | 'webgl';
|
|
|
43
43
|
/**
|
|
44
44
|
* Chrome flags for the GL backend, given the environment and platform.
|
|
45
45
|
*
|
|
46
|
-
* Software GL is
|
|
47
|
-
* Vulkan
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* Everywhere else the real GPU is requested. The Vulkan flags are Linux-only because they are only
|
|
51
|
-
* a fix there: macOS headless already reaches the GPU through ANGLE's Metal backend, and asking it
|
|
52
|
-
* for Vulkan would take that away.
|
|
46
|
+
* Software GL is forced wherever `usesSoftwareGl` holds. Everywhere else the real GPU is
|
|
47
|
+
* requested. The Vulkan flags are Linux-only because they are only a fix there: macOS headless
|
|
48
|
+
* already reaches the GPU through ANGLE's Metal backend, and asking it for Vulkan would take that
|
|
49
|
+
* away.
|
|
53
50
|
*/
|
|
54
51
|
export declare function browserGlArgs(env: Record<string, string | undefined>, platform?: Platform): string[];
|
|
55
52
|
/**
|
|
@@ -61,12 +58,53 @@ export declare function browserGlArgs(env: Record<string, string | undefined>, p
|
|
|
61
58
|
* verify exists to catch.
|
|
62
59
|
*/
|
|
63
60
|
export declare function browserRendererArgs(env: Record<string, string | undefined>, renderer: RendererChoice, platform?: Platform): string[];
|
|
61
|
+
/**
|
|
62
|
+
* How to launch Chrome for a renderer: headless or headed, and with which args.
|
|
63
|
+
*
|
|
64
|
+
* Headed exists for exactly one case — **WebGPU on Linux**, where headless Chrome cannot produce
|
|
65
|
+
* a real-GPU frame that a screenshot can see, in either compositing mode (measured on Chrome 151
|
|
66
|
+
* against an NVIDIA GPU with the Vulkan ICD installed):
|
|
67
|
+
*
|
|
68
|
+
* - With `--disable-gpu-compositing` (the WebGL fix above), WebGPU reports `enabled_readback`,
|
|
69
|
+
* Dawn reaches the native adapter and the game renders — but the presented frames never reach
|
|
70
|
+
* the compositor OR the canvas: `page.screenshot()` and an in-page `drawImage(canvas)` both
|
|
71
|
+
* come back black. Verify's black-frame detection then fails a healthy game.
|
|
72
|
+
* - Without it, GPU compositing comes up and WebGPU reports `enabled` — but Dawn's adapter
|
|
73
|
+
* request dies in the GPU process (`A valid external Instance reference no longer exists`),
|
|
74
|
+
* `requestAdapter()` resolves null, and the game silently runs its WebGL2 fallback, which the
|
|
75
|
+
* renderer probe rightly fails.
|
|
76
|
+
*
|
|
77
|
+
* Headed Chrome has neither problem: GPU compositing initialises against the display, Dawn gets
|
|
78
|
+
* the native adapter, and screenshots carry the real frame. So when webgpu is requested on Linux
|
|
79
|
+
* and a display exists, the plan is headed — with the window parked far off-screen so nothing
|
|
80
|
+
* flashes over the creator's desktop, and `--disable-backgrounding-occluded-windows` so Chrome
|
|
81
|
+
* does not throttle rendering in a window nobody can see. `--disable-gpu-compositing` is dropped
|
|
82
|
+
* in this mode: compositing is the point, and keeping the flag puts the run back in the black
|
|
83
|
+
* readback mode above.
|
|
84
|
+
*
|
|
85
|
+
* Everywhere else — webgl, macOS, CI, software GL, or a Linux box with no display (ssh, systemd,
|
|
86
|
+
* detached agents) — the plan is headless with the exact args `browserRendererArgs` always chose.
|
|
87
|
+
*/
|
|
88
|
+
export interface BrowserLaunchPlan {
|
|
89
|
+
headless: boolean;
|
|
90
|
+
args: string[];
|
|
91
|
+
}
|
|
92
|
+
export declare function browserLaunchPlan(env: Record<string, string | undefined>, renderer: RendererChoice, platform?: Platform): BrowserLaunchPlan;
|
|
93
|
+
/**
|
|
94
|
+
* The plan for the automatic software-GL re-run: `browserLaunchPlan` as if
|
|
95
|
+
* `BITMAGIC_BROWSER_SOFTWARE_GL=true` had been set.
|
|
96
|
+
*
|
|
97
|
+
* Both verify paths (one-shot and `--watch`) relaunch through this after `shouldRetrySoftwareGl`
|
|
98
|
+
* says the machine's GPU stack is the likely culprit, so neither has to know which env var forces
|
|
99
|
+
* the fallback.
|
|
100
|
+
*/
|
|
101
|
+
export declare function softwareGlLaunchPlan(env: Record<string, string | undefined>, renderer: RendererChoice, platform?: Platform): BrowserLaunchPlan;
|
|
64
102
|
/**
|
|
65
103
|
* Whether a failed run is worth one automatic re-run on software GL.
|
|
66
104
|
*
|
|
67
105
|
* A lost WebGL context is a property of the machine's GPU/driver/compositor stack, not of the
|
|
68
106
|
* game (see the file header) — the software path sidesteps that stack entirely. Pointless when
|
|
69
|
-
* software GL was already in effect, which is what
|
|
107
|
+
* software GL was already in effect, which is what `usesSoftwareGl` establishes.
|
|
70
108
|
*/
|
|
71
109
|
export declare function shouldRetrySoftwareGl(observations: {
|
|
72
110
|
webglContextLost: boolean;
|