Log inGet started
Package

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

ModuleControls
preseed.asc.asc_skySky dome, sun + ambient lights, the ELEV sunset curve
preseed.asc.asc_starsThe star field's reveal as night falls
preseed.asc.asc_terrainValley floor, the four parallax ridge bands, aerial haze
preseed.asc.asc_cloudsThe volumetric cumulus deck: fill shader, palette, raymarch
preseed.asc.asc_shotsThe six-shot camera choreography, sway and buffet
preseed.asc.asc_particlesRising motes and valley mist
preseed.asc.asc_creditsDot-matrix title and the three credit cards
preseed.asc.asc_gradeBloom, 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.applySetup creates the sun and ambient entities it then looks up. Geo.material mints 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.