Skip to content
TAURION
13Block Lifecycle

Block Lifecycle

The exact per-block processing order and ongoing operations.

In plain language (for players): Taurion's world moves forward one step at a time, and one step happens every time the blockchain produces a new block (think of a block as a single tick of the game's clock — roughly a few seconds). Each tick, the game does the same chores in the same fixed order: it reads everyone's submitted actions, resolves combat, advances any timed jobs (called ongoing operations — things like prospecting a region, repairing armour, copying a blueprint, building a vehicle, or constructing a building), runs mining, then moves everyone. Because every computer running the game does these chores in the exact same order, everyone agrees on what happened — no central server decides. A timed job finishes on the specific future block it was scheduled for; until then your character or building is "busy". One important consequence: combat is resolved first each tick, so a character can be killed before the job it was working on ever completes. This document explains all of that precisely, including the data a game client reads to draw progress bars.

Jargon used below, in plain terms: vCHI (also called "Cubit") is the in-game soft currency you spend on services (see Game Overview §2 for how it is minted by burning WCHI); a blueprint original (bpo) is a reusable master plan that you get back when a job finishes, while a blueprint copy (bpc) is a one-time-use plan that is consumed; a foundation is a half-built building (it becomes a real building once enough materials are dropped into it); a fitment is an equippable upgrade module for a vehicle. Item and vehicle codes like raw a or rv st are internal names — their player-facing display names (e.g. Trimideum, Raider) are given alongside throughout, and the full mapping lives in §8.6.

This document is the authoritative reference for how the Taurion game state advances each block, and for the ongoing-operation system (timed actions such as prospecting, armour repair, blueprint copying, item construction, building construction and delayed building-config changes). It also documents the pending-state JSON that a UI consumes to render optimistic, unconfirmed moves.

All rules below come from the Game State Processor (GSP) source tree. The GSP is the single source of truth: the chain replays every move deterministically through this exact code, so the per-block order and the ongoing-operation effects described here are consensus rules, not client conventions.

Time model: there is one game "tick" per Xaya/Polygon block. Every duration in this document is measured in blocks, and the block height is the universal game clock. There is no sub-block timing.


1. The per-block game loop (UpdateState)#

Every block, the GSP calls PXLogic::UpdateState. The top-level entry (src/logic.cpp:206) wraps the SQLite database and forwards to the height/timestamp extracting overload (src/logic.cpp:77), which builds the per-block Context (chain, map, height, timestamp) and a FameUpdater, then calls the core implementation (src/logic.cpp:97).

The block-data JSON passed in has this shape (built by the libxayagame framework, not by players):

{
  "block": { "height": 1234567, "timestamp": 1718200000 },
  "admin": [ /* admin/config moves, e.g. god-mode commands */ ],
  "moves": [ /* array of player move objects for this block */ ]
}

height is a uint64; timestamp is an int64 (Unix seconds). Both are asserted to be present (src/logic.cpp:82-89).

1.1 Exact order of operations#

The core UpdateState runs these steps in this exact order every block (src/logic.cpp:97-126). The ordering is consensus-critical and is commented in the source; do not assume any other order in a client simulation.

