# Lane C — Machine (Rube Goldberg toolkit)

**Owns:** `src/machine/`, `demos/lane-c.html`, `src/demo/lane-c.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
Player-triggered contraptions with mandatory telegraphs (GDD §5.4). Contraptions
are data-placeable parts that chain via signals — the course is a machine.

## Deliverables (`src/machine/`)
1. `telegraph.ts` — helper: given a mesh + duration (default 0.5s), play an
   unmissable windup (flash + shake/scale pulse + optional emissive) then resolve.
   EVERY contraption that applies force or dumps paint MUST telegraph first.
   This is a design pillar, not decoration.
2. `parts.ts` — each part takes a config `{id, position, ..., onSignal?, emits?}`,
   registers physics + visuals, listens/emits on `world.events`:
   - **PressurePlate** `{massThreshold}` — depresses under weight; uses blob
     effective mass (`body.mass()`); emits its `emits` signal on trip.
   - **SeeSaw** — plank on Rapier revolute joint; purely physical (heavy blob
     dominates it).
   - **SpringBoot** `{impulse, direction}` — on signal: telegraph → kick
     anything in its strike volume.
   - **BucketDump** `{color, radius}` — on signal: telegraph (teeter!) → tips;
     emits `paint:request-splat {point, color, radius}` for everything under it
     (paint lane renders the splat; you also spawn a brief visual slosh mesh).
   - **ConveyorBelt** `{velocity}` — surface that adds velocity to bodies on it.
   - **BubbleArch** `{fraction}` — sensor volume; on blob overlap emits
     `paint:request-cleanse {fraction}` + bubble particle burst.
   - **Fan** `{force, direction}` — constant force volume (pushes light blobs
     hard, heavy blobs barely — mass does the work).
3. `surfaces.ts` — `SurfaceMaterial` registry per contracts (`normal`, `ice`,
   `oil`, `honey`, `mud`, `water`): friction/restitution + tags; helper
   `applySurface(collider, name)` + a way for the blob controller to query the
   surface under a body (`surfaceAt(point)`).
4. Chain demo `demos/lane-c.html` + `src/demo/lane-c.ts`: heavy test ball (your
   own stub — do NOT import Lane A) rolls onto **PressurePlate → SpringBoot →
   ball lands on SeeSaw → tips into BucketDump trigger → bucket teeters and
   dumps** (log the paint event), plus a ConveyorBelt and a BubbleArch on the
   side, and an ice + honey patch to feel surfaces. Debug key T re-runs the ball.

## Acceptance
- `npm run build` passes; the full chain runs end-to-end on one ball with a
  visible telegraph before every kick/dump.
- PressurePlate does NOT trip below its mass threshold (test with a light ball).
- Surfaces measurably change rolling behavior.
- No edits outside owned paths.

## Contracts (frozen — read `src/contracts.ts` first)
Emit: `machine:signal {id}`, `paint:request-splat`, `paint:request-cleanse`.
Nothing in this lane imports from `src/paint/` or `src/blob/` — events only.
