sky
Stage 3 of THE SECOND SUN. The director calls `Sky.tick(t, dt, frame)` once per rendered frame: after the shore has moved and after what lives on it has moved, and before the lens that has to see all three.
secondsun.sky — the event, and all the light
Stage 3 of THE SECOND SUN. The director calls Sky.tick(t, dt, frame) once per
rendered frame: after the shore has moved and after what lives on it has moved,
and before the lens that has to see all three.
Everything here is a pure function of the film clock. Seek to 58 and the light, the sky and the air are what film second 58 says they are, with nothing run through in front of them.
The key light changes SOURCE four times
| act | t | key | shadows |
|---|---|---|---|
shore | 0 – 21.6 | the moon, azimuth 25, elevation 38 | long, cool |
secondsun | 21.6 – ~50 | a point source on the southern horizon, azimuth 186, climbing −1 → 22 | hard, raking north, shortening |
oven | ~50 – 70 | none — the whole hemisphere emits | none |
lid | 70 – 92 | none — a dull red ash ceiling | none |
Sky.key(t) returns that state as data — source ("moon" / "fireball" /
"none"), color, direction, azimuth, elevation, intensity, shadows —
so nobody has to infer what is lighting the frame from its colour.
The source changes in one frame, under a whiteout. The bearing does not:
Sky.keyAzimuth(t) swings the key from the moon's north-north-east round
through the east to due south between the flash and t = 27.5, which is the sweep
shot 5 is cut to watch.
Three lights, and they are not interchangeable
procedural_sky.shader reads ONE quantity off the scene's sun — the elevation of
whichever directional light holds it — and derives its whole state from it: where
its disc is drawn, whether the star field renders, and whether the authored
zenithColor / horizonColor reach the frame at all. A moon is a light forty
degrees up, and a sky whose sun is forty degrees up is a day sky with no stars in
it.
So the quantity the sky reads and the quantity that lights the ground are carried separately:
Sky_sunproxy— zero intensity, lights nothing, HOLDS the scene's sun. It stands where the sky should think the light is: below the horizon in act I so the stars are alight, on the fireball's own bearing in act II so the disc IS the fireball, and near the zenith afterwards so the authored orange reaches the frame unmixed.Sky_key— holds nothing, lights everything, stands where the light actually comes from.Sky_ambient— the sky's own contribution to the ground. In act III it is the ONLY contribution, which is what "no directional key, and therefore no shadow anywhere" means in practice.
That split is also why the fireball is a body rather than a sprite: the sky draws its disc only above its own horizon, so a disc whose elevation crosses zero climbs out of the sea by itself, and its aureole comes free.
Sky_moon is real geometry — a pbr sphere with an emissive of its own, at 9 km,
0.85 degrees of angular radius — because the disc the sky could have drawn is the
one the proxy is holding below the horizon.
The air
Sky.air(t) drives @builtin::systems.atmosphere.fog: colour, extinction per
metre, height falloff, and how far the medium goes toward the key's colour when
you look into it. Clear and humid at the top — a 730 m sightline — and 200 m of
ash and steam at the end.
What moves
Seven GPU emitters, each a pure function of t through its rate.
Sky.rates(t) is the whole table.
| emitter | live | what it is |
|---|---|---|
Sky_ejecta | 50 – 80 | re-entry streaks converging out of the zenith, drawn along their own velocity and stretched |
Sky_ignition | 46 – 66 | the conifer edge catching, south face first |
Sky_embers | 46 – end | ember lift off the burning edge |
Sky_smoke | 47 – end | the forest making its own weather |
Sky_dust | 62 – 75 | the blast's dust wall, crossing the lens ON BLAST |
Sky_ash | 68 – end | ash falling through a band over the ground, following the lens across the flat |
Sky_steam | 80 – end | black water back over ground that is still hot |
Particles cannot rewind — the simulation runs on the engine's clock, not the
film's — so they are driven as STATE, not history: each emitter is told the rate
this second calls for, and a jump in t clears every live particle so a seek
never shows the previous second's air.
Heat shimmer is a post effect, secondsun_heat, whose phase is film time, so
it advances with the film and with nothing else.
Reading it back
local Sky = require("secondsun.sky")
Sky.report(58) -- key, ambient, sky, air, rates and shimmer at film second 58
Scoped to this part · feeds back into the world's score.