Moves Reference
Every on-chain command a player can send, with full JSON schemas.
For players: what is a "move"?#
Taurion is a fully on-chain game: there is no central server that decides what happens. Instead, every action you take — moving a vehicle, mining ore, buying an item, creating a character — is a small instruction (a "move") that you broadcast to the Xaya blockchain. A piece of software called the GSP (Game State Processor) reads each move, checks the rules, and updates the shared game world the same way for everyone. This document lists every kind of move the GSP understands.
You normally never write these moves by hand — your game client builds them for you. But because the rules below are exactly what the network enforces, they tell you precisely what is and isn't possible: how much a character costs, why a transfer might silently fail, how trading fees are split, and so on.
A few words you'll see throughout:
- Move — one instruction to the game (e.g. "character 5, start mining").
- vCHI / Cubit — the in-game coin used for fees, trading, and refining.
- WCHI / CHI — the real Polygon cryptocurrency, used only to create characters and to buy vCHI in the burnsale.
- Faction — the team you join when you start: Red, Green, or Blue. It is permanent.
- Character / vehicle — your unit on the map. A character pilots a vehicle (e.g. a Raider) and can equip fitments (gear like guns or scanners).
- Building / foundation — a structure on the map. A foundation is a half-built building; once enough resources are dropped into it, it finishes into a full building.
- Item codes — internally items use short codes (e.g.
raw a); this doc shows the player-facing display name beside each, e.g. Trimideum (raw a).
The rest of this document is the precise technical specification, with JSON examples and source citations for developers building a game client.
This document is the authoritative, exhaustive specification of every move type the Taurion Game State Processor (GSP) accepts. It is derived directly from the GSP source (the single source of truth), primarily:
src/moveprocessor.cpp/src/moveprocessor.hpp— top-level dispatch, account, character, building, and coin (vCHI) moves; god-mode admin.src/services.cpp/src/services.hpp— in-building service operations (s).src/trading.cpp/src/trading.hpp— DEX trading operations (x).src/jsonutils.cpp— JSON parsing limits for IDs, coin amounts, quantities, coords.src/movement.cpp— waypoint encoding/decoding.proto/roconfig/params.pb.text— economy/consensus parameters.
Display names are the ones shown in the game UI; the codes are what appears on-chain.
Internal codes are shown in backticks, e.g. Trimideum (raw a), Raider (rv st).
Conventions in this doc
- "vCHI" / "Cubit" is the in-game coin (the GSP code uses the names
Amount, "coins", and "vCHI" interchangeably). 1 vCHI =COIN= 100,000,000 satoshi (database/amount.hpp:31).- "WCHI"/"CHI" is the real Polygon currency, sent via the move's
outfield (dev payment / burn). Character creation and the burnsale are paid in WCHI.- All IDs (characters, buildings, DEX orders) must be integers in the range
(0, 999999999)exclusive —IdFromJson,src/jsonutils.cpp:148-155.- All item quantities must be integers in
(0, 2^50]—QuantityFromJson,src/jsonutils.cpp:137-145withMAX_QUANTITY = 1<<50(database/inventory.hpp:54).- All vCHI amounts must be integers in
[0, 100000000000]—CoinAmountFromJson,src/jsonutils.cpp:127-135(MAX_COIN_AMOUNT = 100,000,000,000).- The processor is forgiving: invalid sub-commands are silently skipped and logged as warnings; the rest of the move still executes. There is no all-or-nothing rejection except for the structural CHECKs noted below.
1. The Top-Level Move Envelope#
Every Taurion transaction reaches the GSP as one element of a JSON array. Xaya wraps
the player's game move in this envelope (ExtractMoveBasics,
src/moveprocessor.cpp:71-108):
{
"name": "domob",
"move": { /* the game move object — see below */ },
"out": {
"0xb960bECa8623165a3094a629d6A4775857a14d28": 5.0,
"0x2659deaa71B51124bA6e6493b795486b3027Dbc9": 1.0
}
}
| Field | Type | Meaning |
|---|---|---|
name |
string | The Xaya account/player name making the move. Must be a string (a non-string aborts the whole block with a CHECK — src/moveprocessor.cpp:89). |
move |
object | The game move. If it is not an object (number, string, bool, array), the whole move is ignored — src/moveprocessor.cpp:82-86. An empty object {} is accepted but does nothing. |
out |
object | (optional) Map of payout address → WCHI amount. Only two addresses are meaningful to the GSP. |
Dev payment & burn (the out map)#
ExtractMoveBasics (src/moveprocessor.cpp:95-105) reads two amounts from out:
paidToDev— WCHI sent to the dev address. Mainnet dev address:0xb960bECa8623165a3094a629d6A4775857a14d28(proto/roconfig/params.pb.text:26, a Xaya-team multisig). This pays for character creation only.burnt— WCHI sent to the burn address0x2659deaa71B51124bA6e6493b795486b3027Dbc9(proto/roconfig/params.pb.text:29). This feeds the burnsale (mint vCHI).
A non-numeric value here aborts the block with a CHECK
(xaya::ChiAmountFromJson, src/moveprocessor.cpp:100; test
InvalidDataFromXaya, moveprocessor_tests.cpp:136-139). Overpaid/unused amounts
are logged as a warning but cause no error (src/moveprocessor.cpp:1355-1358).
Order of processing within one move#
Critical for combining sub-commands in one transaction
(MoveProcessor::ProcessOne, src/moveprocessor.cpp:1287-1359):
- Account row auto-created if it doesn't exist (uninitialised) — line 1299.
- Coin operations (
vc) — first, even for uninitialised accounts, so vCHI works as a standalone currency and explicit transfers get priority over implicit spends — line 1312. - Game-start gate: if the
GameStartfork is not yet active, processing stops here. (A fork here is a rule change that switches on at a fixed block height — theGameStartfork is the moment the game world opens.) Only coin/burnsale ops work before game start — lines 1317-1318 (testGameStartTests,moveprocessor_tests.cpp:451-501). - DEX operations (
x) — line 1322 (work even for uninitialised accounts). - Account update (
a→init) — line 1326. - If the account is still uninitialised after step 5, the rest of the move is skipped — lines 1333-1339.
- Character updates (
c) — before creation, so a freshly created char can't be acted on in the same move — line 1347. - Character creation (
nc) — line 1348. - Building updates (
b) — line 1350. - Service operations (
s) — line 1351.
A single move object may contain any combination of the top-level keys
vc, x, a, c, nc, b, s.
Top-level move keys (summary)#
| Key | Handler | What it does | Section |
|---|---|---|---|
vc |
TryCoinOperation |
vCHI mint / burn / transfer | 2 |
a |
TryAccountUpdate |
account init (choose faction) | 3 |
nc |
TryCharacterCreation |
create new character(s) | 4 |
c |
TryCharacterUpdates |
all character actions | 5 |
b |
TryBuildingUpdates |
building config / transfer | 6 |
s |
TryServiceOperations |
in-building services | 7 |
x |
TryDexOperations |
DEX trading | 8 |
2. Coin (vCHI) Moves — vc#
Handled by ParseCoinTransferBurn (src/moveprocessor.cpp:475-557) and
TryCoinOperation (src/moveprocessor.cpp:2425-2475). The vc value must be an
object; anything else is ignored. Sub-operations execute in a fixed order:
mint → burn → transfer (test MintBeforeBurnBeforeTransfer,
moveprocessor_tests.cpp:394-427).
{ "name": "domob", "move": { "vc": {
"m": {},
"b": 90,
"t": { "alice": 20, "bob": 5 }
}}}
| Field | Type | Meaning / units | Validation |
|---|---|---|---|
m |
object | Mint vCHI via the burnsale. Must be an empty object {}. Mints coins in exchange for the WCHI in out.burn. |
Non-empty / non-object is ignored (src/moveprocessor.cpp:489-500). |
b |
integer | Amount of vCHI to burn (destroy from own balance). | Must be a valid coin amount; if it exceeds balance, the burn is dropped (set to 0) — src/moveprocessor.cpp:502-516. |
t |
object | Map of recipient name → vCHI amount to transfer. | Each invalid entry skipped; transfers that would exceed remaining balance skipped (src/moveprocessor.cpp:518-553). |
Detailed rules#
- Balance accounting: transfers/burns are applied against a running
total; once balance is exhausted, later entries are skipped (not failed). Burn has priority over transfers (burn parsed first).CHECK_LE(total, balance)guarantees no overdraft (src/moveprocessor.cpp:555). - Self-transfers are no-ops and do not consume balance, so the balance stays
available for the other recipients in the same
tmap (src/moveprocessor.cpp:544-548; testSelfTransfer,moveprocessor_tests.cpp:354-361). - Transfer order within
tis by key (the JSON object iteration order, which is sorted): testTransferOrder(moveprocessor_tests.cpp:429-447) shows{"z":10,"a":101,"middle":99}from a balance of 100 results inaskipped (too big),middlepaid 99,zskipped (only 1 left). - Extra fields in
vcare tolerated (testExtraFieldsAreFine,moveprocessor_tests.cpp:298-305). - Recipients that don't have an account row get an uninitialised one created
automatically (
src/moveprocessor.cpp:2465-2472). - vCHI ops are allowed before game start and for uninitialised accounts.
The burnsale (minting vCHI from WCHI)#
m: {} plus a WCHI burn (out→burn address) buys vCHI at staged prices
(ComputeBurnsaleAmount, src/burnsale.cpp). Stages
(proto/roconfig/params.pb.text:49-52):
| Stage | vCHI available (amount_sold) |
Price (price_sat, WCHI satoshi per vCHI) |
|---|---|---|
| 1 | 10,000,000,000 | 10,000 (0.0001 WCHI) |
| 2 | 10,000,000,000 | 20,000 (0.0002 WCHI) |
| 3 | 10,000,000,000 | 50,000 (0.0005 WCHI) |
| 4 | 20,000,000,000 | 100,000 (0.001 WCHI) |
Minted coins are added to balance, tracked in the account's burnsale_balance
proto field, and the "burnsale" money-supply counter is incremented
(src/moveprocessor.cpp:2437-2444). Example (test Minting,
moveprocessor_tests.cpp:363-378): burning 1,000,000 WCHI in stage 1 mints
10,000,000,000 vCHI. Leftover WCHI after the burnsale caps out is unused.
Cost: the WCHI you put in out.burn. No vCHI cost (it produces vCHI).
3. Account Moves — a#
Handled by TryAccountUpdate → MaybeInitAccount
(src/moveprocessor.cpp:2366-2423). The only account operation is initialisation
(choosing a faction). This is irreversible.
{ "name": "domob", "move": { "a": { "init": { "faction": "b" } } } }
| Path | Type | Meaning | Validation |
|---|---|---|---|
a |
object | The account-update container. Non-object ignored. | — |
a.init |
object | Account initialisation. Non-object ignored (src/moveprocessor.cpp:2370-2371). |
— |
a.init.faction |
string | Faction code: "r" Red, "g" Green, "b" Blue (database/faction.cpp:50-55). |
Must be a valid string; "a" (ancient) and any other value are rejected (src/moveprocessor.cpp:2381-2401). |
Rules#
initmust contain exactly one field (faction); extra fields reject the whole init (src/moveprocessor.cpp:2403-2407; testInvalidInitialisation,moveprocessor_tests.cpp:195-211). Note: extra fields outsideinitbut insideaare fine (test uses{"a":{"x":42,"init":{"faction":"b"}}}).- An already-initialised account cannot change faction
(
src/moveprocessor.cpp:2375-2379; testInitialisationOfExistingAccount,moveprocessor_tests.cpp:213-224). - Faction
"a"(ANCIENT) is explicitly rejected — only the three player factions are valid (src/moveprocessor.cpp:2395-2397). - Init + character creation can be combined in one move (account update runs first):
test
InitialisationAndCharacterCreation(moveprocessor_tests.cpp:226-242).
Cost: none (vCHI/WCHI). It is a free state change.
4. Character Creation — nc#
Handled by TryCharacterCreation (src/moveprocessor.cpp:110-169).
The nc value must be an array. Each element creates one character.
{
"name": "domob",
"move": { "nc": [ {}, {} ] },
"out": { "0xb960bECa8623165a3094a629d6A4775857a14d28": 20.0 }
}
| Field | Type | Meaning | Validation |
|---|---|---|---|
nc |
array | One creation request per element. Non-array ignored (:116-117). |
— |
nc[i] |
object | Must be an empty object {} — zero fields (:137-141). Faction is taken from the account, not specified here. |
Non-object or any extra field (even {"faction":"r"}) → that entry skipped (test InvalidCommands, moveprocessor_tests.cpp:519-531). |
Rules#
- The account must already be initialised (have a faction). Otherwise no chars are
created (test
AccountNotInitialised,moveprocessor_tests.cpp:533-541). The new character inherits the account's faction (src/moveprocessor.cpp:121-123). - Cost:
character_cost×COINin WCHI per character, paid viaoutto the dev address.character_cost = 10(proto/roconfig/params.pb.text:3), so 10 WCHI per character. The cost is deducted frompaidToDevfor each created char (src/moveprocessor.cpp:144-166). - If
paidToDevruns out, processing thencarray returns immediately (no point trying later entries) —src/moveprocessor.cpp:145-154. Underpaying by 1 satoshi rejects creation (testDevPayment,moveprocessor_tests.cpp:584-605). - Character limit:
character_limit = 20per account (proto/roconfig/params.pb.text:4). At the limit, creation stops (src/moveprocessor.cpp:156-163; testCharacterLimit,moveprocessor_tests.cpp:634-655). - vCHI airdrop (TEST ONLY): each created character grants the owner
VCHI_AIRDROP = 1000vCHI (src/moveprocessor.cpp:56-57, 1366-1371). The source marks thisFIXME: For the full game, remove this. TestVChiAirdrop(moveprocessor_tests.cpp:569-582) confirms 2,000 vCHI for two characters. - The character is spawned at a faction spawn building (
SpawnCharacter). Spawn building IDs: Red→6, Green→4, Blue→5 (proto/roconfig/params.pb.text:33-47).
5. Character Update Moves — c#
Handled by TryCharacterUpdates (src/moveprocessor.cpp:171-249) and
PerformCharacterUpdate (src/moveprocessor.cpp:1846-1905). This is the workhorse
move; it contains all character actions.
Envelope shapes#
c may be:
- a single object (one operation), or
- an array of objects (batched operations).
(src/moveprocessor.cpp:179-192)
Each operation object must have an id identifying the target character. id may
be:
- a single integer ID, or
- an array of IDs — the same operation is applied to each
(
src/moveprocessor.cpp:206-247; testMultipleCharacters,moveprocessor_tests.cpp:769-805).
{ "name": "domob", "move": { "c": [
{ "id": 1, "wp": "<encoded>" },
{ "id": [2, 3], "send": "alice" }
]}}
Validation for every c operation#
idmust parse as a valid ID; invalid IDs in a list are skipped individually (src/moveprocessor.cpp:219-228; testInvalidUpdate,moveprocessor_tests.cpp:876-918).- The character must exist (
:230-236) and be owned byname(:238-244). A failed owner check does not abort other operations (testOwnerCheck,moveprocessor_tests.cpp:859-874). - All actions within one
coperation execute in the fixed order defined byPerformCharacterUpdate(see each sub-action below). Many can be combined.
Sub-action execution order (one c entry)#
src/moveprocessor.cpp:1846-1905:
send(transfer character)prospect(start prospecting)v(change vehicle)fit(set fitments)mine(start mining)wp(set waypoints)wpx(extend waypoints)speed(chosen speed)fb(found building)ref(mobile refining)drop(drop loot)pu(pick up loot)eb(enter building)xb(exit building)
This ordering is deliberate; e.g. mining before waypoints prevents "move + mine" simultaneously; drop before pickup frees cargo; enter before exit means specifying both = just enter.
5.1 Transfer character — send#
MaybeTransferCharacter (src/moveprocessor.cpp:1413-1451).
{ "id": 1, "send": "alice" }
| Field | Type | Meaning |
|---|---|---|
send |
string | Recipient account name. Non-string ignored. |
Rules: recipient must be an initialised account of the same faction
(:1431-1445) and must not be at the character limit (:1422-1429). Invalid
transfers are silently skipped (test InvalidTransfer,
moveprocessor_tests.cpp:824-857). A character created in the same move cannot be
transferred (updates run before creation; test CreationAndUpdate,
moveprocessor_tests.cpp:729-747). Cost: none.
5.2 Movement — wp (set waypoints) and wpx (extend)#
ParseCharacterWaypoints (src/moveprocessor.cpp:559-619),
MaybeSetCharacterWaypoints (:1453-1483); extension in
ParseCharacterWaypointExtension (:621-659), MaybeExtendCharacterWaypoints
(:1485-1499).
{ "id": 1, "wp": "DwAAAB4AAAA...", "speed": 750000 }
| Field | Type | Meaning |
|---|---|---|
wp |
string | null | Encoded waypoint path. null ⇒ stop movement (clear waypoints). |
wpx |
string | Encoded waypoints to append to existing path. |
speed |
integer | Chosen speed cap (see 5.3). |
Waypoint encoding (src/movement.cpp:49-114): the array of {x,y} hex
coordinates is JSON-serialised, deflate-compressed (zlib raw, windowBits −15), and
base64-encoded. Decoding limits: max uncompressed size MAX_WAYPOINT_SIZE = 1<<20
bytes, max compression ratio 3 (src/movement.cpp:80). The client must produce this
exact format; a plain JSON array string is invalid (test BasicWaypoints,
moveprocessor_tests.cpp:980-1035 shows "foo", [], {x,y} etc. all rejected).
wp rules:
- Must be a string or explicit
null(:580-587). - Character must not be busy (no ongoing operation) —
:589-595. - Character must not be inside a building —
:597-604. - Setting waypoints stops any current movement and stops mining
(
StopCharacter,StopMining,:1464-1465). - A character with
speed == 0cannot move: waypoints are ignored (it is still stopped) —:1472-1478; testWaypointsWithZeroSpeed(moveprocessor_tests.cpp:1056-1076). wp: nullclearsmovemententirely (testEmptyWaypoints,moveprocessor_tests.cpp:1037-1054).
wpx rules:
- Only valid if the character is already moving (has existing waypoints), or sets
wpin the same move (extension runs afterwp) —:638-644; testWaypointExtension(moveprocessor_tests.cpp:1078-1146). wpx: nullis invalid (not the "stop" sentinel; onlywphonours null).
Cost: none.
5.3 Chosen speed — speed#
MaybeSetCharacterSpeed (src/moveprocessor.cpp:1380-1409).
| Field | Type | Meaning |
|---|---|---|
speed |
unsigned integer | Voluntary speed cap, in the same units as the character's intrinsic speed (milli-tiles/block). |
Rules:
- Only valid if the character has active movement (processed after
wp), so you can set waypoints + speed in one move (:1388-1394; testChosenSpeedWorks,moveprocessor_tests.cpp:1160-1177). - Must be
> 0and≤ MAX_CHOSEN_SPEED = 1,000,000(1k tiles/block,src/moveprocessor.hpp:59,src/moveprocessor.cpp:1397-1403). Floats, negatives, 0, objects, and1000001are all rejected (testChosenSpeedInvalid,moveprocessor_tests.cpp:1179-1217).
Cost: none.
5.4 Prospecting — prospect#
ParseCharacterProspecting (src/moveprocessor.cpp:833-879),
MaybeStartProspecting (:1520-1544).
{ "id": 1, "prospect": {} }
| Field | Type | Meaning |
|---|---|---|
prospect |
object | Must be an empty object {}. |
Rules:
- Non-object or non-empty object rejected (
:840-849; testInvalid,moveprocessor_tests.cpp:2937-2963). - Character must have the
prospecting_blocksability (:851-855). - Must not be busy (
:857-862) and not inside a building (:864-870). - The region (derived from the character's position) must be prospectable per
CanProspectRegion— not currently being prospected, and either never prospected or pastprospection_expiry_blocks(5000 mainnet, 100 testnet —proto/roconfig/params.pb.text:9,test_params.pb.text:5). Re-prospecting an expired region clears the old result (:1530-1532; testReprospecting,moveprocessor_tests.cpp:2913-2935). - On success: movement stops, an ongoing operation is created lasting
prospecting_blocksblocks, and the region records the prospecting character (:1527-1543; testSuccess,moveprocessor_tests.cpp:2877-2911). - With multiple characters racing for the same region in one block, the first valid
one wins (test
MultipleCharacters/OrderOfCharactersInAMove,moveprocessor_tests.cpp:3000-3064).
Cost: none (time cost only — the busy period).
5.5 Mining — mine#
ParseCharacterMining (src/moveprocessor.cpp:881-954), MaybeStartMining
(:1546-1557).
{ "id": 1, "mine": {} }
| Field | Type | Meaning |
|---|---|---|
mine |
object | Must be an empty object {}. |
Rules:
- Non-object / non-empty object rejected (
:887-897; testInvalidMoveJson,moveprocessor_tests.cpp:3139-3153). - Character must have the
miningability (:899-903), not be busy (:905-910), not be inside a building (:912-917), and not be moving (has_movement()⇒ reject) —:925-930. - Region must be prospected (
:932-940) and have resource left > 0 (:942-951). - Sets
mining.active = true. If the move both sets waypoints and mines, mining is processed first then waypoints clear it ⇒ ends up not mining (testMiningAndWaypointsInSameMove,moveprocessor_tests.cpp:3197-3213). - Setting
wp: null(stop) clearsmining.active(testWaypointsStopMining,moveprocessor_tests.cpp:3108-3122).
Cost: none.
5.6 Change vehicle — v#
ParseChangeVehicle (src/moveprocessor.cpp:981-1038), MaybeChangeVehicle
(:1591-1613).
{ "id": 1, "v": "rv st" }
| Field | Type | Meaning |
|---|---|---|
v |
string | Internal item code of the target vehicle, e.g. Raider (rv st), Looter (rv s), Barracuda (bv st), Scarab (gv st). |
Rules:
- Must be a string naming a valid vehicle item (
data->has_vehicle()) —:1021-1026. - Character must have full HP (armour and shield both at max) —
:992-998,HasFullHp(:963-977). TestNoFullHp(moveprocessor_tests.cpp:1576-1599). - Character must be inside a building (
:1000-1006) that is not a foundation (:1012-1019). - The owner must own a unit of that vehicle in the building inventory (the vehicle
the character is currently in does not count) —
:1028-1035; testChangeToSameType(moveprocessor_tests.cpp:1673-1699).
On execute (:1591-1613): the character's current inventory is dropped to the
building inventory, all fitments removed (returned to inventory), the old vehicle is
added back to inventory, the new vehicle removed from inventory, and stats are
re-derived (HP reset to the new max). Test BasicUpdate
(moveprocessor_tests.cpp:1615-1636). Cost: none (no fee — just the item swap).
5.7 Set fitments — fit#
ParseSetFitments (src/moveprocessor.cpp:1040-1123), MaybeSetFitments
(:1615-1637).
{ "id": 1, "fit": ["lf gun", "lf scanner"] }
(here Light Rail Gun (lf gun) and Graviton Spectrometer (lf scanner)).
| Field | Type | Meaning |
|---|---|---|
fit |
array of strings | The complete desired fitment list (replaces all current fitments). A fitment is a piece of equipment slotted onto the vehicle (weapon, scanner, cargo expander, etc.). Each must be a valid fitment item code. |
Rules:
- Must be an array; each element a string naming a valid fitment (
data->has_fitment()) —:1046,:1076-1093. Non-array, object values, and non-fitment items reject the whole command (testsInvalidFormat/InvalidItems,moveprocessor_tests.cpp:1290-1326). - Character must have full HP (
:1049-1055), be inside a building (:1057-1063), and that building must not be a foundation (:1067-1073). - Required fitments must be available in the owner's building inventory — existing
fitments already on the character count toward availability (they're conceptually
removed then re-added) —
:1095-1111; testExistingFitmentsReused(moveprocessor_tests.cpp:1440-1456). The check happens before the inventory-drop, so items in the character's cargo don't help (testItemsNotAvailable,moveprocessor_tests.cpp:1384-1403). - The combination must be valid for the vehicle (
CheckVehicleFitments) — slot/complexity limits —:1113-1120; testInvalidFitments(moveprocessor_tests.cpp:1405-1418).
On execute: drop cargo to building, remove old fitments, consume new ones from
building inventory, attach them, re-derive stats. Vehicle change (v) is processed
before fit, so you can change vehicle and refit in one move (test
ChangeVehicleBeforeFitmentsAndPickup, moveprocessor_tests.cpp:1701-1731).
Cost: none.
5.8 Found building (place foundation) — fb#
ParseFoundBuilding (src/moveprocessor.cpp:1169-1251), MaybeFoundBuilding
(:1639-1669).
{ "id": 1, "fb": { "t": "b r", "rot": 3 } }
| Field | Type | Meaning |
|---|---|---|
fb |
object | Foundation placement request. Exactly two fields (t, rot) — :1180. A foundation is the empty shell of a building you place on the map and later finish by dropping in resources. |
fb.t |
string | Building type code, e.g. a Refinery (b r). Must be a valid, constructible building. |
fb.rot |
unsigned integer 0–5 | Shape rotation in 60° steps (ParseBuildingConfig, :1153-1159). |
Rules:
fbmust have exactly the two fields and parse (:1180-1184; testInvalidFormat,moveprocessor_tests.cpp:1751-1793rejects bad type,rot: -1,rot: 6,rot: 4.0, extra/missing fields).- Character must not be busy (
:1186-1191) and not be inside a building (:1193-1199). - The building type must have
constructiondata (has_construction()) — types likeitemmakerthat cannot be constructed are rejected (:1201-1206; testUnconstructibleBuilding,moveprocessor_tests.cpp:1821-1830). - If the building's construction specifies a faction, it must match the character's
faction (
:1208-1220; testFactionCheck,moveprocessor_tests.cpp:1860-1883). - The character must hold the foundation resources in cargo
(
construction.foundationmap) — e.g. real refineryb rneeds Agarite (mat a) ×6,400,000 and Borolium (mat b) ×1,600,000 (proto/roconfig/buildings/b_r.pb.text:35-41). Insufficient ⇒ reject (:1222-1235; testNotEnoughResources,moveprocessor_tests.cpp:1832-1842). - The footprint must fit on the map (
CanPlaceBuilding) with the character temporarily removed from obstacle checks (:1237-1248; testCannotPlaceBuilding,moveprocessor_tests.cpp:1844-1858).
On execute (:1639-1669): a new building is created (foundation = true) at the
character's position, foundation resources are consumed from cargo, and the
character automatically enters the new foundation. You can then drop more resources
(drop runs after fb) and/or exit (xb runs last). Tests Success,
FoundationBeforeDrop, FoundationBeforeExit
(moveprocessor_tests.cpp:1885-1960). Cost: the foundation resources (items), no
vCHI.
See Section 6.4 for how a foundation becomes a finished building.
5.9 Mobile refining — ref#
TryMobileRefining (src/moveprocessor.cpp:407-423), parsed by
ServiceOperation::ParseMobileRefining (src/services.cpp:1245-1275). This lets a
character with the refining proto refine ore in the field (no building needed).
{ "id": 1, "ref": { "i": "raw a", "n": 12 } }
| Field | Type | Meaning |
|---|---|---|
ref |
object | Refining request. Exactly two fields (:1254). |
ref.i |
string | Item code of the resource to refine (e.g. Trimideum raw a). |
ref.n |
integer | Amount of raw resource to consume. Must be a multiple of the per-step input. |
Rules (shared with the building refining service, 7.1):
- Item must exist and be refinable (
has_refines()) —src/services.cpp:176-195. n > 0and an exact multiple ofinput_units(adjusted by the character's refining efficiency modifier) —src/services.cpp:197-225. Otherwise invalid (testInvalid,moveprocessor_tests.cpp:2284-2298:n:10rejected when one step is not 10).- The character must hold ≥
nof the resource in cargo. - Cost:
steps × refData.cost()vCHI, paid from the owner's account, with no service fee (mobile, not in a building) —src/services.cpp:986-997. TestWorks(moveprocessor_tests.cpp:2300-2312): refining 12 oftest ore(per-step 6) = 2 steps, costs 20 vCHI, yields outputs. - Refining always produces less cargo volume than it consumes, so cargo never
overflows (
src/services.cpp:255-266). - Processed before
drop/pu, so outputs can be dropped and freed cargo reused (testsBeforeDrop,BeforePickup,moveprocessor_tests.cpp:2314-2364).
5.10 Drop loot — drop#
MaybeDropLoot (src/moveprocessor.cpp:1749-1793), parsed by
ParseDropPickupFungible (:812-831).
{ "id": 1, "drop": { "f": { "raw a": 5, "mat b": 2 } } }
| Field | Type | Meaning |
|---|---|---|
drop |
object | Drop container. Exactly one field, f (:824-828). |
drop.f |
object | Map of item code → quantity to drop from the character's cargo. |
Rules:
fmust be an object; the container must have onlyf(extra fields ⇒ ignore) —:818-828; testInvalidDrop(moveprocessor_tests.cpp:2419-2448).- Unknown item codes are skipped (
ParseFungibleQuantities,:777-808); invalid quantities skipped; dropping more than held drops only what's held (MoveFungibleBetweenInventories,:1684-1745; testBasicDrop,moveprocessor_tests.cpp:2481-2513). - Destination depends on location:
- In a foundation ⇒ items go to the building's
construction_inventory, and this may trigger construction start if requirements are now met (:1764-1774; testsDropInFoundation/StartBuildingConstruction). - In a finished building ⇒ items go to the owner's building inventory
(
:1776-1782). - On the map ⇒ items go to ground loot at the character's tile (
:1784-1792).
- In a foundation ⇒ items go to the building's
Cost: none.
5.11 Pick up loot — pu#
MaybePickupLoot (src/moveprocessor.cpp:1795-1844).
{ "id": 1, "pu": { "f": { "raw a": 100 } } }
| Field | Type | Meaning |
|---|---|---|
pu |
object | Pickup container. Exactly one field, f. |
pu.f |
object | Map of item code → quantity to pick up. |
Rules:
- Same parsing as
drop(ParseDropPickupFungible); testInvalidPickUp(moveprocessor_tests.cpp:2450-2479). - Limited by the character's free cargo space; partial pickups happen if cargo
fills (
:1806, maxSpace arg toMoveFungibleBetweenInventories). Items are processed alphabetically by code, which matters when cargo fills mid-pickup (testOrderOfItems,moveprocessor_tests.cpp:2619-2650). - Source:
- In a foundation ⇒ cannot pick up (no inventory) —
:1816-1823; testPickupInFoundation. - In a finished building ⇒ from the owner's building inventory (
:1826-1832). - On the map ⇒ from ground loot (
:1836-1842).
- In a foundation ⇒ cannot pick up (no inventory) —
dropruns beforepu, so you can drop to free cargo then pick up in one move (testRelativeOrder,moveprocessor_tests.cpp:2594-2617).
Cost: none.
5.12 Enter building — eb#
ParseEnterBuilding (src/moveprocessor.cpp:661-727), MaybeEnterBuilding
(:1501-1509).
{ "id": 1, "eb": 100 }
| Field | Type | Meaning |
|---|---|---|
eb |
integer | null | Building ID to queue entering, or null to cancel a queued enter. |
Rules:
- If already inside a building, cannot queue entering another (
:670-677; testAlreadyInBuilding,moveprocessor_tests.cpp:2098-2114). nullclears the queued enter target (:681-689; testValidClear).- Otherwise must be a valid existing building ID (
:691-707). Invalid values ({}, strings,0,-10, nonexistent IDs) are rejected and leave the prior state unchanged (testsInvalidSet/InvalidClear,moveprocessor_tests.cpp:1979-2038). - Can only enter ancient buildings or buildings of the character's own faction
(
:709-720). - Setting "enter" only sets a flag; it is valid even when the character is busy
(test
BusyIsFine,moveprocessor_tests.cpp:2080-2096). Actual entering happens later when the character reaches the building.
Cost: none.
5.13 Exit building — xb#
ParseExitBuilding (src/moveprocessor.cpp:729-767), MaybeExitBuilding
(:1511-1518).
{ "id": 1, "xb": {} }
| Field | Type | Meaning |
|---|---|---|
xb |
object | Must be an empty object {}. |
Rules:
- Must be an empty object; non-empty/non-object rejected (
:734-744; testInvalid,moveprocessor_tests.cpp:2118-2143). - Character must not be busy (
:746-752) and must currently be inside a building (:754-760). - On success the character is placed on a random free tile within ~5 of the building
centre (
LeaveBuilding; testValid,moveprocessor_tests.cpp:2180-2198). ebis processed beforexb; sending both while inside = just enter (the exit becomes invalid). When inside and sendingxb+eb, the result is exit only / enter-target cleared (testEnterAndExitWhenInside,moveprocessor_tests.cpp:2200-2217). Becausexbruns last, you can pick up items from inside and exit in the same move (testInventoryBeforeExit,moveprocessor_tests.cpp:2219-2241).
Cost: none.
6. Building Moves — b#
TryBuildingUpdates (src/moveprocessor.cpp:343-405), TryBuildingUpdate
(:326-341). Only a building's owner can update it. Like c, the b value can
be a single object or an array of objects, each with an id.
{ "name": "domob", "move": { "b": [
{ "id": 102, "sf": 10, "xf": 250 },
{ "id": 102, "send": "alice" }
]}}
| Field | Type | Meaning |
|---|---|---|
b[i].id |
integer | Building ID. Must exist (:379-384). |
b[i].sf |
unsigned integer | Service-fee percent (see 6.1). |
b[i].xf |
unsigned integer | DEX-fee basis points (see 6.2). |
b[i].send |
string | Transfer building to another account (see 6.3). |
Common validation#
idmust parse (:371-377).- Ancient buildings cannot be updated by anyone (
:386-392; testAncientCannotBeUpdated,moveprocessor_tests.cpp:3295-3305). - Only the owner may update (
:394-401; testNotOwner,moveprocessor_tests.cpp:3307-3317). - A single
bentry may setsf,xf, andsendtogether (TryBuildingUpdate,:326-341). Config updates and transfer are independent.
6.1 Service fee — sf#
MaybeUpdateServiceFee (src/moveprocessor.cpp:258-273).
- Type must be an unsigned integer (floats, negatives, strings rejected) —
:261. - Range
0 … MAX_SERVICE_FEE_PERCENT = 1000percent (src/moveprocessor.cpp:48,:265-269). Above 1000 rejected (testSetServiceFee,moveprocessor_tests.cpp:3359-3414). - This is the markup charged to other-faction players using the building's
services. The percentage is applied to the base cost, rounded up
(
src/services.cpp:1011-1015).
6.2 DEX fee — xf#
MaybeUpdateDexFee (src/moveprocessor.cpp:279-294).
- Unsigned integer, range
0 … MAX_DEX_FEE_BPS = 3000basis points (30%) —src/moveprocessor.cpp:54,:286-290. (A basis point is one hundredth of a percent, so 100 bps = 1%, and 3000 bps = 30%.) TestSetDexFee(moveprocessor_tests.cpp:3416-3471). - This is the building owner's cut on DEX trades in addition to the base fee
dex_fee_bps = 300(3% mainnet;proto/roconfig/params.pb.text:19). See Section 8.
Delayed application of config changes#
sf/xf changes do not apply immediately. They are scheduled as an ongoing
operation that takes effect after building_update_delay blocks — 120 blocks
mainnet, 10 testnet (proto/roconfig/params.pb.text:18, test_params.pb.text:9).
PerformBuildingConfigUpdate (src/moveprocessor.cpp:1907-1921). Multiple config
updates to the same building in one block create multiple scheduled ops (test
ArrayUpdate, moveprocessor_tests.cpp:3319-3357).
6.3 Transfer building — send#
MaybeTransferBuilding (src/moveprocessor.cpp:298-324).
sendmust be a string naming an initialised account of the same faction as the building (:307-321). Uninitialised / wrong-faction / nonexistent / non-string all rejected (testTransfer,moveprocessor_tests.cpp:3473-3516).- Transfer applies immediately (
PerformBuildingTransfer,:1923-1930), unlike config changes.
Cost (all building moves): none.
6.4 Building construction (foundation → finished)#
Construction is not a b move; it is driven by dropping resources. A foundation
(created by fb) accumulates resources in
its construction_inventory via drop. When the full requirement
(construction.full_building map) is met, construction auto-starts and runs for
construction.blocks blocks (MaybeStartBuildingConstruction,
src/moveprocessor.cpp:1771-1774; test StartBuildingConstruction,
moveprocessor_tests.cpp:2771-2836). Example real refinery b r
(proto/roconfig/buildings/b_r.pb.text): foundation costs mat a×6.4M + mat b×1.6M;
full building additionally needs mat a×128M + mat b×32M; blocks: 10.
7. Service Operations — s#
TryServiceOperations (src/moveprocessor.cpp:425-446), ServiceOperation::Parse
(src/services.cpp:1169-1243). The s value must be an array of operation
objects. Each operation happens inside a building identified by b, with a type
t.
{ "name": "domob", "move": { "s": [
{ "b": 100, "t": "ref", "i": "raw a", "n": 6 },
{ "b": 100, "t": "fix", "c": 200 }
]}}
Common envelope#
| Field | Type | Meaning |
|---|---|---|
b |
integer | Building ID. Must exist and not be a foundation (src/services.cpp:1192-1205). |
t |
string | Operation type: ref, fix, rve, cp, bld. |
The account must be initialised; the building must offer the requested service
(offered_services in the building config) — IsFullyValid/IsSupported
(src/services.cpp:1064-1103). The account must have enough vCHI for base + fee
(:1091-1100).
Cost model (all s operations)#
GetCosts (src/services.cpp:986-1016):
- Base cost = operation-specific (below), paid in vCHI by the account.
- Service fee =
ceil(base × sf% / 100)paid to the building owner, but fee is zero if the building is ancient or owned by the operator themselves (:999-1009). On execute, base+fee is deducted; fee credited to owner (:1127-1145).
s is processed after coin ops and character updates (tests
ServicesAfterCoinOperations, ServicesAfterCharacterUpdates,
moveprocessor_tests.cpp:3597-3643). Test Works
(moveprocessor_tests.cpp:3549-3571) shows a full multi-op example.
7.1 Refining — ref#
RefiningOperation (src/services.cpp:68-266). Same as mobile refining (5.9) but in
a building.
| Field | Type | Meaning |
|---|---|---|
i |
string | Resource to refine. |
n |
integer | Amount; must be an exact multiple of the per-step input units. |
Object must have exactly 4 fields (b,t,i,n) — ParseItemAmount,
src/services.cpp:1147-1167. Base cost = steps × refData.cost(). Consumes the
resource from the building inventory, produces refined outputs.
7.2 Armour repair — fix#
RepairOperation (src/services.cpp:273-437).
| Field | Type | Meaning |
|---|---|---|
c |
integer | Character ID to repair (must be owned by the operator, inside this building, not busy, with missing armour). |
Object must have exactly 3 fields (b,t,c) — :426-428. Base cost =
ceil(missingArmourHp × armour_repair_cost_millis / 1000) vCHI. With
armour_repair_cost_millis = 100 (proto/roconfig/params.pb.text:12) this is 1 vCHI
per 10 HP repaired (src/services.cpp:376-391). Repair takes
ceil(missingHp / armour_repair_hp_per_block) blocks, with
armour_repair_hp_per_block = 100 (proto/roconfig/params.pb.text:11,
src/services.cpp:404-418). Shields
regenerate naturally and are not part of repair.
7.3 Reverse engineering — rve#
RevEngOperation (src/services.cpp:444-594). Converts artefacts into blueprints.
| Field | Type | Meaning |
|---|---|---|
i |
string | Artefact type, e.g. Ancient Artefact (common) art c. |
n |
integer | Number of artefacts to reverse-engineer. |
Exactly 4 fields. Base cost = n × revEngData.cost(). Each artefact is consumed
and, per a probability roll, may yield a blueprint chosen from the artefact's
possible_outputs (filtered to neutral items + the operator's faction). Success chance
decreases as more of that item type have already been found
(RevEngSuccessChance(existingCount), :580-593) — a global rarity mechanic. Uses
randomness (xaya::Random).
7.4 Blueprint copy — cp#
BlueprintCopyOperation (src/services.cpp:601-735). A blueprint is the recipe used
to construct a vehicle or item. An original blueprint can be copied repeatedly; this
operation makes single-use copies (item code suffix bpc, "blueprint copy") from an
original.
| Field | Type | Meaning |
|---|---|---|
i |
string | Original blueprint item (must be an original blueprint). |
n |
integer | Number of copies to make. |
Exactly 4 fields. Base cost = n × bp_copy_cost × complexity (bp_copy_cost = 1
mainnet; proto/roconfig/params.pb.text:13). The original is consumed (1) and an
ongoing op produces n copies, one every construction_blocks × complexity blocks
(GetBpCopyBlocks, :739-751; construction_blocks = 1 mainnet, :16).
7.5 Construction (vehicle / item) — bld#
ConstructionOperation (src/services.cpp:763-951). Builds vehicles or fitments from
blueprints.
| Field | Type | Meaning |
|---|---|---|
i |
string | Blueprint (original or copy bpc). |
n |
integer | Number of items to construct. |
Exactly 4 fields. Requires the building to offer vehicle_construction (for vehicle
outputs) or item_construction (otherwise) — :833-843. Base cost =
n × construction_cost × complexity (construction_cost = 1 mainnet, :15). Consumes
the construction_resources per output and the blueprint(s): 1 original or n
copies (:884-898, :931-934). If the output item has a faction, it must match the
operator's faction (:854-867). Each item takes
construction_blocks × complexity blocks (GetConstructionBlocks, :955-960).
8. DEX Trading Moves — x#
TryDexOperations (src/moveprocessor.cpp:448-473), DexOperation::Parse
(src/trading.cpp:607-682). The DEX (decentralised exchange) is the in-game
marketplace: players place buy orders (bids) and sell orders (asks) for items,
priced in vCHI, and the GSP matches them automatically. The x value must be an
array. All DEX activity happens inside a (non-foundation) building, where each
account has a per-building inventory and orders are matched per building+item. DEX ops
run before character updates and after coin ops (tests
AfterCoinOperations/BeforeCharacterUpdates, moveprocessor_tests.cpp:3696-3745).
When you place an order, the cost is held aside (escrowed) until the order fills or is cancelled: a bid escrows the vCHI you'd pay, an ask escrows the items you'd sell.
The op type is inferred from which fields are present (the object size disambiguates):
| Op | Required fields | Field count |
|---|---|---|
| Transfer | b, i, n, t |
4 |
| Bid (buy) | b, i, n, bp |
4 |
| Ask (sell) | b, i, n, ap |
4 |
| Cancel | c |
1 |
Common fields:
| Field | Type | Meaning |
|---|---|---|
b |
integer | Building ID (must exist, not a foundation — src/trading.cpp:111-139). |
i |
string | Item code being traded. |
n |
integer | Quantity (1 … 2^50). |
t |
string | (transfer) Recipient account name. |
bp |
integer | (bid) Buy price, vCHI per unit. |
ap |
integer | (ask) Sell price, vCHI per unit. |
c |
integer | (cancel) Order ID to cancel. |
{ "name": "domob", "move": { "x": [
{ "b": 100, "i": "raw a", "n": 10, "t": "alice" },
{ "b": 100, "i": "raw a", "n": 50, "ap": 25 },
{ "b": 100, "i": "raw a", "n": 30, "bp": 20 },
{ "c": 12345 }
]}}
8.1 Item transfer — t#
TransferOperation (src/trading.cpp:156-225). Moves items between two accounts'
inventories inside the same building. The sender must hold ≥ n of i in that
building (:178-195). Self-transfer is a no-op. Recipient account auto-created if
needed. Cost: none (no fee on transfers). Test Works
(moveprocessor_tests.cpp:3677-3694).
8.2 Bid (buy order) — bp#
BidOperation (src/trading.cpp:344-418). Places a buy order at bp vCHI/unit. To be
valid the account must hold the full n × bp vCHI (:360-376). The bid first matches
existing asks at or below bp (QueryToMatchBid); matched items are credited to the
buyer's building inventory and the seller is paid (minus fees). Matched units are
charged at the resting ask's price, not at bp — so if you bid higher than the best
ask, you pay the lower ask price and keep the difference (:386-396). Any unfilled
remainder is placed as a resting bid, and only that remainder has its vCHI escrowed at
bp/unit (:417).
8.3 Ask (sell order) — ap#
AskOperation (src/trading.cpp:423-500). Places a sell order at ap vCHI/unit. The
account must hold at least n of i in the building (:439-456). The ask first
matches existing bids at or above ap; matched units are paid at the resting bid's
price, not at ap — so if you ask lower than the best bid, you are paid the higher bid
price (:477-478). The seller is paid (minus fees) and items move to the buyer. Items
for the unfilled remainder are escrowed when that remainder rests on the book (:499).
DEX fee model#
PayToSellerAndFee (src/trading.cpp:291-328). On each fill, the seller pays a
fee:
totalBps = base dex_fee_bps (300 = 3% mainnet) + building owner's xf.- Total fee =
ceil(cost × totalBps / 10000)(rounded up, so splitting orders can't dodge fees; max 1 vCHI rounding per fill). - Owner's portion =
floor(cost × ownerBps / 10000)(rounded down). - The remainder beyond the owner's cut is effectively burned (paid neither to seller
nor owner). Ancient buildings have
ownerBps = 0enforced (:304-305).
8.4 Cancel order — c#
CancelOrderOperation (src/trading.cpp:507-593). Cancels an order by ID; only the
order's owner can cancel (:528-547). On cancel, escrow is refunded: a bid
refunds quantity × price vCHI to the account; an ask refunds the items to the
account's building inventory (:559-593).
Malformed DEX ops (e.g.
{"x":"invalid"}, wrong field counts) are logged and skipped; valid ones in the same array still execute (testWorks,moveprocessor_tests.cpp:3677-3694;src/moveprocessor.cpp:465-471).
9. God-Mode / Admin Moves (cmd → god)#
Admin only. These are not player moves. They arrive via the separate admin command channel (
ProcessAdmin,src/moveprocessor.cpp:1265-1285), not the normal move array. Each admin entry is{"cmd": {...}}; the GSP readscmd.god.
[ { "cmd": { "god": { "teleport": [ { "id": 1, "pos": { "x": 5, "y": -42 } } ] } } } ]
Gating: when god-mode is allowed#
HandleGodMode (src/moveprocessor.cpp:2346-2364) executes god commands only if
params.god_mode is true; otherwise the command is logged and ignored
(:2352-2356).
IMPORTANT — current deployment:
god_mode: trueis set in the baseproto/roconfig/params.pb.text:31, which is the mainnet base config. As built, god mode is enabled on mainnet/Polygon, testnet, and regtest in this tree. The unit testGodModeDisabledTests(moveprocessor_tests.cpp:4088-4127) still asserts god mode is disabled onChain::MAIN, so this test would fail against the current config — see Open Questions. The original design intent was god-mode = regtest-only.
The structure of admin processing: ProcessAdmin requires a JSON array (else CHECK
abort — test InvalidAdminFromXaya, moveprocessor_tests.cpp:142-149); each element
must be an object with a cmd. A non-object cmd or missing god is a silent no-op
(test AllAdminDataAccepted, moveprocessor_tests.cpp:167-178). The whole god
object processes these sub-commands in order (:2358-2363):
9.1 setchar — set vehicle/fitments#
MaybeGodSetChar (src/moveprocessor.cpp:2278-2342). Array of {id, v?, f?}.
id— character ID.v— vehicle code (validated as a vehicle).f— array of fitment codes (validated; invalid ones skipped).- Re-derives all stats and resets HP to max. Runs before
sethpsosethpcan override HP (:2358).
9.2 teleport — move characters#
MaybeGodTeleport (src/moveprocessor.cpp:1950-1990). Array of {id, pos} where
pos is {x,y}. Teleports the character and stops its movement. Invalid entries
skipped (test Teleport/InvalidTeleport, moveprocessor_tests.cpp:3765-3807). A
pos with extra keys like z is invalid.
9.3 sethp — set HP / max HP#
MaybeGodAllSetHp (src/moveprocessor.cpp:2050-2058). Object with b (buildings) and
c (characters) arrays. Each entry {id, a?, s?, ma?, ms?}:
aarmour,sshield (current HP);mamax armour,msmax shield.- Only valid unsigned-int values are applied; others (floats, negatives, bools)
ignored per-field (test
SetHp,moveprocessor_tests.cpp:3809-3879).
9.4 build — create buildings#
MaybeGodBuild (src/moveprocessor.cpp:2063-2140). Array of
{t, o, c, rot} (exactly 4 fields):
tbuilding type,ccentre{x,y},rot0–5.oowner: a name string (account must be initialised; faction taken from it) ornullfor an ancient building.- Does not check placement validity (so tests can place freely) —
:2120-2124. Buildings are created already-finished (finished_heightset). TestBuild(moveprocessor_tests.cpp:3881-3938).
9.5 drop — spawn loot#
MaybeGodDropLoot (src/moveprocessor.cpp:2168-2243). Array of tiles, each exactly 2
fields: a fungible map plus one target — either pos: {x,y} (ground) or
building: {id, a} (building inventory for account a). Creates items from nothing.
Tests ValidDropLoot/InvalidDropLoot (moveprocessor_tests.cpp:3940-4041).
9.6 giftcoins — mint vCHI to accounts#
MaybeGodGiftCoins (src/moveprocessor.cpp:2249-2276). Object of name → vCHI amount.
Credits balances (creating accounts if needed) and increments the "gifted"
money-supply counter. Non-integer amounts skipped (test GiftCoins,
moveprocessor_tests.cpp:4043-4082).
10. Quick Reference: Constants & Limits#
| Constant | Value (mainnet) | Source |
|---|---|---|
COIN (1 vCHI in satoshi) |
100,000,000 | database/amount.hpp:31 |
character_cost (WCHI per char) |
10 | params.pb.text:3 |
character_limit (per account) |
20 | params.pb.text:4 |
VCHI_AIRDROP per new char (TEST) |
1,000 | moveprocessor.cpp:57 |
MAX_CHOSEN_SPEED |
1,000,000 | moveprocessor.hpp:59 |
MAX_SERVICE_FEE_PERCENT |
1,000 (%) | moveprocessor.cpp:48 |
MAX_DEX_FEE_BPS (owner) |
3,000 (bps) | moveprocessor.cpp:54 |
base dex_fee_bps |
300 (3%) | params.pb.text:19 |
building_update_delay |
120 blocks | params.pb.text:18 (testnet 10) |
armour_repair_cost_millis |
100 (1 vCHI/10 HP) | params.pb.text:12 |
armour_repair_hp_per_block |
100 | params.pb.text:11 |
bp_copy_cost / construction_cost |
1 / 1 | params.pb.text:13 / :15 |
bp_copy_blocks / construction_blocks |
1 / 1 | params.pb.text:14 / :16 |
prospection_expiry_blocks |
5,000 | params.pb.text:9 (testnet 100) |
min_region_ore / max_region_ore |
2,000,000 / 100,000,000 | params.pb.text:21-22 (testnet 10 / 20) |
MAX_ID (exclusive) |
999,999,999 | jsonutils.cpp:40 |
MAX_QUANTITY |
2^50 | database/inventory.hpp:54 |
MAX_COIN_AMOUNT |
100,000,000,000 | jsonutils.cpp:38 |
MAX_WAYPOINT_SIZE (uncompressed) |
1,048,576 bytes | movement.cpp:45 |
Mainnet vs testnet/regtest differences#
proto/roconfig/test_params.pb.text (merged on TEST/MUMBAI and REGTEST/GANACHE):
prospection_expiry_blocks 100, bp_copy_cost 100, bp_copy_blocks 10,
construction_cost 100, construction_blocks 10, building_update_delay 10,
dex_fee_bps 1000 (10%), min/max_region_ore 10/20, a small prize list.
Chain routing: MAIN/POLYGON → base only; TEST/MUMBAI → +testnet merge;
REGTEST/GANACHE → +testnet +regtest merge (proto/roconfig.cpp:88-110).
11. Open Questions#
God-mode enabled on mainnet (build vs test contradiction).
params.pb.text:31hasgod_mode: truein the mainnet base config, so as-built the admin god commands run on mainnet/Polygon. ButGodModeDisabledTests(moveprocessor_tests.cpp:4088-4127) asserts god mode is disabled onChain::MAIN. Either the test is now stale or the config change is a deliberate/accidental override. A designer needs to confirm whether god-mode admin commands are intended to be live on production Polygon.vCHI airdrop on character creation.
VCHI_AIRDROP = 1000per new character is flaggedFIXME: For the full game, remove this(moveprocessor.cpp:1366). It is unclear whether this is still wanted in the AAA relaunch economy or must be removed.Who receives the admin command channel. The source gates god-mode by
params.god_modebut does not itself restrict who may submit admin commands — that is enforced by the surrounding Xaya/GSP daemon configuration (admin commands come from a privileged channel), which is outsidemoveprocessor.cpp. The exact provenance/authorisation of admin commands should be documented from the daemon layer.GameStartfork activation height on Polygon. Processing of all non-coin moves is gated on theGameStartfork being active (moveprocessor.cpp:1317). The actual activation height/parameters live insrc/forks.*and the deployment config, not in the move processor; the precise mainnet value should be cross-checked there.Exact DEX order-matching priority (price-time priority, tie-breaking) is in
database/dex.cpp(QueryToMatchBid/QueryToMatchAsk), not in the move processor. This reference covers the move schema; the matching algorithm itself warrants a dedicated section sourced from the DEX table code.