@bitmagic/cli 0.1.64-dev.0 → 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.
Files changed (155) hide show
  1. package/README.md +80 -5
  2. package/assets/blender-level/README.md +25 -0
  3. package/assets/blender-level/audit.py +232 -0
  4. package/assets/blender-level/bmlevel/__init__.py +294 -0
  5. package/assets/blender-level/bmlevel/core.py +438 -0
  6. package/assets/blender-level/bmlevel/scene_bpy.py +186 -0
  7. package/assets/blender-level/bmlevel/scene_null.py +44 -0
  8. package/assets/blender-level/example_level.py +80 -0
  9. package/assets/blender-level/find_blender.py +62 -0
  10. package/assets/blender-level/review.py +117 -0
  11. package/assets/blender-level/sync_world.py +85 -0
  12. package/assets/blender-level/tests/test_core.py +189 -0
  13. package/dist/assets/keep-glb.d.ts +20 -0
  14. package/dist/assets/keep-glb.js +45 -0
  15. package/dist/assets/keep-glb.js.map +1 -0
  16. package/dist/cli.d.ts +5 -0
  17. package/dist/cli.js +2 -0
  18. package/dist/cli.js.map +1 -1
  19. package/dist/commands/assets.d.ts +3 -0
  20. package/dist/commands/assets.js +33 -3
  21. package/dist/commands/assets.js.map +1 -1
  22. package/dist/commands/cover.js +7 -1
  23. package/dist/commands/cover.js.map +1 -1
  24. package/dist/commands/forge.js +70 -11
  25. package/dist/commands/forge.js.map +1 -1
  26. package/dist/commands/generate.d.ts +93 -0
  27. package/dist/commands/generate.js +491 -18
  28. package/dist/commands/generate.js.map +1 -1
  29. package/dist/commands/init.d.ts +11 -0
  30. package/dist/commands/init.js +33 -1
  31. package/dist/commands/init.js.map +1 -1
  32. package/dist/commands/judge.d.ts +0 -11
  33. package/dist/commands/judge.js +15 -2
  34. package/dist/commands/judge.js.map +1 -1
  35. package/dist/commands/levels.d.ts +1 -1
  36. package/dist/commands/levels.js +14 -3
  37. package/dist/commands/levels.js.map +1 -1
  38. package/dist/commands/reference.js +5 -1
  39. package/dist/commands/reference.js.map +1 -1
  40. package/dist/commands/reset-account.js +2 -11
  41. package/dist/commands/reset-account.js.map +1 -1
  42. package/dist/commands/tools.d.ts +1 -0
  43. package/dist/commands/tools.js +73 -0
  44. package/dist/commands/tools.js.map +1 -0
  45. package/dist/commands/trailer.js +18 -4
  46. package/dist/commands/trailer.js.map +1 -1
  47. package/dist/editor/asset-detail-panel.d.ts +8 -3
  48. package/dist/editor/asset-detail-panel.js +11 -1
  49. package/dist/editor/asset-detail-panel.js.map +1 -1
  50. package/dist/editor/hud-replay-page.js +317 -22
  51. package/dist/editor/hud-replay-page.js.map +1 -1
  52. package/dist/editor/shell-page.js +117 -7
  53. package/dist/editor/shell-page.js.map +1 -1
  54. package/dist/forge/apply-modifications.js +70 -2
  55. package/dist/forge/apply-modifications.js.map +1 -1
  56. package/dist/forge/progress.d.ts +10 -0
  57. package/dist/forge/progress.js +7 -6
  58. package/dist/forge/progress.js.map +1 -1
  59. package/dist/forge/run-pipeline.d.ts +8 -0
  60. package/dist/forge/run-pipeline.js +37 -2
  61. package/dist/forge/run-pipeline.js.map +1 -1
  62. package/dist/forge/stream.d.ts +7 -1
  63. package/dist/forge/stream.js +5 -2
  64. package/dist/forge/stream.js.map +1 -1
  65. package/dist/forge/two-d-gate.d.ts +72 -0
  66. package/dist/forge/two-d-gate.js +198 -0
  67. package/dist/forge/two-d-gate.js.map +1 -0
  68. package/dist/generate/animation-from-video.d.ts +11 -3
  69. package/dist/generate/animation-from-video.js +12 -2
  70. package/dist/generate/animation-from-video.js.map +1 -1
  71. package/dist/generate/model.d.ts +11 -0
  72. package/dist/generate/model.js +41 -1
  73. package/dist/generate/model.js.map +1 -1
  74. package/dist/generate/prop.d.ts +9 -0
  75. package/dist/generate/prop.js +59 -9
  76. package/dist/generate/prop.js.map +1 -1
  77. package/dist/generate/video-review.d.ts +28 -0
  78. package/dist/generate/video-review.js +120 -0
  79. package/dist/generate/video-review.js.map +1 -0
  80. package/dist/generate/video.d.ts +52 -0
  81. package/dist/generate/video.js +177 -0
  82. package/dist/generate/video.js.map +1 -0
  83. package/dist/project/art-style.d.ts +34 -0
  84. package/dist/project/art-style.js +63 -0
  85. package/dist/project/art-style.js.map +1 -0
  86. package/dist/project/jobs.d.ts +1 -1
  87. package/dist/project/jobs.js.map +1 -1
  88. package/dist/prompt.d.ts +9 -0
  89. package/dist/prompt.js +36 -0
  90. package/dist/prompt.js.map +1 -0
  91. package/dist/publish/bundle.d.ts +12 -0
  92. package/dist/publish/bundle.js +32 -0
  93. package/dist/publish/bundle.js.map +1 -1
  94. package/dist/render/qr-png.d.ts +6 -3
  95. package/dist/render/qr-png.js +8 -8
  96. package/dist/render/qr-png.js.map +1 -1
  97. package/dist/scaffold/blender-level-skill-files.d.ts +26 -0
  98. package/dist/scaffold/blender-level-skill-files.js +570 -0
  99. package/dist/scaffold/blender-level-skill-files.js.map +1 -0
  100. package/dist/scaffold/claude-settings.js +2 -0
  101. package/dist/scaffold/claude-settings.js.map +1 -1
  102. package/dist/scaffold/project-files.d.ts +2 -1
  103. package/dist/scaffold/project-files.js +135 -43
  104. package/dist/scaffold/project-files.js.map +1 -1
  105. package/dist/scaffold/project.d.ts +9 -0
  106. package/dist/scaffold/project.js +29 -3
  107. package/dist/scaffold/project.js.map +1 -1
  108. package/dist/scaffold/reference-guidance.d.ts +1 -1
  109. package/dist/scaffold/reference-guidance.js +2 -1
  110. package/dist/scaffold/reference-guidance.js.map +1 -1
  111. package/dist/scaffold/skill-files.js +3 -1
  112. package/dist/scaffold/skill-files.js.map +1 -1
  113. package/dist/scaffold/unity-skill-files.js +1 -2
  114. package/dist/scaffold/unity-skill-files.js.map +1 -1
  115. package/dist/tools/bundles.d.ts +21 -0
  116. package/dist/tools/bundles.js +67 -0
  117. package/dist/tools/bundles.js.map +1 -0
  118. package/dist/trailer/ffmpeg.d.ts +10 -0
  119. package/dist/trailer/ffmpeg.js +51 -6
  120. package/dist/trailer/ffmpeg.js.map +1 -1
  121. package/dist/trailer/hud-raster.d.ts +12 -12
  122. package/dist/trailer/hud-raster.js +85 -32
  123. package/dist/trailer/hud-raster.js.map +1 -1
  124. package/dist/trailer/hud-state.d.ts +75 -4
  125. package/dist/trailer/hud-state.js +132 -17
  126. package/dist/trailer/hud-state.js.map +1 -1
  127. package/dist/trailer/io.d.ts +2 -0
  128. package/dist/trailer/io.js +6 -1
  129. package/dist/trailer/io.js.map +1 -1
  130. package/dist/trailer/link.d.ts +1 -0
  131. package/dist/trailer/link.js +15 -0
  132. package/dist/trailer/link.js.map +1 -0
  133. package/dist/trailer/make.d.ts +2 -0
  134. package/dist/trailer/make.js +1 -0
  135. package/dist/trailer/make.js.map +1 -1
  136. package/dist/trailer/merge.js +32 -11
  137. package/dist/trailer/merge.js.map +1 -1
  138. package/dist/trailer/music-beats.js +4 -39
  139. package/dist/trailer/music-beats.js.map +1 -1
  140. package/dist/trailer/render.d.ts +2 -0
  141. package/dist/trailer/render.js +94 -48
  142. package/dist/trailer/render.js.map +1 -1
  143. package/dist/trailer/timeline-types.d.ts +19 -0
  144. package/dist/trailer/timeline-types.js +42 -2
  145. package/dist/trailer/timeline-types.js.map +1 -1
  146. package/dist/trailer/title-raster.js +2 -1
  147. package/dist/trailer/title-raster.js.map +1 -1
  148. package/dist/trailer/titles.js +6 -1
  149. package/dist/trailer/titles.js.map +1 -1
  150. package/dist/trailer/video-chain.d.ts +96 -8
  151. package/dist/trailer/video-chain.js +183 -0
  152. package/dist/trailer/video-chain.js.map +1 -1
  153. package/dist/verify/browser.js +4 -1
  154. package/dist/verify/browser.js.map +1 -1
  155. 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;