Biome
Population dynamics as an API: a biome holds coupled populations (species, resources, agent pools) with birth/death rates and carrying capacity, interaction terms couple them (Lotka-Volterra predator-prey, competition, symbiosis), and gameplay harvest events and designer interventions apply pressure. Nothing ticks in the background - state is integrated deterministically on read, RK4 from the last checkpoint.
When to reach for it: wildlife that collapses if players over-hunt, fish stocks that deplete and recover, NPC labor pools, zombie-horde difficulty, bot-trader populations for economy testing.
When not to: discrete per-entity lifecycles belong to FSM; a plain shared counter belongs to KV Store; resources that accrue on a schedule rather than evolve by ODE belong to Chrono.
Concepts
-
A biome is the simulation unit - coupled populations sharing one time base and one integrator (RK4 by default, Euler optional).
-
Time is virtual hours - every timestamp is a float on the biome's own timeline. A biome bound to an Almanac world with
use_virtual_timereads "now" from that world's clock; otherwise "now" is the latest checkpoint, and the model only moves when asked as-of a later time or the checkpoint advances. The binding is live: pausing the Almanac clock freezes the biome's "now" until it resumes. Almanac time can never pull a biome backwards past its own checkpoint, since the resolved time is the later of the two.Two sharp edges on that binding, both verified.
--world-idis not validated at create time, so a typo returns201and the biome silently falls back to its checkpoint horizon rather than reporting an error. And deleting the Almanac world rewinds a bound biome to0.0on its next read, with a200and no warning - so treat a bound world as a dependency you do not delete while biomes point at it. -
Populations are slug-addressed pools (
species,resource,agent_pool) with birth/death rates per virtual hour. Logistic growth caps atcarrying_capacity; sizes floor at zero. -
Interactions couple two populations -
predator_prey(source preys on target),competition, orsymbiosis- via a coefficient map. -
State is computed on read - a deterministic integration from
(checkpoint, parameters, events, elapsed time); same inputs, same trajectory, replayable. -
Pressure is append-only - harvest events (deduplicated by
event_id) and interventions are logged and applied at their virtual timestamps during integration. -
Mutations are admin-gated - all writes except harvest reporting need a platform admin token (
403 insufficient_permissionsotherwise); reads and harvest are open to any authenticated caller.
Build a model
snug biome create --name "docs4-valley"
snug biome populations add -b <biome_id> -s pop_deer -n "Red Deer" \
--carrying-capacity 5000 --birth-rate 0.8 --death-rate 0.2 --initial-size 4200
snug biome populations add -b <biome_id> -s pop_wolf -n "Grey Wolf" \
--carrying-capacity 800 --birth-rate 0.3 --death-rate 0.4 --initial-size 120
snug biome interactions add -b <biome_id> -t predator_prey \
-s pop_wolf -d pop_deer -c '{"predation_rate":0.0006,"conversion_efficiency":0.1}'create returns a generated biome_id; population IDs are
{biome_id}:{slug} and every other command accepts the slug. Re-adding a
slug is 409 population_already_exists. biome list takes the
shared search grammar.
Read the state
snug --output json biome state -b <biome_id> # "now"
snug --output json biome state -b <biome_id> --at-virtual 48 # as-of / what-ifOver HTTP the same read is enveloped; captured live at virtual hour 72, after the harvest and restock recorded below:
curl -H "Authorization: Bearer $SNUG_API_TOKEN" \
"http://localhost:4000/api/v1/biomes/<biome_id>/state?at_virtual=72"{
"status": 200,
"msg": "OK",
"data": {
"at_virtual": 72.0,
"populations": [
{ "id": "...:pop_wolf", "size": 800.0, "trend": "stable", "by_region": null },
{ "id": "...:pop_deer", "size": 930.497..., "trend": "rising", "by_region": null }
]
}
}An explicit at_virtual is a pure what-if: it never stamps alert state or
fans out crossings. Only the authoritative "now" read and checkpoint
advances fire threshold alerts.
Harvest pressure
Gameplay reports kills and spawns as signed deltas at a virtual time. Not admin-gated.
snug biome harvest -b <biome_id> -p pop_deer --delta=-12 \
-k player_hunt --event-id kill-001 --at-virtual 2The first run returned { "accepted": 1, "duplicates": 0 }; the identical
command again returned { "accepted": 0, "duplicates": 1 } - event_id
makes ingestion idempotent. Write negative deltas as --delta=-12; a
space-separated -12 is parsed as a flag and rejected client-side. Kinds
are player_hunt, resource_gather, npc_consume, spawn; an unknown
slug is 404 population_not_found. The HTTP endpoint batches up to 500
events and answers 202.
Interventions
Designer actions with effective-from semantics (admin token):
snug biome intervene -b <biome_id> --type restock --population pop_deer \
--amount 2000 --effective-from-virtual 50restock adds, cull removes, and parameter_change patches parameters
(e.g. intrinsic.birth_rate) from the given virtual time onward. The log
replays in timestamp order during integration - verified: a state read
past hour 50 shows the herd jump, then get eaten back down by the wolves.
Projections
Forward integration over a horizon, with warnings, equilibrium estimates, and a downsampled trajectory for charting:
snug --output json biome project -b <biome_id> --horizon-virtual-hours 288 --samples 4{
"equilibria": { "...:pop_deer": 885.67..., "...:pop_wolf": 800.0 },
"trajectory": [ { "at_virtual": 0.0, "sizes": { ... } }, ... ],
"warnings": [ {
"type": "OVERPOPULATION_RISK", "population_id": "...:pop_wolf",
"eta_virtual_hours": 288.0, "confidence": 0.7,
"cause": "population reaches carrying capacity within the horizon"
} ]
}Warning types are EXTINCTION_RISK, OVERPOPULATION_RISK, and
OSCILLATION (predator-prey limit cycles, reported with the pair). A
horizon past the configured maximum fails with 400 invalid_request: horizon exceeds maximum of 8760 virtual hours.
Checkpoints
Advancing the checkpoint folds state forward, prunes folded harvest events, and is the authoritative point where alerts fire (admin token):
snug --output json biome checkpoint -b <biome_id> --at-virtual 48{
"biome_id": "...", "at_virtual": 48.0, "populations_advanced": 2,
"alerts": [ { "band": "collapse", "direction": "entered", "size": 892.34..., ... } ]
}A target earlier than the current checkpoint is rejected with
400 invalid_request: requested time precedes the current checkpoint,
while re-sending the current time is an idempotent no-op. A recurring
server-driven checkpoint chain exists via BIOME_CHECKPOINT_WEBHOOK_*
(off by default).
Threshold alerts
Alert bands, crossing detection, and the PubSub/WebSocket/webhook fanout have their own page: Threshold alerts.
Limits and configuration
Per-biome caps default to 64 populations and 256 interactions, integration
is bounded at 100000 steps per evaluation, projections at 8760 virtual
hours, and harvest events expire after 30 days - all tunable via the
BIOME_* variables in the
Biome CONFIG reference.