ServicesBiome

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_time reads "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-id is not validated at create time, so a typo returns 201 and the biome silently falls back to its checkpoint horizon rather than reporting an error. And deleting the Almanac world rewinds a bound biome to 0.0 on its next read, with a 200 and 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 at carrying_capacity; sizes floor at zero.

  • Interactions couple two populations - predator_prey (source preys on target), competition, or symbiosis - 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_permissions otherwise); 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-if

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

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

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

Reference

On this page