# BLOBBO Workshop — custom assets + in-game editor (plan, 2026-07-18)

**Goal:** John designs/builds models (Blender, or farm-generated GLB) and drops
them into the live game without touching code — plus an in-browser editor to
preview, fit, and export the mapping.

## Why this is easy here
Physics and visuals are fully decoupled: every collider is a primitive; every
mesh is cosmetic. Swapping a mesh can never break gameplay. The ONE special
case is the blobbo body — the paint system stamps into a canvas texture through
the mesh's UVs — so a custom blob needs clean, non-overlapping UVs and its
material's `map` slot handed to `PaintSkin`.

## Reskinnable slots (the inventory)
| Slot id | Today | Notes |
|---|---|---|
| `blob.body` | UV sphere | PAINTABLE — needs clean UVs; SkinnedMesh OK (idle/wobble clip plays via mixer; squash still applies at group level) |
| `blob.face` | procedural googly eyes | separate from paintable body, by design |
| `ghost.body` | translucent blob copy | auto-derived from blob.body (desaturated) |
| `cannon.base` / `cannon.barrel` | cylinders | per-colour tint applied on top |
| `machine.plate` | box + base | pressure plate |
| `machine.boot` | boot+spring primitive | farm GLB already exists (`assets/meshes/prop-spring-boot.glb`) |
| `machine.bucket` | cylinder shell + fill | farm GLB exists (`prop-paint-bucket.glb`) |
| `machine.arch` | frame + bubbles | bubble cleanser |
| `machine.belt` | striped box | conveyor |
| `course.scenery.*` | giant boxes (cereal box etc.) | free-place decorative slots; toaster GLB exists |
| `course.tramp` | orange pad | gap launcher |
| `course.tunnel` | slab + posts | MINI tunnel |
| `course.finish` | pink podium | finish pad |
| `fx.puddle` | flat glossy slab | material/decal upgrade slot |
| UI: title wordmark, HUD | CSS/DOM | swap via CSS/vars, out of scope for v1 |

## Architecture (two lanes + integration)

### Lane I — Asset runtime (`src/assets/`)
`assets/manifest.json` maps slot → `{url, offset, rotation, scale, options}`.
`AssetRegistry` (GLTFLoader + cache) resolves slots; **every construction site
falls back to today's procedural primitive when a slot is empty or fails to
load** — an empty manifest must produce today's game, byte-for-byte behavior.
Construction sites (createBlob, cannon barrel, machine parts, greybox scenery)
ask the registry instead of building directly, via a tiny `slotMesh(world,
'machine.boot', buildProcedural)` hook. Blob slot additionally: UV sanity check
(warn if overlapping/absent), PaintSkin adoption of the delivered material, and
optional AnimationMixer for a named idle clip. Local override layer:
manifest + GLB blobs can live in IndexedDB (set by the editor) and take
precedence — so custom assets are playtestable on the LIVE site with zero deploys.

### Lane J — Workshop editor (`/editor.html`, `src/editor/`)
A separate page sharing the game's modules. Three panels:
1. **Slots** — every slot with live thumbnail; click to select.
2. **Stage** — the actual game world (greybox + machines idling) rendering the
   current assets; orbit camera; selected slot highlighted.
3. **Fit** — drag-drop a `.glb` from disk onto a slot → instant swap in the
   stage; sliders/inputs for offset/rotation/scale; paintability report for
   blob.body (UV coverage, tri count, material count); "Test drive" button
   boots the REAL game in-page with overrides active.
Export: ⬇ `manifest.json` download + "Save locally" (IndexedDB override, with a
persistent "custom assets active — clear?" pill in the game). No server writes:
committing an asset pack = John drops the GLB in `assets/live/` + manifest in
the repo (documented in the panel's "ship it" help text).

### Integration (main session)
Wire registry into game.ts construction, ship `/blobbo/editor.html`, verify
(empty-manifest parity, toaster GLB in a scenery slot, blob swap with the farm
mesh incl. paint), deploy. Ship the 4 existing farm GLBs as the first pack.

## Blender/authoring conventions (put in the editor's help panel too)
- Y-up, -Z forward, meters; blob body ≈ 1.0u diameter, origin at CENTER;
  props origin at ground-contact center. Single material preferred.
- Paintable blob: one UV island set, no overlaps, no mirrored UVs.
- Rigged: single armature, clips named `idle` (loop) and optionally `roll`;
  keep ≤ ~40 bones (mobile later). GLB embedded textures ≤2048².
- Licensing per asset-pipeline skill: 🟢 web-ok only (farm-generated = ok).

## Out of scope v1 (candidate wave 4)
Course layout editing (move/add machines, puddles, boxes → course JSON),
animation retarget beyond idle clips, multi-pack management, shared packs.
