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 likeraw aorrv stare 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:109 → src/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:110 → src/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:112 → src/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:121 → src/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 opselecting 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'sprospecting_characterto 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 withheight = current + prospecting_blocksandmutable_prospection(). - Duration:
character.proto.prospecting_blocks(src/moveprocessor.cpp:1536), which is a per-vehicle stat. Starter vehicles useprospecting_blocks: 10— e.g. the Red starterrv st(display name Raider) (proto/roconfig/items/vehicles_starter.pb.text:18). It is asserted> 0. Fitments (equippable upgrade modules) can modify it via aStatModifier(a multiplier/offset applied to a base stat) (proto/config.proto:203). - Completion (
src/ongoings.cpp:178-182): callsFinishProspecting, which writes the prospection result into the region (resource type, ore amount, and any prize roll), clearsprospecting_character, and clears the character'songoing(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
prospectionresult (src/moveprocessor.cpp:1532) and starts over (subject toprospection_expiry_blocks, 5000 mainnet / 100 regtest, governing reprospect eligibility — see config proto).
3.2 Armour repair (kArmourRepair)#
- Created: by the
repairservice 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 atcurrent + 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): setsHP.armourto the regen data'smax_hp.armour(full armour) and clears the character'songoing. Confirmed byOngoingsTests.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
bpcopyservice 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 withheight = 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 = 1mainnet (proto/roconfig/params.pb.text:16),10regtest (proto/roconfig/test_params.pb.text:11). Note: the per-complexity copy tuning parambp_copy_blocksexists in config (proto/config.proto:567) but the actual duration code path usesconstruction_blocks, notbp_copy_blocks(see Open questions). - Per-copy cost (paid up-front at the service op):
bp_copy_cost * complexityper copy, summed overnumcopies (GetBaseCost,src/services.cpp:630-635). Unlike the duration, the cost path does use the dedicatedbp_copy_costparam (bp_copy_cost = 1mainnetproto/roconfig/params.pb.text:13,100regtestproto/roconfig/test_params.pb.text:8). - Processing (
src/ongoings.cpp:43-67): each pass adds onecopy_typeto the inventory and decrementsnum_copies. If copies remain, the op re-schedules itself tocurrent + durationwith the decremented count. On the final copy, it also returns theoriginal_type(thebpo) to the inventory. - Example (
OngoingsTests.BlueprintCopy,src/ongoings_tests.cpp:213-256): 20 copies ofbow bpcfrombow bpo. EachbaseDurationblocks onebow bpcis produced; after the 20th pass, thebow bpois refunded and the op deleted. (bowhere 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 toBUILDING_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
constructservice op (src/services.cpp:917-951). Construction resources are removed up-front; if from an original blueprint, thebpois removed once (-1); if from copies, allnumcopies are removed (-num). An ongoing is created linked to the building withheight = current + GetConstructionBlocks(output). - Payload (
proto/ongoing.proto:87-111):account,output_type,num_items(still to build), and optionaloriginal_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 oneoutput_type, decrementsnum_items, and re-schedules atcurrent + duration. On the final pass it also refunds oneoriginal_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).
- From an original (
- Interruption (building destroyed): if from an original, the
original_typeis 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 inconstruction.full_building; once all are present and there is no existing construction op, it creates an ongoing linked to the building withheight = current + construction.blocks(the building type's configured build time) and sets the building'songoing_constructionback-reference. - Completion (
FinishBuildingConstruction,src/ongoings.cpp:113-143):- Deducts the
full_buildingmaterials 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, setsfoundation = false, clearsongoing_construction, setsage_data.finished_height = current. - Recomputes building combat stats via
UpdateBuildingStats(foundation HP → full-building HP).
- Deducts the
- Example (
OngoingsTests.BuildingConstruction,src/ongoings_tests.cpp:334-366): ahueslifoundation 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, noongoing_construction,finished_height=10, armour jumps from 1 to the full-building value (100 in test). (huesli,foo,bar,zerospaceare unit-test fixture names, not shipped game content.) - Interruption (building destroyed while a foundation): the entire
construction_inventoryis 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 withheight = current + params.building_update_delay, carrying the newBuilding.Configproto. Delay:building_update_delay = 120blocks mainnet (proto/roconfig/params.pb.text:18),10regtest (proto/roconfig/test_params.pb.text:12). - Completion (
src/ongoings.cpp:206-218): merges (not assigns) the storednew_configinto the building's live config. Merge semantics mean fields unset in the update are left untouched. - Multiple updates at the same height: processed in
idorder (creation order), each merged in turn, so the last-created update wins per field. Confirmed byOngoingsTests.BuildingConfigUpdate(src/ongoings_tests.cpp:368-442): two ops at height 13 settingservice_fee_percentto 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'sprospecting_characterflag 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)insideDeleteCharacter(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_inventoryis 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::OngoingOperations → ResultsAsArray(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_height → end_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 whenIsBusy()(i.e. it has anongoingfield). The official client readsvData.busyand resolves it via itsgetOngoinglookup. - 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:
getpendingstateRPC →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):
- Coin transfer/burn (
ParseCoinTransferBurn→AddCoinTransferBurn). - DEX operations (
TryDexOperations). - (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:
waypointspresent only if a waypoint intent was parsed;dropandpickupare always present booleans (src/pending.cpp:425-426).enterbuilding: an integer building ID, or JSONnullif the move is an enter cancellation (enterBuilding == EMPTY_ID) (src/pending.cpp:411-417).exitbuildingis an object{ "building": <currentBuildingId> }(src/pending.cpp:418-423).prospecting/miningare region IDs, omitted when not pending.foundbuilding,changevehicle,fitmentsomitted 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_heightwas reached.
Open questions#
bp_copy_blocksis dead config;bp_copy_costis live. Verified against source: the copy cost path does usebp_copy_cost(GetBaseCost,src/services.cpp:630-635, readingparams.bp_copy_cost()), so that param is a live tuning knob. The copy duration path, however, usesparams.construction_blocks * complexity(GetBpCopyBlocks,src/services.cpp:739-751) and never readsbp_copy_blocks(proto/config.proto:567) — sobp_copy_blocksis effectively vestigial and tuning it has no effect on copy timing. A designer should decide whether to wirebp_copy_blocksintoGetBpCopyBlocksor remove it from the config to avoid confusion.- Prospecting
prospection_expiry_blocksinteraction 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 insrc/prospecting.cpp/database/region.cppfor a complete prospecting lifecycle doc. - God-mode in shipped configs:
god_mode: trueis set in both the mainnetparams.pb.textand 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.