Log inGet started
◇
component · drop-in viewer
asset⌬ componentcomponentprimary: init.luau·originates fromworld 0fc7535e-6…

StormDirector

StormDirector handles everything about a lightning strike apart from the particles. The storm graph (the Generator on the same entity) owns the bolts, rain, splashes, sparks and smoke, and runs on one looping clock. StormDirector reads that same clock every frame and, through the…

byzero-proxy @ DESKTOP-DB3UJOJ·posted 11d ago
What it does

StormDirector

StormDirector handles everything about a lightning strike apart from the particles. The storm graph (the Generator on the same entity) owns the bolts, rain, splashes, sparks and smoke, and runs on one looping clock. StormDirector reads that same clock every frame and, through the shared storm.schedule, pours each strike's light over the rest of the scene: the sun swings to the bolt and throws the tower's shadow, point lights blaze at the strike, the cloud deck and the puddles light up, and the thunder follows at the speed of sound. Between strikes the values hold still, so its resting cost is one clock read.

The fx_storm scene uses it on the storm entity, next to the Generator that plays the storm.storm graph, with moonIntensity = 0.3 and ambientIntensity = 0.05.

It requires storm.schedule. It runs at executionOrder = 60. It finds these by name, and finds them again every 2 seconds, whenever one of their entities is gone, and while it is still looking for the generator:

NameWhat it reads
sunLight: the moonlight that swings toward each bolt
ambientLight: the ambient level
FlashKeyLight: the point light that blazes at the strike
FlashFillLight: the fill light high in the cloud
stormGenerator: the storm clock
Thunder1, Thunder2, Thunder3Audio: the thunder sounds
storm_sky, storm_groundmaterials, resolved once as assets

Fields

FieldTypeDefaultDescription
flashGainnumber1.0Scene-wide brightness of a strike's light, as a multiplier on the tuned defaults.
moonIntensitynumber1.7Moonlight before any strike: the sun light's intensity between strikes.
ambientIntensitynumber0.26The ambient light's intensity between strikes.
thunderVolumenumber1.0Thunder loudness.

All fields are Sync.

What each frame does

  1. Reads the generator's time and turns it into the storm phase with schedule.phase.
  2. Sums every strike's weighted envelope into flash (held at most 3, then scaled by flashGain) and its weighted afterglow into glow (held at most 1.2). The strike with the largest envelope + afterglow leads the lighting.
  3. Carries each strike's target and top from the graph's own space into the world through the storm entity's position and rotation (Transform.localToWorld).
  4. In play (update), cues the thunder. Each strike's thunder plays when the phase passes the strike time plus the distance from the camera drawing the viewport to the point halfway up the strike, divided by 343. The strikes take turns over Thunder1 to Thunder3; the volume is thunderVolume (times 0.85 for a ground strike) and each strike gets its own pitch near 1.
  5. When flash, glow or the leading strike has moved since the last write, it updates:
    • the sun's direction, lerped from the moonlight direction toward the bolt's direction as the flash burns, its intensity (moonIntensity + flash * 7 + glow * 0.6) and its colour, from { 0.36, 0.46, 0.78 } toward { 0.72, 0.80, 1.0 };
    • the ambient light's intensity (ambientIntensity + flash * 0.55 + glow * 0.08);
    • FlashKey, moved near the leading strike, at intensity flash * 9000 + glow * 700, and FlashFill at flash * 7000 + glow * 500;
    • storm_sky's flash and bolt_dir (the direction from the camera drawing the viewport toward the bolt's top), and storm_ground's flash and bolt_pos (the strike target).

Lifecycle

  • update(dt): runs the frame above with thunder cues.
  • editorUpdate(dt): runs the same frame, skipping the thunder cues.

Both use dt when given and 0.016 otherwise.

Interface

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

conforms to

zero/source-extract/v2

StormDirector: everything about a lightning strike that is not particles. The storm graph (Generator on this entity) owns the bolts, rain, splashes, sparks and smoke, and runs on one looping clock. This component reads that same clock every frame and, through the shared schedule, pours each strike's light over the rest of the scene: the sun swings to the bolt and throws the tower's shadow, point lights blaze at the strike, the cloud deck and the puddles light up, and the thunder follows at the speed of sound. Between strikes nothing changes, so nothing is written: the resting cost is one clock read.

resolveAll( ) → void

lerp(a: number, b: number, t: number) → number

argtypedescription
anumber
bnumber
tnumber

normalize(x: number, y: number, z: number) → void

argtypedescription
xnumber
ynumber
znumber

toWorld(p: { number }) → void

Where a point of the storm graph stands in the world. The schedule states every strike in the graph's own space, and the graph stands where the `storm` entity does.

argtypedescription
p{ number }

eye( ) → void

Where the storm is watched from this frame: the camera drawing the viewport, or the storm itself while no camera draws.

cueThunder(ph: number) → void

Play each strike's thunder when the clock passes the flash plus the time sound takes to arrive.

argtypedescription
phnumber

handlesGone( ) → boolean

