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

weather

The weather of First Light. The scene attaches it to the `Weather` entity; from there it reads the clock and the run's forecast seed and, every frame, writes `Rules.state.weather` and puts the weather into the world:

byPolar Night·posted 1mo ago
What it does

firstlight.weather

The weather of First Light. The scene attaches it to the Weather entity; from there it reads the clock and the run's forecast seed and, every frame, writes Rules.state.weather and puts the weather into the world:

  • Snowfall — a particle column around the player's eye whose flakes drift at the wind's own speed and stretch along their path, so calm air holds slow dots and a blizzard holds horizontal streaks.
  • Fog — the scene's height fog, closing from the horizon at calm to a few paces in a blizzard, and a dome that closes the sky by the same optical depth, so a whiteout is a whiteout overhead too.
  • Spindrift — the snow plain's spindrift_gain, spindrift_speed and sparkle_gain follow the wind: loose snow tears downwind across the crust as the storm rises, and the ice glint dies in it.
  • Sound — the wind and howl loops, crossfading with the storm. The wind is a breath at calm and full in a storm; the howl is silent below the storm band and full in a blizzard.
  • Events — Rules.events.emit("storm", { storm, band, windMs }) whenever the band changes.

The blackboard

Rules.state.weather carries storm (0..1), band (calm, wind, storm or blizzard), windMs, windDir (radians, the direction the wind blows toward), visibilityM, airC, chillC, snowing (0..1) and aurora (0..1). The body slice reads chillC; the sky slice reads aurora; the HUD reads windMs and asks firstlight.forecast for tomorrow.

Fields

fieldmeaning
forceStorm-1 follows the forecast; a value in [0, 1] holds the storm there. For tests and cutscenes; leave it at -1.
snowDensityScale on the snowfall's particle rate.

comp:setForce(storm) sets forceStorm; comp:current() returns the blackboard section, which is where the shown weather is read from.

The shown storm eases toward its target rather than jumping, so a forced value takes a few seconds to arrive and the forecast's own changes roll in over minutes.

What it owns in the scene

Four temporary children under Weather, spawned at runtime and left out of the scene's record: wx_snowfall (the emitter), wx_wind and wx_howl (the loops), wx_shroud (the sky dome, drawn with firstlight.weather.shroud).

Interface

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

conforms to

zero/source-extract/v2

firstlight.weather — the weather of First Light, on the scene's `Weather` entity. Reads the clock and the run's forecast seed, and every frame writes `Rules.state.weather`, then makes the weather visible and audible: snowfall that goes horizontal in wind, fog that closes the horizon to a few paces in a blizzard, spindrift streaming over the snow plain, and the wind and howl loops crossfading with the storm. Emits `storm` on the event bus whenever the band changes. The forecast itself is `firstlight.forecast`; this component is what the forecast does to the world.

ModelAudio

clamp(x: number, lo: number, hi: number) → number

argtypedescription
xnumber
lonumber
hinumber

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

argtypedescription
anumber
bnumber
tnumber

smoothstep(e0: number, e1: number, x: number) → number

argtypedescription
e0number
e1number
xnumber

toLinear(c: number) → number

argtypedescription
cnumber

forgetPushes( ) → void

dropChildren( ) → void

spawnShroud(me: ?) → void

argtypedescription
me?

spawnSnowfall(me: ?) → void

argtypedescription
me?

spawnWind(me: ?) → void

argtypedescription
me?

spawnHowl(me: ?) → void

argtypedescription
me?

ensureChildren( ) → void

The children are found by name each time a handle is missing, so a hot reload of this file (which resets these locals while the children live on) picks them back up, and a missing one is spawned again.

eyePosition( ) → void

The camera the flakes fall around: the play camera when there is one, the editor's otherwise, the anchor itself when neither has spawned yet.

pushFog(m: ?, density: number) → void

argtypedescription
m?
densitynumber

pushShroud(density: number) → void

The sky closes the way the ground does: the dome's opacity is the fog's optical depth straight up, so it is what the fog pass would paint over the sky if the sky had a depth to paint at.

argtypedescription
densitynumber

pushSnowMaterial(m: ?) → void

argtypedescription
m?

restoreSnowMaterial( ) → void

pushSnowfall(m: ?, open: number) → void

argtypedescription
m?
opennumber

pushAudio(m: ?) → void

argtypedescription
m?

writeBlackboard(m: ?) → void

argtypedescription
m?

drive(dt: number) → void

argtypedescription
dtnumber

awake( ) → void

update(dt: number) → void

A long frame is stepped as a quarter second, so the weather eases through its bands rather than jumping one on a stall.

argtypedescription
dtnumber

editorUpdate(_dt: number) → void

With the gameplay clock held, the frame's dt is 0; the weather still eases in the editor, so the tick is measured on the wall clock instead.

argtypedescription
_dtnumber

onDestroy( ) → void

