World stages

Keep long-term numbers (oxygen, heat, population) as counters with bands, derived counters, and stages reached when counters pass marks: config, actions, conditions, the stage.reach hook.

Source: module-kits:kits/world_stages/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs world-stagesMCP tool: gripforge_docs { "topic": "world-stages" }

Counters and progress stages (world.stages) #

What it is for #

world.stages is the long game of a sandbox: numbers that move, and marks along the way. The kit does not know what a number means. Counters are plain numbers per id, pushed up or down by machines, scripts or other kits, kept between a min and a max. Derived counters are a sum, or the share of one counter in a group. A stage is reached once, when all its conditions hold, and runs actions (unlock a recipe or a structure, set a variable, start a mission). Use it for terraforming, a growing town, corruption, or any long-term goal. The progress panel is a ui.webui fragment shown in the HUD.

Turn it on #

{ "id": "world.stages", "config": {
  "counters": [{ "id": "oxygen", "label": "Oxygen", "max": 100 }, { "id": "water" }],
  "index": { "id": "ti", "label": "Index" } } }

Stages are documents of the world_stages collection.

First example #

<!-- test: kits/game_engine/host/tests/world_stages.rs::stages_are_reached_once_and_run_their_actions --> Two stages on the counter oxygen (max 100):

{ "id": "air", "name": "Air", "counter": "oxygen", "atLeast": 50,
  "actions": [{ "type": "add_counter", "counter": "water", "amount": 7 }] }
{ "id": "full", "name": "Full", "counter": "oxygen", "atLeast": 100 }

add("oxygen", 60) reaches Air once: stage.reached is emitted and water gains 7. add("oxygen", 500) is clamped to 100 and reaches Full; Air is never reached again. The variables stage.current and stage.next follow.

Settings and defaults #

SettingTypeDefaultMeaning
countersarray (max 24)[]Counter definitions: id, label, unit, start, color, min, max, target ([low, high] band), world. Other counters appear when something pushes them.
derivedarray (max 24)[]Read-only counters: sum: [ids], or share: { of, among } times scale. Usable wherever a counter id is.
indexobjectnoneOne counter that is the sum of of (default: all): id, label, of.

A stage document is { id, name, counter, atLeast?, atMost?, when?, actions?, world? }. counter + atLeast is a mark to rise to, atMost a mark to come down to, both a range. when is a list of { counter, atLeast?, atMost? } that must all hold at once (a balance). A stage reached stays reached.

Services, events, variables #

  • world.stages (service): counters(), value(id), rate(id), add(id, amount) (a gain is scaled by multipliers, a loss is not; nothing on a derived id), set(id, value), multiply(id, factor, source) (a lasting multiplier on what a counter gains), multiplier(id), band(id), inBand(id) (true without a band), stages(), current(), next(), scope(id), scopeId().
  • stage.reached (event): { stage, name, counter, value }, once per stage.
  • counter.changed (event): { counter, value }, at most twice a second per counter, derived ones included.
  • Variables: stage.current, stage.next, stage.progress, and counter.<id>.
  • Action add_counter ({ counter, amount }); conditions counter_at_least, counter_at_most, counter_between, stage_reached.
  • HUD view progress: the stage reached, the way to the next one and each counter with its rate and band.

Progress of a rising condition starts from the last mark passed on the same counter; of a falling one, from where the counter was when the previous stage was reached.

Hooks and modding #

stage.reach (hook): { stage, name, counter, value, actions }, asked right before a stage is reached. Mutable actions (false: reached and told, actions do not run). Cancellable: the stage is not reached; it is asked again once a counter of its conditions has moved.

{ "id": "story_gate", "hook": "stage.reach", "match": { "stage": "blue_sky" }, "if": [{ "var": "story.open", "op": "==", "value": false }], "cancel": true }

A mod may override counters, derived and index, and add, patch or remove documents of world_stages.

Worlds #

With several worlds (travel.worlds), scope(id) puts the live counters, stages, multipliers and baselines aside and takes the set kept for id (a fresh one the first time). A counter or stage with world exists only in that world. A world put aside is frozen. All sets are saved.

Engines #

The logic is the same Rust implementation everywhere; the progress panel is a ui.webui fragment.

When it does not do what you expect #

  • A stage never fires: its conditions were never all true at once, or a stage.reach tap cancels it.
  • add does nothing on a derived counter: derived counters are read-only.
  • A counter does not rise past a number: it has a max.

Limits #

It does not decide what counters mean or produce them; machines, scripts or other kits push them.

Versions #

Kit 1.5.0; it provides world.stages 1.3.0 and requires game.engine@^1.