Custom GLBs drop into 14 named slots without code changes; an empty manifest
produces today's game by construction (the fallback builders are the original
code moved into a closure, and an empty registry returns the caller's own
object by identity).
- src/assets/{slots,manifest,idb,registry,blobBody}.ts — schema + validation,
GLTF cache with per-instance material cloning, fit nodes, IndexedDB override
layer, paintability report.
- Slot hooks in createBlob, parts, cannon, greybox, puddles, ghost. Mesh-only:
no collider, physics or logic line is touched. Animated sub-parts (plate cap,
belt chevrons, fan blades, cannon pivot) stay procedural so a custom model
cannot stop them moving.
- public/assets/ — the build had NO asset copy step at all, so every asset URL
would have 404'd in production. public/ is vite's default publicDir, so this
needs no vite.config change.
- Tests: 63 headless checks + a farm-GLB audit that fires PaintSkin's own
raycast against the fitted body.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ghost (src/ghost/):
- format.ts: pure, headless-testable core — int16 (cm position / milli scale)
quantization, base64 localStorage envelope (blobbo:ghost), linear sampling.
- recorder.ts: 15Hz fixed-step recorder, ~5min cap, saves quantized track on a
new best (time <= best, matching game.ts's pre-emit best update).
- player.ts: translucent (~0.35) desaturated ghost + eyes, slight bob, replays
in race time with interpolation; spawns on race:started, despawns on finish.
- install.ts: installGhost(world, blob) wiring both to the frozen race events.
Title (src/ui/title.ts): installTitle(world, {ready}) — CSS bubble-letter
BLOBBO wordmark with paint-drip accents, pitch, controls card, PLAY. Loading
blob-dot while a boot promise is pending. Emits ui:play exactly once inside the
user gesture (audio-unlock friendly), then fades and never returns. Shows saved
best time + "ghost ready".
Demo (demos/lane-h.html + src/demo/lane-h.ts): scripted 20s circle run — title
gates start, run 1 records, later runs replay the ghost on the same path.
Keys: G wipe ghost, R restart, P snapshot.
verify-drift.ts: numeric acceptance (esbuild+node) — max drift 0.0058m vs 0.5m
blob radius. Emits ui:play only; no edits outside owned paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>