# Step Function (file:line) What it does
1 Construct dynamic obstacle map DynObstacles dyn(db, ctx) src/logic.cpp:101 Builds the in-memory map of vehicles/buildings used for movement collision this block.
2 Process admin moves mvProc.ProcessAdmin(blockData["admin"]) src/logic.cpp:103 Applies admin/god-mode commands (only where enabled, see §6).
3 Process all player moves mvProc.ProcessAll(blockData["moves"]) src/logic.cpp:104 Parses and applies every player move in the block: account/character creation, waypoints, enter/exit, drop/pickup, start prospecting/mining, found building, change vehicle/fitments, service ops, DEX ops, coin transfers/burns. This is where most ongoing operations are created.
4 Prune damage lists fame.GetDamageLists().RemoveOld(... damage_list_blocks()) src/logic.cpp:106 Removes attacker/victim relationships older than damage_list_blocks (100 on mainnet).
5 HP update / combat AllHpUpdates(db, dyn, fame, rnd, ctx) src/logic.cpp:109src/combat.cpp:1387 Deals combat damage, applies fame for kills, processes kills (drops loot, deletes dead characters/buildings — this is where ongoings get interrupted on death, §4), and regenerates HP.
6 Ongoing operations ProcessAllOngoings(db, rnd, ctx) src/logic.cpp:110src/ongoings.cpp:148 Completes/advances every ongoing operation whose target height equals the current height (§3).
7 Mining ProcessAllMining(db, rnd, ctx) src/logic.cpp:112src/mining.cpp:41 For every actively-mining character, rolls a random yield and adds resources to cargo.
8 Movement ProcessAllMovement(db, dyn, ctx) src/logic.cpp:113 Advances every moving character one step along its path (subject to obstacles, speed, retries).
9 Enter buildings ProcessEnterBuildings(db, dyn, ctx) src/logic.cpp:119 Moves characters that requested entry (and are now adjacent/inside range) into their building.
10 Find combat targets FindCombatTargets(db, rnd, ctx) src/logic.cpp:121src/combat.cpp:402 Selects targets for next block's combat.
11 (debug) Slow validation ValidateStateSlow(db, ctx) src/logic.cpp:124 Only compiled with ENABLE_SLOW_ASSERTS (the guard #ifdef is at src/logic.cpp:123); consistency checks, no state change.

Key ordering subtleties documented in the source:

  • Damage is applied at the start of the tick, before ongoings, mining and movement (step 5 before steps 6–8). A character that will die this block dies before its mining/movement/ongoing would otherwise have progressed. Its ongoing operation is therefore cancelled in step 5 (see §4), and it is gone before step 6 runs.
  • Ongoings (step 6) finish before mining (step 7) and movement (step 8). A prospecting operation that completes this block is finished before the same block's mining pass — but a character that just finished prospecting is not yet mining (mining must be started by a separate move).
  • Enter-building (step 9) is deliberately after moves and movement but before combat targeting (src/logic.cpp:115-118): players enter "as soon as possible" (possibly the same instant the enter move confirms), and a character that enters a building this block will not be targeted by combat next block.
  • Exit-building is processed inside step 3 (move processing), immediately, not deferred — exiting takes effect at once and drops the character at a random nearby spot (src/moveprocessor.cpp:1512, src/movement.cpp).

1.2 Determinism & randomness#

Several steps consume randomness (xaya::Random& rnd): combat hit rolls, kill loot drops, mining yields, prospecting results, prize rolls. The RNG is seeded deterministically from the block, so every node computes identical results. A client cannot predict random outcomes (mining yield, prospect prize, fitment drop chance) ahead of confirmation — these must be shown as ranges, not exact values, in optimistic UI.


2. The ongoing-operation data model#

An ongoing operation is a timed action stored in the ongoing_operations table. It has a target block height at which the GSP will process it, and is linked to at most one character and/or one building.

2.1 Database row (database/ongoing.hpp:36-43, database/ongoing.cpp)#

Column Type Meaning
id int64 Unique operation ID (from the global pxd ID counter).
height int64 Block height at which this op is next processed.
character int64 | NULL Associated character ID, or NULL.
building int64 | NULL Associated building ID, or NULL.
proto blob Serialized OngoingOperation proto (the op-specific payload).

The proto (proto/ongoing.proto:142) carries:

  • start_height (uint32) — the block the operation began, set at creation (database/ongoing.cpp:32). Used by clients to compute a progress bar.
  • a oneof op selecting exactly one of the six operation types (proto/ongoing.proto:152-160).

2.2 The six operation types (proto/ongoing.proto:152-160)#

oneof field Tag Linked to Created by Section
prospection 101 character start-prospecting move §3.1
armour_repair 102 character repair service op §3.2
blueprint_copy 103 building bpcopy service op §3.3
item_construction 104 building construct service op §3.4
building_construction 105 building dropping enough materials into a foundation §3.5
building_update 106 building building owner config-update move §3.6

OngoingProspection and ArmourRepair are empty "flag" messages — the only needed datum is implicit (region from character position; target armour from character max-HP) (proto/ongoing.proto:29-50).

2.3 Query semantics (database/ongoing.cpp:142-156)#

QueryForHeight(h) selects all rows with height <= h, ordered by id. Processing then asserts each row's height equals the current height (src/ongoings.cpp:166) — there must never be a leftover row from a past block. Operations are processed in ascending id order, which is also the order in which they were created. Multiple ops at the same height are deterministic by ID (important for the building-config-update merge ordering — §3.6).

After processing, DeleteForHeight(h) removes all rows with height == h (exact match only, so a corrupt past-height row would surface as an assert rather than be silently dropped) (src/ongoings.cpp:229, database/ongoing.cpp:180-192).

Re-scheduling vs completion: some ops (blueprint copy, item construction from an original) are processed once per produced unit. On each processing pass they either re-schedule themselves to a later height (still one row, updated height) or complete and are deleted. See §3.3 and §3.4.


3. Each ongoing-operation type in detail#

Processing happens in ProcessAllOngoings (src/ongoings.cpp:148), which loads the linked character and/or building, then switches on the op type (src/ongoings.cpp:176-223). After processing a character op, it asserts the character is no longer busy (src/ongoings.cpp:225-226).

3.1 Prospection (kProspection)#

  • Created: by a start-prospecting move (src/moveprocessor.cpp:1521-1544). The move sets the region's prospecting_character to this character, clears any previous prospection result, stops the character's movement (StopCharacter, src/moveprocessor.cpp:1534), then creates an ongoing linked to the character with height = current + prospecting_blocks and mutable_prospection().
  • Duration: character.proto.prospecting_blocks (src/moveprocessor.cpp:1536), which is a per-vehicle stat. Starter vehicles use prospecting_blocks: 10 — e.g. the Red starter rv st (display name Raider) (proto/roconfig/items/vehicles_starter.pb.text:18). It is asserted > 0. Fitments (equippable upgrade modules) can modify it via a StatModifier (a multiplier/offset applied to a base stat) (proto/config.proto:203).
  • Completion (src/ongoings.cpp:178-182): calls FinishProspecting, which writes the prospection result into the region (resource type, ore amount, and any prize roll), clears prospecting_character, and clears the character's ongoing (character becomes free).
  • Interruption: if the prospecting character is killed before completion, the kill processor cancels it and clears the region's prospecting_character (src/combat.cpp:1118-1134) — the region is left un-prospected and re-prospectable.
  • Restart: a fresh start-prospecting move on an already-prospected region clears the old prospection result (src/moveprocessor.cpp:1532) and starts over (subject to prospection_expiry_blocks, 5000 mainnet / 100 regtest, governing reprospect eligibility — see config proto).

3.2 Armour repair (kArmourRepair)#

  • Created: by the repair service operation inside a building (src/services.cpp:403-418). Requirements (src/services.cpp:340-373): the character must be owned by the caller, inside this building, not already busy, and missing some armour HP.
  • Duration: ceil(missingHp / armour_repair_hp_per_block) blocks (src/services.cpp:408-411). armour_repair_hp_per_block = 100 (mainnet, proto/roconfig/params.pb.text:11). So repairing 850 missing HP → 9 blocks. The op is scheduled at current + blocksBusy.
  • Cost (paid up-front at the service op, not at completion): ceil(missingHp * armour_repair_cost_millis / 1000) vCHI (src/services.cpp:376-391). armour_repair_cost_millis = 100 (proto/roconfig/params.pb.text:12) → 1 vCHI per 10 HP.
  • Completion (src/ongoings.cpp:184-189): sets HP.armour to the regen data's max_hp.armour (full armour) and clears the character's ongoing. Confirmed by OngoingsTests.ArmourRepair (src/ongoings_tests.cpp:158-181): armour 850 → 1000, character no longer busy, op deleted.
  • Interruption: cancelled on character death like any character op (§4). No partial credit and no cost refund.

3.3 Blueprint copy (kBlueprintCopy)#

Copies a blueprint original (bpo) into blueprint copies (bpc), one copy per processing pass.

  • Created: by the bpcopy service op (src/services.cpp:713-735). The original blueprint is removed from the player's building inventory immediately (AddFungibleCount(original, -1)). An ongoing is created linked to the building with height = current + GetBpCopyBlocks(copy).
  • Payload (proto/ongoing.proto:57-72): account, original_type, copy_type, num_copies (total copies still queued).
  • Per-copy duration: GetBpCopyBlocks(bpcType) = params.construction_blocks * complexity(for_item) (src/services.cpp:739-751). construction_blocks = 1 mainnet (proto/roconfig/params.pb.text:16), 10 regtest (proto/roconfig/test_params.pb.text:11). Note: the per-complexity copy tuning param bp_copy_blocks exists in config (proto/config.proto:567) but the actual duration code path uses construction_blocks, not bp_copy_blocks (see Open questions).
  • Per-copy cost (paid up-front at the service op): bp_copy_cost * complexity per copy, summed over num copies (GetBaseCost, src/services.cpp:630-635). Unlike the duration, the cost path does use the dedicated bp_copy_cost param (bp_copy_cost = 1 mainnet proto/roconfig/params.pb.text:13, 100 regtest proto/roconfig/test_params.pb.text:8).
  • Processing (src/ongoings.cpp:43-67): each pass adds one copy_type to the inventory and decrements num_copies. If copies remain, the op re-schedules itself to current + duration with the decremented count. On the final copy, it also returns the original_type (the bpo) to the inventory.
  • Example (OngoingsTests.BlueprintCopy, src/ongoings_tests.cpp:213-256): 20 copies of bow bpc from bow bpo. Each baseDuration blocks one bow bpc is produced; after the 20th pass, the bow bpo is refunded and the op deleted. (bow here is a unit-test fixture item, not a shipped game item — real item codes and their display names are in §8.6.)
  • Interruption (building destroyed): the original_type (bpo) is added to the building's total inventory before drop rolls (src/combat.cpp:1208-1214), so the original is recoverable as floor loot (subject to BUILDING_INVENTORY_DROP_PERCENT). Copies already produced are normal inventory.

3.4 Item construction (kItemConstruction)#

Builds vehicles/fitments from a blueprint (original or copies).

  • Created: by the construct service op (src/services.cpp:917-951). Construction resources are removed up-front; if from an original blueprint, the bpo is removed once (-1); if from copies, all num copies are removed (-num). An ongoing is created linked to the building with height = current + GetConstructionBlocks(output).
  • Payload (proto/ongoing.proto:87-111): account, output_type, num_items (still to build), and optional original_type (present only when building from an original).
  • Per-item duration: GetConstructionBlocks(itm) = params.construction_blocks * complexity(itm) (src/services.cpp:955-960).
  • Two completion modes (src/ongoings.cpp:72-108):
    • From an original (has_original_type): items are produced in series, one per processing pass. Each pass adds one output_type, decrements num_items, and re-schedules at current + duration. On the final pass it also refunds one original_type (bpo). (OngoingsTests.ItemConstructionFromOriginal, src/ongoings_tests.cpp:258-305).
    • From copies (no original_type): all items are produced at once in a single pass — finished = num_items, all dispensed, op deleted. (OngoingsTests.ItemConstructionFromCopy, src/ongoings_tests.cpp:307-332).
  • Interruption (building destroyed): if from an original, the original_type is added to the building inventory before drop rolls (src/combat.cpp:1216-1222). Already-produced items are normal inventory; copies consumed up-front are not refunded.

3.5 Building construction (kBuildingConstruction)#

Upgrades a player-placed foundation into a finished building.

  • Created: a foundation starts construction automatically once enough materials have been deposited into it (MaybeStartBuildingConstruction, src/buildings.cpp:124-149). It checks every required material in construction.full_building; once all are present and there is no existing construction op, it creates an ongoing linked to the building with height = current + construction.blocks (the building type's configured build time) and sets the building's ongoing_construction back-reference.
  • Completion (FinishBuildingConstruction, src/ongoings.cpp:113-143):
    • Deducts the full_building materials from the construction inventory.
    • Transfers all remaining (surplus) construction-inventory resources into the owner's account-inventory inside the now-finished building.
    • Clears construction_inventory, sets foundation = false, clears ongoing_construction, sets age_data.finished_height = current.
    • Recomputes building combat stats via UpdateBuildingStats (foundation HP → full-building HP).
  • Example (OngoingsTests.BuildingConstruction, src/ongoings_tests.cpp:334-366): a huesli foundation with construction inventory {foo:5, bar:42, zerospace:10}; after completion the recipe consumes some, leaving {foo:2, bar:42, zerospace:0} credited to the owner; foundation=false, no ongoing_construction, finished_height=10, armour jumps from 1 to the full-building value (100 in test). (huesli, foo, bar, zerospace are unit-test fixture names, not shipped game content.)
  • Interruption (building destroyed while a foundation): the entire construction_inventory is folded into the total inventory and subject to floor-drop rolls (src/combat.cpp:1240-1241).

3.6 Building config update (kBuildingUpdate)#

A delayed owner-config change (DEX fee, service fee, etc.), delayed to prevent front-running of fee changes.

  • Created: by a building owner's config-update move (PerformBuildingConfigUpdate, src/moveprocessor.cpp:1907-1921). An ongoing is created linked to the building with height = current + params.building_update_delay, carrying the new Building.Config proto. Delay: building_update_delay = 120 blocks mainnet (proto/roconfig/params.pb.text:18), 10 regtest (proto/roconfig/test_params.pb.text:12).
  • Completion (src/ongoings.cpp:206-218): merges (not assigns) the stored new_config into the building's live config. Merge semantics mean fields unset in the update are left untouched.
  • Multiple updates at the same height: processed in id order (creation order), each merged in turn, so the last-created update wins per field. Confirmed by OngoingsTests.BuildingConfigUpdate (src/ongoings_tests.cpp:368-442): two ops at height 13 setting service_fee_percent to 3 then 4 → final value 4; an empty-config update is a no-op merge.

4. Interruption: death and destruction#

Ongoings are cancelled/cleaned up during step 5 (HP/combat) of the loop, inside the kill processor.

4.1 Character death (KillProcessor::ProcessCharacter, src/combat.cpp:1109-1162)#

  • If the character IsBusy(), its ongoing is looked up. If it is a prospection, the region's prospecting_character flag is cleared so the region can be prospected again (src/combat.cpp:1118-1134).
  • All ongoing operations linked to the character are deleted via ongoings.DeleteForCharacter(id) inside DeleteCharacter (src/combat.cpp:1073-1081).
  • Armour repair in progress is simply lost (no refund — cost was paid at start).

4.2 Building destruction (KillProcessor::ProcessBuilding, src/combat.cpp:1164-1277)#

Before deleting the building, the processor folds recoverable resources from its ongoings into the total inventory (then subject to floor-drop rolls):

  • Blueprint copy: the in-progress original_type (bpo) is returned (src/combat.cpp:1208-1214).
  • Item construction: if from an original, the original_type (bpo) is returned (src/combat.cpp:1216-1222).
  • Open DEX bids refund reserved coins to owners; open asks return their reserved items to the inventory (src/combat.cpp:1226-1236).
  • A foundation's construction_inventory is added too (src/combat.cpp:1240-1241).
  • Finally all ongoings for the building are deleted via ongoings.DeleteForBuilding(id) (src/combat.cpp:1274).

Note: building-config-update ongoings are simply discarded on destruction (nothing to refund).


5. Constants reference (mainnet vs regtest)#

All from the params proto (proto/config.proto:531-613) and the text configs. The config is built by chain (proto/roconfig.cpp:83-141): mainnet/Polygon use the base config with no merge; testnet (TEST/Mumbai) merges the testnet_merge block; regtest/Ganache merges both testnet_merge and then regtest_merge (which is where test_params.pb.text lives) on top. Since the testnet block does not override any of these params (proto/roconfig/testnet.pb.text has no params section), testnet effectively uses the mainnet values shown below, and regtest is just mainnet with test_params.pb.text applied.

Param Mainnet (params.pb.text) Regtest (test_params.pb.text) Used by
construction_blocks 1 10 bp-copy & item-construction per-complexity duration (§3.3, §3.4)
construction_cost 1 100 item-construction base cost (vCHI per complexity)
bp_copy_blocks 1 10 declared but not used by the copy duration path (see Open questions)
bp_copy_cost 1 100 blueprint-copy base cost
building_update_delay 120 10 delayed config update (§3.6)
armour_repair_hp_per_block 100 (inherits 100) repair duration (§3.2)
armour_repair_cost_millis 100 (inherits 100) repair cost (1 vCHI / 10 HP)
damage_list_blocks 100 (inherits 100) step 4 pruning
prospection_expiry_blocks 5000 100 region reprospect eligibility
character_limit 20 (inherits 20) validated each block
dex_fee_bps 300 (3%) 1000 (10%) DEX seller fee

Per-building construction time is construction.blocks from the building type (not a global param). Prospecting duration is the per-vehicle prospecting_blocks stat (10 for starters), not a global param.


6. Admin / god-mode#

Admin moves (step 2) are applied by ProcessAdmin. God-mode commands are only honored where params.god_mode is true — it is true in both mainnet params.pb.text:31 and regtest test_params.pb.text:20 configs as checked in, which lets operators inject state for testing. Designers should treat god-mode as a development/testing facility, not a player-facing mechanic.


7. Reading ongoing operations from the confirmed state#

Confirmed ongoings are exposed via the getongoings RPC (src/pxrpcserver.cpp:548-556), which returns a JSON array of all ongoing operations (GameStateJson::OngoingOperationsResultsAsArray(tbl.QueryAll()), src/gamestatejson.cpp:767-772); they also appear inside getnullstate's full state under the ongoings key (src/gamestatejson.cpp:797). The official web client fetches them through the getongoings RPC and indexes them by ID for lookup via a getOngoing(operationId) helper.

7.1 Ongoing JSON shape — one array element (GameStateJson::Convert<OngoingOperation>, src/gamestatejson.cpp:483-570)#

{
  "id": 42,
  "start_height": 1234560,
  "characterid": 17,          // present only if linked to a character
  "buildingid": 5,            // present only if linked to a building
  "operation": "bpcopy",      // see table below
  "end_height": 1234580,      // op height + per-type endDelta (see note)
  "account": "domob",         // type-specific fields follow...
  "original": "bow bpo",
  "output": { "bow bpc": 20 }
}

operation string values (src/gamestatejson.cpp:502-565):

op_case operation Extra fields
prospection "prospecting"
armour_repair "armourrepair"
blueprint_copy "bpcopy" account, original, output = {copy_type: num_copies}
item_construction "construct" account, output = {output_type: num_items}, original (if from original)
building_construction "build"
building_update "config" newconfig (the converted config proto)

end_height caveat for progress bars: end_height is the operation's next processing height plus a per-type endDelta so it reflects the true final completion, not the next pass:

  • blueprint copy: endDelta = (num_copies - 1) * GetBpCopyBlocks(copy_type) (src/gamestatejson.cpp:524-526).
  • item construction from an original: endDelta = (num_items - 1) * GetConstructionBlocks(output_type) (src/gamestatejson.cpp:546-548).
  • all other types: endDelta = 0.

A progress bar should therefore use start_heightend_height for the whole job, and (for serialized ops) start_height → row height for the current unit.

7.2 Character / building back-references#

  • A busy character carries "busy": <ongoingId> in its JSON (src/gamestatejson.cpp:283-284), set only when IsBusy() (i.e. it has an ongoing field). The official client reads vData.busy and resolves it via its getOngoing lookup.
  • A foundation under construction carries construction.ongoing = <ongoingId> (src/gamestatejson.cpp:437-438).

8. Pending (unconfirmed) state for optimistic UI#

When a player broadcasts a move, it sits in the mempool before being mined. The GSP maintains a pending state that simulates those mempool moves on top of the last confirmed block, so a UI can render optimistic results immediately. This is served by:

  • getpendingstate RPC → game.GetPendingJsonState() (src/pxrpcserver.cpp:482-487).
  • waitforpendingchange(oldVersion) RPC for long-polling pending changes (src/pxrpcserver.cpp:489-494).

The pending state is a best-effort prediction, rebuilt from scratch on every mempool change (PendingMoves::Clear, src/pending.cpp:699-704). It does not run combat, mining, movement steps, or RNG — it only reflects the intent of each parsed move. It is never authoritative; the confirmed state (§7) always wins.

8.1 How pending is built (src/pending.cpp)#

For each mempool move, PendingMoves::AddPendingMove (src/pending.cpp:706-725) builds a Context at confirmed-height + 1 (src/pending.cpp:717-718), then PendingStateUpdater::ProcessMove (src/pending.cpp:647-691) parses it exactly like a real move would be parsed, but instead of mutating the database it records the intent into the in-memory PendingState. The top-level dispatch order (src/pending.cpp:668-690):

  1. Coin transfer/burn (ParseCoinTransferBurnAddCoinTransferBurn).
  2. DEX operations (TryDexOperations).
  3. (only if the account is already initialised) character updates, character creation, building updates, and service operations.

Character-update intents are parsed in PerformCharacterUpdate (src/pending.cpp:566-633): prospecting, mining, pickup/drop, waypoints, waypoint-extension, enter/exit building, found building, change vehicle, set fitments, and mobile refining.

8.2 Conflict heuristics (important for client correctness)#

The pending simulator encodes "best-guess" rules for mutually-exclusive intents in the same/nearby blocks. A client should mirror these expectations:

  • Pending prospecting beats waypoints: if a character is pending to start prospecting, incoming waypoints are ignored (src/pending.cpp:128-134).
  • Setting waypoints cancels pending mining: starting movement stops mining, so pending mining is cleared when waypoints are added (src/pending.cpp:139-145).
  • Pending prospecting clears pending waypoints (src/pending.cpp:216-222) and blocks pending mining (src/pending.cpp:235-241).
  • Pending waypoints block pending mining (src/pending.cpp:243-249).
  • Waypoint extension (wpx) appends to the confirmed waypoints (copied in if not replacing) (src/pending.cpp:147-157).
  • Region IDs are stable: pending prospecting/mining region IDs are derived from the character's current position, which cannot change within the pending rebuild, so duplicate intents must reference the same region (asserted) (src/pending.cpp:202-211, src/pending.cpp:251-258).
  • Found-building dedupe: only the first pending found-building move per character is kept (src/pending.cpp:271-285).
  • Pickup in a foundation is ignored in pending (src/pending.cpp:585-590).

8.3 Pending state JSON (PendingState::ToJson, src/pending.cpp:517-542)#

{
  "buildings":  [ { "id": 5, ... } ],
  "characters": [ { "id": 17, ... } ],
  "accounts":   [ { "name": "domob", ... } ],
  "newcharacters": [
    { "name": "domob", "creations": [ { "faction": "r" } ] }
  ]
}

Per-character entry (CharacterState::ToJson, src/pending.cpp:397-442)

{
  "id": 17,
  "waypoints": [ {"x": 10, "y": -3}, {"x": 11, "y": -3} ],
  "enterbuilding": 5,            // building id, or null to cancel a pending enter
  "exitbuilding": { "building": 5 },
  "drop": true,
  "pickup": false,
  "prospecting": 1234,           // region id (only if pending prospecting)
  "mining": 1234,                // region id (only if pending mining)
  "foundbuilding": { "type": "r rt", "rotationsteps": 2 },
  "changevehicle": "rv st",
  "fitments": [ "fit a", "fit b" ]
}

(In the example above, rv st is the Red starter vehicle Raider; r rt, fit a, fit b are illustrative placeholder codes. See §8.6 for the real code-to-display-name mapping.)

Field rules:

  • waypoints present only if a waypoint intent was parsed; drop and pickup are always present booleans (src/pending.cpp:425-426).
  • enterbuilding: an integer building ID, or JSON null if the move is an enter cancellation (enterBuilding == EMPTY_ID) (src/pending.cpp:411-417).
  • exitbuilding is an object { "building": <currentBuildingId> } (src/pending.cpp:418-423).
  • prospecting/mining are region IDs, omitted when not pending.
  • foundbuilding, changevehicle, fitments omitted unless present.

Per-building entry (BuildingState::ToJson, src/pending.cpp:382-395)

{ "id": 5, "newconfig": { "dex_fee_bps": 50 }, "sentto": "andy" }

newconfig is the merged pending config delta (omitted if empty); sentto is the pending new owner for a transfer (omitted if empty).

Per-account entry (AccountState::ToJson, src/pending.cpp:453-489)

{
  "name": "domob",
  "coinops": {
    "minted": 0,
    "burnt": 100,
    "transfers": { "andy": 250 }
  },
  "serviceops": [ /* service-op pending JSON, see §8.4 */ ],
  "dexops":     [ /* DEX-op pending JSON, see §8.5 */ ]
}

coinops, serviceops, dexops are each omitted when empty.

8.4 Service-op pending JSON (ServiceOperation::ToPendingJson, src/services.cpp:1106-1125)#

Every service op produces a common envelope plus a type-specific body:

{
  "type": "bpcopy",
  "building": 5,             // present if building-based
  "character": 17,           // present if character-based
  "cost": { "base": 20, "fee": 1 },
  ... type-specific fields ...
}

Type-specific bodies (SpecificToPendingJson):

type Source Extra fields
"refining" src/services.cpp:231 refining input/output
"armourrepair" src/services.cpp:397 character
"reveng" src/services.cpp:538 reverse-engineering fields
"bpcopy" src/services.cpp:703 original, output = {copy: num}
"construct" src/services.cpp:907 blueprint, output = {output: num}

cost.base is the service's base cost; cost.fee is the building owner's service fee (src/services.cpp:1116-1122). A UI can show exact predicted cost from this.

8.5 DEX-op pending JSON (DexOperation::ToPendingJson, src/trading.cpp)#

op Source Shape
"transfer" src/trading.cpp:198-204 { "op":"transfer", "building":5, "item":"raw a", "num":10, "to":"andy" }
"bid" / "ask" src/trading.cpp:331-337 { "op":"bid", "building":5, "item":"raw a", "num":10, "price":3 }
"cancel" src/trading.cpp:551-557 { "op":"cancel", "order":99 }

The item-operation base (PendingItemOperation, src/trading.cpp:142) provides building, item, num; new orders add op and price.

8.6 Display names for items#

Internal item codes used throughout the JSON (e.g. raw a, bow bpo, rv st) map to human-readable names — the display names are the ones shown in the game UI. Examples: Trimideum (raw a), Talon (raw b), Agarite (mat a), Raider (rv st, Red starter), Barracuda (bv st, Blue starter).


9. Practical client checklist#

  • Drive everything off block height. Compute progress as (currentHeight - start_height) / (end_height - start_height) and account for the per-unit re-scheduling of serialized ops (§3.3, §3.4, §7.1).
  • Treat pending as optimistic only. Merge pending entries onto confirmed state for display, but expect the confirmed state to differ (especially for RNG outcomes: mining yield, prospect prizes, fitment-drop survival).
  • Respect pending conflict heuristics (§8.2) so the optimistic UI does not show a character both prospecting and moving, etc.
  • Show costs from pending service/DEX JSON (cost.base + cost.fee) — these are computed by the same code that will run on confirmation.
  • Combat happens first each block. A "busy" character can still die before its ongoing completes; do not assume an ongoing will finish just because its end_height was reached.

Open questions#

  1. bp_copy_blocks is dead config; bp_copy_cost is live. Verified against source: the copy cost path does use bp_copy_cost (GetBaseCost, src/services.cpp:630-635, reading params.bp_copy_cost()), so that param is a live tuning knob. The copy duration path, however, uses params.construction_blocks * complexity (GetBpCopyBlocks, src/services.cpp:739-751) and never reads bp_copy_blocks (proto/config.proto:567) — so bp_copy_blocks is effectively vestigial and tuning it has no effect on copy timing. A designer should decide whether to wire bp_copy_blocks into GetBpCopyBlocks or remove it from the config to avoid confusion.
  2. Prospecting prospection_expiry_blocks interaction with reprospecting is referenced by the config but the exact eligibility check (cooldown vs. resource exhaustion) lives in the prospecting/region code, not the ongoing-processing code reviewed here; confirm the precise reprospect gate in src/prospecting.cpp / database/region.cpp for a complete prospecting lifecycle doc.
  3. God-mode in shipped configs: god_mode: true is set in both the mainnet params.pb.text and regtest configs as checked into the repo (proto/roconfig/params.pb.text:31). Whether the released mainnet binary disables it (e.g. via a build/CI override) is not determinable from the source tree alone and should be confirmed with ops before relying on its absence.