Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·originates fromworld db2c16ac-a…

sky

The SKY lane of THE FARJAN (part 9, TIDES): the weather, the sky, the volumetric squall, the horizon and the rain veil.

byzero-proxy @ DESKTOP-DB3UJOJ·posted 3mo ago
What it does

preseed/tides/sky

The SKY lane of THE FARJAN (part 9, TIDES): the weather, the sky, the volumetric squall, the horizon and the rain veil.

What it owns

  • The scene's ProceduralSky — gradient, hour, sun disc and disc bearing, driven from preseed.tides.state. It drives the SCENE's sky entity and puts back what it found in M.exit; a part that spawns its OWN ProceduralSky beside it does not get a second sky, it gets a fight, because SkyConfig is one global record every Sky component pushes into.
  • The volumetric squall — a cellular cumulus field, raymarched, self-shadowed, with the sun attenuating through it.
  • The horizon — warm low height-fog that closes the sea/sky join and hides the ocean clipmap's far edge.
  • The rain veil from t = 25.

The pipeline is this part's own

tid_cloud_fill writes the density volume, tid_cloud_march marches it into tid_cloud_rt, and tid_cloud_composite resolves and blends it. None of the three is shared with any other part, and M.exit releases all of them.

It does not go through preseed.volume, and the reason is written at the top of init.luau: that module marches through preseed.emission_raymarch, which builds its ray from unproject(ndc, 0) -> unproject(ndc, 1) and tests depth < 1.0 for "a surface is here" — both correct before the engine's projection was reversed, and both wrong after it. Its target is also a single fixed 1600x900 buffer shared with every volume in the demo.

The numbers that are load-bearing

  • Every frequency in the fill is bounded by the VOXEL PITCH — 98 m horizontally, 63 m vertically at 224 x 72 x 224 over an 22 km box. A feature shorter than about three voxels does not resolve, it aliases, and because the cell map and the warp are functions of x and z only, that aliasing is constant down a column and reads as vertical fluting. Detail finer than that is added at MARCH time, where there is no grid.
  • The sun's extinction taps are a jittered cone, not a fixed ladder. Four fixed offsets comb-filter the medium along the sun's axis; with the sun near horizontal that axis is horizontal too, and the ripple lies in planes of constant X.
  • The march jitters WITHIN each step, not at the ray's start. The step grows with distance, so a start offset bounded by the near step leaves most of the quantisation coherent between neighbours out at the deck.

Contract

Reads State.weather, State.light, State.palette, State.sea (windDirection and windSpeed — the wind that moves the cloud is the wind that raises the sea) and State.sunDirection. Owns State.weather.

M.update must be called LAST in the part's update, after whatever moved the camera: the march builds its ray basis from the camera pose.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

preseed/tides/sky.module — THE FARJAN, SKY lane: weather, sky, volumetric clouds, horizon, atmosphere. Owns, for part 9 and nothing else: * the ProceduralSky backdrop (gradient + hour), driven from State.palette and State.light. The HOUR is what orients the sun — a direction written onto the Light is overwritten the next time the sky syncs, which is why none is written anywhere in this lane. * the volumetric squall: a real-scale cellular cumulus field raymarched through a TIDES-OWNED pipeline, self-shadowed, with the sun attenuating through it. * the horizon: warm low height-fog that softens the sea/sky join, hides the ocean clipmap's far edge, and gives the wake a depth cue. * the rain veil from t = 25. WHY THIS PART DOES NOT USE preseed.volume. That module marches through `preseed.emission_raymarch` into ONE fixed 1600x900 target shared by every volume in the demo, and both of those are wrong for this part in ways a caller cannot reach: * The engine's projection is REVERSED — near projects to z = 1 and far to z = 0 (`volumetricSky.viewProjFromCam` says so in its own comment). The shared marcher builds its ray as `unproject(ndc, 0) -> unproject(ndc, 1)`, which under that convention runs from the FAR plane back toward the camera, and it tests `depth < 1.0` for "a surface is here" when empty sky now reads 0.0. The consequence was measured in round 1: any non-zero occluder range erased the entire sky, so the only usable setting was the one that disabled depth testing altogether and let cloud draw over the ferry. * The shared target is written by every part's volume. A raymarch of this part's deck landing in a target another module also dispatches into is the pixel-level 50/50 lattice round 1 shipped: measured duty 0.501, spatially uncorrelated, temporally uncorrelated, alternating the sky colour against the cloud colour. So the march, the target and the composite are this part's own: `tid_cloud_march.computeShader` writes into `tid_cloud_rt`, and `tid_cloud_composite.shader` resolves and blends it. Nothing else in the demo touches any of the three, and `M.exit` releases all of them. SCALE. The volume is authored at REAL scale — 18 km across, 350 m to 4600 m — so `cloudBase` / `cloudTop` in metres from the contract mean what they say. TWO SPEEDS. The squall FRONT sweeps cinematically fast (thirty seconds is a whole weather event), but the cloud FIELD drifts at the real wind speed the sea is raised by. Separating them is what stops the clouds visibly rocketing across the sky while the weather still closes in on schedule. The sun direction the clouds are lit by, and the bearing the sun-slot is carved along, are both read LIVE from the scene's resolved lighting. The sky model derives the sun from the hour, so reading it back is the only way the clouds, the shadows and the glitter track cannot disagree.

