@bitmagic/cli 0.1.52-dev.1 → 0.1.52-dev.11
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 +217 -11
- package/dist/assets/add.d.ts +40 -0
- package/dist/assets/add.js +229 -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 +61 -0
- package/dist/assets/file-kind.js +195 -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/voxelize-glb.d.ts +37 -0
- package/dist/assets/voxelize-glb.js +65 -0
- package/dist/assets/voxelize-glb.js.map +1 -0
- package/dist/assets/vxl-summary.d.ts +32 -0
- package/dist/assets/vxl-summary.js +118 -0
- package/dist/assets/vxl-summary.js.map +1 -0
- package/dist/bin.js +25 -0
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +15 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/assets.d.ts +56 -0
- package/dist/commands/assets.js +328 -0
- package/dist/commands/assets.js.map +1 -0
- package/dist/commands/cover.js +2 -3
- package/dist/commands/cover.js.map +1 -1
- package/dist/commands/forge.d.ts +2 -9
- package/dist/commands/forge.js +2 -13
- package/dist/commands/forge.js.map +1 -1
- package/dist/commands/generate.d.ts +41 -9
- package/dist/commands/generate.js +234 -54
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/judge.d.ts +7 -7
- package/dist/commands/judge.js +53 -25
- package/dist/commands/judge.js.map +1 -1
- package/dist/commands/levels.d.ts +33 -0
- package/dist/commands/levels.js +241 -0
- package/dist/commands/levels.js.map +1 -0
- package/dist/commands/number-flag.d.ts +7 -0
- package/dist/commands/number-flag.js +38 -0
- package/dist/commands/number-flag.js.map +1 -0
- package/dist/commands/publish.d.ts +6 -1
- package/dist/commands/publish.js +42 -15
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/run-deps.d.ts +27 -0
- package/dist/commands/run-deps.js +43 -0
- package/dist/commands/run-deps.js.map +1 -0
- package/dist/commands/verify.d.ts +4 -0
- package/dist/commands/verify.js +94 -51
- package/dist/commands/verify.js.map +1 -1
- package/dist/editor/journal.d.ts +48 -1
- package/dist/editor/journal.js +44 -0
- package/dist/editor/journal.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 +228 -53
- package/dist/editor/server.js.map +1 -1
- package/dist/editor/shell-page.js +221 -116
- package/dist/editor/shell-page.js.map +1 -1
- package/dist/editor/voxel-save.d.ts +47 -0
- package/dist/editor/voxel-save.js +49 -0
- package/dist/editor/voxel-save.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +16 -0
- package/dist/errors.js.map +1 -1
- package/dist/forge/apply-modifications.d.ts +18 -1
- package/dist/forge/apply-modifications.js +15 -32
- package/dist/forge/apply-modifications.js.map +1 -1
- package/dist/forge/browser-host.js +9 -4
- package/dist/forge/browser-host.js.map +1 -1
- package/dist/forge/voxelize-session.d.ts +61 -0
- package/dist/forge/voxelize-session.js +137 -0
- package/dist/forge/voxelize-session.js.map +1 -0
- package/dist/generate/model.d.ts +36 -0
- package/dist/generate/model.js +90 -0
- package/dist/generate/model.js.map +1 -0
- package/dist/generate/prop.d.ts +52 -4
- package/dist/generate/prop.js +169 -102
- package/dist/generate/prop.js.map +1 -1
- package/dist/generate/stream.d.ts +7 -0
- package/dist/generate/stream.js +2 -1
- package/dist/generate/stream.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.js +72 -57
- package/dist/judge/facts.js.map +1 -1
- package/dist/judge/record.d.ts +3 -0
- package/dist/judge/record.js +3 -0
- package/dist/judge/record.js.map +1 -1
- package/dist/levels/registry.d.ts +88 -0
- package/dist/levels/registry.js +309 -0
- package/dist/levels/registry.js.map +1 -0
- package/dist/messages/check.d.ts +30 -0
- package/dist/messages/check.js +119 -0
- package/dist/messages/check.js.map +1 -0
- package/dist/messages/notice.d.ts +58 -0
- package/dist/messages/notice.js +54 -0
- package/dist/messages/notice.js.map +1 -0
- package/dist/messages/refresh.d.ts +19 -0
- package/dist/messages/refresh.js +42 -0
- package/dist/messages/refresh.js.map +1 -0
- package/dist/project/jobs.d.ts +1 -1
- package/dist/project/jobs.js.map +1 -1
- package/dist/project/platform-declaration.d.ts +51 -0
- package/dist/project/platform-declaration.js +81 -0
- package/dist/project/platform-declaration.js.map +1 -0
- package/dist/publish/client.d.ts +13 -0
- package/dist/publish/client.js +13 -34
- package/dist/publish/client.js.map +1 -1
- package/dist/publish/platform.d.ts +42 -0
- package/dist/publish/platform.js +58 -0
- package/dist/publish/platform.js.map +1 -0
- package/dist/scaffold/project-files.js +124 -22
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/verify/artifacts.d.ts +19 -0
- package/dist/verify/artifacts.js +34 -0
- package/dist/verify/artifacts.js.map +1 -0
- package/dist/verify/browser.d.ts +10 -0
- package/dist/verify/browser.js +79 -9
- package/dist/verify/browser.js.map +1 -1
- package/dist/verify/classify.d.ts +37 -1
- package/dist/verify/classify.js +66 -2
- package/dist/verify/classify.js.map +1 -1
- package/dist/verify/device.d.ts +43 -0
- package/dist/verify/device.js +59 -0
- package/dist/verify/device.js.map +1 -0
- package/dist/verify/iterate.d.ts +21 -2
- package/dist/verify/iterate.js +43 -36
- package/dist/verify/iterate.js.map +1 -1
- package/dist/verify/mobile-parity.d.ts +42 -0
- package/dist/verify/mobile-parity.js +84 -0
- package/dist/verify/mobile-parity.js.map +1 -0
- package/dist/verify/platform.d.ts +47 -0
- package/dist/verify/platform.js +89 -0
- package/dist/verify/platform.js.map +1 -0
- package/dist/verify/result.d.ts +90 -6
- package/dist/verify/result.js +89 -10
- package/dist/verify/result.js.map +1 -1
- package/dist/verify/url.d.ts +9 -1
- package/dist/verify/url.js +10 -2
- package/dist/verify/url.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -186,14 +186,18 @@ browser reloads itself on every change and the assets panel shows each generatio
|
|
|
186
186
|
| `bitmagic check` | Typecheck (`tsc --noEmit`) against the vendored engine. Fast; does not prove the game runs. |
|
|
187
187
|
| `bitmagic dev [--port <port>] [--editor-port <port>] [--no-open]` | Build, then serve everything and keep it running: `tsc --watch`, `vite` on 3010, and a **Game / Editor** view on 3011 that reloads itself when the project changes. Opens the view in your browser; `--no-open` (or `BITMAGIC_NO_OPEN=1`) skips that. The one command to leave open. See **The dev view** below. |
|
|
188
188
|
| `bitmagic reload [--port <port>]` | Tell a running `bitmagic dev` to reload the browser now. For agents: run it when you finish a round of edits. Exits 0 and does nothing when no dev server is running. |
|
|
189
|
-
| `bitmagic verify [--timeout <ms>] [--renderer webgpu\|webgl] [--fast] [--watch]` | Build, boot the game in a headless browser,
|
|
189
|
+
| `bitmagic verify [--timeout <ms>] [--renderer webgpu\|webgl] [--platform desktop\|mobile\|both] [--fast] [--watch]` | Build, boot the game in a headless browser, press the engine's Play button like a player would, and report what broke. A game without the button passes as long as gameplay starts by itself (auto-starting genres, custom start UIs); a run where gameplay never starts fails. The settle is adaptive: the run ends as soon as the live engine proves stable gameplay (state, rendered frames, no fresh errors, player standing), with `--timeout` as the cap. The verdict includes live engine state — actual GPU backend, fps, player position, physics body count, gameplay events (see **Gameplay events** below) — recorded in `result.json`. `--renderer` pins the pipeline (default `webgpu`, what players run; a silent WebGPU→WebGL2 fallback **fails** the run, and `--renderer webgl` is the deliberate escape hatch). A run failing on a lost WebGL context retries once on software rendering automatically. `--fast` skips the screenshot pipeline (then there is no thumbnail candidate from this run). `--watch` keeps everything warm — incremental `tsc --watch`, one vite, one browser — and re-verifies on file change in seconds; `--fast` is the watch default (`--no-fast` restores the screenshot). `--platform` picks what the run emulates — see **Verifying the mobile path** below. Every run also reports the engine's mobile-parity check: a desktop action with no touch button is a warning, and a **failure** when `game.json` declares `primaryPlatform: "mobile"`. Writes the artifacts the publish gate reads (see below). |
|
|
190
190
|
| `bitmagic build` | Bundle the game into one self-contained `.bitmagic/build/index.html`. Rarely needs to be run by hand — `publish` builds automatically when the project changed. |
|
|
191
191
|
| `bitmagic publish [--visibility public\|private] [--name <name>] [--description <desc>] [--force]` | Verify-gate, build if needed, and upload straight to GCS. **Private unless `--visibility public` is passed.** |
|
|
192
192
|
| `bitmagic upgrade [--engine <version>] [--force]` | Refresh vendored platform files and the CLI's own skills to the currently published engine. Refuses a dirty git tree unless `--force`. Also records the project's `environment` in `bitmagic.json` if it has none yet — the engine it just vendored came from that api-server — and says so when it does. |
|
|
193
|
-
| `bitmagic generate <skybox\|sound\|image\|character\|animation\|prop\|vehicle> ...` | Generate one asset directly into `src/work/world.json`. Costs sparks. `prop` upgrades an existing placeholder
|
|
193
|
+
| `bitmagic generate <skybox\|background\|block-type\|sound\|image\|character\|animation\|model\|prop\|vehicle> ...` | Generate one asset directly into `src/work/world.json`. Costs sparks. `model` mints a **new** 3D object from a prompt, `prop` upgrades an existing placeholder **in place**, `vehicle` adds a new drivable one, `animation` takes a parameter spec rather than a prompt, `block-type` registers a custom voxel block — see below. `skybox` and `background` write the **same** field: pick one. |
|
|
194
|
+
| `bitmagic assets add <file> [--name <n>] [--asset-id <id>] [--keep-glb] [--voxel-size <m>] [--max-voxel-size <m>] [--height <m>] [--hollow\|--solid] [--force]` | Upload **your own** file — `.glb`, `.vxl`, `.png/.jpg/.webp`, `.opus/.ogg/.m4a/.mp3`, `.json` — into the game's assets and write its entry into `src/work/world.json`. A `.glb` is voxelized in a headless browser (Chrome + a free port in 3000–3199, like `prop`) unless `--keep-glb`. Free — no sparks. See **Adding your own assets** below. |
|
|
195
|
+
| `bitmagic assets list` | The assets in `world.json`: id, name, type, size, placed instances, and total references. Local, no network. |
|
|
196
|
+
| `bitmagic assets remove <assetId> [--force]` | Remove an asset from `world.json` (the uploaded file stays in storage). Refuses while anything in `world.json` still references it; `--force` removes the asset and its placed instances together. |
|
|
194
197
|
| `bitmagic cover [--prompt "..."] [--force] [--no-start-screen]` | Generate the game's cover art from `GAME-DESIGN.md`: sets the start screen, saves `.bitmagic/cover.webp`, becomes the publish thumbnail. Costs sparks. |
|
|
195
|
-
| `bitmagic judge [--genre <name>] [--min-score <1-10>]` | Grade the screenshot `bitmagic verify` captured against a quality rubric built from `GAME-DESIGN.md` (plus an optional genre hint) with a vision model, and print a scorecard: four dimension scores and at most three actionable findings, also written to `.bitmagic/judge.json`. Informs rather than gates — exits 0 whatever the score, unless `--min-score` sets a bar (exit 4 below it). Warns when the project changed since the screenshot. Costs sparks (one small model call). |
|
|
198
|
+
| `bitmagic judge [--genre <name>] [--platform desktop\|mobile] [--min-score <1-10>]` | Grade the screenshot `bitmagic verify` captured against a quality rubric built from `GAME-DESIGN.md` (plus an optional genre hint) with a vision model, and print a scorecard: four dimension scores and at most three actionable findings, also written to `.bitmagic/judge.json`. Informs rather than gates — exits 0 whatever the score, unless `--min-score` sets a bar (exit 4 below it). `--platform` picks which frame to grade; with no flag it grades the mobile one when the game is mobile-primary and `screenshot.mobile.png` exists, and the mobile rubric folds touch-HUD legibility, thumb reach and phone framing into the existing `readability` and `polish` dimensions rather than adding a fifth score. Warns when the project changed since the screenshot. Costs sparks (one small model call). |
|
|
196
199
|
| `bitmagic forge --prompt "..." [--city\|--dungeon\|--platformer\|--freeform] [--name <name>] [--resume <jobId>]` | Design and bake a whole playable level into `world.json`. Costs sparks, and takes many minutes — the bake alone can take twenty. `--resume <jobId>` picks a failed run back up from the last step that finished, rather than redoing the whole thing. |
|
|
200
|
+
| `bitmagic levels <list\|add\|rename\|set-start\|remove> ...` | Manage the levels `forge` creates: which one boots, their names, registering a bake you already have, deleting one. Local, no network, no sparks. See **Managing levels** below. |
|
|
197
201
|
|
|
198
202
|
Every command exits non-zero on failure with a single-line message (no stack trace) and, where a
|
|
199
203
|
distinction is useful to an automated caller, a specific exit code — see **Exit codes** below.
|
|
@@ -317,10 +321,12 @@ my-game/
|
|
|
317
321
|
publish/
|
|
318
322
|
source.zip # the source archive each publish uploads (see Publishing)
|
|
319
323
|
verify/
|
|
320
|
-
result.json # bitmagic verify's verdict — ok, failures, warnings, fingerprint, at, renderer, settle, snapshot
|
|
324
|
+
result.json # bitmagic verify's verdict — ok, failures, warnings, fingerprint, at, platform, renderer, settle, snapshot, mobileParity (and platforms[] after a --platform both run)
|
|
321
325
|
screenshot.png # the publish thumbnail when there is no cover art (not written by --fast runs)
|
|
326
|
+
screenshot.mobile.png # the same for a --platform mobile run
|
|
322
327
|
console.log # captured browser console output
|
|
323
|
-
console.
|
|
328
|
+
console.mobile.log # the mobile run's console, kept separate so a --platform both run keeps both
|
|
329
|
+
console.retry.log # the software-GL retry's console, when a lost context forced one
|
|
324
330
|
```
|
|
325
331
|
|
|
326
332
|
### Gameplay events
|
|
@@ -334,6 +340,38 @@ spontaneous events are judged): the player dying 3+ times in the first 10 second
|
|
|
334
340
|
and the match ending within 5 seconds of starting. A vendored engine too old to know
|
|
335
341
|
`?eventlog=1` yields `events: null` and no output — run `bitmagic upgrade` to get the session.
|
|
336
342
|
|
|
343
|
+
### Verifying the mobile path
|
|
344
|
+
|
|
345
|
+
Most of what the engine does differently on a phone is decided by one branch — the touch
|
|
346
|
+
controls, a pixel-ratio clamp, a lower level-detail tier, a lighter light warmup, postFX and SSR
|
|
347
|
+
off, a rescaled HUD, fullscreen on start. `--platform mobile` runs that branch for real:
|
|
348
|
+
Playwright emulates an iPhone 14 (a phone viewport, touch input, device pixel ratio 3), the game
|
|
349
|
+
boots with the engine's own `?platform=mobile` override, and Play is **tapped** rather than
|
|
350
|
+
clicked. That is more than the web Creator's Mobile tab can do — it letterboxes an iframe and
|
|
351
|
+
moves a mouse pointer around, which never reaches the branch at all.
|
|
352
|
+
|
|
353
|
+
With no flag, the platform comes from the project: `mobile` when `src/work/game.json` declares
|
|
354
|
+
`primaryPlatform: "mobile"`, `desktop` otherwise. `--platform both` runs one after the other and
|
|
355
|
+
passes only if both do. A game whose `world.json` sets `worldProfileData.mobileOrientation:
|
|
356
|
+
"landscape"` is verified on its side.
|
|
357
|
+
|
|
358
|
+
The mobile run writes `screenshot.mobile.png`, which `bitmagic judge` grades by default for a
|
|
359
|
+
mobile-primary game and `bitmagic publish` uses as the thumbnail when there is no cover art.
|
|
360
|
+
|
|
361
|
+
**The parity gate.** The engine already checks, at every game start, whether every desktop key
|
|
362
|
+
has a touch button to match — and logs the gaps. `bitmagic verify` now reads that, on desktop
|
|
363
|
+
runs too, and names both the key and the system that registered it:
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
warning: These actions have no touch button, so phone players cannot reach them: KeyF(CombatSystem).
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
It is a **failure** rather than a warning when `game.json` says `primaryPlatform: "mobile"`: on a
|
|
370
|
+
game built for phones, an action a phone cannot reach is not a portability nicety. The fix is to
|
|
371
|
+
bind both sides in one call — `playerController.registerCustomAction({ action, desktop: { keys:
|
|
372
|
+
['KeyF'] }, mobile: { label: 'ATTACK', behavior: 'tap' } })` — never a raw `registerKeyHandler`.
|
|
373
|
+
The gaps are also in `result.json` as `mobileParity`.
|
|
374
|
+
|
|
337
375
|
## Cover art
|
|
338
376
|
|
|
339
377
|
`bitmagic cover` generates the game's cover from `GAME-DESIGN.md` — the same artwork the web
|
|
@@ -467,9 +505,15 @@ did without inferring it from a diff:
|
|
|
467
505
|
|
|
468
506
|
```
|
|
469
507
|
{"at":"…","event":"object.moved","objectId":"inst_…","position":{"from":{…},"to":{…}}}
|
|
508
|
+
{"at":"…","event":"terrain.saved","voxelUrl":"https://…/game-terrain-….vxl"}
|
|
470
509
|
{"at":"…","event":"hq.requested","assetId":"asset_…","prompt":"a mossy stone arch","command":"bitmagic generate prop …"}
|
|
471
510
|
```
|
|
472
511
|
|
|
512
|
+
Most lines are worked out by comparing `world.json` before and after a save. The sculpting ones are
|
|
513
|
+
not: a terrain save changes one URL that says nothing about what was carved, and a save that
|
|
514
|
+
**failed** changes no file at all — so the editor reports those itself rather than leaving your
|
|
515
|
+
agent to infer a thing that left no trace.
|
|
516
|
+
|
|
473
517
|
### Regenerating a placeholder
|
|
474
518
|
|
|
475
519
|
Forged levels are built from placeholder assets — box-model stand-ins. Select one and the object
|
|
@@ -493,10 +537,111 @@ carries the URL: retry with `--glb-url <url>` and you are not charged again.
|
|
|
493
537
|
Both ports must fall between 3000 and 3199. That is the window the asset CDN allows as an origin;
|
|
494
538
|
outside it the game loads with no terrain and no error.
|
|
495
539
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
540
|
+
### Sculpting the ground
|
|
541
|
+
|
|
542
|
+
Click the ground and you are in the engine's terrain tools; **Save & Exit** in their toolbar keeps
|
|
543
|
+
the work. The sculpted volume is uploaded and `src/work/world.json` gets the one line that points at
|
|
544
|
+
it, the same surgical write every other edit here makes.
|
|
545
|
+
|
|
546
|
+
That upload is the only part of `bitmagic dev` that needs an account — everything else runs offline.
|
|
547
|
+
Logged out, the save fails and **says so in the toolbar, leaving your edits where they are**: run
|
|
548
|
+
`bitmagic login`, then press Save & Exit again. Nothing is thrown away without you saying so, and
|
|
549
|
+
`terrain.saveFailed` lands in the journal either way, so your agent knows what happened.
|
|
550
|
+
|
|
551
|
+
Per-object voxel sculpting works the same way — select a voxel object, edit it, Save & Exit.
|
|
552
|
+
|
|
553
|
+
## Adding your own assets
|
|
554
|
+
|
|
555
|
+
`bitmagic assets add` is the Creator's Assets-tab upload for a project your own agent edits: a
|
|
556
|
+
Blender model, a sound library, a texture, a `.vxl` exported from the Creator or a config JSON goes
|
|
557
|
+
into storage and into `src/work/world.json` in one command. Nothing here costs sparks.
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
bitmagic assets add ./models/lantern.glb # voxelized in a headless browser → a `vxl` asset
|
|
561
|
+
bitmagic assets add ./models/lantern.glb --keep-glb # kept as a polygon mesh (`glb`), 2 m tall
|
|
562
|
+
bitmagic assets add ./models/lantern.glb --voxel-size 0.05 --height 1.2 --hollow
|
|
563
|
+
bitmagic assets add ./sfx/door-slam.mp3 --name doorSlam # audio, uploaded as-is
|
|
564
|
+
bitmagic assets add ./textures/rune.png # image; the thumbnail is the image
|
|
565
|
+
bitmagic assets add ./data/waves.json # config data for `loadJsonAsset`
|
|
566
|
+
bitmagic assets list
|
|
567
|
+
bitmagic assets remove asset_1755600000000_k3j9x1
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
What each file becomes:
|
|
571
|
+
|
|
572
|
+
| File | Asset `type` | Notes |
|
|
573
|
+
|---|---|---|
|
|
574
|
+
| `.glb` | `vxl` | Uploaded first (kept as `sourceGlbUrl`), then the engine voxelizes it: `--voxel-size` (finest, default 0.1 m), `--max-voxel-size` (flat areas, default 0.5 m), `--height` (scale to this height; default the model's own size). Interiors fill solid for prop-scale models and stay hollow above 6 m, matching `generate prop` — a solid-filled building is millions of wasted voxels. `--hollow` and `--solid` override that either way. Needs Chrome and a free port between 3000 and 3199. |
|
|
575
|
+
| `.glb --keep-glb` | `glb` | A polygon mesh, no voxelizing, no browser. `--height` sets `targetHeight` (default 2 m). |
|
|
576
|
+
| `.vxl` | `vxl` | Must be the binary VXL3 format (versions 3-9) the Creator and the engine export. Bounds, voxel size and voxel count are read from the file's own header. |
|
|
577
|
+
| `.png .jpg .jpeg .webp` | `image` | Uploaded as-is. |
|
|
578
|
+
| `.opus .ogg .m4a .mp3` | `audio` | Uploaded as-is. The engine plays all four; the Creator would transcode to `.opus` for a smaller download, and the command prints the `ffmpeg` line that does the same. |
|
|
579
|
+
| `.json` | `json` | The config-data assets `game/agent-docs/json-assets.md` describes. |
|
|
580
|
+
| `.gltf` | refused | Its buffers and textures live in files beside it, and the game loads one URL. Export a self-contained `.glb`. |
|
|
581
|
+
| `.vox .qb .fbx .spz .ply .sog .mp4` | refused | Each needs a conversion the Creator runs server-side (voxel-model import, FBX animation import, splat transcode, video faststart). Upload those through the Creator's Assets tab. |
|
|
582
|
+
|
|
583
|
+
The name defaults to the filename without its extension (`--name` overrides). The id is minted as
|
|
584
|
+
`asset_<timestamp>_<random>` unless `--asset-id` gives one — re-running with the same id **replaces**
|
|
585
|
+
the asset, which is how a `--asset-id` retry stays idempotent. A replacement **merges**: fields the
|
|
586
|
+
upload does not determine (`fitBox`, `vehicleFitment`, `collision`, `light`, …) survive it, and the
|
|
587
|
+
asset's cached thumbnail is dropped so the dev view re-renders. Replacing a still-referenced asset
|
|
588
|
+
with a *different* type is refused unless you pass `--force`, because every reference to it would
|
|
589
|
+
then resolve to something the engine cannot render. Every entry records
|
|
590
|
+
`production: { method: "uploaded", sourceUrl, sourceName }`.
|
|
591
|
+
|
|
592
|
+
Uploads are read with symlinks refused and capped at 512 MB: the destination is a permanent public
|
|
593
|
+
URL that nothing can delete, so the command will not follow a link to a file you did not mean to
|
|
594
|
+
publish.
|
|
595
|
+
|
|
596
|
+
The upload is refused before anything is sent when the project's `gameId` is not one the logged-in
|
|
597
|
+
account owns, and every failure before the write says `world.json is unchanged`. A `.glb` whose bake
|
|
598
|
+
fails after the upload prints where the GLB landed and the exact `--asset-id` to re-run with, so the
|
|
599
|
+
retry replaces that attempt instead of adding a second asset. While it runs, the upload shows in the
|
|
600
|
+
dev view's assets panel like any other job (`.bitmagic/jobs/`).
|
|
601
|
+
|
|
602
|
+
`bitmagic assets remove` edits `world.json` only — the file stays in storage, exactly as the
|
|
603
|
+
Creator's Remove leaves it (published games keep loading; every upload is immutable). It looks first
|
|
604
|
+
for anything in `world.json` still pointing at the asset — any `assetId`, `<thing>AssetId` or
|
|
605
|
+
`<thing>AssetIds` key, so placed objects, a level's `vwldAssetId`, a map, doors, key items and a
|
|
606
|
+
village's `buildingAssetIds` all count — and refuses while there is, listing where; `--force`
|
|
607
|
+
removes the asset and its placed instances in one write and reports the rest. `assets list` shows
|
|
608
|
+
the same total in its `REFS` column, so the two commands never disagree about what is in use. Game
|
|
609
|
+
code that names the id (`engine.playSound(...)`, `spawnFromAsset(...)`) is not checked, and neither
|
|
610
|
+
is a lookup by *name* — `loadJsonAsset` resolves the first asset with a matching name, so `add`
|
|
611
|
+
warns when a name is already taken.
|
|
612
|
+
|
|
613
|
+
## Generating a 3D model
|
|
614
|
+
|
|
615
|
+
`bitmagic generate model` turns a sentence into a new placeable asset: the Asset Forger builds a
|
|
616
|
+
mesh, the engine voxelizes it, and the result lands in `assets[]` under a fresh id.
|
|
617
|
+
|
|
618
|
+
```bash
|
|
619
|
+
bitmagic generate model --prompt "a mossy stone arch"
|
|
620
|
+
bitmagic generate model --prompt "a market stall" --name marketStall --height 3
|
|
621
|
+
bitmagic generate model --prompt "a cathedral" --height 30 # hollow above 6 m, automatically
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Place it from game code by name, or drag it in from the dev view's Add-object palette.
|
|
625
|
+
|
|
626
|
+
**Which of the three 3D lanes you want** matters, because picking wrong wastes a generation:
|
|
627
|
+
|
|
628
|
+
| Command | Makes | Existing instances |
|
|
629
|
+
|---|---|---|
|
|
630
|
+
| `generate model --prompt "..."` | a **new** asset from a prompt | none yet — nothing is placed |
|
|
631
|
+
| `generate prop --asset <id> --prompt "..."` | replaces the mesh of an **existing** placeholder | **all of them upgrade at once** |
|
|
632
|
+
| `assets add <file>` | a new asset from a file you already have | none yet |
|
|
633
|
+
|
|
634
|
+
Like `prop` and `vehicle` it needs Chrome and a free port between 3000 and 3199 — only the engine
|
|
635
|
+
can voxelize, so the CLI drives its own headless browser. `--voxel-size` (default 0.1 m) and
|
|
636
|
+
`--max-voxel-size` (default 0.5 m) set the bake resolution; `--height` scales the model, and
|
|
637
|
+
interiors fill solid at or below 6 m and stay hollow above it (`--hollow` / `--solid` override),
|
|
638
|
+
because a solid-filled building is millions of wasted voxels.
|
|
639
|
+
|
|
640
|
+
It costs the same sparks as generating the same object in the web editor — both bill the Asset
|
|
641
|
+
Forger call under one price, so the lane you choose never changes what a creator pays.
|
|
642
|
+
|
|
643
|
+
If the bake fails after the mesh exists, the error prints the exact
|
|
644
|
+
`--glb-url … --asset-id …` command to retry the browser step **without paying again**.
|
|
500
645
|
|
|
501
646
|
## Drivable vehicles
|
|
502
647
|
|
|
@@ -527,6 +672,45 @@ exact `--glb-url … --voxel-size …` command to retry the browser step without
|
|
|
527
672
|
If the finished asset reports that it will not drive, the project's vendored engine predates
|
|
528
673
|
vehicle-fitment derivation: run `bitmagic upgrade`, then regenerate.
|
|
529
674
|
|
|
675
|
+
## Managing levels
|
|
676
|
+
|
|
677
|
+
`bitmagic forge` bakes levels but could not manage them: only a game's *first* forge became the
|
|
678
|
+
level it boots into, so fixing a wrong start, renaming, or deleting one meant opening the web
|
|
679
|
+
Creator. These do it from here. They edit `src/work/world.json` directly — no network, no sparks,
|
|
680
|
+
no login — and journal each change to `.bitmagic/edit/events.jsonl` so your agent sees it.
|
|
681
|
+
|
|
682
|
+
```bash
|
|
683
|
+
bitmagic levels list
|
|
684
|
+
bitmagic levels set-start <levelId>
|
|
685
|
+
bitmagic levels rename <levelId> "Old Town"
|
|
686
|
+
bitmagic levels add <vwldAssetId> [--name "Cavern"] [--make-start]
|
|
687
|
+
bitmagic levels remove <levelId>
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
`list` shows each level's id, name, which one is the **START**, how many spawn points it owns and
|
|
691
|
+
how many placed objects belong to it — plus a warning when its baked `.vwld` asset has gone missing.
|
|
692
|
+
|
|
693
|
+
**`set-start` is the one that matters most.** `worldProfileData` keeps three *mirror* fields —
|
|
694
|
+
`voxelUrl`, `spawnPoints` and `playerSpawnPosition` — describing the start level, because published
|
|
695
|
+
games boot from those rather than from the level registry. This moves them together. Doing it by
|
|
696
|
+
hand and forgetting the mirrors gives you a game that renders the new level's objects and lighting
|
|
697
|
+
over the **old** level's terrain, with matching collision from a third place and no error anywhere.
|
|
698
|
+
It refuses when the level's `.vwld` asset is missing, since there would be nothing to boot.
|
|
699
|
+
|
|
700
|
+
**`add`** registers a `.vwld` that is already in the project (`bitmagic assets list` shows them) —
|
|
701
|
+
from a forge, or from `bitmagic assets add level.vwld`. It follows `forge`'s rule: a game's first
|
|
702
|
+
level becomes the start, later ones do not unless you pass `--make-start`. On a game that has a
|
|
703
|
+
world but no level registry yet, the existing world is registered as level 1 first, so it is not
|
|
704
|
+
stranded by the one you are adding.
|
|
705
|
+
|
|
706
|
+
**`remove`** takes the level's placed objects, doors and key items with it, and leaves the `.vwld`
|
|
707
|
+
asset alone — it may be large, and nothing here deletes uploaded bytes. It refuses two cases: the
|
|
708
|
+
game's only level, and the **start** level (move the start first, so you never end up with a game
|
|
709
|
+
booting one level's terrain under another's objects).
|
|
710
|
+
|
|
711
|
+
Runtime level switching is engine API — `engine.loadLevel(levelId)` from game code. These commands
|
|
712
|
+
manage the registry, not gameplay.
|
|
713
|
+
|
|
530
714
|
## Publishing
|
|
531
715
|
|
|
532
716
|
`bitmagic publish` is two gated steps in one command:
|
|
@@ -552,14 +736,36 @@ publicly again.
|
|
|
552
736
|
|
|
553
737
|
`--force` overrides a **stale or failed** verify verdict. It does **not** override a missing
|
|
554
738
|
verify record — a project with no cover art has only the verify screenshot to publish a thumbnail
|
|
555
|
-
from, so `bitmagic verify` must have produced at least one
|
|
556
|
-
before `--force` has anything to work with.
|
|
739
|
+
from, so `bitmagic verify` must have produced at least one screenshot — `screenshot.png` or
|
|
740
|
+
`screenshot.mobile.png` — ever, before `--force` has anything to work with.
|
|
557
741
|
|
|
558
742
|
`--name` and `--description` apply to that one publish call only. They are never written back to
|
|
559
743
|
`src/work/game.json`, so pass them again on every future `bitmagic publish` if you want them to
|
|
560
744
|
stick — and note that writing them into `game.json` yourself would change the fingerprint and
|
|
561
745
|
invalidate the verify you just checked.
|
|
562
746
|
|
|
747
|
+
### Which platform the game is catalogued as
|
|
748
|
+
|
|
749
|
+
Publish syncs two indicators from your project files, so bitmagic.ai lists the game as what it
|
|
750
|
+
actually is:
|
|
751
|
+
|
|
752
|
+
| Indicator | Where you set it | Values |
|
|
753
|
+
|---|---|---|
|
|
754
|
+
| `primaryPlatform` | top level of `src/work/game.json` | `desktop` (templates ship this) or `mobile` |
|
|
755
|
+
| `mobileOrientation` | `worldProfileData` in `src/work/world.json` | `portrait` or `landscape`; absent means no lock |
|
|
756
|
+
|
|
757
|
+
Those files are the **source of truth**: each publish re-sends what they say, so a value changed
|
|
758
|
+
elsewhere (an admin edit, say) is replaced by your next publish, and removing `mobileOrientation`
|
|
759
|
+
from `world.json` clears the stored lock. A project whose `game.json` declares no
|
|
760
|
+
`primaryPlatform` at all leaves the stored value alone rather than forcing it to `desktop`.
|
|
761
|
+
|
|
762
|
+
`primaryPlatform` is not only a catalogue entry. It also decides what `bitmagic verify` boots and
|
|
763
|
+
`bitmagic judge` grades by default, and it turns the mobile-parity gap from a warning into a
|
|
764
|
+
failure — see **Verifying the mobile path**. The game itself is playable from a phone either way;
|
|
765
|
+
making it *good* on a phone is code, and the scaffolded `AGENTS.md` tells your agent how: bind every
|
|
766
|
+
action with `registerCustomAction`/`declareMobileActions` so each desktop key gets a touch button,
|
|
767
|
+
and never with a raw `keydown` listener.
|
|
768
|
+
|
|
563
769
|
### The source archive
|
|
564
770
|
|
|
565
771
|
Every publish also uploads a **source archive** — `.bitmagic/publish/source.zip` — alongside the
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Environment } from '../config/environments.js';
|
|
2
|
+
import type { ProjectContext } from '../project/context.js';
|
|
3
|
+
import { type VoxelizeFlags, type VoxelizeGlb } from './voxelize-glb.js';
|
|
4
|
+
export interface AddAssetOptions {
|
|
5
|
+
context: ProjectContext;
|
|
6
|
+
environment: Environment;
|
|
7
|
+
token: string;
|
|
8
|
+
filePath: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
assetId?: string;
|
|
11
|
+
keepGlb: boolean;
|
|
12
|
+
/** Replace a still-referenced asset even when the type changes. See the gate in addAssetFile. */
|
|
13
|
+
force: boolean;
|
|
14
|
+
voxel: VoxelizeFlags;
|
|
15
|
+
fetchImpl?: typeof globalThis.fetch;
|
|
16
|
+
voxelize?: VoxelizeGlb;
|
|
17
|
+
now?: () => Date;
|
|
18
|
+
log: (message: string) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface AddAssetResult {
|
|
21
|
+
assetId: string;
|
|
22
|
+
name: string;
|
|
23
|
+
type: string;
|
|
24
|
+
url: string;
|
|
25
|
+
size: number;
|
|
26
|
+
sourceGlbUrl?: string;
|
|
27
|
+
voxelCount?: number;
|
|
28
|
+
/** True when an asset with this id already existed and was replaced. */
|
|
29
|
+
replaced: boolean;
|
|
30
|
+
/** Anything the creator should know that is not an error — e.g. that audio went up untranscoded. */
|
|
31
|
+
notes: string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Biggest file this lane will upload. Not a server limit — api-server caps nothing — but a local
|
|
35
|
+
* one: every byte is held in memory, gzipped in memory for the compressible types, and the object
|
|
36
|
+
* is immutable once written with no delete endpoint anywhere, so an accidental multi-gigabyte
|
|
37
|
+
* upload cannot be taken back.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MAX_ASSET_BYTES: number;
|
|
40
|
+
export declare function addAssetFile(options: AddAssetOptions): Promise<AddAssetResult>;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bitmagic assets add <file>` — a creator's own file into the game's asset registry.
|
|
3
|
+
*
|
|
4
|
+
* The Creator's Assets tab does this with a file picker; this is the same lane for a project the
|
|
5
|
+
* creator's agent edits: upload the bytes through api-server's signed-URL route, then write the
|
|
6
|
+
* entry into `src/work/world.json`. Nothing here costs sparks — the one slow path (a `.glb`, which
|
|
7
|
+
* the engine has to voxelize in a headless browser) is compute on this machine, not a generation.
|
|
8
|
+
*
|
|
9
|
+
* Order of operations, and why:
|
|
10
|
+
*
|
|
11
|
+
* 1. Classify the file and validate the name BEFORE reading anything, so a format this lane
|
|
12
|
+
* cannot take fails in milliseconds with world.json untouched.
|
|
13
|
+
* 2. Read world.json BEFORE uploading, so a project that is not a Bitmagic project (or whose
|
|
14
|
+
* world.json is unreadable) fails before a file has been pushed to storage for nothing.
|
|
15
|
+
* 3. Upload. From here on the bytes exist in storage whatever happens next — which is fine: every
|
|
16
|
+
* upload is immutable-cached and cheap, and the world.json write below is atomic, so a failure
|
|
17
|
+
* after this point still leaves world.json exactly as it was and says so.
|
|
18
|
+
* 4. For a `.glb` (unless `--keep-glb`): hand the uploaded URL to the engine and let it bake and
|
|
19
|
+
* upload the `.vxl` itself, exactly as `generate prop`/`vehicle` do. The engine returns the
|
|
20
|
+
* complete asset record; only its provenance is corrected to `uploaded`.
|
|
21
|
+
* 5. One `upsertRoot` on `assets[]`, matched by id — so `--asset-id` re-runs replace rather than
|
|
22
|
+
* duplicate — through the same atomic, schema-gated writer the forge uses.
|
|
23
|
+
*/
|
|
24
|
+
import * as fs from 'fs';
|
|
25
|
+
import { CliError, describeErrno, errnoCode } from '../errors.js';
|
|
26
|
+
import { applyModificationsToWorld } from '../forge/apply-modifications.js';
|
|
27
|
+
import { projectWorldJsonPath } from '../forge/run-pipeline.js';
|
|
28
|
+
import { uploadProjectFile } from '../http/signed-upload.js';
|
|
29
|
+
import { isJsonObject, readWorldJson } from '../project/world-json.js';
|
|
30
|
+
import { audioAssetEntry, glbAssetEntry, imageAssetEntry, jsonAssetEntry, voxelizedGlbAssetEntry, vxlAssetEntry, } from './entry.js';
|
|
31
|
+
import { audioTranscodeNote, classifyAssetFile, defaultAssetName, mintAssetId, uploadFilename, validateAssetName, } from './file-kind.js';
|
|
32
|
+
import { findAssetReferences } from './remove.js';
|
|
33
|
+
import { assertEngineHonouredId, voxelizeGlbIntoAsset, } from './voxelize-glb.js';
|
|
34
|
+
import { isGlb, summarizeVxl } from './vxl-summary.js';
|
|
35
|
+
import { formatSize } from './list.js';
|
|
36
|
+
/**
|
|
37
|
+
* Biggest file this lane will upload. Not a server limit — api-server caps nothing — but a local
|
|
38
|
+
* one: every byte is held in memory, gzipped in memory for the compressible types, and the object
|
|
39
|
+
* is immutable once written with no delete endpoint anywhere, so an accidental multi-gigabyte
|
|
40
|
+
* upload cannot be taken back.
|
|
41
|
+
*/
|
|
42
|
+
export const MAX_ASSET_BYTES = 512 * 1024 * 1024;
|
|
43
|
+
function readFileBytes(filePath) {
|
|
44
|
+
// Refuse a symlink rather than following it. This command's documented caller is an AI agent
|
|
45
|
+
// running with a path from a prompt or from a cloned template, and the destination is a public,
|
|
46
|
+
// immutable, undeletable URL recorded in world.json — so a link pointing at a credentials file
|
|
47
|
+
// or an .ssh key is an exfiltration with no undo. `lstat` does not follow, unlike `readFileSync`.
|
|
48
|
+
let stats;
|
|
49
|
+
try {
|
|
50
|
+
stats = fs.lstatSync(filePath);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const code = errnoCode(error);
|
|
54
|
+
if (code === 'ENOENT')
|
|
55
|
+
throw new CliError(`No such file: ${filePath}. world.json is unchanged.`);
|
|
56
|
+
throw new CliError(`Cannot read ${filePath}: ${describeErrno(error)}. world.json is unchanged.`);
|
|
57
|
+
}
|
|
58
|
+
if (stats.isSymbolicLink()) {
|
|
59
|
+
throw new CliError(`${filePath} is a symlink, and this command uploads to a permanent public URL. `
|
|
60
|
+
+ 'Pass the real file instead. world.json is unchanged.');
|
|
61
|
+
}
|
|
62
|
+
if (stats.isDirectory()) {
|
|
63
|
+
throw new CliError(`${filePath} is a directory, not a file. world.json is unchanged.`);
|
|
64
|
+
}
|
|
65
|
+
if (stats.size === 0)
|
|
66
|
+
throw new CliError(`${filePath} is empty. world.json is unchanged.`);
|
|
67
|
+
if (stats.size > MAX_ASSET_BYTES) {
|
|
68
|
+
throw new CliError(`${filePath} is ${Math.round(stats.size / (1024 * 1024))} MB; this command uploads at most `
|
|
69
|
+
+ `${MAX_ASSET_BYTES / (1024 * 1024)} MB. world.json is unchanged.`);
|
|
70
|
+
}
|
|
71
|
+
let bytes;
|
|
72
|
+
try {
|
|
73
|
+
bytes = fs.readFileSync(filePath);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// Every failure mode is mapped, so none escapes as a raw stack trace that would break this
|
|
77
|
+
// lane's "world.json is unchanged" contract (EACCES, ELOOP, EISDIR, ERR_FS_FILE_TOO_LARGE...).
|
|
78
|
+
throw new CliError(`Cannot read ${filePath}: ${describeErrno(error)}. world.json is unchanged.`);
|
|
79
|
+
}
|
|
80
|
+
return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
81
|
+
}
|
|
82
|
+
function vxlEntryBuilder(summary) {
|
|
83
|
+
return (entryBase) => vxlAssetEntry(entryBase, summary);
|
|
84
|
+
}
|
|
85
|
+
function requireVxlSummary(bytes, filename) {
|
|
86
|
+
const summary = summarizeVxl(bytes);
|
|
87
|
+
if (summary === null) {
|
|
88
|
+
throw new CliError(`${filename} is not a VXL3 file this engine can load. Export it from the Creator or the engine `
|
|
89
|
+
+ '(only the binary VXL3 format, versions 3-9, is supported). world.json is unchanged.');
|
|
90
|
+
}
|
|
91
|
+
return summary;
|
|
92
|
+
}
|
|
93
|
+
export async function addAssetFile(options) {
|
|
94
|
+
const { context, environment, token, filePath, log } = options;
|
|
95
|
+
const now = options.now ?? (() => new Date());
|
|
96
|
+
// 1. Everything that can be refused for free.
|
|
97
|
+
const file = classifyAssetFile(filePath, { keepGlb: options.keepGlb });
|
|
98
|
+
const filename = uploadFilename(filePath);
|
|
99
|
+
const name = validateAssetName(options.name ?? defaultAssetName(filePath));
|
|
100
|
+
const assetId = options.assetId ?? mintAssetId();
|
|
101
|
+
// 2. The world this is going into, before anything leaves the machine.
|
|
102
|
+
const worldPath = projectWorldJsonPath(context.root);
|
|
103
|
+
const world = readWorldJson(worldPath);
|
|
104
|
+
const notes = [];
|
|
105
|
+
const assets = Array.isArray(world.assets) ? world.assets : [];
|
|
106
|
+
const existing = assets.find((item) => isJsonObject(item) && item.id === assetId);
|
|
107
|
+
const replaced = existing !== undefined;
|
|
108
|
+
// Replacing an id is the most destructive thing this command does, and it used to be the only
|
|
109
|
+
// one with no gate at all — `assets remove` refuses while anything still points at the asset,
|
|
110
|
+
// while `add --asset-id` would happily turn a placed prop into an audio clip. Same rule here:
|
|
111
|
+
// a replacement that CHANGES THE TYPE under live references is refused unless forced, because
|
|
112
|
+
// every one of those references resolves to something the engine cannot render.
|
|
113
|
+
if (existing !== undefined && !options.force) {
|
|
114
|
+
const existingType = typeof existing.type === 'string' ? existing.type : '';
|
|
115
|
+
const references = findAssetReferences(world, assetId);
|
|
116
|
+
if (existingType !== file.assetType && references.length > 0) {
|
|
117
|
+
throw new CliError(`Asset "${assetId}" is a ${existingType} referenced ${references.length === 1 ? 'once' : `${references.length} times`} `
|
|
118
|
+
+ `in world.json, and ${filename} would replace it with ${file.assetType === 'image' ? 'an' : 'a'} `
|
|
119
|
+
+ `${file.assetType}. Those references would resolve to the wrong kind of asset. `
|
|
120
|
+
+ 'Use --force to do it anyway, or omit --asset-id to add a new asset. world.json is unchanged.');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// A name collision is not fatal, but it is silent and it matters: JSON assets are looked up BY
|
|
124
|
+
// NAME (`loadJsonAsset` -> `getAssetUrlByName`, first match wins), so a second asset sharing a
|
|
125
|
+
// name is simply never loaded. Warned rather than refused — duplicate names are legal, and the
|
|
126
|
+
// creator may well want two `rock`s they address by id.
|
|
127
|
+
const nameClash = assets.some((item) => isJsonObject(item) && item.name === name && item.id !== assetId);
|
|
128
|
+
if (nameClash) {
|
|
129
|
+
notes.push(`Another asset is already named "${name}". Assets addressed by name (JSON config assets, `
|
|
130
|
+
+ 'vehicles spawned by name) resolve to the FIRST match, so give this one a distinct --name '
|
|
131
|
+
+ 'if game code looks it up that way.');
|
|
132
|
+
}
|
|
133
|
+
// `let`, and nulled after the upload: a 300 MB Blender export otherwise stays reachable from this
|
|
134
|
+
// frame for the whole bake — ten minutes of a headless Chrome doing the voxelization on the same
|
|
135
|
+
// machine — for bytes that were consumed minutes earlier.
|
|
136
|
+
let bytes = readFileBytes(filePath);
|
|
137
|
+
const size = bytes.byteLength;
|
|
138
|
+
// A `.vxl` is inspected before the upload so a file that is not one is refused for free. The
|
|
139
|
+
// parse is carried to the switch below as a builder rather than re-parsed there — `summarizeVxl`
|
|
140
|
+
// gunzips the whole body, so doing it twice for one add is real work for no answer.
|
|
141
|
+
// Same idea as the .vxl parse below: prove the bytes are what the extension claims BEFORE the
|
|
142
|
+
// upload, so a mislabelled file costs nothing instead of an upload plus a browser launch.
|
|
143
|
+
if ((file.kind === 'glb-voxelize' || file.kind === 'glb-keep') && !isGlb(bytes)) {
|
|
144
|
+
throw new CliError(`${filename} is named .glb but does not start with the glTF magic — it is not a binary GLB. `
|
|
145
|
+
+ 'A .gltf renamed, a truncated download and a Git-LFS pointer all look like this. '
|
|
146
|
+
+ 'world.json is unchanged.');
|
|
147
|
+
}
|
|
148
|
+
const buildVxlEntry = file.kind === 'vxl'
|
|
149
|
+
? vxlEntryBuilder(requireVxlSummary(bytes, filename))
|
|
150
|
+
: null;
|
|
151
|
+
// 3. Upload.
|
|
152
|
+
log(`Uploading ${filename} (${formatSize(size)})…`);
|
|
153
|
+
const upload = await uploadProjectFile(environment, token, { gameId: context.metadata.gameId, filename, contentType: file.contentType, bytes }, { fetch: options.fetchImpl ?? globalThis.fetch });
|
|
154
|
+
bytes = null;
|
|
155
|
+
const url = upload.url;
|
|
156
|
+
log(`Uploaded: ${url}`);
|
|
157
|
+
const base = { id: assetId, name, url, size, sourceName: filename, at: now().toISOString() };
|
|
158
|
+
// 4. The record — from the engine for a voxelized GLB, built here for everything else.
|
|
159
|
+
let entry;
|
|
160
|
+
switch (file.kind) {
|
|
161
|
+
case 'image':
|
|
162
|
+
entry = imageAssetEntry(base);
|
|
163
|
+
break;
|
|
164
|
+
case 'audio': {
|
|
165
|
+
entry = audioAssetEntry(base);
|
|
166
|
+
const note = audioTranscodeNote(file.extension);
|
|
167
|
+
if (note !== null)
|
|
168
|
+
notes.push(note);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'json':
|
|
172
|
+
entry = jsonAssetEntry(base);
|
|
173
|
+
break;
|
|
174
|
+
case 'vxl':
|
|
175
|
+
// Non-null for exactly this kind, set above in the same `file.kind` test.
|
|
176
|
+
entry = buildVxlEntry(base);
|
|
177
|
+
break;
|
|
178
|
+
case 'glb-keep':
|
|
179
|
+
entry = glbAssetEntry(base, options.voxel.targetHeight);
|
|
180
|
+
break;
|
|
181
|
+
case 'glb-voxelize': {
|
|
182
|
+
// Names --asset-id explicitly. Unlike `generate prop`/`vehicle` this command has no
|
|
183
|
+
// --glb-url, so "run it again" re-uploads the file and, without a pinned id, MINTS A NEW ONE
|
|
184
|
+
// and leaves the first upload orphaned in storage (uploads are immutable and nothing deletes
|
|
185
|
+
// them). Re-running under the same id is what makes the retry idempotent.
|
|
186
|
+
const retryHint = 'Nothing was charged and world.json is unchanged. Re-run with '
|
|
187
|
+
+ `\`--asset-id ${assetId}\` so the retry replaces this attempt instead of adding a second `
|
|
188
|
+
+ `asset (the GLB from this run is already uploaded at ${url}).`;
|
|
189
|
+
const voxelize = options.voxelize ?? voxelizeGlbIntoAsset;
|
|
190
|
+
const engineAsset = await voxelize({
|
|
191
|
+
context, environment, token, name, glbUrl: url, assetId, voxel: options.voxel, retryHint,
|
|
192
|
+
requestPrefix: 'upload', log,
|
|
193
|
+
});
|
|
194
|
+
assertEngineHonouredId(engineAsset, assetId, name, `the GLB is already uploaded at ${url}`);
|
|
195
|
+
entry = voxelizedGlbAssetEntry(engineAsset, base);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// 5. One atomic write.
|
|
200
|
+
//
|
|
201
|
+
// MERGED over the existing record, not substituted for it. An asset accumulates authored state
|
|
202
|
+
// the uploader knows nothing about — `fitBox` (without which the dev view's HQ regeneration
|
|
203
|
+
// refuses outright), `vehicleFitment` (without which a vehicle stops driving), `collision`,
|
|
204
|
+
// `light`, `flattenTerrain`, `lodVariants` — and writing a fresh record over the top silently
|
|
205
|
+
// discarded all of it. `editor/server.ts` states the same rule for its own asset write: "An
|
|
206
|
+
// updater, not a replacement". The new record wins field by field, so everything this upload
|
|
207
|
+
// actually determined still lands.
|
|
208
|
+
const value = existing === undefined ? entry : { ...existing, ...entry };
|
|
209
|
+
const modification = {
|
|
210
|
+
type: 'upsertRoot',
|
|
211
|
+
path: ['assets'],
|
|
212
|
+
predicate: (item) => isJsonObject(item) && item.id === assetId,
|
|
213
|
+
value,
|
|
214
|
+
};
|
|
215
|
+
applyModificationsToWorld(worldPath, [modification], { subject: 'The uploaded asset' });
|
|
216
|
+
const finalUrl = typeof entry.url === 'string' ? entry.url : url;
|
|
217
|
+
return {
|
|
218
|
+
assetId,
|
|
219
|
+
name,
|
|
220
|
+
type: typeof entry.type === 'string' ? entry.type : file.assetType,
|
|
221
|
+
url: finalUrl,
|
|
222
|
+
size: typeof entry.size === 'number' ? entry.size : size,
|
|
223
|
+
...(typeof entry.sourceGlbUrl === 'string' ? { sourceGlbUrl: entry.sourceGlbUrl } : {}),
|
|
224
|
+
...(typeof entry.voxelCount === 'number' ? { voxelCount: entry.voxelCount } : {}),
|
|
225
|
+
replaced,
|
|
226
|
+
notes,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=add.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/assets/add.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The world.json `assets[]` record for a file a creator uploaded, kind by kind.
|
|
3
|
+
*
|
|
4
|
+
* Kept pure — inputs in, a plain object out — so the shape each kind writes is unit-tested
|
|
5
|
+
* without a project on disk, and so `add.ts` stays about the order of operations (classify, read,
|
|
6
|
+
* upload, bake, write) rather than about which fields go where.
|
|
7
|
+
*
|
|
8
|
+
* Every shape mirrors what the Creator's Assets tab writes for the same file
|
|
9
|
+
* (`useAssetsEditor.ts` `handleFileUpload`, `assetsGlbKeepImport.ts`), including `production`:
|
|
10
|
+
* `uploaded` is the one provenance only the uploading side can know — the engine's voxelize
|
|
11
|
+
* handler stamps `generated` because it sees the same GLB whether a person or a generator made it,
|
|
12
|
+
* and the Creator overwrites that too.
|
|
13
|
+
*/
|
|
14
|
+
import type { VxlSummary } from './vxl-summary.js';
|
|
15
|
+
export interface UploadedAssetBase {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
/** Where the uploaded bytes are served from. */
|
|
19
|
+
url: string;
|
|
20
|
+
/** Raw file size in bytes — what the Creator records, not the gzipped transfer size. */
|
|
21
|
+
size: number;
|
|
22
|
+
/** The file's basename, kept as provenance. */
|
|
23
|
+
sourceName: string;
|
|
24
|
+
/** ISO timestamp for `production.at`. */
|
|
25
|
+
at: string;
|
|
26
|
+
}
|
|
27
|
+
/** An image: the thumbnail IS the image, as the Creator and the image generator both record. */
|
|
28
|
+
export declare function imageAssetEntry(input: UploadedAssetBase): Record<string, unknown>;
|
|
29
|
+
export declare function audioAssetEntry(input: UploadedAssetBase): Record<string, unknown>;
|
|
30
|
+
export declare function jsonAssetEntry(input: UploadedAssetBase): Record<string, unknown>;
|
|
31
|
+
/**
|
|
32
|
+
* A `.vxl` uploaded as-is, with the metadata its own header states — the fields the Creator gets
|
|
33
|
+
* back from `/api/analyze-asset` for the same file.
|
|
34
|
+
*/
|
|
35
|
+
export declare function vxlAssetEntry(input: UploadedAssetBase, summary: VxlSummary): Record<string, unknown>;
|
|
36
|
+
/** The Creator's keep-as-GLB import: `type: 'glb'`, the mesh as its own source, 2 m tall by default. */
|
|
37
|
+
export declare const DEFAULT_GLB_TARGET_HEIGHT = 2;
|
|
38
|
+
export declare function glbAssetEntry(input: UploadedAssetBase, targetHeight: number | undefined): Record<string, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* The record the engine returned from `CREATE_ASSET_FROM_GLB_URL`, re-stamped as an upload. The
|
|
41
|
+
* engine's own `production` says `generated` with the GLB as `sourceUrl`; the method is corrected,
|
|
42
|
+
* the source URL kept (it is the uploaded GLB), and the creator's filename added.
|
|
43
|
+
*/
|
|
44
|
+
export declare function voxelizedGlbAssetEntry(engineAsset: Record<string, unknown>, input: Pick<UploadedAssetBase, 'url' | 'sourceName' | 'at'>): Record<string, unknown>;
|