p7_ascension.module
README.md
p7_ascension — ASCENSION (part 12 of 13, 35 s)
The finale. A real-time sunset over a ridged mountain valley; motes of light rise from the dark as the camera climbs through a self-shadowed cumulus deck, breaks out over a glowing cloud sea, and tilts up into a star-filled night for the credits.
This file is only the orchestrator. Every piece of the finale lives in its
own module under preseed/asc/, one owner each. Edit an aspect in its own
file; this one changes only when the wiring between aspects changes.
The aspects
| Module | Controls |
|---|---|
preseed.asc.asc_sky | Sky dome, sun + ambient lights, the ELEV sunset curve |
preseed.asc.asc_stars | The star field's reveal as night falls |
preseed.asc.asc_terrain | Valley floor, the four parallax ridge bands, aerial haze |
preseed.asc.asc_clouds | The volumetric cumulus deck: fill shader, palette, raymarch |
preseed.asc.asc_shots | The six-shot camera choreography, sway and buffet |
preseed.asc.asc_particles | Rising motes and valley mist |
preseed.asc.asc_credits | Dot-matrix title and the three credit cards |
preseed.asc.asc_grade | Bloom, tonemap, vignette, letterbox bars |
How aspects are kept apart
No aspect requires another. Where two are coupled, the value crosses through this file as an explicit argument, so two agents editing two files cannot reach each other. There are exactly two published signals:
sun = asc_sky.update(...) -- { elev, nightMix, dir, skyGuid }
cam = asc_shots.update(...) -- { pos, rot, fov }
sun goes to asc_stars, asc_terrain and asc_clouds. cam goes to asc_clouds.
P.duration goes to asc_credits. That is the entire coupling surface.
Those two update hooks are therefore mandatory and run first. Every
other hook is optional: an owner can add build / enter / update / exit
to their module and the orchestrator will start calling it without being
edited.
Each aspect gets a private state table (ctx.data.st.<key>) and never sees
another's. Small pure helpers (seq, clamp01) are deliberately duplicated
into the modules that need them rather than shared: if seq were shared,
retuning the motes would silently change the sunset.
Ordering that is load-bearing
- Build: asc_sky first —
Lighting.applySetupcreates thesunandambiententities it then looks up.Geo.materialmints keys from a monotonic counter, so the rest keep their authored order. - Update: asc_sky → (stars, terrain) → asc_shots → asc_clouds. The deck builds its inverse view-projection from this frame's camera, so the camera must be settled first.
- Exit: the volume is released before the particle systems.
The part contract
name, duration = 35, fadeIn = 1.5, fadeOut = 3.0, shots, and
build / enter / update / exit, consumed by preseed.director.
P.shots is re-exported from asc_shots because the director evaluates that
list itself and writes the camera before calling P.update; asc_shots then
overwrites with the swayed pose.
ctx.center is {6000, 0, 0}. The fades mean t = 0 is black by design and
t > 32 is mostly black — read the picture in t ∈ [1.5, 32].
Previewing
local Preview = require("preseed.preview")
return Preview.at("ASCENSION", 12) -- freeze at part-local t
then capture { source = "screen" }. Check renderedFrom.role == "active":
this demo's post stack is a content render feature that runs only for the live
viewport, so an offscreen capture silently reuses a stale frame.
Known leaks past exit
exit releases the cloud volume and the two particle systems. Deliberately
left as-is by the aspect split: ProceduralSky.syncSunToLight = false, the
letterbox bars, asc_sky_mat's night-state, the runtime materials and meshes,
and the Lighting.applySetup sun/ambient/clear colour all survive into
p_endcard.