# FIELDWORK build notes

## What the prototype proves
One repair can be the whole game if the world it spreads through is physical. The chain is: truck and crate, mud, cloth sail, drive repair, power bus, pump, tank, canals, crops, penstock, Great Wheel, nine-stage Works.

## Structure
- `src/layout.js`: world layout, the Cut (valley polyline), height function with pad flattening.
- `src/terrain.js`: 320x320 grid at 0.8 m, moisture, ruts, berms, crop growth; outer terrain with shader-driven petals, rings and swirl, masked away from the Cut.
- `src/vehicle.js`: raycast suspension, soil-limited tyres (Bekker/Mohr-Coulomb from moisture), reflected driveline inertia with implicit slip damping, winch.
- `src/cloth.js`, `src/sailrig.js`: XPBD sail (stretch 4e-6, shear 2e-5, bend 1e-3 compliance, from Textile Academy's xpbdSolver.js), corner pins, torque. Canvas material: linen map/normal/roughness plus a back-light term so the shaded side glows.
- `src/cord.js`: the cord puzzle. Loops are tubes in rotor space with a z order; a loop can be pulled only when no higher loop crosses it. `machine.bound` adds a huge brake hold until it is solved; `flags.cordFree` latches it.
- `src/machine.js`, `src/plant.js`: windmill drive, bearings, shear key, bus voltage, pump, tank, canals, crops, farm robots.
- `src/mega.js`: the Works, driven by accumulated wheel radians E (stages at 0, 6, 14, 28, 38, 46, 68, 82, 104; E_END 125).
- `src/fx.js`: feel layer (tyre spray, objective beacon, minimap, rumble, atmosphere, penstock supports). Reads simulation state only.
- `blender/build_machines.py` builds `assets/fieldwork-machines.glb`.
- `src/render.js`: quality tier, composer (render, NaN scrub, bloom, output, grade), PMREM environment, dynamic resolution.
- `src/robot.js` (Seven), `src/drone.js` (Mote): load `assets/fieldwork-characters.glb` (built by `blender/characters/chars_main.py`). Clips are NLA tracks exported with forced sampling; the tread belt is an InstancedMesh placed along the same stadium loop Blender uses. The engine transform docks Seven in the truck cradle at local (0, 0.42, -3.35).
- `src/vehicle.js` and `src/world.js` take the truck and props from `assets/fieldwork-truck.glb` and `assets/fieldwork-props.glb` (`blender/vehicles/truck_main.py`, `props_main.py`), with primitive fallbacks if a load fails.
- `assets/fallen-firehawk.glb`: `blender/fallen_firehawk.py` (poses the Firehawk game's robot, builds the drift; writes `blender/fallen-firehawk.base.glb`) then `blender/weather_firehawk.py` (materials, wear mask in COLOR_0, join by material). In game the colour attribute is renamed `fwWear` and read by `patchStandard(mat, 'wreck')`.
- Depot set: `blender/vehicles/build_depot.py` adds Prop_Warehouse, Prop_Gantry, Prop_FencePost2 and Prop_FenceBay to the props GLB (`export_props.py`); `depot_preview.py` renders them in Cycles.
- `src/kraken.js` + `assets/fieldwork-kraken.glb` (`blender/kraken/kraken_main.py -- --export`; `kraken_preview.py <views> [pose]` renders it). Bones `T<k>_<s>` (6 x 12); the game bends each about its tentacle's lift axis and the vertical, computed in the bone's own rest frame, so it does not depend on exporter axis conventions. Pivots (EyeLid_*, Node_Cover_*, Node_Breaker_*) are driven by post-multiplying their rest quaternion (three's Euler order differs from Blender's). `bus.leech` is the drain. Debug: `FW.kraken.wake(false)`, `FW.kraken.cutNode(n)`.
- `src/daynight.js`: 7-minute day; the moon takes the shadow light at night. `src/touch.js`: phone controls. `src/cutscene.js`: the opening, which drives `mega.override`, `robot.sleep` and Mote's `script` state, and owns the camera while `game.cine` is set.
- Finale: `src/finale.js` (phases storm, engine, combine, reborn, fight, ko, outro; acts as `game.cine`), `src/titan.js` (Titan: a scaled clone of `assets/firehawk.glb` with the FIRE HAWK brawler's state machine, moves and AI; `_dressCombiner` hangs `CB@<joint>@<part>` nodes and `CBA@<joint>@<anchor>` anchors from `assets/fieldwork-titans.glb` on the joints, `_dressKraken` rebinds the `KM_*` skinned meshes to the clone's joint nodes in rest pose), `src/ocean.js` (SpectralOcean FFT, Ocean, Rain). Built by `blender/titans/titans_main.py` (needs TRUCK_GLB, FH_GLB, SR71_GLB env paths), previewed with `titans_preview.py -- cb|km`. Water level: fight at bed + 2.75 so it stays inside the sink's rim (pad dh -3.2). Ending phases ko → tame → part → dawn (`_ko`, `_tame`, `_part`, `_dawn`; `skipEnding()`); cue names are prefixed per phase because `_cuesDone` is shared. `kraken.tame()` / state 'tamed' is the saved post-finale Kraken (`flags.krakenTamed`, negative `leech` feeds the bus). Debug: `FW.finaleCtl.preload()`, `.start()`, `.skip()`.
- `src/title.js`: TitleStage, the start-menu set piece. `place(x, z)` stands it on a spot facing the basin centre (STAGE is the south slope at (-18, 100)); `enter()` borrows Seven (lifted out of the truck if needed) and Mote and sets the hour (PHASE); `exit()` puts everything back. Camera in the stage frame: `TitleStage.cam` / `camPortrait` = [x, y over the perch, z, look x, look y, look z].
- `src/occlude.js`: Occluders. `collect(centre, radius, skipRoots)` once per shot, then `update(camPos, targets, pad)` each frame hides small meshes, instances (collapsed matrix) and wires on the sight lines. Used by the cutscene's close shots and by the finale.
- Unstuck: `safeSpot(from, clearance, {wet, maxR, fallback})` / `unstuck()` in main.js (`FW.unstuck()`). Mote rescue: `updateRescue()` (hold T, phases fetch → lift → carry → lower; `FW.rescue()` starts one).
- `src/sky.js`, `src/look.js`, `src/grass.js`, `src/water.js`, `src/geo.js`, `src/textures.js`: the look layer. Shared uniforms live in `look.js` (`U`).

## Look layer notes
- Terrain carries a per-vertex `aSoil` (moisture, green, sink, pad + 2*field) and real normals; only the changed vertex band is uploaded each frame. `terrain.tex` is a float copy of height/moisture/green/pad refreshed at 4 Hz for the grass.
- Materials are patched once at boot by `upgradeScene`; materials created later are left as they are. Set `material.userData.fwSkip` to opt out.
- Any mesh without a normal attribute gets an up vector in the patch, but give custom geometry normals anyway: a NaN pixel is scrubbed before bloom, not before the main render.
- The environment map is regenerated when the Works' atmosphere has moved by 0.03.

## Gear convention
Pitch radius 1, axis +Z, thickness 1, tooth 0 on +X. Module 0.2 in the gear wall (R = N x 0.2), 0.05 in the shed. Meshing phase is computed, not eyeballed. Planetary: sun 24, planets 36 x 3, ring 96 fixed, carrier at 24/120 of the sun speed.

## Tuning facts worth remembering
- Sail tension 0.95 to 1.0 is best; above 1.05 the membrane tears.
- Bus power is soft-gated: V = (omega_3 - 2.2) / 4.0 with a 1.6 s smoothing; the sluice motor and the Power objective need V above 0.45 (about 0.55 rad/s on the rotor, reachable with the intake fully open and the straps in the working band).
- Wheel target speed = 0.22 x saturate(overflow / 0.06)^0.8.
- Press the period key to fast-forward the Works x6; `?debug` exposes `window.FW`, `?reset` clears the save, `?autostart` skips the title, `?intro` / `?nointro` force or skip the opening, `?day=` and `?time=` set the day length and start phase, `?q=low` is the phone tier.

## Open ideas
Moving pipeline flow along the pressure mains, truck damage and repair, second machine, screen-space ambient occlusion.
