Replace the greybox with a generated art set - #5
Conversation
The game was a greybox: every visible solid was a MeshBuilder box built from
the collision map and skinned with canvas-generated noise, there were no 3D
models at all, and the player's hands were empty. This adds a reproducible
asset pipeline and wires its output into the yard.
Pipeline (tools/art):
- Blender 4.5 Python generators for every model, run headless. Seeded, so
a rebuild is byte-identical — verified across two full builds.
- numpy PBR texture generator: 7 materials x albedo/normal/ORM at 1024,
tileable, plus an equirectangular IBL environment.
- build-assets.mjs orchestrates both and writes a provenance manifest.
- preview.py renders a hero shot per model, so assets can be reviewed
(CLAUDE.md requires previewing and validating every asset).
Models: container, tank, deck, pipe_rack, wall, hardpoint, stair, lamp_mast,
character, carbine. 10 models (1.08 MB) + 22 textures (1.91 MB) = 3.0 MB,
against a 15 MB shell budget.
Integration keeps the existing invariant that what you see is what you
collide with: props are tiled and scaled to fill the same collision volumes,
never the reverse.
Three fixes that were not cosmetic:
- IBL is mandatory, not polish. A physically-based metal is lit almost
entirely by what it reflects, so with no scene.environmentTexture every
steel, rust and grating surface rendered pure black.
- Babylon's default 4-lights-per-mesh cap silently dropped lamps depending
on where you stood; raised to 6 now that all eleven masts are lit.
- The glTF loader's __root__ node is scaled (1,1,-1) to convert
right-handed glTF into Babylon's left-handed space, which pointed the
carbine's barrel back at the player. The viewmodel corrects for it.
Also: the PWA runtime cache matched .glb but not .webp, so an offline launch
would have loaded untextured models.
Weapon viewmodel is excluded from photo mode — the vantages are environment
showcases for the marketing site, and a rifle across the lower third hides
the yard they exist to show. Marketing captures regenerated from the new
scene.
apps/game/src/assets.test.ts guards the boundary between Blender and the
engine: a renamed material slot un-skins a mesh silently, and nothing else
would catch it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
Closes #1.
The game was a greybox — every visible solid was a
MeshBuilderbox built from the collision map and skinned with runtime canvas noise, there were no 3D models, and the player's hands were empty.Pipeline (
tools/art)Everything is generated, nothing downloaded, so the whole set satisfies CLAUDE.md's provenance rule:
Seeded and byte-reproducible — verified across two full builds, all 32 artefacts identical.
What ships
10 models (container, tank, deck, pipe_rack, wall, hardpoint, stair, lamp_mast, character, carbine) and 7 PBR materials at 1024² plus an IBL environment. 3.0 MB total against a 15 MB shell budget.
Props are tiled and scaled to fill the same collision volumes, so "what you see is what you collide with" still holds.
Three fixes that weren't cosmetic
scene.environmentTextureevery steel, rust and grating surface rendered pure black.__root__is scaled(1,1,-1)to convert right-handed glTF into Babylon's left-handed space, aiming the barrel at the player.Also fixed: the PWA runtime cache matched
.glbbut not.webp, so an offline launch would have loaded untextured models.Notes
apps/game/src/assets.test.tsguards the Blender↔engine boundary — a renamed material slot un-skins a mesh silently and nothing else would catch it.188 tests pass (was 179). Format, lint and typecheck clean.
🤖 Generated with Claude Code