@bitmagic/cli 0.1.64-dev.1 → 0.1.64-dev.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -5
- package/assets/blender-level/README.md +25 -0
- package/assets/blender-level/audit.py +232 -0
- package/assets/blender-level/bmlevel/__init__.py +294 -0
- package/assets/blender-level/bmlevel/core.py +438 -0
- package/assets/blender-level/bmlevel/scene_bpy.py +186 -0
- package/assets/blender-level/bmlevel/scene_null.py +44 -0
- package/assets/blender-level/example_level.py +80 -0
- package/assets/blender-level/find_blender.py +62 -0
- package/assets/blender-level/review.py +117 -0
- package/assets/blender-level/sync_world.py +85 -0
- package/assets/blender-level/tests/test_core.py +189 -0
- package/dist/assets/keep-glb.d.ts +20 -0
- package/dist/assets/keep-glb.js +45 -0
- package/dist/assets/keep-glb.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/assets.d.ts +3 -0
- package/dist/commands/assets.js +33 -3
- package/dist/commands/assets.js.map +1 -1
- package/dist/commands/cover.js +7 -1
- package/dist/commands/cover.js.map +1 -1
- package/dist/commands/forge.js +70 -11
- package/dist/commands/forge.js.map +1 -1
- package/dist/commands/generate.d.ts +88 -1
- package/dist/commands/generate.js +400 -21
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.js +33 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/judge.d.ts +0 -11
- package/dist/commands/judge.js +15 -2
- package/dist/commands/judge.js.map +1 -1
- package/dist/commands/levels.d.ts +1 -1
- package/dist/commands/levels.js +14 -3
- package/dist/commands/levels.js.map +1 -1
- package/dist/commands/reference.js +5 -1
- package/dist/commands/reference.js.map +1 -1
- package/dist/commands/reset-account.js +2 -11
- package/dist/commands/reset-account.js.map +1 -1
- package/dist/commands/tools.d.ts +1 -0
- package/dist/commands/tools.js +73 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/trailer.js +18 -4
- package/dist/commands/trailer.js.map +1 -1
- package/dist/editor/asset-detail-panel.d.ts +8 -3
- package/dist/editor/asset-detail-panel.js +11 -1
- package/dist/editor/asset-detail-panel.js.map +1 -1
- package/dist/editor/hud-replay-page.js +317 -22
- package/dist/editor/hud-replay-page.js.map +1 -1
- package/dist/editor/shell-page.js +117 -7
- package/dist/editor/shell-page.js.map +1 -1
- package/dist/forge/apply-modifications.js +70 -2
- package/dist/forge/apply-modifications.js.map +1 -1
- package/dist/forge/progress.d.ts +10 -0
- package/dist/forge/progress.js +7 -6
- package/dist/forge/progress.js.map +1 -1
- package/dist/forge/run-pipeline.d.ts +8 -0
- package/dist/forge/run-pipeline.js +37 -2
- package/dist/forge/run-pipeline.js.map +1 -1
- package/dist/forge/stream.d.ts +7 -1
- package/dist/forge/stream.js +5 -2
- package/dist/forge/stream.js.map +1 -1
- package/dist/forge/two-d-gate.d.ts +72 -0
- package/dist/forge/two-d-gate.js +198 -0
- package/dist/forge/two-d-gate.js.map +1 -0
- package/dist/generate/animation-from-video.d.ts +11 -3
- package/dist/generate/animation-from-video.js +12 -2
- package/dist/generate/animation-from-video.js.map +1 -1
- package/dist/generate/model.d.ts +11 -0
- package/dist/generate/model.js +41 -1
- package/dist/generate/model.js.map +1 -1
- package/dist/generate/prop.d.ts +9 -0
- package/dist/generate/prop.js +59 -9
- package/dist/generate/prop.js.map +1 -1
- package/dist/generate/video-review.d.ts +28 -0
- package/dist/generate/video-review.js +120 -0
- package/dist/generate/video-review.js.map +1 -0
- package/dist/project/art-style.d.ts +34 -0
- package/dist/project/art-style.js +63 -0
- package/dist/project/art-style.js.map +1 -0
- package/dist/prompt.d.ts +9 -0
- package/dist/prompt.js +36 -0
- package/dist/prompt.js.map +1 -0
- package/dist/publish/bundle.d.ts +12 -0
- package/dist/publish/bundle.js +32 -0
- package/dist/publish/bundle.js.map +1 -1
- package/dist/render/qr-png.d.ts +6 -3
- package/dist/render/qr-png.js +8 -8
- package/dist/render/qr-png.js.map +1 -1
- package/dist/scaffold/blender-level-skill-files.d.ts +26 -0
- package/dist/scaffold/blender-level-skill-files.js +570 -0
- package/dist/scaffold/blender-level-skill-files.js.map +1 -0
- package/dist/scaffold/claude-settings.js +2 -0
- package/dist/scaffold/claude-settings.js.map +1 -1
- package/dist/scaffold/project-files.d.ts +2 -1
- package/dist/scaffold/project-files.js +125 -42
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.d.ts +9 -0
- package/dist/scaffold/project.js +29 -3
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scaffold/reference-guidance.d.ts +1 -1
- package/dist/scaffold/reference-guidance.js +2 -1
- package/dist/scaffold/reference-guidance.js.map +1 -1
- package/dist/scaffold/skill-files.js +3 -1
- package/dist/scaffold/skill-files.js.map +1 -1
- package/dist/scaffold/unity-skill-files.js +1 -2
- package/dist/scaffold/unity-skill-files.js.map +1 -1
- package/dist/tools/bundles.d.ts +21 -0
- package/dist/tools/bundles.js +67 -0
- package/dist/tools/bundles.js.map +1 -0
- package/dist/trailer/ffmpeg.d.ts +10 -0
- package/dist/trailer/ffmpeg.js +51 -6
- package/dist/trailer/ffmpeg.js.map +1 -1
- package/dist/trailer/hud-raster.d.ts +12 -12
- package/dist/trailer/hud-raster.js +85 -32
- package/dist/trailer/hud-raster.js.map +1 -1
- package/dist/trailer/hud-state.d.ts +75 -4
- package/dist/trailer/hud-state.js +132 -17
- package/dist/trailer/hud-state.js.map +1 -1
- package/dist/trailer/io.d.ts +2 -0
- package/dist/trailer/io.js +6 -1
- package/dist/trailer/io.js.map +1 -1
- package/dist/trailer/link.d.ts +1 -0
- package/dist/trailer/link.js +15 -0
- package/dist/trailer/link.js.map +1 -0
- package/dist/trailer/make.d.ts +2 -0
- package/dist/trailer/make.js +1 -0
- package/dist/trailer/make.js.map +1 -1
- package/dist/trailer/merge.js +32 -11
- package/dist/trailer/merge.js.map +1 -1
- package/dist/trailer/music-beats.js +4 -39
- package/dist/trailer/music-beats.js.map +1 -1
- package/dist/trailer/render.d.ts +2 -0
- package/dist/trailer/render.js +94 -48
- package/dist/trailer/render.js.map +1 -1
- package/dist/trailer/timeline-types.d.ts +19 -0
- package/dist/trailer/timeline-types.js +42 -2
- package/dist/trailer/timeline-types.js.map +1 -1
- package/dist/trailer/title-raster.js +2 -1
- package/dist/trailer/title-raster.js.map +1 -1
- package/dist/trailer/titles.js +6 -1
- package/dist/trailer/titles.js.map +1 -1
- package/dist/trailer/video-chain.d.ts +96 -8
- package/dist/trailer/video-chain.js +183 -0
- package/dist/trailer/video-chain.js.map +1 -1
- package/dist/verify/browser.js +4 -1
- package/dist/verify/browser.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `building-levels-in-blender` skill: a hand-designed low-poly world written as a Python script
|
|
3
|
+
* against the shipped `bmlevel` library, built by Blender into one GLB plus the collider / door /
|
|
4
|
+
* light / landmark JSON, audited with ray casts, reviewed from rendered stills, and loaded by the
|
|
5
|
+
* engine's `MeshLevel`. Distilled from a creator's space-station game whose agent installed Blender
|
|
6
|
+
* and hand-rolled every part of this — the build vocabulary, the audits, the review renders, the
|
|
7
|
+
* level loader — and hit the two failures the skill now names up front (a GLB fetched from a local
|
|
8
|
+
* path that 404s after publish; NPC spawns routed into an absent voxel grid).
|
|
9
|
+
*
|
|
10
|
+
* Same contract as `unity-skill-files.ts`: pure functions returning file contents, registered in
|
|
11
|
+
* `SHIPPED_SKILLS` (project.ts). The Python and Blender scripts ship in the package's
|
|
12
|
+
* `assets/blender-level/` (see `files` in package.json) rather than being rendered into every
|
|
13
|
+
* project; the skill's step 1 copies them into `tools/blender-level/`.
|
|
14
|
+
*
|
|
15
|
+
* The skill's first section is the gate: the path is for frontier models with Blender available,
|
|
16
|
+
* and everything else goes to `bitmagic forge`. The CLI cannot tell which model drives it, so the
|
|
17
|
+
* gate is the agent's own call — stated once, plainly, where the planning skill sends it.
|
|
18
|
+
*
|
|
19
|
+
* Backticks and `${` are escaped for the template literal; `project-files.test.ts` checks the
|
|
20
|
+
* rendered frontmatter and that no escape leaks into the output.
|
|
21
|
+
*/
|
|
22
|
+
export function renderBlenderLevelsSkill() {
|
|
23
|
+
return `---
|
|
24
|
+
name: building-levels-in-blender
|
|
25
|
+
description: Build a hand-designed low-poly 3D world — an interior, a space station, a house and its garden, a stylised set — in Blender from a short Python script instead of voxel terrain, and load it through the engine's MeshLevel: one GLB plus the collider, door, light and landmark JSON, audited with ray casts and reviewed from rendered stills before the game ever runs it. Use it when a creator asks for "low poly", "stylised 3D", a "hand-built level", an enclosed interior, "not voxels", or "in Blender" — and when such a level has walls you fall through, doors that do not open, z-fighting seams, or shows an empty world after publish.
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# Building a level in Blender
|
|
29
|
+
|
|
30
|
+
A mesh level is one GLB that IS the whole world, plus a JSON that says what the art cannot: which
|
|
31
|
+
surfaces are solid, where the rooms, doors, lights and named points are. You write the level as a
|
|
32
|
+
Python script against \`bmlevel\` (game coordinates: metres, Y up, +Z forward), Blender builds it,
|
|
33
|
+
\`audit.py\` fires thousands of rays at the colliders, \`review.py\` renders every door and room, and
|
|
34
|
+
the engine's \`MeshLevel\` loads the result in six lines (\`engine/agent-docs/mesh-level.md\`). The
|
|
35
|
+
level that taught this skill was a five-module space station; every rule below was paid for there.
|
|
36
|
+
|
|
37
|
+
## 0. Is this path yours?
|
|
38
|
+
|
|
39
|
+
Take it only when BOTH hold:
|
|
40
|
+
|
|
41
|
+
- **You are a frontier model** — Claude Fable / Opus 5, GPT-6 class. Building a level part by part
|
|
42
|
+
in code, reading a contact sheet and diagnosing a failed ray audit is sustained spatial reasoning
|
|
43
|
+
over hundreds of lines; a smaller model produces a plausible script and a level with holes in it.
|
|
44
|
+
If you are not sure you are one, you are not.
|
|
45
|
+
- **Blender is installed or installable here.** Run the probe below. If it cannot be installed
|
|
46
|
+
(no admin rights, a sandbox), stop.
|
|
47
|
+
|
|
48
|
+
Otherwise use \`bitmagic forge\` (AGENTS.md, "Forging a whole level") — it builds a real level from a
|
|
49
|
+
prompt with no tooling beyond Chrome. The forge is also the right answer for natural terrain of
|
|
50
|
+
any kind; this path is for places that are designed, not grown.
|
|
51
|
+
|
|
52
|
+
**Declare the medium first.** A Blender level is a low-poly game, and the rest of the toolchain
|
|
53
|
+
assumes voxels until told otherwise: put \`"artStyle": "low-poly"\` at the top level of
|
|
54
|
+
\`src/work/game.json\` (or scaffold with \`bitmagic init --art-style low-poly\`). That one field
|
|
55
|
+
switches the cover and reference prompts to low-poly, makes \`assets add\` and \`generate model|prop\`
|
|
56
|
+
keep meshes instead of voxelizing, makes \`generate character\` keep the textured rig, drops the
|
|
57
|
+
engine's voxel lighting/bloom presets, and gives \`bitmagic judge\` the medium when
|
|
58
|
+
\`## Art direction\` is missing. Set it before the first cover, or the cover is a picture of cubes.
|
|
59
|
+
|
|
60
|
+
\`\`\`
|
|
61
|
+
python3 tools/blender-level/find_blender.py --check # prints "Blender 4.x at <path>", or how to install it
|
|
62
|
+
export BLENDER="$(python3 tools/blender-level/find_blender.py)"
|
|
63
|
+
\`\`\`
|
|
64
|
+
|
|
65
|
+
Install if needed: macOS \`brew install --cask blender\`, Debian/Ubuntu \`sudo apt install blender\`,
|
|
66
|
+
Windows \`winget install BlenderFoundation.Blender\`. Blender 4.2 or newer.
|
|
67
|
+
|
|
68
|
+
## 1. Install the library into the project
|
|
69
|
+
|
|
70
|
+
The scripts ship inside the Bitmagic CLI package; the CLI copies them for you (do not go looking
|
|
71
|
+
for the package on disk — under pnpm it is a shim, and the store is not where they live):
|
|
72
|
+
|
|
73
|
+
\`\`\`
|
|
74
|
+
bitmagic tools install blender-level && mkdir -p tools/level build
|
|
75
|
+
grep -qx 'build/' .gitignore || echo 'build/' >> .gitignore
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
\`tools/blender-level/README.md\` lists what each file does. Level scripts go in \`tools/level/\`,
|
|
79
|
+
outputs in \`build/level/\`.
|
|
80
|
+
|
|
81
|
+
## 2. Plan the place in words, then as a table
|
|
82
|
+
|
|
83
|
+
Write the \`## Art direction\` of \`GAME-DESIGN.md\` first — \`bitmagic judge\` scores against it, and a
|
|
84
|
+
reference image (\`bitmagic reference\`) keeps every pass aimed at the same look. Then list, before
|
|
85
|
+
any code: every **room** with its size in metres, every **door** with which two rooms it joins,
|
|
86
|
+
every **route** a player must be able to walk, the **spawn**, and the named points the game needs
|
|
87
|
+
(extraction pad, guard posts, an altar). That table becomes \`volume()\`, \`door()\`, \`link()\`,
|
|
88
|
+
\`spawn()\` and \`landmark()\` calls — and the audit checks exactly those, so a room you did not
|
|
89
|
+
declare is a room nothing checks.
|
|
90
|
+
|
|
91
|
+
## 3. Write \`tools/level/<name>.py\`
|
|
92
|
+
|
|
93
|
+
Start from \`tools/blender-level/example_level.py\` — a two-room hut, porch, stairs and courtyard
|
|
94
|
+
using every helper. The vocabulary:
|
|
95
|
+
|
|
96
|
+
\`\`\`python
|
|
97
|
+
import os, sys
|
|
98
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'blender-level'))
|
|
99
|
+
from bmlevel import *
|
|
100
|
+
|
|
101
|
+
mat('plaster', (0.82, 0.78, 0.7)) # rgb 0..1; glass* renders transparent, emissive* glows
|
|
102
|
+
with context('hall', origin=(0, 0, 0), yaw=0): # a named part with its own local frame; nests
|
|
103
|
+
slab('floor', (0, 0), (12, 8), 'plaster') # top surface at y=0
|
|
104
|
+
wall('north', (-6, -4), (6, -4), 3.5, 'plaster', opening={'at': 6, 'width': 1.6, 'height': 2.4})
|
|
105
|
+
door('north door', (0, 0, -4), yaw=0, width=1.6, height=2.4, jamb='timber')
|
|
106
|
+
stairs('up', (4, 0, 0), rise=3, run=5, material='stone', rail='brass')
|
|
107
|
+
decor('cornice', (0, 3.3, -3.85), (12, 0.2, 0.3), 'plaster') # never collides
|
|
108
|
+
prism('bay', [(-2, 4), (2, 4), (0, 6)], 0, 3.5, 'plaster') # convex outline, extruded
|
|
109
|
+
text('sign', 'HALL', (0, 2.9, -4.2), 'brass', size=0.4, both_faces=True)
|
|
110
|
+
lamp('centre', (0, 3.2, 0), '#ffd9a0', intensity=10, distance=14)
|
|
111
|
+
volume('hall', (0, 1.75, 0), (12, 3.5, 8), tags=['indoors'])
|
|
112
|
+
landmark('altar', (0, 0.1, 5), tags=['objective'])
|
|
113
|
+
spawn((0, 0.1, 2), yaw=3.14159)
|
|
114
|
+
link('hall', 'yard', door='north door')
|
|
115
|
+
export('build/level', 'temple')
|
|
116
|
+
\`\`\`
|
|
117
|
+
|
|
118
|
+
Rules that are not style:
|
|
119
|
+
|
|
120
|
+
- **Everything solid by default.** \`box()\`, \`wall()\`, \`slab()\`, \`prism()\` and \`stairs()\` become
|
|
121
|
+
colliders; \`decor()\` and \`text()\` never do. Cladding, trim, lamps, fasteners and rugs are
|
|
122
|
+
\`decor()\` — a collider on a 5 mm strip is a step the player trips on and the audit flags.
|
|
123
|
+
- **Name everything, uniquely.** Names are the \`.blend\` outliner and the audit's error messages.
|
|
124
|
+
- **Overlap, never abut.** Two slabs meeting edge to edge leave a seam a ray, and a capsule, slips
|
|
125
|
+
through; overlap them by 5 cm. Two faces on the same plane z-fight; offset by 5 cm or make the
|
|
126
|
+
outer one deeper (the door header the library builds is 4 cm deeper than its jambs for this).
|
|
127
|
+
- **Convex prisms only** — a concave outline raises; split it. Mitred junctions are two prisms.
|
|
128
|
+
- **Doors are records, not meshes.** \`door()\` writes a \`DoorDefinition\`; the engine builds and
|
|
129
|
+
animates the leaf. \`jamb=\` adds solid jambs and a header so the wall reads as a doorway.
|
|
130
|
+
|
|
131
|
+
## 4. Build
|
|
132
|
+
|
|
133
|
+
\`\`\`
|
|
134
|
+
"$BLENDER" -b --python tools/level/<name>.py
|
|
135
|
+
\`\`\`
|
|
136
|
+
|
|
137
|
+
It prints \`LEVEL … colliders … doors … volumes …\`, \`BOUNDS\`, and a \`REGISTER bitmagic assets add …\`
|
|
138
|
+
line to keep for step 7. Outputs: \`build/level/<name>.glb\` (one draw call per material),
|
|
139
|
+
\`<name>.blend\` (named, editable parts), \`<name>.mesh-level.json\`, \`<name>.doors.json\`. A syntax
|
|
140
|
+
or geometry error prints a Python traceback naming the helper and the object — fix and rebuild;
|
|
141
|
+
builds take seconds. \`python3 tools/level/<name>.py --dry-run\` gives the counts without Blender.
|
|
142
|
+
|
|
143
|
+
## 5. Render and look
|
|
144
|
+
|
|
145
|
+
\`\`\`
|
|
146
|
+
"$BLENDER" -b --python tools/blender-level/review.py -- build/level/<name>.blend build/level/<name>.mesh-level.json build/level/review/
|
|
147
|
+
\`\`\`
|
|
148
|
+
|
|
149
|
+
Read \`build/level/review/contact-sheet.png\` (the log says which frame is which), then any single
|
|
150
|
+
frame that looks wrong. You are looking for: a door with no wall around it, a room whose ceiling
|
|
151
|
+
is missing, stairs that end in the air, a sign that reads backwards, two surfaces flickering into
|
|
152
|
+
one another. Fix the script, rebuild, re-render. Do not go on to the audit while the sheet is
|
|
153
|
+
wrong; the audit finds holes, not ugliness.
|
|
154
|
+
|
|
155
|
+
## 6. Audit
|
|
156
|
+
|
|
157
|
+
\`\`\`
|
|
158
|
+
"$BLENDER" -b --python tools/blender-level/audit.py -- build/level/<name>.mesh-level.json # doors open: floors, doorways, routes
|
|
159
|
+
"$BLENDER" -b --python tools/blender-level/audit.py -- build/level/<name>.mesh-level.json --doors closed # envelopes sealed with the leaves in
|
|
160
|
+
\`\`\`
|
|
161
|
+
|
|
162
|
+
Every check prints its ray count and its failures with coordinates; exit 1 means the level is
|
|
163
|
+
not done. A failed \`floor_continuity\` is a hole a player falls through; a failed \`door_clearance\`
|
|
164
|
+
or \`link_clearance\` is a doorway or route the player cannot walk (a jamb in the opening, a table
|
|
165
|
+
on the line between two rooms, a step that ends short); a failed \`sealed_envelope\` is a gap in a
|
|
166
|
+
roof or wall. \`coplanar_overlap\` is a warning: two faces on one plane, which flicker. Never
|
|
167
|
+
answer a failure by removing the volume or the link that found it.
|
|
168
|
+
|
|
169
|
+
## 7. Register the GLB, sync world.json, write the game code
|
|
170
|
+
|
|
171
|
+
\`\`\`
|
|
172
|
+
bitmagic assets add build/level/<name>.glb --name <name> --keep-glb --asset-id level-<name> --height <native height> # the REGISTER line from step 4
|
|
173
|
+
python3 tools/blender-level/sync_world.py build/level/<name>.doors.json build/level/<name>.mesh-level.json src/work/world.json
|
|
174
|
+
cp build/level/<name>.mesh-level.json src/work/
|
|
175
|
+
\`\`\`
|
|
176
|
+
|
|
177
|
+
\`--keep-glb\` keeps it a polygon mesh and puts a CDN URL in \`assets[]\`, which is the only thing a
|
|
178
|
+
published bundle can load — a GLB fetched from \`src/work/\` works in \`bitmagic dev\` and shows an
|
|
179
|
+
empty world after \`bitmagic publish\`. \`--height\` is the level's real height: the entry's default
|
|
180
|
+
is 2 m. \`sync_world.py\` upserts the doors into \`worldProfileData.doors[]\` (creator-set keys and
|
|
181
|
+
locks survive), sets the spawn, and sets \`terrain.shape\` to \`none\` so no voxel ground is built
|
|
182
|
+
under the level. Then, in \`src/work/Game.ts\`, after the player controller exists:
|
|
183
|
+
|
|
184
|
+
\`\`\`ts
|
|
185
|
+
import { MeshLevel, DEFAULT_MESH_LEVEL_OPTIONS } from 'engine/meshlevel/MeshLevel.js';
|
|
186
|
+
import { DEFAULT_FALL_RESCUE_OPTIONS } from 'engine/FallRescue.js';
|
|
187
|
+
import levelJson from './<name>.mesh-level.json';
|
|
188
|
+
|
|
189
|
+
const level = new MeshLevel(this.engine, { glb: { assetId: 'level-<name>' }, level: { data: levelJson } }, DEFAULT_MESH_LEVEL_OPTIONS);
|
|
190
|
+
const loaded = await level.load();
|
|
191
|
+
const spawn = level.landmark('spawn');
|
|
192
|
+
this.playerController.teleportTo(spawn.position[0], spawn.position[1], spawn.position[2], spawn.yaw);
|
|
193
|
+
this.playerController.configureFallRescue({ ...DEFAULT_FALL_RESCUE_OPTIONS, killPlaneY: loaded.bounds.minY - 50 });
|
|
194
|
+
\`\`\`
|
|
195
|
+
|
|
196
|
+
\`engine/agent-docs/mesh-level.md\` has the rest: \`landmarksTagged('guard')\` for NPC posts
|
|
197
|
+
(\`npc.spawn(x, z, y)\` — pass the storey's Y for interiors), \`volumesAt(position)\` for room logic,
|
|
198
|
+
the lighting modes, and \`samples/mesh-level-setup.ts\`.
|
|
199
|
+
|
|
200
|
+
## 8. Check, verify, walk it
|
|
201
|
+
|
|
202
|
+
\`bitmagic check\`, then \`bitmagic verify\`: the console must show one \`[MeshLevel] loaded …\` line
|
|
203
|
+
with the collider and light counts you expect, and no \`404\` for the GLB. Then walk it in the
|
|
204
|
+
\`bitmagic dev\` browser — through every door, up every stair, into every room — because an audit
|
|
205
|
+
proves geometry, not that the place is good to be in. \`bitmagic verify --platform mobile\` too if
|
|
206
|
+
phones matter.
|
|
207
|
+
|
|
208
|
+
## 9. Judge, then iterate in passes
|
|
209
|
+
|
|
210
|
+
\`bitmagic judge\` scores the verify screenshot against your art direction. Improve the level in
|
|
211
|
+
passes, each one a full loop (edit script → build → review → audit → sync → verify):
|
|
212
|
+
|
|
213
|
+
1. **Structure** — rooms, doors, routes, stairs, the spawn. Audit clean, walkable.
|
|
214
|
+
2. **Detail** — cladding, casings, coffers, deck plates, signage, props. All \`decor()\` unless the
|
|
215
|
+
player should bump into it; keep every travel clearance.
|
|
216
|
+
3. **Surfaces and light** — materials, emissive strips, warm and cool lamps, a spot or two with
|
|
217
|
+
shadows. Look at it in the real game, not only in the render.
|
|
218
|
+
|
|
219
|
+
\`references/level-design-passes.md\` says what each pass contains and when to stop.
|
|
220
|
+
|
|
221
|
+
## Where the deep material is
|
|
222
|
+
|
|
223
|
+
| Read when… | File |
|
|
224
|
+
|---|---|
|
|
225
|
+
| a piece is mirrored, rotated, at the wrong height, or the \`.blend\` no longer matches the GLB | \`references/blender-conventions.md\` |
|
|
226
|
+
| an audit check fails and the fix is not obvious, or a render looks wrong | \`references/audits-and-review.md\` |
|
|
227
|
+
| the level is walkable but plain, and you want it to look like the reference | \`references/level-design-passes.md\` |
|
|
228
|
+
| registering, syncing doors, spawning NPCs, lighting, publishing, or a level that is empty in the published game | \`references/engine-handoff.md\` |
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
export function renderBlenderConventionsReference() {
|
|
232
|
+
return `# Blender conventions the library relies on
|
|
233
|
+
|
|
234
|
+
Every rule here is one the space-station level broke first and paid for in a rebuild.
|
|
235
|
+
|
|
236
|
+
## Coordinates: you write game space, the library flips once
|
|
237
|
+
|
|
238
|
+
| You write | Meaning |
|
|
239
|
+
|---|---|
|
|
240
|
+
| positions \`(x, y, z)\` | metres, **Y up, +Z forward**, in the current \`context()\` frame |
|
|
241
|
+
| \`yaw\` | radians about +Y; an entity's forward at yaw θ is \`(sin θ, 0, cos θ)\` (\`engine/agent-docs/coordinate-system.md\`) |
|
|
242
|
+
| \`size\` | full extents \`(width, height, depth)\` — the engine halves them |
|
|
243
|
+
|
|
244
|
+
Blender is Z up. \`bmlevel\` places a game point at Blender \`(x, -z, y)\`. That map is a 90° rotation
|
|
245
|
+
about X (determinant +1), not a mirror, so a yaw about game +Y is the **same angle** about Blender
|
|
246
|
+
+Z, and \`export_yup=True\` on the glTF exporter undoes the placement exactly: what the script said
|
|
247
|
+
is what the engine reads. The collider JSON is written in game coordinates untouched, which is why
|
|
248
|
+
art and physics line up without a single correction on the TypeScript side.
|
|
249
|
+
|
|
250
|
+
Do not "fix" a piece that looks mirrored by negating an axis in the script. Something else is
|
|
251
|
+
wrong — usually yaw applied to the wrong frame, or a \`context(yaw=…)\` you forgot you were inside.
|
|
252
|
+
|
|
253
|
+
## The .blend is named parts; the GLB is one mesh per material
|
|
254
|
+
|
|
255
|
+
\`export()\` saves the \`.blend\` **before** it joins anything, so every \`box()\` you named is a separate
|
|
256
|
+
object in a collection named after its \`context()\`. Then it duplicates every mesh, joins the
|
|
257
|
+
copies per material and exports only those — one draw call per material, the GLB the engine wants.
|
|
258
|
+
Consequences:
|
|
259
|
+
|
|
260
|
+
- Edit the script, not the \`.blend\`. A hand edit in Blender is lost on the next build. The \`.blend\`
|
|
261
|
+
is for LOOKING: open it when a render shows something you cannot place from the script.
|
|
262
|
+
- Keep the material count small (six to twelve). Each is a draw call; a material per object is a
|
|
263
|
+
level that renders at 20 fps on a phone.
|
|
264
|
+
- A material name is a contract with the engine: \`glass*\` renders as a thin transparent pane and
|
|
265
|
+
casts no shadow, \`emissive*\` is guaranteed to glow (\`glow=\` sets the exported emission strength;
|
|
266
|
+
the engine's bloom picks it up). Name a window material \`glass_pane\`, a light strip \`emissive_strip\`.
|
|
267
|
+
- \`alpha < 1\` exports blended transparency; the engine only treats it as glass when the name says so.
|
|
268
|
+
|
|
269
|
+
## Text without fonts
|
|
270
|
+
|
|
271
|
+
\`text()\` uses Blender's bundled font, extrudes it and converts it to a mesh, so signage needs no
|
|
272
|
+
font file and survives the per-material join. Text reads from local −Z at yaw 0, i.e. it faces a
|
|
273
|
+
player approaching along +Z. \`both_faces=True\` adds a mirrored copy for the other approach — do
|
|
274
|
+
that for any sign on a header seen from both sides; a sign that reads backwards from one side is
|
|
275
|
+
the single most common review-sheet finding.
|
|
276
|
+
|
|
277
|
+
## Solids, decoration, and the two failures in between
|
|
278
|
+
|
|
279
|
+
- \`box\`, \`wall\`, \`slab\`, \`prism\`, \`stairs\` are solid: a collider record each. \`decor\`, \`text\` are
|
|
280
|
+
not. \`glazing()\` is both — a non-solid pane you can see through and a solid collider you cannot
|
|
281
|
+
walk through.
|
|
282
|
+
- A decoration that should have been solid: the player walks through a pillar. A solid that should
|
|
283
|
+
have been decoration: a 5 mm deck plate becomes a step, \`link_clearance\` fails, and walking
|
|
284
|
+
stutters. The default is solid because the second failure is loud and the first is not.
|
|
285
|
+
- \`prism()\` colliders are convex hulls. A concave outline raises rather than silently becoming its
|
|
286
|
+
hull (which would fill the concavity with invisible wall). Split it: an L is two rectangles, a
|
|
287
|
+
mitred corridor end is a triangle plus a rectangle.
|
|
288
|
+
- \`stairs()\` makes visual treads and ONE ramp hull, so walking up is smooth; \`treads_solid=True\`
|
|
289
|
+
when the player must be able to stand on an individual step (a dropped item, an NPC post).
|
|
290
|
+
|
|
291
|
+
## Overlap, offset, depth: the three z-fighting rules
|
|
292
|
+
|
|
293
|
+
1. **Overlap, never abut.** Two slabs meeting edge to edge leave a hairline a ray falls through
|
|
294
|
+
and a capsule catches on. Overlap by 5 cm. The audit's \`floor_continuity\` finds the seam.
|
|
295
|
+
2. **Offset coplanar faces by 5 cm.** A cladding panel flush with the wall it decorates flickers.
|
|
296
|
+
Push it out 5 cm (or in 5 cm, as an inset). \`coplanar_overlap\` warns about the ones on solids.
|
|
297
|
+
3. **Give layered parts distinct depths.** Casing 0.30 m, header 0.34 m, trim 0.26 m: no two
|
|
298
|
+
layers share a face plane. The door helper does this for its own jambs and header.
|
|
299
|
+
|
|
300
|
+
Bevels (\`bevel=\`) catch light on edges and read as "made", not "boxed"; 1–5 cm on furniture and
|
|
301
|
+
casings, none on walls.
|
|
302
|
+
|
|
303
|
+
## Mitred junctions
|
|
304
|
+
|
|
305
|
+
Where a corridor meets a room at an angle, a square corridor end leaves a wedge of open air beside
|
|
306
|
+
the doorway and a rectangular wall cutout leaves a slot above it. Build both ends on the same
|
|
307
|
+
mitre plane: the corridor floor, walls and roof as \`prism()\`s whose end edge lies on the angled
|
|
308
|
+
plane, and the room wall as two \`prism()\`s flanking the same plane. The station's ten junctions were
|
|
309
|
+
rebuilt this way after a player reported "gaps and pillars" that the centre-line audit had not
|
|
310
|
+
caught — which is why \`door_clearance\` now samples the full width of every opening.
|
|
311
|
+
|
|
312
|
+
## What the exporter does, and why each flag
|
|
313
|
+
|
|
314
|
+
| Flag | Why |
|
|
315
|
+
|---|---|
|
|
316
|
+
| \`export_format='GLB'\` | one binary file for \`bitmagic assets add\` |
|
|
317
|
+
| \`use_selection=True\` | only the per-material joins, not the named parts |
|
|
318
|
+
| \`export_apply=True\` | bakes object transforms so the engine gets world-space vertices |
|
|
319
|
+
| \`export_yup=True\` | the coordinate round trip above |
|
|
320
|
+
| \`export_lights=False\`, \`export_cameras=False\` | lights come from the JSON, cameras are review-only |
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
export function renderBlenderAuditsReference() {
|
|
324
|
+
return `# Audits and review renders
|
|
325
|
+
|
|
326
|
+
The audit fires rays at the **colliders in the JSON** — the physics the engine will build — never at
|
|
327
|
+
the art, because decoration is exactly what must not be solid. The renders show the **art in the
|
|
328
|
+
.blend**, so a level can pass the audit and look wrong, or look right and fail. Do both, in that
|
|
329
|
+
order: look first (cheap, catches design errors), audit second (catches the errors you cannot see).
|
|
330
|
+
|
|
331
|
+
## The checks
|
|
332
|
+
|
|
333
|
+
| Check | Mode | What it fires | Failure means | Usual fix |
|
|
334
|
+
|---|---|---|---|---|
|
|
335
|
+
| \`floor_continuity\` | open, closed | a 0.5 m grid over every \`volume()\`, one ray down from 1 m above its floor, must hit within 1.5 m | a hole in the floor, a slab that ends short, a volume declared over open air | overlap the slabs by 5 cm; move the volume to the floor's real extent; if the volume is a stairwell, lower it or split it per storey |
|
|
336
|
+
| \`door_clearance\` | open | rays across every \`door()\` opening at three heights and every 25 cm of its width | something solid in the doorway: a jamb built into the opening, a lintel too low, a wall segment that did not stop at the opening | check the \`opening={'at': …}\` distance from the wall's START; check the door's \`yaw\` matches the wall's direction; check \`height\` against the lintel |
|
|
337
|
+
| \`link_clearance\` | open | a player capsule (radius 0.4 m, 0.5 m and 1.5 m high) marched every 0.5 m from volume A's centre to the door to volume B's centre, plus a floor probe under it | a route the player cannot walk: furniture on the line, a step that ends short of the floor, a doorway narrower than 0.8 m, no floor between the rooms | move the prop off the walk line; overlap the ramp with the landing; widen the door; add the missing floor |
|
|
338
|
+
| \`sealed_envelope\` | closed | from a 1 m grid inside every roofed volume: one ray up and four rays outward, each must hit within the volume plus 1 m | a gap in a roof or wall, a corner where two walls do not meet, a doorway with no door record (nothing to seal it) | extend the wall through the corner; add the door(); mark an open-sided space \`roofed=False\` |
|
|
339
|
+
| \`coplanar_overlap\` | both (warning) | pairs of unrotated box colliders sharing a face plane, same facing, overlapping extents | two visible faces flicker into each other | offset by 5 cm or change one depth |
|
|
340
|
+
|
|
341
|
+
"Mode" is \`--doors\` open (default) or \`closed\`: the leaves from \`<name>.doors.json\` are added as
|
|
342
|
+
solids in closed mode, which is the only mode in which a room can be sealed. Run both.
|
|
343
|
+
|
|
344
|
+
Every failure prints its first example with coordinates (game space) and \`--json out.json\` writes
|
|
345
|
+
all of them. Locate a coordinate by asking which \`context()\` it falls in and subtracting that
|
|
346
|
+
context's origin.
|
|
347
|
+
|
|
348
|
+
## Reading a failure you do not believe
|
|
349
|
+
|
|
350
|
+
- **A ray at exactly a slab's edge fails.** Rays that graze an edge miss. The fix is the same as
|
|
351
|
+
for a real seam — overlap the slabs — because the player's capsule grazes edges the same way.
|
|
352
|
+
- **\`link_clearance\` fails at the door waypoint.** The march probes for the floor from 0.5 m above
|
|
353
|
+
waist height at the door; a threshold higher than the door's floor point, or a step directly
|
|
354
|
+
under the header, trips it. Put the door's \`pos\` at the floor of its threshold.
|
|
355
|
+
- **\`sealed_envelope\` fails along a whole wall.** The wall does not reach the roof: \`wall(height=…)\`
|
|
356
|
+
is measured from \`base\`, and a ceiling slab's top is at \`y\` with its underside at \`y - thickness\`,
|
|
357
|
+
so a 3 m wall under a slab whose top is at 3.2 m and thickness 0.2 m just touches — overlap them.
|
|
358
|
+
- **\`floor_continuity\` fails everywhere in one volume.** The volume's \`pos\` is its CENTRE, not its
|
|
359
|
+
floor; \`(x, height/2, z)\` for a room whose floor is at y = 0.
|
|
360
|
+
|
|
361
|
+
Never make a failure go away by deleting the \`volume()\` or \`link()\` that found it. A level with
|
|
362
|
+
no volumes passes every check and holds no claim.
|
|
363
|
+
|
|
364
|
+
## The review set
|
|
365
|
+
|
|
366
|
+
\`review.py\` renders, from the JSON: \`overview\` (top-down, the whole extent), \`door-<id>-a\` and \`-b\`
|
|
367
|
+
(4 m back from each side of every door, eye height), \`stairs-<name>\` (from the foot, looking up),
|
|
368
|
+
\`volume-<name>\` (inside every room, from one end looking along it). Then \`contact-sheet.png\`,
|
|
369
|
+
four per row in the order the log prints.
|
|
370
|
+
|
|
371
|
+
What to look for, frame by frame:
|
|
372
|
+
|
|
373
|
+
- **overview** — the footprint matches the plan; nothing floats outside the walls; corridors meet
|
|
374
|
+
rooms with no wedge of sky beside the doorway.
|
|
375
|
+
- **doors** — a wall on both sides and a header above; the sign readable from THIS side; the floor
|
|
376
|
+
continuous through the opening; the far room visible through it (a black doorway is a room with
|
|
377
|
+
no light AND no fill — usually a missing volume light).
|
|
378
|
+
- **stairs** — treads reach the landing; the rail follows the slope; nothing pokes through a step.
|
|
379
|
+
- **volumes** — a ceiling; walls meeting at the corners; props sitting on the floor, not in it.
|
|
380
|
+
|
|
381
|
+
The renderer adds its own fill light so interiors are legible; it shows geometry, not the game's
|
|
382
|
+
lighting. Judge the lighting in \`bitmagic verify\`'s screenshot and in the dev browser.
|
|
383
|
+
|
|
384
|
+
## When a render is right and the game is wrong
|
|
385
|
+
|
|
386
|
+
The two are the same geometry only if the GLB you registered is the one you just built. After
|
|
387
|
+
every rebuild: register again (same \`--asset-id\`, the entry is replaced), re-run \`sync_world.py\`,
|
|
388
|
+
copy the JSON into \`src/work/\`, and check \`[MeshLevel] loaded\` in the verify console reports the
|
|
389
|
+
new collider count. A verify that passes on stale files is the trap the station fell into three
|
|
390
|
+
times.
|
|
391
|
+
`;
|
|
392
|
+
}
|
|
393
|
+
export function renderBlenderPassesReference() {
|
|
394
|
+
return `# Level design in passes
|
|
395
|
+
|
|
396
|
+
A level that is only walls and floors is walkable and dead. The station that taught this skill
|
|
397
|
+
went from boxes to a place people wanted to screenshot in four passes, each a full loop of edit →
|
|
398
|
+
build → review → audit → sync → verify. The passes, what goes in each, and when to stop.
|
|
399
|
+
|
|
400
|
+
## Pass 1 — structure
|
|
401
|
+
|
|
402
|
+
Rooms, corridors, doors, stairs, the spawn, every route. Nothing decorative. Stop when: the audit
|
|
403
|
+
is clean in both modes, every door opens onto a floor, every stair reaches a landing, and you have
|
|
404
|
+
walked every \`link()\` in the dev browser. Sizes that read well: rooms 6–12 m across, 3–4 m high;
|
|
405
|
+
corridors 3–4 m wide (a 2 m corridor is a pipe); doors 1.4–2 m wide and 2.2–2.6 m high; treads
|
|
406
|
+
0.28–0.32 m deep and 0.16–0.18 m high. Bigger than a house, smaller than a hangar.
|
|
407
|
+
|
|
408
|
+
## Pass 2 — detail
|
|
409
|
+
|
|
410
|
+
Everything the eye reads as "built". All \`decor()\` unless the player should bump into it.
|
|
411
|
+
|
|
412
|
+
- **Layered casings around every door**: an outer casing 5 cm proud of the wall, an inner recessed
|
|
413
|
+
band, a crown over the header. Three depths, three widths.
|
|
414
|
+
- **Cladding bands** on long walls: panels inset 5 cm every 4–6 m with a thin vertical trim between
|
|
415
|
+
them, a service channel at waist height, a coffer or beam line at the ceiling. A blank wall longer
|
|
416
|
+
than 6 m is the second most common judge finding.
|
|
417
|
+
- **Deck plates**: tile the floor slab with plates 4 × 4 m, 1.2 cm high, 6 cm apart, an occasional
|
|
418
|
+
plate in the alternate material; fasteners at the corners; a shallow grate every few plates.
|
|
419
|
+
- **Ceiling coffers** or beams on a 3–4 m rhythm, with a recessed light housing in each.
|
|
420
|
+
- **Guidance**: a floor strip in the room's accent colour running the route between doors; signs
|
|
421
|
+
on every door header, both faces; a module or room name on the wall you see on entering.
|
|
422
|
+
- **Furniture at the walls, not on the line.** The audit will tell you when a prop blocks a route,
|
|
423
|
+
but the rule is simpler: props go against walls and in corners; the middle of a room is for
|
|
424
|
+
walking and fighting.
|
|
425
|
+
- **Rails on every stair and gallery edge** (\`rail=\`), posts every third step.
|
|
426
|
+
|
|
427
|
+
Stop when: the review sheet shows no blank wall and no floating detail, and the audit is still
|
|
428
|
+
clean (detail is the pass that breaks clearances — a casing 20 cm into a doorway is a failed
|
|
429
|
+
\`door_clearance\`).
|
|
430
|
+
|
|
431
|
+
## Pass 3 — surfaces and light
|
|
432
|
+
|
|
433
|
+
- **Materials**: six to twelve. A warm neutral for walls, a dark deck, one trim metal, one accent
|
|
434
|
+
per room, one glass, one emissive. Roughness 0.4–0.7 on walls, 0.2–0.35 on deck plates so they
|
|
435
|
+
catch light; metal only on rails and fasteners.
|
|
436
|
+
- **Lights**: one warm \`lamp()\` per room near its centre at ceiling height minus 0.5 m, cool fill in
|
|
437
|
+
corridors, \`emissive_*\` strips where the eye should travel (door frames, guidance, consoles). One
|
|
438
|
+
or two \`spot()\`s with shadows over the places that matter (the spawn, the objective). Point lights
|
|
439
|
+
share a small pool in the engine — a room with eight lamps gets four of them; put the count into
|
|
440
|
+
emissive materials instead.
|
|
441
|
+
- **The engine's interior preset dims the sun for you**; a level that looks washed out in the game
|
|
442
|
+
after looking fine in the render has an \`exterior\` mode set, or its own sun in \`lightingConfig\`.
|
|
443
|
+
|
|
444
|
+
Stop when: \`bitmagic judge\` no longer names lighting or flat surfaces, and the level reads the same
|
|
445
|
+
in the verify screenshot as in the reference image.
|
|
446
|
+
|
|
447
|
+
## Pass 4 — the reference image
|
|
448
|
+
|
|
449
|
+
Only now: put the reference image beside the review sheet and the verify screenshot and list the
|
|
450
|
+
five biggest differences. Fix those five, in order. Then stop — a level is done when the next
|
|
451
|
+
change would be for you, not the player.
|
|
452
|
+
|
|
453
|
+
## What not to do
|
|
454
|
+
|
|
455
|
+
- Do not rebuild the whole script for one room; add a \`context()\` or edit one.
|
|
456
|
+
- Do not make decoration solid "to be safe". Make it \`decor()\` and keep the clearances.
|
|
457
|
+
- Do not add a light per lamp housing. Add the housing as \`emissive_*\` decor and one real light per room.
|
|
458
|
+
- Do not put the whole level in one \`context()\`. One per room or module: the \`.blend\` outliner and
|
|
459
|
+
the audit's coordinates both read in room-local metres.
|
|
460
|
+
`;
|
|
461
|
+
}
|
|
462
|
+
export function renderBlenderEngineHandoffReference() {
|
|
463
|
+
return `# Handing the level to the engine
|
|
464
|
+
|
|
465
|
+
The engine side is \`MeshLevel\` — \`engine/agent-docs/mesh-level.md\` is the full reference and
|
|
466
|
+
\`engine/agent-docs/samples/mesh-level-setup.ts\` the compile-checked sample. This page is the
|
|
467
|
+
seam: what the build wrote, what each command does with it, and the four ways it goes wrong.
|
|
468
|
+
|
|
469
|
+
## The files the build wrote
|
|
470
|
+
|
|
471
|
+
| File | Goes | Carries |
|
|
472
|
+
|---|---|---|
|
|
473
|
+
| \`build/level/<name>.glb\` | \`bitmagic assets add … --keep-glb\` → CDN, an \`assets[]\` entry with id \`level-<name>\` | the art, one mesh per material |
|
|
474
|
+
| \`build/level/<name>.mesh-level.json\` | \`cp\` into \`src/work/\`, static \`import\` in \`Game.ts\` | colliders, volumes, landmarks, lights, \`extras.links\` |
|
|
475
|
+
| \`build/level/<name>.doors.json\` | \`sync_world.py\` → \`worldProfileData.doors[]\` | \`DoorDefinition\` records the engine animates |
|
|
476
|
+
| \`build/level/<name>.blend\` | nowhere; open it to look | the named parts |
|
|
477
|
+
|
|
478
|
+
## Registering: \`--keep-glb\` and \`--height\`
|
|
479
|
+
|
|
480
|
+
\`\`\`
|
|
481
|
+
bitmagic assets add build/level/<name>.glb --name <name> --keep-glb --asset-id level-<name> --height <native height>
|
|
482
|
+
\`\`\`
|
|
483
|
+
|
|
484
|
+
- Without \`--keep-glb\` the file is voxelized into a \`.vxl\` and the level becomes a blocky prop.
|
|
485
|
+
In a project whose game.json says \`artStyle: "low-poly"\` keeping is already the default and the
|
|
486
|
+
flag is harmless; keep passing it so the line works in a project that forgot to declare.
|
|
487
|
+
|
|
488
|
+
## What \`artStyle: "low-poly"\` flips
|
|
489
|
+
|
|
490
|
+
| Where | voxel (absent) | low-poly |
|
|
491
|
+
|---|---|---|
|
|
492
|
+
| \`bitmagic cover\`, \`bitmagic reference make\` | MagicaVoxel-style prompt lead and tail | flat-shaded low-poly lead and tail; same image model |
|
|
493
|
+
| \`bitmagic assets add x.glb\` | voxelized in a headless browser | kept as a \`glb\` asset (\`--voxelize\` to bake) |
|
|
494
|
+
| \`bitmagic generate model\` / \`prop\` | voxel master baked in the browser | mesh generated, re-uploaded, registered as \`glb\` (\`--voxelize\` to bake) |
|
|
495
|
+
| \`bitmagic generate character\` | voxelized rig, library search | textured low-poly rig, no library (\`--voxelize\` to override) |
|
|
496
|
+
| \`bitmagic generate vehicle\` | voxel car | voxel car anyway — the builder is voxel-native; it says so |
|
|
497
|
+
| engine on load | sunset hemisphere light + faint bloom | neither; \`MeshLevel\`'s own lighting stands |
|
|
498
|
+
| \`bitmagic judge\` with no \`## Art direction\` | grades style as it finds it | judges within low-poly |
|
|
499
|
+
- The entry's \`targetHeight\` defaults to 2 m. \`MeshLevel\` does not scale by it, but anything else
|
|
500
|
+
that reads the asset (the Creator's asset panel, a placement) would squash a 40 m station into a
|
|
501
|
+
2 m box. The build's \`REGISTER\` line has the real number.
|
|
502
|
+
- Re-registering with the same \`--asset-id\` replaces the entry; the game code does not change.
|
|
503
|
+
- **A GLB that is not an asset is not in the published game.** \`bitmagic publish\` bundles one HTML
|
|
504
|
+
file whose assets are absolute CDN URLs; a path under \`src/work/\` or \`build/\` resolves in
|
|
505
|
+
\`bitmagic dev\` and in \`bitmagic verify\` and returns 404 from the published page, which then shows
|
|
506
|
+
the sky and a falling player. \`MeshLevel\` warns when handed a local \`{ url }\`; use \`{ assetId }\`.
|
|
507
|
+
|
|
508
|
+
## Syncing: doors, spawn, terrain
|
|
509
|
+
|
|
510
|
+
\`\`\`
|
|
511
|
+
python3 tools/blender-level/sync_world.py build/level/<name>.doors.json build/level/<name>.mesh-level.json src/work/world.json
|
|
512
|
+
\`\`\`
|
|
513
|
+
|
|
514
|
+
- **Doors** are upserted by id. The build owns \`position\`, \`rotationY\`, \`width\`, \`height\`,
|
|
515
|
+
\`thickness\`, \`animation\`; a \`kind: 'locked'\`, \`keyId\`, \`assetId\`, \`color\` or \`autoOpenRadius\` set
|
|
516
|
+
in world.json by the creator (or by you, for a key-and-lock design — \`engine/agent-docs/doors-and-locks.md\`)
|
|
517
|
+
survives a rebuild. Doors the build no longer has are listed, never deleted.
|
|
518
|
+
- **Spawn**: the \`spawn\` landmark becomes \`playerSpawnPosition\` and \`playerSpawnRotation\`. The game
|
|
519
|
+
code teleports there too (below) so the two cannot disagree.
|
|
520
|
+
- **Terrain**: \`terrain.shape\` becomes \`'none'\` — no voxel ground, no invisible safety plane. That
|
|
521
|
+
is the mode \`MeshLevel\` needs; it also means a level with no floor under the spawn is a player
|
|
522
|
+
falling for ever, which \`configureFallRescue\` turns into a respawn.
|
|
523
|
+
|
|
524
|
+
Run it after EVERY rebuild; a door moved in the script and not synced is a leaf animating in a wall.
|
|
525
|
+
|
|
526
|
+
## The game code
|
|
527
|
+
|
|
528
|
+
\`\`\`ts
|
|
529
|
+
import { MeshLevel, DEFAULT_MESH_LEVEL_OPTIONS } from 'engine/meshlevel/MeshLevel.js';
|
|
530
|
+
import { DEFAULT_FALL_RESCUE_OPTIONS } from 'engine/FallRescue.js';
|
|
531
|
+
import levelJson from './<name>.mesh-level.json';
|
|
532
|
+
|
|
533
|
+
// After setupPlayerController(), before any NPC spawns.
|
|
534
|
+
this.level = new MeshLevel(this.engine, { glb: { assetId: 'level-<name>' }, level: { data: levelJson } }, DEFAULT_MESH_LEVEL_OPTIONS);
|
|
535
|
+
const loaded = await this.level.load();
|
|
536
|
+
const spawn = this.level.landmark('spawn');
|
|
537
|
+
this.playerController.teleportTo(spawn.position[0], spawn.position[1], spawn.position[2], spawn.yaw);
|
|
538
|
+
this.playerController.configureFallRescue({ ...DEFAULT_FALL_RESCUE_OPTIONS, killPlaneY: loaded.bounds.minY - 50 });
|
|
539
|
+
|
|
540
|
+
const guards = this.engine.registerNpc('guards', new NpcIdleBehavior(), { autoRespawn: false }); // any INpcBehavior — engine/agent-docs/npc-system.md
|
|
541
|
+
for (const post of this.level.landmarksTagged('guard')) {
|
|
542
|
+
await guards.spawn(post.position[0], post.position[2], post.position[1]); // (x, z, y): Y picks the storey
|
|
543
|
+
}
|
|
544
|
+
\`\`\`
|
|
545
|
+
|
|
546
|
+
- \`landmark(name)\` throws with the known names when one is missing; a typo cannot spawn the boss
|
|
547
|
+
at the origin. \`landmarksTagged(tag)\` for posts, \`volumesAt(position)\` for "which room am I in".
|
|
548
|
+
- NPC spawns on a mesh level resolve the floor with raycasts (there is no voxel grid). Pass the
|
|
549
|
+
storey's Y for interior spawns, or an upstairs room resolves to the roof above it.
|
|
550
|
+
- \`DEFAULT_MESH_LEVEL_OPTIONS.lighting.mode\` is \`interior\`: the sun is dimmed through
|
|
551
|
+
\`lightingConfig\`, the template's hemisphere light reused, JSON point lights pooled, the first two
|
|
552
|
+
shadow-casting spots cached. An outdoor level (the courtyard game) wants
|
|
553
|
+
\`{ ...DEFAULT_MESH_LEVEL_OPTIONS, lighting: { ...DEFAULT_MESH_LEVEL_LIGHTING, mode: 'exterior' } }\`.
|
|
554
|
+
- \`dispose()\` removes bodies, lights and the scene, and restores the lighting config and the spawn
|
|
555
|
+
finder it replaced — call it before loading another level.
|
|
556
|
+
|
|
557
|
+
## Verify, and the four ways it fails silently
|
|
558
|
+
|
|
559
|
+
\`bitmagic verify\` must print \`[MeshLevel] loaded "<cdn url>" — N colliders …\` with the count the
|
|
560
|
+
build printed. Then:
|
|
561
|
+
|
|
562
|
+
1. **Verify passes, published game is empty** — the GLB was a local path (above).
|
|
563
|
+
2. **Verify passes, doors animate inside walls** — \`sync_world.py\` was not re-run after a rebuild.
|
|
564
|
+
3. **Verify passes, zero NPCs** — spawns given without the storey's Y on a multi-floor level, or
|
|
565
|
+
posts declared inside a wall; the console shows the finder returning null for each.
|
|
566
|
+
4. **Verify passes, the level is bright white** — \`lighting.mode\` is \`exterior\`, or \`lightingConfig\`
|
|
567
|
+
in world.json sets a sun the preset then merges with. The preset only lowers what it names.
|
|
568
|
+
`;
|
|
569
|
+
}
|
|
570
|
+
//# sourceMappingURL=blender-level-skill-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blender-level-skill-files.js","sourceRoot":"","sources":["../../src/scaffold/blender-level-skill-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8MR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,iCAAiC;IAC/C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyFR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmER,CAAC;AACF,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkER,CAAC;AACF,CAAC;AAED,MAAM,UAAU,mCAAmC;IACjD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyGR,CAAC;AACF,CAAC"}
|
|
@@ -70,6 +70,8 @@ const ALLOWED_COMMANDS = [
|
|
|
70
70
|
'Bash(bitmagic generate:*)',
|
|
71
71
|
'Bash(bitmagic cover:*)',
|
|
72
72
|
'Bash(bitmagic forge:*)',
|
|
73
|
+
// Copies script bundles the CLI ships into the project's tools/. Local, no network.
|
|
74
|
+
'Bash(bitmagic tools:*)',
|
|
73
75
|
];
|
|
74
76
|
/**
|
|
75
77
|
* The settings a project with no `.claude/settings.json` at all gets.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-settings.js","sourceRoot":"","sources":["../../src/scaffold/claude-settings.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"claude-settings.js","sourceRoot":"","sources":["../../src/scaffold/claude-settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ArtStyle } from '@bitmagic/asset-core/art-style';
|
|
1
2
|
import { type AgentId } from './agent-targets.js';
|
|
2
3
|
import type { EnvironmentName } from '../config/environments.js';
|
|
3
4
|
/**
|
|
@@ -215,6 +216,6 @@ export declare function renderJudgeSkill(): string;
|
|
|
215
216
|
* more to an image model than how the input mapping works — but nothing enforces them: the whole
|
|
216
217
|
* file is read as prose.
|
|
217
218
|
*/
|
|
218
|
-
export declare function renderGameDesignMd(idea?: string): string;
|
|
219
|
+
export declare function renderGameDesignMd(idea?: string, artStyle?: ArtStyle): string;
|
|
219
220
|
export declare function renderClaudeMd(): string;
|
|
220
221
|
export declare function renderThemingSkill(): string;
|