Alerts & logging
What it models
Section titled “What it models”Eight threshold checks run at the end of every tick, after every effect has landed — one per kind of bloom among them. Each holds a flag: it writes one line when the reading crosses into trouble, stays quiet while the condition persists, and clears silently when the reading comes back. Alongside them the log collects everything the tank did that a reader would want to see afterwards.
Status
Section titled “Status”| Mechanic | Status | Why |
|---|---|---|
| The latch | settled | One line per crossing, a flag that lives in state, and no repetition while the condition holds. It does exactly what it says. |
| Log entry shape | settled | Free text for humans with its volumes, temperatures and coverages kept as quantities, a typed event discriminator for code, and a count wherever an entry accounts for more than one organism. |
| Water-quality thresholds | settled | Free NH₃, nitrite, nitrate and oxygen alert at their tolerance edge, and hardiness moves each fish’s own harm edge out from it. Ammonia reads the free fraction, so the total it takes moves with pH and temperature. |
| Water-level threshold | settled | Alerts where its harm starts, read off the tunable that starts it, so retuning the harm moves the alert with it. |
| Bloom thresholds | settled | A bloom harms nothing; it takes light. Each kind alerts on its level (The alerts), so the keeper is told which kind to answer and with which verb, and a bare tank alerts on coverage by the same rule. |
| CO₂ threshold | scaffolding | The line keepers treat as too much, with nothing behind it — no organism in the engine reads dissolved CO₂ as a stressor. |
| Behaviour on a drained tank | scaffolding | Concentrations read 0 when there is no water and the water-level check requires water > 0, so the alert layer goes silent exactly when the tank is empty. Fish loads follow the same zeroes, so a drained tank clears them as clean water would and charges no flow — its water-level stressor carries it. |
| Clearing | missing | A condition resolving writes nothing. The log records that a tank got into trouble and never that it got out. |
| Retention | missing | The array is appended to and never trimmed, so a long-running tank grows its log without bound. |
| Log persistence | missing | Logs are not saved. A reload starts a fresh history while the latch flags survive it, so an alert already tripped never re-fires its line for the same episode. |
How it works
Section titled “How it works”Two channels
Section titled “Two channels”| Channel | Lives in | Shape | Survives a save |
|---|---|---|---|
| Alert flags | state. | one boolean per condition | yes |
| Log | state. | an append-only array of entries | no |
The flags are what make an alert stateful. The check compares the reading against its threshold, compares that against the flag, and emits a line only on the transition into trouble.
The alerts
Section titled “The alerts”| Alert | Fires when | Where damage actually starts |
|---|---|---|
| Water level low | below water of capacity, and only while some water remains | at the line: fish take water-level damage below it |
| High ammonia | free NH₃ above 0.02 ppm | past the line for every fish, further out the hardier it is |
| High nitrite | above 0.5 ppm | the same |
| High nitrate | above 80 ppm | the same |
| Low oxygen | below 4 mg/L | the same, below the line |
| High CO₂ | above 40 mg/L, the level keepers treat as too much | nowhere — nothing reads it |
| Green water · Film | the kind’s level passes 1: it covers more than 30 of its habitat, or takes more than 25 % of the plants’ light — its share at each plant’s mean leaf, weighted by leaf area | nowhere — a bloom takes light, and a plant starves only past its own edge |
The four water-quality alerts fire before any fish takes damage; water level fires exactly where its damage starts; the blooms and CO₂ have no harm of their own behind them. Each bloom alert states the figure further past its line and names the action its habitat is taken out by: a water change for green water, a scrub for film. Ammonia reads the unionized fraction of the total, so the same test-kit reading crosses it sooner the higher the pH and the warmer the water:
| pH at 25 °C | Total ammonia at the line |
|---|---|
| 6.5 | ≈ 11 ppm |
| 7.0 | ≈ 3.6 ppm |
| 7.5 | ≈ 1.1 ppm |
| 8.0 | ≈ 0.37 ppm |
Every surface bands the total-ammonia reading at that moving line.
What gets logged
Section titled “What gets logged”The log is an event stream, not a time series. A quiet tank running for a thousand ticks writes nothing at all: no readings are sampled, no summary is emitted per hour, and nothing is written unless something happened.
| Source | Written when |
|---|---|
simulation | The tank is built; a fish or plant dies; a brood laid, a hatch or a birth; an offshoot |
user | Any applied action — a refused one logs nothing |
scrub | The scrub action, filed under its own channel |
evaporation · nitrogen-cycle · gas-exchange · algae | The alert lines, each under the system whose reading tripped |
Severity has two values. Every alert line and every death is a warning;
everything else is info.
The entry
Section titled “The entry”| Field | Holds |
|---|---|
tick | The hour it happened, which is what makes the log reconstructable |
source | The system that emitted it |
severity | info or warning |
message | Free text, for humans only; {0}, {1}… stand for its quantities |
quantities | Present where a line states a volume, a temperature or a bloom’s coverage, kept in litres, °C and its 0–100 mass |
event | Present only on the lifecycle moments a caller reacts to |
count | Present where one entry accounts for many organisms |
A figure the reader has a unit for — a water change’s volumes, the tank and room
a run was created with, a heater’s target — stays a quantity rather than text, so
each reader renders the line in its own units: logText(entry, format), metric
unless handed a format.
Eight typed events exist: eggs-laid, eggs-hatched, fry-born,
fish-died, plant-died, plant-propagated, algae-died and fry-sold.
Four of them carry a count — a laying, a hatch, a live birth and a fry sale —
because the number is the point. Deaths, offshoots and a bloom’s die-back are one entry per
organism, so they need none; a die-back states the coverage it took instead, so
a reader can weigh a crash against a thin bloom fading. Only a bloom there was
any of to see logs one: spores starving in a dark tank die back as dust,
unlogged.
The discriminator is the contract. A caller that parses the free text is reading a field that exists to be rewritten.
The log as the tank’s history
Section titled “The log as the tank’s history”Every line carries the tick it happened at, so the array is a reconstructable timeline of everything the tank did. It is also the only such record: the state is a snapshot of one hour, carrying no more past than the last day’s light.
That makes two absences load-bearing. The array is never trimmed, so it grows for as long as the tank runs. And it is not part of the persisted state, so saving and reloading gives a tank with its full biology intact and no memory of how it got there.
Key tunables
Section titled “Key tunables”waterLevelAlertLine fires at a tunable, waterLevelStressThreshold, listed
under Livestock. The rest are
module constants; the four water-quality lines are the
fish tolerance edges, defined with Livestock.
| Constant | Meaning | Value |
|---|---|---|
FREE_ | Free (unionized) NH₃ at which the alert fires | 0.02 ppm |
NITRITE_ | Nitrite at which the alert fires | 0.5 ppm |
NITRATE_ | Nitrate at which the alert fires | 80 ppm |
OXYGEN_ | Dissolved oxygen below which the alert fires | 4 mg/L |
HIGH_ | Dissolved CO₂ above which the alert fires | 40 mg/L |
PLANT_ | Share of the plants’ light a bloom alerts past | 25 % |
BLOOM_ | Coverage a bloom alerts past | 30 |
| Neighbour | Read | Written |
|---|---|---|
| Water & gases | oxygen and co2, as concentrations | — |
| Nitrogen cycle | ammonia, nitrite and nitrate, converted from mass to ppm against standing water | — |
| Environment | water against tank. | — |
| Livestock | water, the level’s line | — |
| Algae | Every kind’s mass directly — algae is an organism, not a resource | — |
| Plants · Light | The share of the planting’s light each kind takes | — |
| Plants | — | Receives a plant-died entry per plant lost and a plant-propagated entry per offshoot |
| Algae | — | Receives an algae-died entry per die-back of a bloom that showed, naming the kind and stating the coverage it took |
| Livestock | — | Receives death entries and one per brood laid, hatch or birth |
| Actions | — | Receives one entry per applied action |
| State & persistence | — | alert is persisted; logs are not |
Source
Section titled “Source”src/simulation/alerts/ — the registry and one module per condition;
src/simulation/core/ — the entry shape and the typed event union.