Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·originates fromworld b0f23b4e-e…

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.

byPolar Night·posted 1mo ago
What it does

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.

local Forecast = require("firstlight.forecast")

For the HUD and the journal

-- 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

bandstormwindvisibility
calmbelow 0.175a breaththe horizon
wind0.175 .. 0.5256 .. 12 m/shundreds of metres
storm0.525 .. 0.8512 .. 22 m/stens of metres
blizzard0.85 and upup to Rules.WIND_MAX_MSa 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.

Interface

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

conforms to

zero/source-extract/v2

strict

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

band(storm: number) → string

The band a storm value falls in: calm, wind, storm or blizzard.

argtypedescription
stormnumber

windMs(storm: number) → number

Wind speed in m/s for a storm value: a breath at calm, WIND_MAX_MS in a blizzard.

argtypedescription
stormnumber

visibilityM(storm: number) → number

How far the eye reaches, in metres, for a storm value: the horizon at calm, a few paces in a whiteout.

argtypedescription
stormnumber

snowing(storm: number) → number

Flake density 0..1 for a storm value. Snow starts falling in the wind band and fills the air in a blizzard.

argtypedescription
stormnumber

build(seed: number) → void

Build the thirty-day forecast for `seed`. Pure: the same seed gives the same table.

argtypedescription
seednumber

forecast( ) → void

The current run's forecast (from `Rules.state.run.seed`), built once per seed.

forDay(day: number) → DayForecast

The forecast for one day of the current run: `{ day, storm, windMs, airC, aurora, band, blizzard }`. Days clamp to 1..30, so `forDay(clock.day + 1)` is tomorrow's barometer reading.

argtypedescription
daynumber

blizzardStart( ) → number

The day the blizzard begins in the current run.

gust(seed: number, day: number, t: number) → number

Gusts inside a day: three slow waves with phases fixed by the seed, so the same minute of the same day gusts the same way on every load.

argtypedescription
seednumber
daynumber
tnumber

at(day: number, t: number) → Moment

The weather at fraction `t` (0..1) of `day`: neighbouring days blended, gusts added, and the whole moment derived — wind, visibility, air and chill temperature, flake density, aurora, band.

argtypedescription
daynumber
tnumber

moment(storm: number, day: number, t: number) → Moment

Derive a whole moment from one storm value at `day`, `t` — what `at` returns, and what a forced storm reads as.

argtypedescription
stormnumber
daynumber
tnumber

words(band: string) → string

The forecast as a barometer would say it: one word per band, for the HUD and the journal.

argtypedescription
bandstring
⌬ Types
DayForecast = {Moment = {

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

7items
▣
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
backing path · firstlight/forecast.module

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬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.