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

CascadeAgents

Five strategies, from doing nothing to actively hunting the largest connected region. Each pays the same energy costs a player does, so the comparison is between strategies and not between budgets.

by◐m3rck·posted 21d ago
What it does

CascadeAgents — Phase 25, non-human baselines

Five strategies, from doing nothing to actively hunting the largest connected region. Each pays the same energy costs a player does, so the comparison is between strategies and not between budgets.

idnamewhat it does
do_nothingDo Nothingnever intervenes — the control condition
randomRandom Interventionacts at random whenever it can afford to
density_thresholdDensity Thresholdfires a controlled burn once occupancy passes a threshold
largest_clusterLargest Clusterburns the centroid of the largest connected region
firebreakFirebreakcuts a line across any region that grows too large
local Agents = require("CascadeAgents")
local rows = Agents.runAll({ seed = 20250922, width = 128, height = 128, ticks = 700 })
print(Agents.formatComparison(rows))

Headless

These run through Core.runHeadless — no rendering, no UI, no frames — so a strategy can be measured far faster than it can be watched.

Agents.makeRunner(id, opts) returns an incremental runner (stepChunk(n), progress(), finish()), and Agents.runAllChunked drives one for every strategy while yielding, which is what the instrument's B key uses. A long baseline run therefore happens behind a board that keeps moving.

Agents.largestRegionInfo(sim) gives a region's centroid and bounding box; it costs one pass over the board, so it is asked for on the agent's decision cadence and never per tick.

Interface

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

conforms to

zero/source-extract/v2

CascadeAgents — Phase 25, non-human baselines. Five strategies, from doing nothing to actively hunting the largest connected region. Each one pays the same energy costs the player does, so the comparison is between strategies and not between budgets. These run headless through Core.runHeadless: no rendering, no UI, no frames — the same rules at the speed the CPU can push them.

largestRegionInfo(sim: any) → any

--!desc Centroid and bounding box of the current largest connected --! region. Costs one pass over the board, so it is asked for on the --! agent's decision cadence, never per tick.

argtypedescription
simany

randCell(sim: any, rng: any) → any

argtypedescription
simany
rngany

make(_opts: any) → void

argtypedescription
_optsany

make(opts: any) → void

argtypedescription
optsany

make(opts: any) → void

argtypedescription
optsany

make(opts: any) → void

argtypedescription
optsany

make(opts: any) → void

argtypedescription
optsany

byId(id: string) → any

--!desc Look a strategy up by id.

argtypedescription
idstring

run(strategyId: string, opts: any) → any

--!desc Run one strategy headless and return its summary. --!arg opts { width, height, seed, ticks, decideEvery, params, radius }

argtypedescription
strategyIdstring
optsany

runAll(opts: any) → any

--!desc Run every baseline on one seed and one horizon, for the --! comparison table.

argtypedescription
optsany

formatComparison(rows: any) → string

--!desc Format a comparison table for a terminal / log.

argtypedescription
rowsany

makeRunner(strategyId: string, opts: any) → any

--!desc Prepare one strategy for incremental, yielding execution. The --! returned runner steps in chunks so a long baseline can run across --! frames instead of freezing the world.

argtypedescription
strategyIdstring
optsany

act(t: number) → void

argtypedescription
tnumber

progress( ) → void

stepChunk(count: number) → void

argtypedescription
countnumber

finish( ) → void

runAllChunked(opts: any, onProgress: any) → any

--!desc Run every baseline on one seed and horizon, yielding between --! chunks so the live world keeps drawing. `onProgress(strategyName, --! fraction, resultsSoFar)` is called as it goes.

argtypedescription
optsany
onProgressany

Sub-parts

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

