Game State Schema
The complete shape of the world state returned by the GSP.
In plain language (start here if you are a player)#
Taurion has no central server that "owns" the game. Instead, every player's computer can run a small program called the Game State Processor (GSP) that reads the blockchain and works out the current state of the world: where every vehicle is, who owns which building, what loot is on the ground, how much each account has in its wallet, and so on. The GSP then hands that world to the game client (the 3D map you actually look at) as one big bundle of data in JSON format — JSON is just a plain-text way of writing structured data that programs and humans can both read.
This document lists everything in that bundle. You do not need to read code to
follow it: each section describes one kind of thing in the game (an account, a
vehicle/character, a building, a region of the map, an ongoing job like
prospecting, and so on) and shows a real example. Where the game's internal
short codes appear (for example raw a for the ore Trimideum, or rv st
for the Raider vehicle), the human-readable display name is shown next to
the code. A few recurring technical terms are explained the first time they
appear.
For developers building a new client, the rest of the document is exact: field names, types, when each field is present or absent, and the source line in the GSP that produces it.
This document is the authoritative reference for the game-state JSON that a Taurion client receives from the Game State Processor (GSP). It describes every entity type, every field, its type, units, semantics, when it is present or absent, and a realistic example for each.
The single source of truth is the GSP serializer
src/gamestatejson.cpp and its test file src/gamestatejson_tests.cpp, plus the
underlying protocol-buffer definitions in proto/. Field names and shapes shown
here are produced exactly by gamestatejson.cpp — they are not a paraphrase.
Codebase layout note. The serializer is
src/gamestatejson.cpp, but the database accessors and protos it uses live at the repository top level (database/,proto/), not undersrc/. Citations below use the path as it appears on disk.
1. How the client obtains this JSON#
All state JSON is produced by class GameStateJson
(src/gamestatejson.hpp:39) — the serializer, i.e. the code that turns the
GSP's internal database/protocol-buffer state into the JSON shapes shown here.
It is exposed to clients through the GSP's
JSON-RPC server (src/pxrpcserver.cpp) and a thin REST wrapper
(src/rest.cpp). Each RPC reads a consistent database snapshot at the current
confirmed block.
| RPC method | Serializer entry point | Returns | Source |
|---|---|---|---|
getaccounts |
Accounts() |
array of account objects | src/pxrpcserver.cpp:504, src/gamestatejson.cpp:716 |
getbuildings |
Buildings() |
array of building objects | src/pxrpcserver.cpp:515, src/gamestatejson.cpp:745 |
getcharacters |
Characters() |
array of character objects | src/pxrpcserver.cpp:526, src/gamestatejson.cpp:752 |
getgroundloot |
GroundLoot() |
array of ground-loot objects | src/pxrpcserver.cpp:537, src/gamestatejson.cpp:759 |
getongoings |
OngoingOperations() |
array of ongoing-operation objects | src/pxrpcserver.cpp:548, src/gamestatejson.cpp:766 |
getregions {fromheight} |
Regions(h) |
array of region objects modified since h |
src/pxrpcserver.cpp:559, src/gamestatejson.cpp:773 |
getmoneysupply |
MoneySupply() |
money-supply + burnsale object | src/pxrpcserver.cpp:582, src/gamestatejson.cpp:643 |
getprizestats |
PrizeStats() |
prizes object | src/pxrpcserver.cpp:593, src/gamestatejson.cpp:692 |
gettradehistory {building,item} |
TradeHistory(item,building) |
array of trade objects | src/pxrpcserver.cpp:606, src/gamestatejson.cpp:780 |
getbootstrapdata |
BootstrapData() |
{regions:[...]} (all regions) |
src/pxrpcserver.cpp:618, src/gamestatejson.cpp:805 |
The official web client calls these directly: getbuildings, getaccounts,
getregions, getgroundloot, getongoings, getbootstrapdata, and
getcharacters.
FullState() (src/gamestatejson.cpp:789) bundles everything into one object
with keys accounts, buildings, characters, groundloot, ongoings,
moneysupply, regions (since height 0), prizes. The header notes it is
"mainly for debugging and testing, not production" — production clients should
call the targeted RPCs above. The full-state object is what the test file
asserts against, so every example below is a fragment of that object.
{
"accounts": [ ... ],
"buildings": [ ... ],
"characters": [ ... ],
"groundloot": [ ... ],
"ongoings": [ ... ],
"moneysupply": { ... },
"regions": [ ... ],
"prizes": { ... }
}
getbootstrapdata / /bootstrap.json.gz#
BootstrapData() returns {"regions": <all regions>} — i.e. Regions(0), every
region (not only recently modified ones). It is large and expensive, so the REST
layer serves a gzip-cached copy at /bootstrap.json.gz, refreshed every
--rest_bootstrap_refresh_seconds (default 3600 s)
(src/rest.cpp:22, src/rest.cpp:59). The official client uses bootstrap data on
startup.
getregions height limit#
getregions takes fromheight and returns only regions changed since that
height. The GSP rejects a fromheight that is more than
MAX_REGIONS_HEIGHT_DIFFERENCE blocks behind the tip with error
GETREGIONS_FROM_TOO_LOW (src/pxrpcserver.cpp:49, :567). That limit is
2 * 60 * 24 * 3 = 8640 blocks (roughly three days at ~30 s/block;
src/pxrpcserver.cpp:49). Clients that fall too far behind must re-fetch the
full set via getbootstrapdata.
2. Conventions used by the serializer#
These rules apply to every entity and are essential to reading the JSON.
2.1 Absent vs. null#
The serializer omits a key entirely when the corresponding data is absent —
it does not emit null. The test harness encodes this: in
gamestatejson_tests.cpp, an expected value of null for a key asserts the key
is absent from the real output (src/gamestatejson_tests.cpp:73). So whenever
an example below shows "x": null, it means "the key x is not present".
Clients must treat "key missing" and "feature absent" as equivalent.
2.2 Integers and ID encoding#
- All integers go through
IntToJson(src/jsonutils.cpp), which preserves signedness and 64-bit width. Entity IDs and coin/quantity amounts can exceed 2^53, so clients must parse them as 64-bit integers / BigInt, not JS numbers, to avoid precision loss. - Database IDs are positive integers; valid range is
0 < id < 999999999(MAX_ID,src/jsonutils.cpp). A sentinel "no ID" internally isDatabase::EMPTY_ID; in JSON the corresponding key is simply omitted.
2.3 Coordinates#
A map coordinate (axial hex) is {"x": <int>, "y": <int>}
(CoordToJson, src/jsonutils.cpp). Both are signed integers.
2.4 Factions#
Faction is a single-letter string (FactionToString,
database/faction.cpp:27):
| Code | Faction |
|---|---|
"r" |
Red |
"g" |
Green |
"b" |
Blue |
"a" |
Ancient (NPC / neutral, e.g. ancient buildings) |
2.5 Item codes vs. display names#
Inventory and item fields use internal item codes (hardcoded strings).
Examples: raw a = "Trimideum", mat a = "Agarite", mat b = "Borolium",
rv st = "Raider". The display names are the ones shown in the game UI; clients
should map codes to
these names for the UI. Prize items are stored under the synthetic code
"<prize name> prize" (e.g. "gold prize",
src/prospecting.cpp:160). Blueprint codes follow the pattern <item> bpo
(original) and <item> bpc (copy), e.g. bow bpo, bow bpc
(src/gamestatejson_tests.cpp:1166). The empty string "" is a valid item code
seen in ground loot (src/gamestatejson_tests.cpp:1028).
2.6 Monetary unit (vCHI / "Cubit")#
The in-game coin is vCHI (also called Cubit). The base smallest unit is
1/COIN, where COIN = 100000000 (1e8) (database/amount.hpp:31). Account
balances are stored in whole vCHI integers; burnsale prices are converted to
floating CHI by dividing satoshi prices by COIN (see Money supply, §11).
3. Account#
Produced by Convert<Account> (src/gamestatejson.cpp:305) and post-processed
in Accounts() (src/gamestatejson.cpp:716) to add DEX-reserved balances.
Backed by proto/account.proto plus DB columns.
| Field | Type | Units / values | When present | Semantics |
|---|---|---|---|---|
name |
string | Xaya p/ name |
always | The account / player name. |
minted |
int (uint64) | vCHI | always | burnsale_balance: vCHI minted via the burnsale (the portion that carries over to the full game). account.proto:49. |
balance.available |
int (uint64) | vCHI | always | Spendable balance held by the account. gamestatejson.cpp:316. |
balance.reserved |
int (int64) | vCHI | always (added by Accounts()) |
vCHI locked in the account's open DEX bid orders. gamestatejson.cpp:723,738. |
balance.total |
int (int64) | vCHI | always (added by Accounts()) |
available + reserved. gamestatejson.cpp:739. |
faction |
string | r/g/b |
only if account initialised | Player faction; chosen at first character creation. Absent until then. gamestatejson.cpp:321. |
kills |
int (uint32) | count | only if initialised | Total characters this account has killed. account.proto:33. |
fame |
int (uint32) | points | only if initialised | Account fame; default value for a fresh initialised account is 100 (see example). account.proto:38. |
Initialisation. An account exists as soon as it holds a balance or minted
vCHI, but faction, kills, and fame only appear once the account is
"initialised" (a faction has been chosen). IsInitialised() gates them
(gamestatejson.cpp:319). An account that bought vCHI in the burnsale but never
picked a faction shows minted/balance but no faction/kills/fame.
Note: a new initialised account's default fame is 100, not 0 — see test
KillsAndFame: "foo" setkills=10, never set fame, and reportsfame:100(gamestatejson_tests.cpp:553–555). "bar" setfame=42explicitly.
Example — uninitialised account with open bid (from UninitialisedBalance,
gamestatejson_tests.cpp:571): the account "bar" has a bid for 2 units at price
3 (reserving 2*3 = 6 vCHI):
{
"name": "bar",
"minted": 10,
"balance": { "available": 42, "reserved": 6, "total": 48 }
}
(No faction/kills/fame because it is uninitialised.)
Example — initialised account:
{
"name": "foo",
"faction": "r",
"kills": 10,
"fame": 100,
"minted": 0,
"balance": { "available": 0, "reserved": 0, "total": 0 }
}
4. Character#
Produced by Convert<Character> (src/gamestatejson.cpp:251). Backed by
proto/character.proto plus DB columns (ID, owner, faction, position, building,
volatile movement, HP, regen, target, inventory).
| Field | Type | Units / values | When present | Semantics |
|---|---|---|---|---|
id |
int (uint64) | DB id | always | Character ID. gamestatejson.cpp:256. |
owner |
string | Xaya name | always | Owning account. gamestatejson.cpp:257. |
faction |
string | r/g/b |
always | Faction of the character. gamestatejson.cpp:258. |
vehicle |
string | item code | always | Vehicle item type the character is in, e.g. rv st (Raider). character.proto:82, gamestatejson.cpp:259. |
fitments |
array of string | item codes | always (may be []) |
Fitments mounted on the vehicle, in order. character.proto:85, gamestatejson.cpp:261. |
position |
coord | hex | only when on the map (not in a building) | Current map position. gamestatejson.cpp:269. |
inbuilding |
int (uint64) | building id | only when inside a building | ID of the building the character is in; mutually exclusive with position. gamestatejson.cpp:266. |
enterbuilding |
int (uint64) | building id | only if a pending "enter building" is set | The building the character is trying to enter. gamestatejson.cpp:271. |
speed |
int (uint32) | milli-tiles per block | always | Movement speed of the vehicle (1000 = 1 tile/block). character.proto:114, gamestatejson.cpp:275. |
inventory |
object | see §4.1 | always | Cargo inventory. gamestatejson.cpp:276. |
cargospace |
object | see §4.2 | always | Cargo capacity breakdown. gamestatejson.cpp:277. |
combat |
object | see §4.3 | always | Combat data (HP, attacks, target, attackers). gamestatejson.cpp:274. |
movement |
object | see §4.4 | only if there is any movement data | Waypoints / chosen speed / partial step. gamestatejson.cpp:279. |
busy |
int (uint64) | ongoing op id | only if the character is busy | ID of the ongoing operation occupying the character (e.g. prospecting). character.proto:102, gamestatejson.cpp:283. |
mining |
object | see §4.5 | only if the vehicle can mine | Mining rate + active state. gamestatejson.cpp:286. |
prospectingblocks |
int (uint32) | blocks | only if the vehicle can prospect | Number of blocks this vehicle takes to finish a prospection. character.proto:111, gamestatejson.cpp:291. |
refining |
object | see §4.6 | only if the vehicle is a mobile refinery | Mobile-refinery inefficiency. gamestatejson.cpp:294. |
Mutual exclusivity. A character is either on the map (position) or
inside a building (inbuilding), never both (gamestatejson.cpp:266). Clients
should branch on which key is present.
Example — on the map (Basic, gamestatejson_tests.cpp:111):
{
"id": 1, "owner": "domob", "faction": "r",
"speed": 750,
"position": {"x": -5, "y": 2}
}
Example — inside a building (gamestatejson_tests.cpp:117):
{ "id": 2, "owner": "andy", "faction": "g", "inbuilding": 100 }
Example — vehicle + fitments (gamestatejson_tests.cpp:202):
{ "vehicle": "rv st", "fitments": ["turbo", "bomb"] }
A character with no fitments emits "fitments": []
(gamestatejson_tests.cpp:208).
4.1 inventory#
Convert<Inventory> (src/gamestatejson.cpp:237). One key:
"inventory": { "fungible": { "raw a": 5, "mat b": 10 } }
fungible— object mapping item code → integer count (inventory.proto:37). The amount is "some smallest fraction of the item" (typically whole units). Empty inventory ⇒"fungible": {}. There are no non-fungible inventory entries in the current model.
4.2 cargospace#
GetCargoSpaceJsonObject (src/gamestatejson.cpp:199).
| Field | Type | Units | Semantics |
|---|---|---|---|
total |
int (uint64) | cargo units | The vehicle's total cargo capacity (character.proto:117). |
used |
int (uint64) | cargo units | Space consumed by the current inventory. |
free |
int (uint64) | cargo units | total - used. |
used is the sum over inventory of count * item.space for each item type
(Character::UsedCargoSpace, database/character.cpp:230). In the test, 35 of
item foo (which has space = 10) consumes 350 of 1000
(gamestatejson_tests.cpp:352):
"cargospace": { "total": 1000, "used": 350, "free": 650 }
4.3 combat#
GetCombatJsonObject (src/gamestatejson.cpp:131 for the base, :182 for the
character overload that adds attackers). Backed by proto/combat.proto. The
character version always includes hp; the others are conditional.
| Field | Type | When present | Semantics |
|---|---|---|---|
hp |
object | always | Current/max/regeneration HP. |
attacks |
array | only if the entity has ≥1 attack | List of weapon attacks. |
target |
object | only if a target is currently selected | The current attack target. |
attackers |
array of int | only on characters, only if non-empty | IDs of characters currently attacking this one (from damage lists). gamestatejson.cpp:187. |
combat.hp
HpProtoToJson (src/gamestatejson.cpp:87). Two HP pools — armour
(permanent) and shield (regenerating) (combat.proto:199).
"hp": {
"max": {"armour": 100, "shield": 10},
"current": {"armour": 42, "shield": 5.001},
"regeneration": {"shield": 1.001, "armour": 0.042}
}
max.armour,max.shield— integer maximum HP per pool (RegenData.max_hp,combat.proto:228).current.armour,current.shield— current HP. Fractional HP are represented as a decimal: stored as whole HP plus "milli-HP" (1/1000 HP); the serializer convertsfull + millis/1000(HpValueToJson,gamestatejson.cpp:75). E.g. shield 5 + 1 milli ⇒5.001. Ifmillis == 0the value is a plain integer.regeneration— shield/armour regen rate, in HP per block, again rendered as decimal fromregeneration_mhp("milli-HP per block",combat.proto:231).regeneration_mhp.shield = 1001⇒1.001,regeneration_mhp.armour = 42⇒0.042(gamestatejson_tests.cpp:298,314). Note: a regen of0is still emitted as0here becauseregenerationis built from a fake HP proto and always present (gamestatejson.cpp:169–171); see building example where armour regen is0.
combat.attacks
Each entry comes from proto::Attack (combat.proto:93). Fields are conditional
(gamestatejson.cpp:140):
| Field | Type | When present | Semantics |
|---|---|---|---|
range |
int (uint32) | if the attack has a range | Attack range as L1 hex distance. Absent ⇒ the attack is centred on the attacker (e.g. an AoE around self). 0 ⇒ same-tile only. combat.proto:104. |
area |
int (uint32) | if the attack is AoE | AoE radius around the target (if range present) or the attacker (if not). Absent ⇒ single-target. combat.proto:111. |
friendlies |
bool (true) |
only if the attack targets friendlies | Buff-type attack that affects allies (e.g. shield-regen boost). Emitted only when true. combat.proto:158, gamestatejson.cpp:148. |
damage |
object {min,max} |
only if the attack does damage | Inclusive damage range; actual damage uniform in [min,max]. combat.proto:116, gamestatejson.cpp:151. |
Note the serializer does not emit the proto's armour_percent,
shield_percent, weapon_size, effects, or gain_hp sub-fields — only
range, area, friendlies, and damage.{min,max} are exposed.
Example (gamestatejson_tests.cpp:264):
"attacks": [
{ "range": 5, "damage": {"min": 2, "max": 10} },
{ "area": 1, "damage": {"min": 0, "max": 1} },
{ "area": 10, "friendlies": true }
]
combat.target
TargetIdToJson (src/gamestatejson.cpp:50). Present only when a target is
selected.
"target": { "id": 5, "type": "character" }
type is "character" or "building" (gamestatejson.cpp:58–62); id is the
target entity ID. (Buildings can also have a combat.target — see §6.)
combat.attackers
Array of character IDs currently attacking this character (only present when
non-empty, only on characters). From the damage-list table
(gamestatejson.cpp:187). Example: "attackers": [2]
(gamestatejson_tests.cpp:486).
4.4 movement#
GetMovementJsonObject (src/gamestatejson.cpp:99). The whole object is omitted
if it would be empty. Sub-fields are individually conditional.
| Field | Type | When present | Semantics |
|---|---|---|---|
chosenspeed |
int (uint32) | if a self-imposed speed cap is set | The character travels at min(chosenspeed, actual speed); used to keep convoys together. movement.proto:47. |
waypoints |
array of coord | if the waypoint list is non-empty | Remaining waypoints, in visit order; first element is the current target tile. movement.proto:40. |
partialstep |
int (uint32) | if there is partial progress to the next tile | Volatile sub-tile movement progress. movement.proto:61. |
blockedturns |
int (uint32) | if the character has been blocked | Number of consecutive turns blocked by an obstacle; movement stops after a configured number of retries. movement.proto:68. |
Example (gamestatejson_tests.cpp:178):
"movement": {
"partialstep": 5,
"blockedturns": 3,
"waypoints": [ {"x": -3, "y": 0}, {"x": 0, "y": 42} ]
}
4.5 mining#
GetMiningJsonObject (src/gamestatejson.cpp:215). Present only if the vehicle
has mining capability (character.proto:105).
| Field | Type | Units | When present | Semantics |
|---|---|---|---|---|
rate.min |
int (uint64) | units/block | always (within mining) |
Minimum mining yield per block. character.proto:42. |
rate.max |
int (uint64) | units/block | always | Maximum mining yield per block (yield is random in [min,max]). |
active |
bool | — | always | Whether the character is currently mining. character.proto:53. |
region |
int (uint32) | region id | only if active |
The region currently being mined, derived from position. gamestatejson.cpp:229. |
Examples (gamestatejson_tests.cpp:395,407):
"mining": { "rate": {"min": 0, "max": 5}, "active": false }
"mining": { "rate": {"min": 10, "max": 11}, "active": true, "region": 350146 }
4.6 refining (mobile refinery)#
gamestatejson.cpp:294. Present only if the vehicle can refine on the move
(character.proto:123, MobileRefinery).
| Field | Type | Units | Semantics |
|---|---|---|---|
inefficiency |
int | percent | The input ore cost relative to a stationary refinery, computed as StatModifier(input)(100). A mobile refinery produces the same outputs but consumes more input ore. gamestatejson.cpp:296. |
In the test, input.percent = 100 (i.e. +100%) applied to a base of 100 gives
200 (gamestatejson_tests.cpp:451,463):
"refining": { "inefficiency": 200 }
StatModifier formula: base + base*percent/100 + absolute
(src/modifier.hpp). So inefficiency = 200 means the mobile refinery uses
2× the ore a fixed refinery would for the same step.
5. Inventory (shared shape)#
The inventory object is reused by characters, ground loot, building per-account inventories, building construction inventories, and the building "reserved" section. Its shape is always:
{ "fungible": { "<item code>": <integer count>, ... } }
Source: Convert<Inventory> (src/gamestatejson.cpp:237),
proto/inventory.proto. Zero-count entries are pruned upstream (only present
items appear). The empty item code "" is permitted as a key
(gamestatejson_tests.cpp:1028).
6. Building#
Produced by Convert<Building> (src/gamestatejson.cpp:407). Backed by
proto/building.proto plus DB columns and external tables (inventories, DEX
orders). The building's shape on the map is computed from its type, rotation,
and centre.
| Field | Type | Units / values | When present | Semantics |
|---|---|---|---|---|
id |
int (uint64) | DB id | always | Building ID. gamestatejson.cpp:414. |
type |
string | building type code | always | Building type (e.g. checkmark, r ss). gamestatejson.cpp:415. |
foundation |
bool (true) |
— | only if it is a foundation | true while the building is an unfinished foundation (a building under construction that players are still depositing materials into). Omitted (i.e. full building) otherwise. building.proto:55, gamestatejson.cpp:416. |
faction |
string | r/g/b/a |
always | Owning faction; a = ancient/neutral. gamestatejson.cpp:419. |
owner |
string | Xaya name | only if not Ancient | Owning account. Absent for ancient buildings. gamestatejson.cpp:420. |
centre |
coord | hex | always | The building's centre tile. gamestatejson.cpp:422. |
rotationsteps |
int (uint32) | 0–5 | always | Clockwise 60° rotation steps of the base shape. building.proto:39, gamestatejson.cpp:424. |
config |
object | see §6.1 | always | Owner-configurable fees. gamestatejson.cpp:425. |
tiles |
array of coord | hex | always | All map tiles the building occupies (post-rotation, post-shift). gamestatejson.cpp:427. |
combat |
object | see §4.3 | always | Building combat data (HP, attacks, target). No attackers array for buildings. gamestatejson.cpp:432. |
construction |
object | see §6.2 | only if foundation |
Construction progress + materials. gamestatejson.cpp:434. |
inventories |
object | see §6.3 | only if not a foundation | Per-account stored goods. gamestatejson.cpp:444. |
reserved |
object | see §6.4 | only if not a foundation | Per-account goods locked in open ask orders. gamestatejson.cpp:453. |
orderbook |
object | see §6.5 | only if not a foundation | DEX order book by item. gamestatejson.cpp:458. |
age |
object | see §6.6 | always | Founded/finished block heights. gamestatejson.cpp:461. |
Foundation vs. finished building are mutually exclusive in their extra
sections: a foundation has construction but no inventories/reserved/
orderbook; a finished building has the latter three but no construction
(gamestatejson.cpp:434 vs :442).
Example — basic finished building (gamestatejson_tests.cpp:625):
{
"id": 1,
"type": "checkmark",
"owner": "foo",
"faction": "r",
"centre": {"x": 1, "y": 2},
"rotationsteps": 0,
"tiles": [
{"x": 1, "y": 2}, {"x": 2, "y": 2}, {"x": 1, "y": 3}, {"x": 1, "y": 4}
]
}
Example — ancient building (gamestatejson_tests.cpp:651): "faction":"a"
and no owner key.
6.1 config#
Convert(proto::Building::Config) (src/gamestatejson.cpp:329). Each sub-field
is present only if set; if neither is configured the object is {}.
| Field | Type | Units | Semantics |
|---|---|---|---|
servicefee |
int (uint32) | percent | Surcharge the building owner charges for in-building services, as a percentage of the service's base (burnt) fee. building.proto:88. |
dexfee |
float | percent | Owner's DEX trading fee, paid by the seller out of received vCHI. Stored internally in basis points (dex_fee_bps); the serializer divides by 100 to give a percentage. building.proto:95, gamestatejson.cpp:336. |
Example: dex_fee_bps = 1725 ⇒ "dexfee": 17.25; service_fee_percent = 42
⇒ "servicefee": 42 (gamestatejson_tests.cpp:891,900). A building with no
configured fees: "config": {} (both keys absent,
gamestatejson_tests.cpp:882).
6.2 construction (foundations only)#
gamestatejson.cpp:434.
| Field | Type | When present | Semantics |
|---|---|---|---|
inventory |
inventory object | always (within construction) |
Materials deposited toward completing the building (the construction_inventory proto, building.proto:61). |
ongoing |
int (uint64) | only if a completion op is running | ID of the ongoing "build" operation upgrading this foundation. building.proto:67, gamestatejson.cpp:437. |
Example (gamestatejson_tests.cpp:695,705):
"construction": { "inventory": {"fungible": {"bar": 10}} }
"construction": { "ongoing": 42, "inventory": {"fungible": {}} }
6.3 inventories (finished buildings only)#
Map of account name → that account's inventory object, for goods stored inside
the building (gamestatejson.cpp:444). Accounts with empty inventories are
omitted (the map can be {}).
"inventories": {
"andy": {"fungible": {"bar": 1}},
"domob": {"fungible": {"foo": 2}}
}
(gamestatejson_tests.cpp:737)
6.4 reserved (finished buildings only)#
Map of account name → an inventory object listing item quantities locked in that
account's open ask orders in this building's DEX
(gamestatejson.cpp:453, GetReservedQuantities,
database/dex.cpp:308). Same shape as inventories.
"reserved": {
"andy": {"fungible": {"foo": 5}},
"domob": {"fungible": {"foo": 4, "bar": 1}}
}
(gamestatejson_tests.cpp:741)
The vCHI reserved by bid orders is not here — it is folded into each account's
balance.reserved(§3).
6.5 orderbook (finished buildings only)#
GetOrderbookInBuilding (src/gamestatejson.cpp:347). An object keyed by item
code; each entry has the order book for that item:
| Field | Type | Semantics |
|---|---|---|
item |
string | The item code (same as the map key). |
bids |
array of order | Buy orders, sorted best (highest) price first, then by ascending order ID. gamestatejson.cpp:395. |
asks |
array of order | Sell orders, sorted best (lowest) price first. |
Each order object (gamestatejson.cpp:368):
| Field | Type | Units | Semantics |
|---|---|---|---|
id |
int (uint64) | DB id | Order ID. |
account |
string | Xaya name | Order owner. |
quantity |
int | units | Quantity remaining on the order. |
price |
int | vCHI per unit | Limit price per unit. |
The DB query returns orders ascending by price; the serializer reverses bids so
the best bid is first (gamestatejson.cpp:393). The cost of filling an order is
quantity * price (see Trade history, §10).
Example (gamestatejson_tests.cpp:790):
"orderbook": {
"foo": {
"item": "foo",
"bids": [
{ "id": 103, "account": "domob", "quantity": 1, "price": 3 },
{ "id": 102, "account": "andy", "quantity": 1, "price": 2 },
{ "id": 101, "account": "domob", "quantity": 2, "price": 2 },
{ "id": 105, "account": "domob", "quantity": 3, "price": 1 }
],
"asks": [
{ "id": 104, "account": "domob", "quantity": 1, "price": 8 },
{ "id": 109, "account": "domob", "quantity": 1, "price": 9 },
{ "id": 106, "account": "domob", "quantity": 1, "price": 10 }
]
}
}
6.6 age#
gamestatejson.cpp:461. Block heights of building lifecycle events
(building.proto:106).
| Field | Type | When present | Semantics |
|---|---|---|---|
founded |
int (uint32) | always | Block height when the foundation was placed. building.proto:110. |
finished |
int (uint32) | only if not a foundation | Block height when construction completed. Absent while still a foundation. building.proto:113, gamestatejson.cpp:463. |
Example (gamestatejson_tests.cpp:940):
"age": { "founded": 10, "finished": 12 }
A foundation shows only {"founded": 10} (gamestatejson_tests.cpp:921).
7. Ground loot#
Produced by Convert<GroundLoot> (src/gamestatejson.cpp:470). GroundLoot()
returns only non-empty piles (QueryNonEmpty, gamestatejson.cpp:763); an empty
world yields [].
| Field | Type | When present | Semantics |
|---|---|---|---|
position |
coord | always | Tile where the loot lies. gamestatejson.cpp:475. |
inventory |
inventory object | always | Items dropped here (fungible map). gamestatejson.cpp:476. |
Example (gamestatejson_tests.cpp:1037):
{
"position": {"x": -1, "y": 20},
"inventory": { "fungible": { "foo": 10 } }
}
Loot supports the empty-string item code as a valid key
(gamestatejson_tests.cpp:1028).
8. Ongoing operation#
Produced by Convert<OngoingOperation> (src/gamestatejson.cpp:481). Backed by
proto/ongoing.proto plus DB columns. The operation field is a discriminator;
the remaining fields depend on its value.
8.1 Common fields#
| Field | Type | When present | Semantics |
|---|---|---|---|
id |
int (uint64) | always | Operation ID. gamestatejson.cpp:489. |
start_height |
int (uint32) | always | Block height when the operation started. ongoing.proto:149, gamestatejson.cpp:490. |
end_height |
int (uint64) | always | Block height when the operation completes. Note: for multi-item ops (series construction, multi-copy blueprint copies) this is the operation height plus a per-op delta (see below). gamestatejson.cpp:567. |
characterid |
int (uint64) | only if attached to a character | Associated character. gamestatejson.cpp:491. |
buildingid |
int (uint64) | only if attached to a building | Associated building. gamestatejson.cpp:493. |
operation |
string | always | Operation type discriminator (see below). |
characterid and buildingid are mutually exclusive in practice (an op belongs
to one entity). Example (gamestatejson_tests.cpp:1104):
{ "id": 1, "start_height": 3, "end_height": 5, "characterid": 42 }
end_height and the delta. For most operations end_height equals the DB
"next-processing height". But operations that are processed in series
(item construction from an original, multi-copy blueprint copy) finish one unit
per processing step; the serializer adds
(count - 1) * blocksPerUnit so end_height reflects the final completion
(gamestatejson.cpp:500,525,547,567). See the blueprint-copy and item-build
examples below.
8.2 operation discriminator values#
The oneof op (ongoing.proto:152) maps to these strings
(gamestatejson.cpp:502):
operation |
Proto case | Extra fields |
|---|---|---|
"prospecting" |
prospection |
none |
"armourrepair" |
armour_repair |
none |
"bpcopy" |
blueprint_copy |
account, original, output |
"construct" |
item_construction |
account, output, optional original |
"build" |
building_construction |
none (uses buildingid) |
"config" |
building_update |
newconfig |
prospecting
{ "id": 1, "operation": "prospecting" }
(gamestatejson_tests.cpp:1132) The region being prospected is implicit from the
character's position (ongoing.proto:29).
armourrepair
{ "id": 1, "operation": "armourrepair" }
(gamestatejson_tests.cpp:1150)
bpcopy (blueprint copy)
| Field | Type | Semantics |
|---|---|---|
account |
string | Owner doing the copying (ongoing.proto:61). |
original |
string | Original blueprint item code being copied (original_type, ongoing.proto:64). |
output |
object | Map <copy item code> → num copies (copy_type → num_copies). gamestatejson.cpp:520. |
The copies are produced one at a time; end_height includes
(num_copies-1) * GetBpCopyBlocks(copy_type). In the test bow bpc takes 1000
blocks/copy, 42 copies starting at height 1 with op height 1001 ⇒
end_height = 1001 + 41*1000 = 42001 (gamestatejson_tests.cpp:1172):
{
"id": 1,
"operation": "bpcopy",
"original": "bow bpo",
"output": {"bow bpc": 42},
"start_height": 1,
"end_height": 42001
}
construct (item / vehicle construction)
| Field | Type | When present | Semantics |
|---|---|---|---|
account |
string | always | Owner who receives the finished items (ongoing.proto:91). |
output |
object | always | Map <output item code> → num items (output_type → num_items). gamestatejson.cpp:538. |
original |
string | only if built from an original blueprint | The consumed-and-returned original blueprint type. Building from copies omits this and runs all in parallel. ongoing.proto:109, gamestatejson.cpp:542. |
When original is present, items are produced in series and end_height adds
(num_items-1) * GetConstructionBlocks(output_type). Test: bow takes 1000
blocks/item; 5 items from an original, op height 1001 ⇒ end_height = 5001;
42 items from copies (no original) ⇒ end_height = 1001
(gamestatejson_tests.cpp:1210):
{ "id": 1, "operation": "construct", "output": {"bow": 42},
"start_height": 1, "end_height": 1001 }
{ "id": 2, "operation": "construct", "output": {"bow": 5},
"original": "bow bpo", "start_height": 1, "end_height": 5001 }
build (finish building construction)
Upgrades a foundation to a full building. No extra fields; uses buildingid
(gamestatejson_tests.cpp:1240):
{ "id": 1, "operation": "build", "buildingid": 42 }
config (building config update)
Delayed building-config change (delay prevents front-running). Carries the
pending config (ongoing.proto:135, gamestatejson.cpp:560):
| Field | Type | Semantics |
|---|---|---|
newconfig |
object | The pending Building.Config (same shape as §6.1: servicefee, dexfee). |
Example (gamestatejson_tests.cpp:1281):
{
"id": 1,
"operation": "config",
"newconfig": { "servicefee": 2, "dexfee": 1.5 }
}
With no fields set, "newconfig": {} (gamestatejson_tests.cpp:1266).
9. Region#
Produced by Convert<Region> (src/gamestatejson.cpp:572). Backed by
proto/region.proto plus a DB column for resource-left. Regions(h) returns
only regions whose state changed at height > h
(QueryModifiedSince, gamestatejson.cpp:777); getbootstrapdata uses h=0
for all regions.
| Field | Type | When present | Semantics |
|---|---|---|---|
id |
int | always | Region ID (integer, derived from the hex map). gamestatejson.cpp:579. |
prospection |
object | only if non-empty | Prospection status — see below. gamestatejson.cpp:590. |
resource |
object | only if the region has been prospected | The mineable resource left — see below. gamestatejson.cpp:593. |
A region that was touched in the DB but reverted to a trivial state still appears in
Regionsresults, with neitherprospectionnorresource(i.e. onlyid) — see testEmpty(gamestatejson_tests.cpp:1324). A region never written at all is simply absent.
9.1 prospection#
gamestatejson.cpp:581. Sub-fields are conditional.
| Field | Type | When present | Semantics |
|---|---|---|---|
inprogress |
int (uint64) | only if a character is currently prospecting | Character ID doing the prospection. region.proto:59, gamestatejson.cpp:582. |
name |
string | only if already prospected | Xaya name of whoever prospected it (display only). region.proto:34, gamestatejson.cpp:586. |
height |
int (uint32) | only if already prospected | Block height when it was prospected. region.proto:37, gamestatejson.cpp:587. |
Examples (gamestatejson_tests.cpp:1346):
{ "id": 10, "prospection": { "name": "bar", "height": 107 } }
{ "id": 20, "prospection": { "inprogress": 42 } }
9.2 resource#
Present only once a region has prospection data (gamestatejson.cpp:593).
| Field | Type | Units | Semantics |
|---|---|---|---|
type |
string | item code | The mineable resource type, e.g. sand, raw a. region.proto:48. |
amount |
int | units | Resource units still mineable (GetResourceLeft); decreases as the region is mined; may be 0. gamestatejson.cpp:597. |
Example (gamestatejson_tests.cpp:1369):
{ "id": 10, "resource": { "type": "sand", "amount": 150 } }
10. Trade history#
Produced by Convert<DexTrade> (src/gamestatejson.cpp:605), returned by
gettradehistory(building, item) as an array, most-recent first. Backed by the
DexHistoryTable. Querying an item/building with no history returns []
(gamestatejson_tests.cpp:1556).
| Field | Type | Units | Semantics |
|---|---|---|---|
height |
int | block | Block height of the trade. gamestatejson.cpp:611. |
timestamp |
int | unix seconds | Block timestamp of the trade. gamestatejson.cpp:612. |
buildingid |
int (uint64) | DB id | Building whose DEX hosted the trade. gamestatejson.cpp:614. |
item |
string | item code | Item traded. gamestatejson.cpp:615. |
quantity |
int | units | Quantity traded. gamestatejson.cpp:617. |
price |
int | vCHI/unit | Execution price per unit. gamestatejson.cpp:618. |
cost |
int (int64) | vCHI | Total cost = quantity * price. gamestatejson.cpp:619. |
seller |
string | Xaya name | Seller account. gamestatejson.cpp:622. |
buyer |
string | Xaya name | Buyer account. gamestatejson.cpp:623. |
Example (gamestatejson_tests.cpp:1565):
{
"height": 10,
"timestamp": 1024,
"buildingid": 42,
"item": "foo",
"quantity": 2,
"price": 3,
"cost": 6,
"seller": "domob",
"buyer": "andy"
}
11. Money supply#
Produced by MoneySupply() (src/gamestatejson.cpp:643), returned by
getmoneysupply. Reports the total vCHI in existence by source plus burnsale
stage progress.
| Field | Type | Semantics |
|---|---|---|
total |
int (int64) | Sum of all counted entry values (vCHI). gamestatejson.cpp:685. |
entries |
object | Map of money-source key → amount minted from that source. gamestatejson.cpp:660. |
burnsale |
array | Per-stage burnsale progress (see below). gamestatejson.cpp:664. |
11.1 entries#
The valid keys are exactly "burnsale" and "gifted"
(MoneySupply::GetValidKeys, database/moneysupply.cpp:93):
| Key | Meaning |
|---|---|
burnsale |
vCHI minted by burning CHI in the burnsale. |
gifted |
vCHI gifted by admin/god-mode. |
God-mode gating of gifted. "God-mode" is an admin/testing mode in which
privileged commands (such as giftcoins, which mints vCHI out of thin air) are
honoured. The gifted key is emitted only when god-mode is off-gated:
specifically, the serializer skips gifted when !params.god_mode(), and in
that case CHECK_EQ(ms.Get("gifted"), 0) asserts no gifted coins exist
(gamestatejson.cpp:653). When god-mode is on, gifted is included in entries
(and total). See tests EntriesAndTotal (regtest, god-mode on, gifted
present, gamestatejson_tests.cpp:1399) vs. GiftedOnMainnet (gifted absent,
:1419).
Mainnet caveat — config vs. test disagree. The unit test
roconfig_tests.cpp:69asserts that onMAIN/TESTchainsgod_mode()is false (sogiftedwould be omitted), and onlyREGTESThas it true (:71). However, the checked-in base configproto/roconfig/params.pb.text:31setsgod_mode: truefor the mainnet base, androconfig.cppdoes not clear it forMAIN. As shipped, that makes mainnet god-mode on, which would emitgiftedand disable theCHECK_EQguard — i.e. the stale test no longer matches the config. This is the same discrepancy flagged in Economy & Accounts and Game Overview; treat the live behaviour as "god-mode on (mainnet config),giftedpresent" until the config or test is reconciled. See Open question #4.
"moneysupply": {
"total": 30,
"entries": { "gifted": 10, "burnsale": 20 }
}
11.2 burnsale stages#
One entry per burnsale stage, computed by walking the configured stages and
distributing the total burnsale amount across them
(gamestatejson.cpp:664). Stage config comes from
params.burnsale_stages (field proto/config.proto:601, message
BurnsaleStage at proto/config.proto:517; mainnet values in
proto/roconfig/params.pb.text:49).
| Field | Type | Units | Semantics |
|---|---|---|---|
stage |
int | 1-based | Stage number. gamestatejson.cpp:674. |
price |
float | CHI per vCHI | Price in this stage = price_sat / COIN (1e8). gamestatejson.cpp:675. |
total |
int | vCHI | Total vCHI sellable in this stage (amount_sold). gamestatejson.cpp:676. |
sold |
int | vCHI | How much of this stage has been sold so far. gamestatejson.cpp:677. |
available |
int | vCHI | total - sold remaining in this stage. gamestatejson.cpp:678. |
Mainnet stage configuration (proto/roconfig/params.pb.text:49–52):
| Stage | total (vCHI) |
price_sat |
price (CHI/vCHI) |
|---|---|---|---|
| 1 | 10000000000 | 10000 | 0.0001 |
| 2 | 10000000000 | 20000 | 0.0002 |
| 3 | 10000000000 | 50000 | 0.0005 |
| 4 | 20000000000 | 100000 | 0.0010 |
Example with 25,000,000,000 vCHI sold (fills stages 1–2, half of 3)
(gamestatejson_tests.cpp:1442):
"burnsale": [
{ "stage": 1, "price": 0.0001, "total": 10000000000, "sold": 10000000000, "available": 0 },
{ "stage": 2, "price": 0.0002, "total": 10000000000, "sold": 10000000000, "available": 0 },
{ "stage": 3, "price": 0.0005, "total": 10000000000, "sold": 5000000000, "available": 5000000000 },
{ "stage": 4, "price": 0.0010, "total": 20000000000, "sold": 0, "available": 20000000000 }
]
12. Prizes#
Produced by PrizeStats() (src/gamestatejson.cpp:692), returned by
getprizestats. An object keyed by prize tier name; one entry per configured
prize (params.prizes, field proto/config.proto:611, message
ProspectingPrize at proto/config.proto:498,
proto/roconfig/params.pb.text).
| Field | Type | Semantics |
|---|---|---|
number |
int (uint32) | Total prizes of this tier that exist. proto/config.proto:507, gamestatejson.cpp:701. |
probability |
int (uint32) | "1-in-N" base chance: per successful prospect, the chance to win this prize is 1/probability (modulated by safe-zone "low prize" reduction). proto/config.proto:510, src/prospecting.cpp:168. |
found |
int (uint32) | How many of this tier have already been won. Comes from item count of "<name> prize". gamestatejson.cpp:704. |
available |
int | number - found remaining. gamestatejson.cpp:708. |
The internal won-item code is "<tier name> prize"
(src/prospecting.cpp:160).
Probability detail. On a normal prospect the roll is
ProbabilityRoll(100, 100 * probability) ⇒ effective 1/probability; inside a
configured "low prize" safe zone the numerator is 55 instead of 100, i.e. odds
are reduced to 55% of normal (src/prospecting.cpp:166–168).
Mainnet/testnet difference. Mainnet uses a large prize table
(proto/roconfig/params.pb.text:54–110+), e.g.
{ name: "cash", number: 10, probability: 350000 },
{ name: "Prison Key Card", number: 100, probability: 2655 }. Testnet
replaces the list with three easy tiers
(proto/roconfig/test_params.pb.text:25–27):
| Tier | number | probability |
|---|---|---|
| gold | 3 | 100 |
| silver | 1000 | 10 |
| bronze | 1 | 1 |
Example (testnet config, gamestatejson_tests.cpp:1506):
"prizes": {
"gold": { "number": 3, "probability": 100, "found": 1, "available": 2 },
"silver": { "number": 1000, "probability": 10, "found": 10, "available": 990 },
"bronze": { "number": 1, "probability": 1, "found": 0, "available": 1 }
}
13. Field-presence quick reference#
For client developers, the keys that are conditionally present (and what their absence means):
- Account:
faction/kills/fameabsent ⇒ account not yet initialised. - Character: exactly one of
position/inbuilding.enterbuilding,busy,mining,prospectingblocks,refining,movementall capability/state-gated.combat.target,combat.attacks,combat.attackersconditional;combat.hpalways present. - Building:
foundation,construction⟺ foundation;inventories/reserved/orderbook⟺ finished building.ownerabsent ⇒ ancient.age.finishedabsent ⇒ still a foundation.configsub-keys present only when the fee is set. - Region:
prospection/resourceabsent ⇒ unprospected/untouched;prospection.inprogress⇒ being prospected now;prospection.name/height⇒ already prospected. - Ongoing:
characterid/buildingidone-or-neither; per-operationextra fields per §8.2. - Money supply:
giftedabsent ⇒ god-mode off; present ⇒ god-mode on. Note the checked-in mainnet config has god-mode on (params.pb.text:31), despite a stale test asserting otherwise — see §11.1 and Open question #4.
Open questions#
region.idwidth (resolved).Convert<Region>emitsres["id"] = r.GetId()directly (not viaIntToJson,gamestatejson.cpp:579), unlike all other IDs.Region::GetId()returnsRegionMap::IdT, which isuint32_t(mapdata/regionmap.hpp:42,database/region.hpp:107). Auint32always fits in a JSnumber(max ≈ 4.29e9 < 2^53), so — unlike 64-bit entity IDs —region.idis safe to parse as a plain number. Example values (10, 20,- confirm this.
combatsub-field coverage. The serializer deliberately exposes onlyrange/area/friendlies/damage.{min,max}for attacks and omits proto fields likearmour_percent,shield_percent,weapon_size,effects,gain_hp,low_hp_boosts,self_destructs,received_damage_modifier,hit_chance_modifier,target_size(combat.proto). Whether a future AAA UX needs these surfaced (they affect real combat math but are currently invisible to clients) is a design decision, not a documentation gap.- Cargo unit definition.
cargospacevalues are in abstract "cargo units" =count * item.space; the per-itemspaceconstants live in the item config protos (proto/roconfig/items/*.pb.text) and were not enumerated here (out of scope for the state-JSON topic — covered by the items/economy docs). gifted/ god-mode: config vs. test disagree (unresolved).roconfig_tests.cpp:69asserts mainnetgod_mode() == false, but the checked-in base configproto/roconfig/params.pb.text:31setsgod_mode: trueandroconfig.cppdoes not clear it forMAIN. As-built, mainnet therefore has god-mode on, sogiftedwould be emitted and theCHECK_EQ(ms.Get("gifted"), 0)guard atgamestatejson.cpp:655would not apply (it only runs when god-mode is off). Whether the released mainnet binary ships with god-mode on, or the config is meant to be flipped off (making the test correct again), could not be resolved from the GSP source alone; the move processor gates admin commands onparams.god_mode()(src/moveprocessor.cpp:2352), so with the current config those commands are live on mainnet.