# preseed.tides.state — the TIDES shared contract
Everything that crosses a lane boundary in part 9 ("THE FARJAN"), as **pure
functions of part-local `t`** and nothing else. No state, no side effects, no
entity lookups, no `ctx`.
```lua
local State = require("preseed.tides.state")
State.ship(t) --> { pos = {x,y,z}, forward = {x,0,z}, speed, headingDeg }
State.sea(t) --> { windSpeed, windDirection, choppiness, amplitude,
standingWaveRatio, foamDecay, significantHeight }
State.weather(t) --> { squall01, coverage, cloudBase, cloudTop, wetness, rain01 }
State.light(t) --> { sunElevDeg, sunAzimuthDeg, timeOfDay, sunColor, sunIntensity,
ambientColor, ambientIntensity, keyOcclusion01, exposure }
State.palette(t) --> { skyHorizon, skyZenith, skyGround, cloudLit, cloudShadow,
waterBody, foam, fogColor, hullRed, upperworksWhite,
antifoul, bootTop, cabinEmissive }
```
## Why pure
Hot-reload rebinds a module's function bodies without re-running its
module-level chunk. Module-level mutable state in a shared file therefore keeps
the *old* build's values while running the *new* build's code, and the bug looks
like it is in whichever lane read it last. A file of pure functions cannot do
that.
It is also what makes the wake derivable. Because `ship(t)` is pure, the wake at
time `t` is exactly the locus `{ ship(t - a) : a in [0, 26] }` — sample it and a
scrub, a seek or a hot-reload all produce the identical wake. An accumulating
wake is empty after every seek, and this demo is scrubbed constantly.
**No lane may add module-level mutable state to this file.**
## Ownership
| Function | Owner (may retune the numbers) | Readers |
|---|---|---|
| `ship` | SEA (track & speed) | CAMERA, SEA, FERRY |
| `sea` | SEA | SKY, CAMERA |
| `weather` | SKY | LIGHT, SEA, FERRY |
| `light` | LIGHT | SKY, SEA, FERRY |
| `palette` | LIGHT | everyone |
A lane needing a new field **appends** it. Nobody renames or removes one.
## Fixed facts
- **Axes:** `+X` = ahead (bow), `-X` = astern, `+Y` = up, `-Z` = port,
`+Z` = starboard. The sun sits just off her starboard bow.
- **Music:** 192 BPM, 4/4 — `BAR` = 1.25 s. Thirty seconds is exactly 24 bars.
Nothing changes on a number that is not a multiple of 1.25.
- **`pos.y` is the calm-water datum**, not her true height. `preseed.Buoyant`
owns her vertical; read the entity for her real `y`.
- **`ProceduralSky` owns the sun's direction** via `timeOfDay`. `sunAzimuthDeg`
is the intended bearing the clouds are lit against, not an instruction to
write a direction onto the `Light`.
·computeshader · born here
❒asset # tid_cloud_march
The raymarcher for the TIDES squall. One invocation per pixel of a
TIDES-owned target: rebuild the pixel's ray from the camera basis, clip it to
the cloud volume's world AABB, clamp it at the scene surface, and integrate
front to back through the density volume `tid_cloud_fill` wrote.
It exists as a separate shader from `preseed.emission_raymarch` because three
things this part needs are decided inside a marcher and cannot be reached from
its caller: the engine's depth is REVERSED (1.0 near, 0.0 far and at empty
sky) and has to be unprojected as such or empty sky reads as an occluder; the
target must be the live backbuffer size and owned by one part, so nothing
resamples the march and nothing else writes into it; and the step has to grow
with distance to reach 30 km across an 18 km deck without spending a thousand
uniform steps.
The ray start carries an interleaved-gradient-noise jitter. That turns the
residual step quantisation from a coherent band into a per-pixel dither with
a well-separated neighbourhood, which `tid_cloud_composite` resolves with a
3x3 reconstruction.
Output is premultiplied — `vec4(scatter, 1 - transmittance)` — which is the
form the composite blends over the scene.
The uniform layout is documented at the top of `shader.wgsl`, and
`preseed/tides/sky.module` is the only thing that packs it. Both sides of that
contract are edited together.
·computeshader · born here
❒asset # tid_cloud_fill
Bakes the TIDES squall into the part's 3D density+colour volume, one voxel per
invocation, every frame.
The field is authored at **real scale**: the volume spans roughly 9.6 km across
and reaches from 350 m to 4500 m, so `cloudBase` and `cloudTop` in metres from
`preseed.tides.state` mean what they say and the deck is genuinely far away
rather than a low dome pretending to be sky.
Four things are layered into it:
- **A squall line.** A front advances along the wind axis. Ahead of it, thin
scattered fair-weather cumulus at the high base; behind it, full coverage and
towering congestus. At `t = 0` the front is astern with open sky toward the
sun; by `t ≈ 24` it has swept past the whole visible box.
- **The shelf.** A hard-edged low band riding just above the cloud base behind
the front. This is what takes the sun at `t = 25` and what reads as "a black
shelf" in the last beat.
- **The slot.** A narrow ragged wedge carved toward the sun's bearing, low in
the sky and far out, opened only for bar 22 (`t` 26.25–27.5). It is what lets
one hard shaft back through.
- **The rain veil.** Below the base, the shelf density in the same column is
dragged downward as virga, scaled by `rain01`. Real volumetric medium, so it
hangs where the cloud actually is instead of being a screen-space wash.
Lighting is three density taps toward the sun — 260 m, 700 m and 1500 m — giving
transmittance `exp(-occ)`, which is what produces the lit-rim / dark-core read
that makes cumulus look like cumulus. Colours come from `State.palette(t)`
(`cloudLit` / `cloudShadow`); the sun direction is read live from the scene so
the clouds, the shadows and the glitter track cannot disagree.
Bindings are generated from `bindings.yaml`. The uniform run is 48 `f32`
(192 bytes) packed by `preseed.tides.sky`; the layout is documented at the top
of `shader.wgsl` and both sides must be edited together.
# tid_cloud_composite
The post-process pass that resolves the TIDES cloud march and blends it over
the scene.
`tid_cloud_march` writes one jittered sample per pixel into a TIDES-owned
target, premultiplied as `(scatter, 1 - transmittance)`. This pass runs a 3x3
tent over that target and blends the result. The tent is what turns the
march's per-pixel jitter back into continuous tone: because the jitter comes
from interleaved gradient noise, a pixel's neighbours carry well-separated
offsets, so a nine-tap average resolves nearly the whole of it.
The filter runs on the premultiplied result, which is the only form a weighted
average of a participating medium is linear in.
`preseed/tides/sky.module` binds the march target into `tid_rt` each frame and
owns the pass's lifetime, disabling it when the part ends.