setForce(storm: number) → void

Force the storm scale for a test or a cutscene: a value in [0, 1], or -1 to follow the forecast again.

argtypedescription
stormnumber

current( ) → void

The weather right now, as the blackboard carries it: storm, band, windMs, windDir, visibilityM, airC, chillC, snowing, aurora.

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
◐
material · born here
❒asset
▲ 0↑ born
·
soundclip · pulled in
❒asset
# SoundClip Managed audio asset. `data.zaud` is the engine-native ZAUD payload the runtime decodes and plays; compression settings live in the `.metadata` `settings` block (edit to re-encode).
▲ 0↓ import
▣
module · born here
❒asset
# firstlight.rules The one module every part of First Light requires: `require("firstlight.rules")`. It holds the game's numbers (day length, temperatures, calorie burn, stove fuel rates, bear ranges, starting stores), the layout of the station and the sites around it, the shared blackboard `M.state`, the event bus `M.events`, and the clock. ## The clock `M.tick(dt)` is called once per play frame by the scene entrypoint. It advances `M.state.run.elapsed`, derives `M.state.clock` (day, fraction of day, hour, sun elevation, twilight openness) and emits `day`, `noon` and `firstLight`. `M.sleepToMorning()` jumps to 07:00 next day; `M.restore(elapsed)` rebuilds the clock from a persisted run. Pure helpers: `sunElevation(day, t)`, `twilightSeconds(day)`, `airC(day, t)`, `windChill(airC, windMs)`, `progress(day)`, `rng(seed, salt)`, `dist2d`. ## The blackboard `M.state` has one section per slice. A slice writes its own section and reads the others: `run`, `clock`, `weather`, `body`, `hut`, `stores`, `bear`, `journal`. ## Events `M.events.on(name, fn)` returns an unsubscribe function; `M.events.emit(name, payload)` calls every listener. The names and payloads are listed at the top of `init.luau`. `M.say(text, seconds)` emits `say` for the HUD.
▲ 0↑ born
▣
module · born here
❒asset
# firstlight.forecast The thirty-day forecast of First Light, as a pure function of the world's forecast seed (`Rules.state.run.seed`). The same seed gives the same thirty days on every load, so a journal line about the weather is true for the next run. ```luau local Forecast = require("firstlight.forecast") ``` ## For the HUD and the journal ```luau -- Tomorrow's barometer reading local clock = Rules.state.clock local tomorrow = Forecast.forDay(clock.day + 1) -- tomorrow.storm 0..1 on the storm scale -- tomorrow.windMs m/s -- tomorrow.airC °C at noon, before wind chill -- tomorrow.aurora 0..1 -- tomorrow.band "calm" | "wind" | "storm" | "blizzard" -- tomorrow.blizzard true on one of the trapped days Forecast.words(tomorrow.band) -- "Calm" | "Wind" | "Storm" | "Blizzard" ``` Days clamp to 1..30, so `forDay(31)` is day 30. `Forecast.blizzardStart()` names the first trapped day; the blizzard runs `Rules.BLIZZARD_DAYS` days from there, between `Rules.BLIZZARD_EARLIEST_DAY` and `Rules.BLIZZARD_LATEST_DAY`. ## The live weather What the air is doing right now is on the blackboard, written every frame by the weather component: `Rules.state.weather` carries `storm`, `windMs`, `windDir`, `visibilityM`, `airC`, `chillC`, `snowing` and `aurora`. Read it from there rather than recomputing it. `Forecast.at(day, t)` is the function behind that: neighbouring days blended across the day, gusts added from a seed-fixed phase, and the whole moment derived. `Forecast.moment(storm, day, t)` derives the same record from one storm value. ## The scale | band | storm | wind | visibility | |---|---|---|---| | calm | below 0.175 | a breath | the horizon | | wind | 0.175 .. 0.525 | 6 .. 12 m/s | hundreds of metres | | storm | 0.525 .. 0.85 | 12 .. 22 m/s | tens of metres | | blizzard | 0.85 and up | up to `Rules.WIND_MAX_MS` | a few paces | `Forecast.band(storm)`, `Forecast.windMs(storm)`, `Forecast.visibilityM(storm)` and `Forecast.snowing(storm)` are the curves behind the table. The wind on the plain always blows toward `Forecast.WIND_TOWARD` (east-north-east in xz), the way the snowfield's sastrugi and spindrift stream; `Forecast.WIND_DIR` is that as radians.
▲ 0↑ born
◐
material · born here
❒asset
▲ 0↑ born
·
soundclip · pulled in
❒asset
# SoundClip Managed audio asset. `data.zaud` is the engine-native ZAUD payload the runtime decodes and plays; compression settings live in the `.metadata` `settings` block (edit to re-encode).
▲ 0↓ import
·
scene · born here
❒asset
▲ 0↑ born
backing path · firstlight/weather.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.