trackNum(keys: ?, t: number) → number

argtypedescription
keys?
tnumber

frontS(t: number) → number

The front position. A smoothstep rather than a ramp so the sweep accelerates through the middle beats and is already spent by the time the shelf has to hold the sun down.

argtypedescription
tnumber

driftDistance(t: number) → number

argtypedescription
tnumber

slotAmount(t: number) → number

The slot is open for exactly bar 22 — t 26.25 to 27.5 — with a short ramp on either side so the shaft arrives and leaves rather than switching.

argtypedescription
tnumber

sunToward(t: number) → void

The sun, as a unit vector pointing TOWARD it. Read from the contract's own hour rather than from the scene's Light, and that is deliberate. `src/engine/systems/sky.rs` will not sync the sky's sun onto the lighting config while a directional Light entity exists — the Light is "the sole authoritative writer" — so in this scene the sky orients nothing and `tid_sun`, spawned with no direction, sits at the default straight down. Measured: timeOfDay 18.42, `tid_sun` direction (0, -1, 0). What the audience sees is the sky's own disc, which IS a function of the hour. Aiming the cloud lighting, the slot and the fog lobe at that disc is the only way they cannot disagree with it. LIGHT has to bring the Light to the same bearing; `State.sunDirection(t)` is published for exactly that.

argtypedescription
tnumber

rotByQuat(q: ?, vx: number, vy: number, vz: number) → void

Rotate a unit axis by the camera quaternion. The camera's own space is the wgpu right-handed one the demo's projection is built in: it looks down -Z with +Y up, so forward/right/up come from rotating (0,0,-1), (1,0,0), (0,1,0). Deriving the basis this way rather than from a matrix means the march never has to agree with anybody about a storage order.

argtypedescription
q?
vxnumber
vynumber
vznumber

fillAsset( ) → void

marchAsset( ) → void

sharedCompositeOff( ) → void

build(ctx: ?) → void

argtypedescription
ctx?

applySky(t: number) → void

The backdrop the clouds stand against. Called from build and from update, so the gradient is the part's from the first frame it draws. GROUND IS THE FOG COLOUR. What lies below the horizon in this part is SEA, and past the ocean clipmap's last tile there is no sea to draw — so whatever shows through that gap has to be the colour the fogged sea reaches. The scene's own near-black (0.05, 0.04, 0.04) is what read as a black bar with a stepped top edge sitting on the horizon.

argtypedescription
tnumber

applyFog(t: number) → void

The horizon. A razor seam between sea and sky is the second-most-obvious defect in the part as it stood; this is what softens it. In-scattering brightens the medium toward the sun, which is also what carries the glitter track's haze in beat 2 and what the rain veil thickens into after t = 25.

argtypedescription
tnumber

update(ctx: ?, t: number) → void

Called at the END of the part's update, after the camera has been set: the march builds its ray basis from the camera pose, so a stale pose here is a frame of clouds drawn for where the camera used to be.

argtypedescription
ctx?
tnumber

exit(ctx: ?) → void

argtypedescription
ctx?

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing up to 3 levels deep. Open a part to see what it holds further down.

26+items
▣
module · born here
❒asset
# 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`.
▲ 0↑ born
▣
module · born here
❒asset
# volume A module script.
▲ 0↑ born
·
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.
▲ 0↑ born
·
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.
▲ 0↑ born
✦
shader · born here
❒asset
# 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.
▲ 0↑ born
backing path · preseed/tides/sky.module

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.