The lights and the generator are found by name, and the scene they are in can be unloaded and loaded again, so a handle whose entity is gone is found anew.

tick(dt: number, playing: boolean) → void

argtypedescription
dtnumber
playingboolean

update(dt: ?) → void

argtypedescription
dt?

editorUpdate(dt: ?) → void

argtypedescription
dt?
⌬ Metadata
descriptionLightning-storm director: drives the sun, flash lights, sky and ground materials and thunder from the storm graph's clock.

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.

18+items
▣
module · born here
❒asset
# schedule The storm's clock. Every strike is a pure function of the storm phase, so the storm graph (as expressions over its own time) and the director (as Luau over the generator's clock) agree exactly, and the whole storm loops every `PERIOD` seconds. Two assets require it: the `storm` graph beside it (`storm.procGraph`, which builds its flash, bolt and burst expressions from `STRIKES`, `flashExpr`, `envelopeExpr` and `windowOne`) and the `StormDirector` component of the Procgen Effects Showcase world (lights, materials and thunder from `phase`, `envelope`, `afterglow` and `STRIKES`). ## Exports ### Values - `M.PERIOD`: `13.6`, the loop length in seconds. - `M.OFFSET`: `5.006`, the phase the storm is in at graph time 0. A strike has just landed there, so a paused frame is a hero frame. - `M.STRIKES`: the four strikes, each `{ t, kind, target, top, variant, weight }`. `t` is the phase the strike lands at, `target` where it lands, `top` where the bolt starts, and `weight` its brightness weight. `kind` is `"ground"` (throws sparks and smoke where it hits) or `"tower"` (hits the finial). `target` and `top` are in the storm graph's own space, where the tower stands at the origin; in the world they stand where the entity playing the graph does. | `t` | `kind` | `target` | `top` | `variant` | `weight` | |-----|--------|----------|-------|-----------|----------| | 1.6 | ground | `{ 15, 0, -8 }` | `{ 3, 135, -2 }` | 1 | 1.0 | | 5.0 | tower | `{ 0.05, 48.6, 0.1 }` | `{ -7, 135, 5 }` | 2 | 1.2 | | 8.6 | ground | `{ -19, 0, 7 }` | `{ -30, 135, 12 }` | 3 | 1.0 | | 11.0 | tower | `{ 0.05, 48.6, 0.1 }` | `{ 9, 135, 6 }` | 4 | 1.3 | - `M.PULSES`: the return strokes of a strike, each `{ delay after the leader, strength, decay rate per second }`: `{ 0.00, 1.00, 30 }`, `{ 0.11, 0.80, 26 }`, `{ 0.24, 0.62, 20 }`, `{ 0.43, 0.36, 11 }`. ### Functions - `M.phase(t)`: the storm phase at graph time `t`, `(t + OFFSET) % PERIOD`. - `M.envelope(ph, s)`: how bright strike `s` is at phase `ph`: 0 before the strike, about 1 at the return stroke. It sums every pulse whose delay has passed, each decaying exponentially at its rate. - `M.afterglow(ph, s)`: the cloud glow that lingers after strike `s`, at phase `ph`: `0.55 * exp(-d * 1.7) * min(d * 20, 1)` for `d` seconds after the strike, 0 before it. - `M.envelopeExpr(s)`: the same envelope as `envelope`, written as an anim expression string over `t`. - `M.afterglowExpr(s)`: the same afterglow as `afterglow`, as an expression string over `t`. - `M.flashExpr()`: the scene-wide flash as an expression string: the sum of every strike's envelope times its weight, capped at 3. - `M.windowExpr(only, lag, rise, fall)`: a burst of activity `lag` seconds after each strike, as a bump expression in [0, 1]: it rises with `min(d * rise, 1)` and falls with `exp(-d * fall)`. `only` limits it to strikes of one kind (`"ground"` or `"tower"`); `nil` includes every strike. - `M.windowOne(s, lag, rise, fall)`: the same bump for strike `s` alone. ## Usage ```luau local S = require("~.storm.schedule") local generator = entity.find("storm").component("Generator") local ph = S.phase(generator:getTime()) local flash = 0 for _, strike in ipairs(S.STRIKES) do flash += S.envelope(ph, strike) * strike.weight end -- the same flash, as an anim expression for a procGraph local flashExpr = proc.anim.expr(S.flashExpr()) -- a burst 0.15 s after each ground strike local bursts = S.windowExpr("ground", 0.15, 12, 1.0) ```
▲ 0↑ born
◐
material · born here
❒asset
# storm_sky Sky material for the lightning storm in the `fx_storm` scene. Loaded on its own, the scene's entrypoint makes it the sky (a temporary `StormSky` entity with a `Skybox`); as an overlay on the main scene, the main scene's sky stands. The `StormDirector` component on the `storm` entity resolves it by name and, on each frame where the strike light changes, writes `flash` (the summed strike flash times 1.1 plus the afterglow times 0.6) and `bolt_dir` (the direction from the camera drawing the viewport toward the top of the strongest current strike, with that top's height scaled by 0.6). Shader: `storm_sky` (`storm/shaders/storm_sky.shader`), a sky-domain shader. Its header: a boiling storm deck, with two layers of domain-warped cloud drifting with the wind over a bruised, near-black gradient and a cold haze on the horizon. Every lightning strike lights the deck from inside: `flash` is the strike brightness and `bolt_dir` the direction the bolt leaves the cloud, so the cloud glows brightest around the fork, silvers the thin edges and silhouettes the thick masses in front of it. Render state (the `render` block of mat.yaml): `cull: none`, `depth_write: false`. ## Properties mat.yaml sets only the shader and render state. Every property takes the shader default, and the director writes two of them at runtime: - `flash` 0.0 (range 0 to 20): brightness of the light inside the deck, written by `StormDirector`. - `bolt_dir` (0, 1, 0, 0): direction of the bolt from the viewer, written by `StormDirector`; the glow centres on it. - `wind` 1.0: multiplier on the speed the cloud layers drift. - `exposure` 1.0: multiplier on the final sky colour.
▲ 0↑ born
◐
material · born here
❒asset
# storm_ground Ground material for the lightning storm scene `fx_storm`. The scene's `ground` entity (a built-in cube scaled 90 x 0.2 x 90 at (-100, -0.1, 220)) wears it. The `StormDirector` component on the `storm` entity resolves it by name and, on each frame where the strike light changes, writes `flash` and `bolt_pos` for the strongest current strike. Shader: `storm_ground` (`storm/shaders/storm_ground.shader`). Its README: sodden ground, with churned earth, rock, puddles with animated rain rings, and the lightning bolt's reflection in the pools. From its header: world-space and texture-free; puddles fill the hollows of a procedural height field and mirror the sky, rain rings spread on every wet patch, the strike's reflection is traced from the reflected view ray to the bolt's column, and a distance haze matched to the sky's horizon blends the far ground away. Render state: the defaults (opaque blend, back faces culled, depth write on). ## Properties mat.yaml sets `center_x` and `center_z`, every other property takes the shader default, and the director writes two of them at runtime: - `wet` 0.95: darkens the albedo, lowers roughness and fills more of the hollows with water. - `puddles` 1.0: multiplier on puddle coverage. - `flash` 0.0 (range 0 to 20): strike brightness, written by `StormDirector`; it scales the bolt's reflection in the pools and brightens the distance haze. - `bolt_pos` (0, 0, 0, 0): position of the strike's target, written by `StormDirector`; the shader uses its x and z as the bolt's column. - `fog` 0.011: density of the distance haze, applied as `1 - exp(-distance * fog)`. - `center_x` -100, `center_z` 220: where the keep stands; the ground within 9 units of it stays a drier apron and the puddles fill in from 9 to 16 units out.
▲ 0↑ born
❒
package · born here
❒asset
# effectShowcase The procedural effects of the Procgen Effects Showcase world. Each one is a `.procGraph` that plays through a `Generator`, and every shader, material and module a graph draws with sits beside it in this package, so the package carries everything its graphs need. Each graph's README says what it makes, how to place it and what each of its inputs changes. ## Contents | Folder | Graphs | What they make | |---|---|---| | `combat/` | `impact`, `muzzleFlash`, `tracer` | A round striking a hard surface, the bloom at a barrel as a weapon fires, and the glowing streak of a round in flight. They draw with `fxFlash`, `fxSmoke` and `fxSpark` in `combat/shaders/`. | | `blast/` | `wall_blast` | A charge blowing a breach through a stone block wall, on a 9.6 s loop. | | `storm/` | `storm`, `tower` | A thunderstorm around a lone tower: wind-driven rain, lightning striking on the schedule `schedule.module` keeps, and sparks and smoke where strikes land. `tower` is the stone keep it stands around. | | `rift/` | `arcane_rift` | A rift tearing open in the ground, its motes and rubble moved by the compute shaders in `rift/compute/`. | | `dragon/` | `dragon_breath` | A dragon's fire breath sweeping a courtyard: a flame jet, rolling smoke and drifting embers. | | `launch/` | `launch` | A heavy-lift rocket liftoff on one looping timeline, with the heat-haze material `rk_haze_mat`. | | `waterfall/` | `wf_env`, `wf_water`, `wf_spray`, `wf_foam`, `wf_mist` | A waterfall built in parts: the cliff, basin and boulders, the falling and still water, the droplets, the foam where the falls land, and the mist with its rainbow. Its water materials are in `waterfall/materials/`. | | `shaders/` | | `fx_noise`, the noise functions the waterfall and wall blast shaders include. | ## Playing one A graph plays on a `Generator` component bound to it: ```lua -- `graph` is the graph's reference: the AssetRef `asset.resolve` or a search returns for it. local e = entity.spawn("impact", { position = { 0, 1, 0 } }) e.component.add("Generator", { graph = graph, playOn = "load" }) ``` Set a graph's inputs with the Generator's `params`, e.g. `params = { size = 3 }`.
▲ 0↑ born
backing path · storm/StormDirector.component

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.