# Lane B — Paint **Owns:** `src/paint/`, `demos/lane-b.html`, `src/demo/lane-b.ts`. **Never touches:** `src/contracts.ts`, `src/world.ts`, `src/game.ts`, `src/main.ts`, `package.json`, other lanes' dirs. No new deps — three + rapier only. ## Mission The core invention: paint lands on the blob's body, live per-colour coverage % is computed, and coverage drives buffs (GDD §5.1, §6). MVP colours: RED, GREEN, BLUE (design for all 7 — palette is in contracts). ## Deliverables 1. `src/paint/skin.ts` — `PaintSkin` class, attachable to ANY mesh with clean UVs (implements the `PaintSkin` interface in contracts): - Offscreen canvas (256×256) as the blob's colour mask → THREE.CanvasTexture as the material map over the white base. - `applySplat(uv, color, radius)` stamps a soft-edged irregular splat (quantize stored pixels to exact palette colours so counting is exact). - `splatAtPoint(mesh, worldPoint, color, radius)` — raycast/closest-face → UV → applySplat. Handle UV seams tolerantly (a stamp near seam may wrap). - `coverage(): CoverageReport` — recount every ≤250ms (cached between): bucket pixels by palette colour; fractions of TOTAL surface (unpainted counts in denominator). - `cleanse(fraction)` — erase that fraction of painted pixels (random holes, looks scrubbed), 1 = full clean. 2. `src/paint/cannon.ts` — `PaintCannon`: placed emitter that fires paint-glob projectiles (small coloured spheres, gravity arc, Rapier sensor or manual sweep). On hit: blob → `splatAtPoint`; anything else → despawn (ground decals are V1). Fire on interval AND on event `machine:signal {id}` matching its configured trigger id. Emits `paint:splatted {color, target}`. 3. `src/paint/buffs.ts` — `BuffSystem` (a `System`): every frame maps the blob's `coverage()` → `blob.modifiers` per GDD §6, MVP subset: - threshold 20%, strength scales linearly 20%→70%; - RED `speedMul` up to 1.6 + ember particle trail (cheap sprite points); - GREEN `grip` up to 1.0; - BLUE `waterproof=true` + slick trail flag (`slippery` decal V1 — flag ok); - total coverage → `massMul` 1.0→1.8 (paint = weight, GDD §5.2); - ≥70% one colour = super: modifier at max + `glow` (emissive pulse). 4. HUD `src/paint/hud.ts` — tiny DOM overlay: per-colour % bars + active buffs. 5. Demo `demos/lane-b.html` + `src/demo/lane-b.ts`: a plain rolling test ball (your own stub — do NOT import Lane A) circling between two cannons (red, green) + a cleanse key (C). HUD live. Prove: paint accumulates where hit, %s are sane, buffs kick in at 20%, super at 70%, cleanse works. ## Acceptance - `npm run build` passes; demo shows visible splats accumulating AT hit locations, live HUD %, buff activation visibly changes the test ball (speed/trail/glow), cleanse visibly scrubs. - Coverage recount ≤250ms cadence, no per-frame full readback (perf). - No edits outside owned paths. ## Contracts (frozen — read `src/contracts.ts` first) Listen: `machine:signal`, `paint:request-splat {point, color, radius}` (Lane C's bucket dumps emit this). Emit: `paint:splatted`. Palette: `PALETTE` in contracts.