15items
▣
module · born here
❒asset
# CascadeCore — the model The simulation, and nothing else. This module owns no engine API: it is pure Luau over flat arrays, so the same code steps headless (baseline strategies, batch experiments) at the speed it steps on screen. ## Shape ```lua local Core = require("CascadeCore") local sim = Core.new({ width = 256, height = 256, seed = 42, params = {} }) for _ = 1, 1000 do Core.step(sim) Core.afterTick(sim, 1 / 25) end print(Core.summary(sim).efficiency) ``` `Core.step` advances the model by one tick. `Core.afterTick` does the bookkeeping a driver wants at its own cadence — production accrual, connectivity, pressure, history samples — and takes the wall-clock seconds since the last call. A caller that only wants the physics can skip it. ## Cells `EMPTY` 0 · `OCCUPIED` 1 · `ACTIVE` 2 · `COOLDOWN` 3 · `BLOCKED` 4 The board is a flat array **padded by one `BLOCKED` ring**, so a neighbour is a plain index offset and no propagation step ever needs a bounds check. `Core.idx(sim, x, y)` maps board coordinates to that index. ## Propagating the grid `neighbor_mode` (4 or 8) fixes the neighbourhood. `propagation_mode` picks how fire crosses it: 1. **adjacency** — every occupied neighbour ignites 2. **probabilistic** — each at `spread_probability` 3. **distance** — within `spread_radius`, probability falling with distance 4. **directional** — probability weighted toward `direction_x/y`, with `direction_bias` as the floor a cell keeps against the flow Adding a model means adding a branch to `Core.rebuildNeighbors`, which fills each offset's probability once per parameter change rather than per tick. ## Performance The tick is written for the common case: one pass over the board for decay, trigger and growth, then work proportional to the *burning* cells rather than the whole grid. Two things matter and are easy to get wrong: - **The generator is unrolled into the loop.** A tick draws a number for most cells on the board, and at this VM's call costs a `bit32`-based xorshift made the simulation three times slower than the arithmetic it was producing. See `CascadeRng`. - **Thresholds are integers.** A probability is stored pre-multiplied by the generator's modulus, so the hot comparison never pays for a float divide. Measured: ~5.4 ms per tick at 256×256 on a sparse board, ~7.3 ms at the density the model settles at. ## `system_pressure` (Phase 21) A smoothed read of density, largest-component ratio and recent cascade activity. **It is observational only.** Nothing in this module reads it to decide an outcome — catastrophic events remain emergent consequences of the cellular system, and there is no branch anywhere that spawns one.
▲ 0↑ born
▣
module · born here
❒asset
# CascadeRng — deterministic pseudo-random numbers Seed-reproducible: the same seed, the same parameters and the same player actions replay the same run. ```lua local Rng = require("CascadeRng") local r = Rng.new(20250922) r.next() -- a float in [0, 1) r.int(1, 6) -- an integer in [1, 6] r.chance(0.3) -- true 30% of the time r.getState() -- snapshot the stream position ``` ## Why it is arithmetic only The obvious choice — a xorshift over `bit32` — costs about half a microsecond per draw in this VM, because every `bit32` call crosses the native boundary. A tick makes roughly one draw per cell, so that alone was **three times the cost of the entire simulation**. The Lehmer (Park–Miller) generator here is one multiply and a remainder, both exact in a double because the product stays inside 2^53: s = (s * 48271 + 11) % 2147483647 Measured at roughly ten nanoseconds per draw — fifty times cheaper than the xorshift, with the same statistical job to do. ## Period 2^31 − 2 draws, which is about twenty-five minutes of continuous simulation at full rate on a 256×256 board. Past that the stream repeats; the run is still fully deterministic, and a cellular automaton this chaotic has long since lost any resemblance to the earlier pass. ## `hash2` The same trade, for the procedural fields: a few multiplies and a remainder with every intermediate inside 2^53, so the growth field and the terrain generate identically on every machine and cost nothing.
▲ 0↑ born
▣
module · born here
❒asset
# CascadeStats — everything measured, and every way of getting it out The rule this module exists to enforce: **no number is hidden behind another.** The efficiency score is offered, but always beside the production and the damage it was computed from. ```lua local Stats = require("CascadeStats") Stats.snapshot(sim) -- one flat table of every live number Stats.strategyComparison(sim) -- Phase 13, both strategies side by side Stats.dashboard(sim) -- Phase 26, the whole run Stats.csvCascades(sim) -- Phase 27 Stats.csvSamples(sim) Stats.jsonRun(sim) ``` ## Export `Stats.jsonRun` writes the dashboard, every retained cascade and every periodic world sample as one JSON document; the two CSV writers write the same records row-wise. Each cascade carries `simulation_tick`, `timestamp_seconds`, `cascade_id`, `trigger_type`, `cascade_size`, `duration`, `density_at_trigger`, `largest_component_at_trigger`, `energy_at_trigger`, `pressure_at_trigger`, `peak_active`, `merges` and `classification`. Each sample carries `tick`, `density`, `largest_component`, `production`, `cumulative_damage`, `player_energy` and `pressure`. That is the artefact for analysing the mechanics outside the engine. ## Percentiles Computed over the retained cascade window (the last `history_capacity` events, 1000 by default), not over all time — so the readout answers "what does this world look like now", and the lifetime counters beside it answer "what has it ever done". Both are on screen; neither replaces the other.
▲ 0↑ born
backing path · CascadeAgents.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.