Prospecting & Mining
Region prospecting, ore distribution, prizes and artefact odds.
In one minute (for players)#
In Taurion you gather raw ore by prospecting and then mining, using a
vehicle (your "character" controls one vehicle, e.g. a Raider, code rv st):
- Prospect a region — a cluster of map hexes, not a single tile. Drive your
vehicle onto the region you want and issue the prospect order. Your vehicle sits
still for a fixed number of blocks (a "block" is one tick of the blockchain;
roughly the Polygon block time) while it scans. Faster vehicles and a
Graviton Spectrometer (code
lf scanner) scanner fitting shorten the wait. - When the scan finishes you learn which of the nine ores that region holds
(e.g. Trimideum, code
raw a) and how much. You may also get a bonus Ancient Artefact, and — during the launch competition — a chance at a one-of-a-kind prize. - Mine the region to pull the ore into your vehicle's cargo, block by block,
until the region runs dry or your cargo is full. A Tritanian Drill (code
lf pick) fitting makes you mine faster. - A drained region can be re-prospected later (after a cooldown) to roll a fresh ore type and amount.
Throughout this doc, internal item codes are in backticks and the player-facing
display name (from the game client) is given next to them, e.g. raw a
(Trimideum) or rv st (Raider). The rest of the page is the precise, source-derived
reference behind those four steps.
Authoritative, source-derived reference for Taurion's resource economy: the region system, prospecting (who, how long, what is found, the prize lottery), and mining (rates, cargo limits, region depletion, re-prospecting).
All rules below are taken directly from the Game State Processor (GSP) C++ source
and the read-only config protos (proto/roconfig/). Internal item codes are in
backticks; display names are the ones shown in the official client.
Vehicle/item code prefixes:
ore codes are raw a…raw i; vehicle codes are rv … (Red faction), bv …
(Blue), gv … (Green) plus a size/role suffix (st, s, m, l, sc, mt,
lt, vlt, sa, ma, la, vla).
Chain note. Three configurations exist, layered by merge (
proto/roconfig.cpp:85-141):
- mainnet (
MAIN/POLYGON): base config only.- testnet (
TEST/MUMBAI): base +testnet_merge.testnet.pb.textcontains no prospecting/mining/prize overrides, so testnet behaves exactly like mainnet for everything in this document.- regtest (
REGTEST/GANACHE): base + testnet + regtest merge, and the merge first clearssafe_zonesandparams.prizes(proto/roconfig.cpp:135-136) before applyingtest_*overrides. Numbers fromtest_params.pb.text/test_resourcedist.pb.texttherefore replace their mainnet counterparts and are only used by the unit tests / local nodes.Unless stated otherwise, all numeric values in this doc are mainnet.
1. The region system#
1.1 What a region is#
The world map is a single large hexagonal grid (axial HexCoord, x/y). Every
on-map tile belongs to exactly one region. Regions are the unit of granularity
for prospecting and mining: you do not prospect a tile, you prospect the region
that contains the tile your vehicle is standing on.
- The map has
numTiles = 67,108,864(2^26) tile slots (mapdata/tiledata.hpp:56); not all are passable/on-map. - Region IDs are looked up from compacted embedded map data
(
mapdata/regionmap.cpp), keyed by coordinate:RegionMap::GetRegionId(HexCoord)→RegionMap::IdT(auint32_t,mapdata/regionmap.hpp:42). - Out-of-map coordinates return
RegionMap::OUT_OF_MAP = (uint32_t)-1(mapdata/regionmap.hpp:45). - Region IDs start at 0 and are not fully contiguous — the map was cropped
after generation, so some IDs in the range are missing
(
mapdata/procmap.cpp:593-598). - Every coordinate that maps to a region also maps back to a set of tiles
forming that region's shape via
RegionMap::GetRegionShape(mapdata/regionmap.hpp:62).
The exact total number of regions is not a compiled-in constant; it is a
property of the embedded regiondata.dat blob
(mapdata/regiondata.dat.xz) and is logged at generation time
(mapdata/procmap.cpp:598). It is on the order of hundreds of thousands of
regions, each covering many contiguous hexes. See Open questions.
1.2 Region database row#
Each region that has ever been touched gets a DB row (RegionsTable,
database/region.hpp). The mutable per-region state used by prospecting/mining:
| Field (proto) | Meaning |
|---|---|
prospecting_character |
ID of the character currently prospecting this region (set while in progress). |
prospection.name |
Owner (Xaya account) who completed the prospect. |
prospection.height |
Block height at which prospecting finished. |
prospection.resource |
Internal code of the mine-able resource found here (e.g. raw a). |
resource_left (via GetResourceLeft/SetResourceLeft) |
Remaining mine-able units of that resource. |
A region is in one of these states:
- Untouched — no
prospecting_character, noprospection. Can be prospected. - Being prospected —
prospecting_characterset. Locked. - Prospected, has resource —
prospectionset,resource_left > 0. Mineable. - Prospected, exhausted —
prospectionset,resource_left == 0. Re-prospectable once expiry passes.
2. Prospecting#
2.1 Who can prospect, and the entry conditions#
A character can be ordered to prospect via the prospect move (see §2.5). The
move is accepted only if all of the following hold
(BaseMoveProcessor::ParseCharacterProspecting, src/moveprocessor.cpp:833-879):
| Requirement | Source | Notes |
|---|---|---|
prospect value is an empty JSON object {} |
moveprocessor.cpp:839-849 |
Any extra fields → rejected. |
Vehicle has a prospecting_blocks stat |
moveprocessor.cpp:851-855 |
All real vehicles do; see §2.4. A vehicle without it "cannot prospect". |
| Character is not busy (no ongoing operation) | moveprocessor.cpp:857-862 |
IsBusy() = has an ongoing op (database/character.cpp:219-227). |
| Character is not inside a building | moveprocessor.cpp:864-870 |
IsInBuilding() (database/character.hpp:190-193). |
CanProspectRegion(...) for the region under the character |
moveprocessor.cpp:872-878 |
See §2.2. |
The region is the one containing the character's current position
(moveprocessor.cpp:872-873). A character does not need to be stationary to
issue the move, but starting prospecting stops its movement (§2.3).
2.2 CanProspectRegion — when a region accepts a prospect#
src/prospecting.cpp:29-71. Returns true only if:
- Not already being prospected. If
prospecting_characteris set, reject (prospecting.cpp:34-42). - Never prospected before → accept immediately (
prospecting.cpp:44-45). - Re-prospecting an expired region. If a previous
prospectionexists:- The current block height must be at least
prospection.height + prospection_expiry_blocks(prospecting.cpp:47-58). Earlier than that → reject. prospection_expiry_blocks= 5000 on mainnet (params.pb.text:9); 100 on regtest (test_params.pb.text:6).- AND the region must be exhausted:
resource_left == 0(prospecting.cpp:60-68). If any resource is still mineable, the region cannot be re-prospected even after the expiry window.
- The current block height must be at least
Test coverage of these branches:
ProspectionInProgress, EmptyRegion, ReprospectingExpiration,
ReprospectingResources (prospecting_tests.cpp:65-109). The expiry boundary is
inclusive of "too early": at height = prospection.height + 100 it is still too
early; at +101 it is allowed in the regtest test
(prospecting_tests.cpp:82-93).
2.3 Starting prospecting (state transition)#
MoveProcessor::MaybeStartProspecting, src/moveprocessor.cpp:1520-1544:
- Re-validates with
ParseCharacterProspecting. - Sets
region.prospecting_character = character.id(moveprocessor.cpp:1528). - Clears any old
prospectionon the region (so a re-prospect wipes the previous result immediately) (moveprocessor.cpp:1530-1532). StopCharacter(c)— cancels movement and mining (moveprocessor.cpp:1534), so a vehicle cannot move/mine while prospecting.- Reads the vehicle's effective
prospecting_blocks(must be > 0,moveprocessor.cpp:1536-1537). - Creates an ongoing operation that completes at
current_height + prospecting_blocks(moveprocessor.cpp:1539-1543):character.ongoing = op.id(marks the character busy).op.height = ctx.Height() + blocks(the finish height).op.character_id = c.id.op.proto.prospectionset (operation type tag).
Prospecting cannot be interleaved with mining or movement — only one of these may
be active because issuing prospect calls StopCharacter.
2.4 Duration: prospecting_blocks per vehicle, and fitment modifiers#
Duration is in blocks (≈ Polygon block time per block). Each vehicle has a
base prospecting_blocks. Fitments can reduce it.
Base prospecting_blocks by vehicle (display names from the game UI;
stats from vehicles_starter.pb.text and vehicles_others.pb.text).
The code suffix below is the same for all three factions; prepend the faction
prefix to get the real code: rv (Red), bv (Blue), gv (Green). For example
the starter is rv st (Raider) / bv st (Barracuda) / gv st (Scarab). Starters
are defined in vehicles_starter.pb.text:1-74; the rest in vehicles_others.pb.text.
| Code suffix | Red / Blue / Green name | Size | prospecting_blocks |
mining_rate max |
cargo_space |
|---|---|---|---|---|---|
st |
Raider / Barracuda / Scarab (starter) | STARTER | 10 | 2,000,000 | 1,000,000 |
s |
Looter / Urchin / Mantis | LIGHT | 9 | 4,000,000 | 3,600,000 |
m |
Pillager / Crocodile / Centipede | MEDIUM | 8 | 6,000,000 | 11,500,000 |
l |
Marauder / Moray / Millipede | HEAVY | 7 | 8,000,000 | 27,600,000 |
sc |
Finder / Octo / Flea (scout) | LIGHT | 5 | 0 (cannot mine) | 0 |
mt |
Blood Carrier / Cobra / Horse (transport) | MEDIUM | 8 | 2,000,000 | 125,000,000 |
lt |
Bone Carrier / Trident / Ox (transport) | HEAVY | 7 | 4,000,000 | 350,000,000 |
vlt |
Skull Carrier / Hydra / Elephant (transport) | VERY_HEAVY | 6 | 6,000,000 | 2,600,000,000 |
sa |
Devourer / Gorgon / Bee (attack) | LIGHT | 5 | 8,000,000 | 5,600,000 |
ma |
Exterminator / Siren / Wasp (attack) | MEDIUM | 5 | 10,000,000 | 25,000,000 |
la |
Devastator / Poseidon / Hornet (attack) | HEAVY | 4 | 12,000,000 | 105,000,000 |
vla |
Annihilator / Leviathan / Widow (attack) | VERY_HEAVY | 3 | 14,000,000 | 285,000,000 |
(Mining rate min is always 0; see §3.1. Every value in this table was verified
against vehicles_others.pb.text for all three faction prefixes.)
Fitment modifiers are applied in ApplyFitments
(src/fitments.cpp:162-254). Modifiers of the same kind are summed (not
compounded) and applied once:
- Graviton Spectrometer (
lf scanner):prospecting_blocks: { percent: -20 }(fitments.pb.text:910-924) — reduces prospecting time by 20% per fitment. - Tritanian Drill (
lf pick):mining_rate: { percent: 20 }(fitments.pb.text:889-905) — increases mining yield by 20%.
After applying the prospecting modifier, the result is clamped to at least 1
block: blocks = max(1, modified) (fitments.cpp:243-244). Both are high-slot
fitments. Multiple lf scanner units stack additively (e.g. 3 ⇒ −60%).
2.5 The prospect move (JSON schema)#
A character move is sent under the player's account move; the per-character update
object accepts a prospect key holding an empty object:
{
"c": {
"5": { "prospect": {} }
}
}
"c"= character updates, keyed by character ID (here5).prospectMUST be{}(empty). Non-object or non-empty → rejected silently with a warning (moveprocessor.cpp:840-849).- Prospecting and mining are mutually exclusive in one move; prospecting is
processed before waypoints/mining and calls
StopCharacter(moveprocessor.cpp:1850then mining at1866).
Pending view. While the prospect move is in the mempool, the pending state
exposes it (src/pending.cpp:198-220):
{ "prospecting": <regionId> }
A pending prospect clears any pending waypoints for that character
(pending.cpp:219-221), and a character cannot have both pending prospect and
pending mine (pending.cpp:235-247).
2.6 Finishing a prospect — what is found#
When the ongoing operation matures, ProcessAllOngoings calls
FinishProspecting (src/ongoings.cpp:178-182), then clears
character.ongoing (un-busies it). FinishProspecting
(src/prospecting.cpp:126-179) does, in order:
- Clears
prospecting_character; setsprospection.name = owner,prospection.height = current height(prospecting.cpp:138-143). - Detects the resource via
DetectResource(§2.7) → setsprospection.resource = typeandregion.resource_left = amount(prospecting.cpp:145-151). - Maybe finds an ancient artefact (§2.8) (
prospecting.cpp:153). - Maybe wins a prize from the lottery (§2.9) (
prospecting.cpp:155-178).
Resulting region JSON (gamestatejson.cpp:572-603):
{
"id": 12345,
"prospection": { "name": "domob", "height": 1040 },
"resource": { "type": "raw a", "amount": 57000000 }
}
While in progress, instead of name/height the prospection block carries
{"inprogress": <characterId>} (gamestatejson.cpp:581-591).
2.7 Resource detection — which ore and how much#
DetectResource (src/resourcedist.cpp:116-180) decides the resource type and
initial amount from the character's position and the resource-distribution
map. The resource map is a list of areas, each with a centre coordinate and
one or two possible resource types (resourcedist.pb.text).
Distance fall-off (internal::FallOff, resourcedist.cpp:53-71):
CORE_RADIUS = 400,OUTER_RADIUS = 1000(L1 hex distance) (resourcedist.cpp:35-38).- Within 400 of an area centre → full weight.
- Between 400 and 1000 → linear interpolation down toward 1.
- Beyond 1000 → weight 0 (area does not contribute).
- The interpolation never drops below 1 inside the outer radius and never exceeds
the base value; verified by
ResourceFallOffTests(resourcedist_tests.cpp:46-92). Example:FallOff(700, val) ≈ val/2.
Type selection (resourcedist.cpp:127-170):
- Collect every area within
OUTER_RADIUSof the position; for each, compute the fall-off-weightedchancefromBASE_CHANCE = 100,000,000(resourcedist.cpp:46). - Build a de-duplicated set keyed by
(resource type, area centre), sorted for deterministic ordering independent of proto order (resourcedist.cpp:99-110,127-149; testTypeOrderDeterministic,resourcedist_tests.cpp:191-210). - Pick one resource type by weight:
rnd.SelectByWeight(weights)(resourcedist.cpp:163-169). Probability of a type = its summed weight / total weight. With overlapping areas, the same ore appearing near multiple centres accumulates weight. (RandomTypetest verifies the 3:2:1 split,resourcedist_tests.cpp:153-189.) - If no area is in range, return
type = "raw a"(Trimideum),amount = 0(resourcedist.cpp:155-161; testNothingAvailable,resourcedist_tests.cpp:143-151). Such a region is "prospected" but yields a resource of 0 and is not mineable.
Amount (resourcedist.cpp:172-179):
- Base amount = uniform integer in
[min_region_ore, max_region_ore]. - Mainnet:
min_region_ore = 2,000,000,max_region_ore = 100,000,000(params.pb.text:21-22). - Regtest:
min = 10,max = 20(test_params.pb.text:18-19). - Final amount =
FallOff(distance_to_picked_area_centre, base_amount). So ore found near the edge of an area's influence is scaled down (and is at least 1 if within range). TestMinimumAmount: at distance 900 with base 2 the amount falls to 1 (resourcedist_tests.cpp:242-257).
2.7.1 The nine ores and their named display values
| Code | Display name | Per-unit cargo space |
|---|---|---|
raw a |
Trimideum | 1 |
raw b |
Talon | 1 |
raw c |
Henoix | 1 |
raw d |
Orchanum | 1 |
raw e |
Kalanite | 1 |
raw f |
Voltar | 1 |
raw g |
Ravolute | 1 |
raw h |
Talgarite | 1 |
raw i |
Liberite | 1 |
(space: 1 for every ore: materials.pb.text:63-200.) Refined materials
mat a…mat i (Agarite…I-77E) are produced from these ores by refining, which is
out of scope for this doc.
2.7.2 Resource distribution map (real areas)
There are 88 areas on mainnet (resourcedist.pb.text:90-565). Rarer/higher-tier
ores (raw f…raw i) cluster toward the map extremes; the common Trimideum
(raw a) and Talon (raw b) are spread broadly. The full list with display
names:
| # | Centre (x, y) | Resources (code → name) |
|---|---|---|
| 1 | (2963, 1965) | raw a Trimideum |
| 2 | (4958, -2185) | raw i Liberite, raw h Talgarite |
| 3 | (4348, -1045) | raw h Talgarite, raw g Ravolute |
| 4 | (1898, 3795) | raw f Voltar |
| 5 | (5263, -3055) | raw g Ravolute, raw f Voltar |
| 6 | (3293, 665) | raw g Ravolute, raw f Voltar |
| 7 | (5063, -3695) | raw b Talon |
| 8 | (2028, 2355) | raw a Trimideum |
| 9 | (3898, -1405) | raw i Liberite, raw h Talgarite |
| 10 | (1633, 3025) | raw c Henoix |
| 11 | (2243, 1785) | raw c Henoix |
| 12 | (3033, -95) | raw g Ravolute, raw f Voltar |
| 13 | (1608, 2355) | raw b Talon, raw a Trimideum |
| 14 | (878, 3795) | raw c Henoix |
| 15 | (3248, -1045) | raw h Talgarite, raw g Ravolute |
| 16 | (2768, -485) | raw i Liberite, raw h Talgarite |
| 17 | (763, 3165) | raw b Talon |
| 18 | (4143, -3835) | raw e Kalanite |
| 19 | (1378, 1695) | raw b Talon, raw a Trimideum |
| 20 | (2403, -615) | raw h Talgarite, raw g Ravolute |
| 21 | (3613, -3135) | raw b Talon, raw a Trimideum |
| 22 | (3188, -2345) | raw e Kalanite |
| 23 | (708, 2095) | raw a Trimideum |
| 24 | (443, 2625) | raw a Trimideum |
| 25 | (93, 3285) | raw a Trimideum |
| 26 | (1858, -285) | raw g Ravolute, raw f Voltar |
| 27 | (2438, -2305) | raw a Trimideum |
| 28 | (228, 1875) | raw b Talon, raw a Trimideum |
| 29 | (-172, 2555) | raw a Trimideum |
| 30 | (1818, -1505) | raw b Talon, raw a Trimideum |
| 31 | (848, 375) | raw f Voltar |
| 32 | (2648, -3265) | raw a Trimideum |
| 33 | (1583, -1215) | raw a Trimideum |
| 34 | (2918, -3905) | raw b Talon, raw a Trimideum |
| 35 | (-682, 3195) | raw b Talon, raw a Trimideum |
| 36 | (1993, -2675) | raw a Trimideum |
| 37 | (-227, 1705) | raw c Henoix |
| 38 | (-897, 2665) | raw c Henoix |
| 39 | (1483, -2115) | raw a Trimideum |
| 40 | (993, -1195) | raw e Kalanite |
| 41 | (-1457, 3605) | raw c Henoix |
| 42 | (1703, -3115) | raw a Trimideum |
| 43 | (-57, 365) | raw c Henoix |
| 44 | (2023, -3935) | raw e Kalanite |
| 45 | (-617, 1285) | raw f Voltar |
| 46 | (598, -1425) | raw b Talon, raw a Trimideum |
| 47 | (-137, -55) | raw e Kalanite |
| 48 | (-1607, 2805) | raw a Trimideum |
| 49 | (908, -2445) | raw b Talon, raw a Trimideum |
| 50 | (-482, 295) | raw d Orchanum |
| 51 | (-697, 705) | raw h Talgarite, raw g Ravolute |
| 52 | (-1542, 2175) | raw f Voltar |
| 53 | (1223, -3615) | raw b Talon, raw a Trimideum |
| 54 | (423, -2355) | raw a Trimideum |
| 55 | (173, -1855) | raw e Kalanite |
| 56 | (-357, -1075) | raw b Talon, raw a Trimideum |
| 57 | (-1607, 1405) | raw d Orchanum |
| 58 | (-2307, 2645) | raw g Ravolute, raw f Voltar |
| 59 | (-857, -335) | raw f Voltar |
| 60 | (-2187, 2145) | raw b Talon, raw a Trimideum |
| 61 | (-3117, 3865) | raw f Voltar |
| 62 | (-1177, -635) | raw h Talgarite, raw g Ravolute |
| 63 | (-2192, 1195) | raw b Talon, raw a Trimideum |
| 64 | (-1247, -1015) | raw f Voltar |
| 65 | (-2752, 1835) | raw a Trimideum |
| 66 | (-2112, 235) | raw d Orchanum |
| 67 | (-4057, 3885) | raw g Ravolute, raw f Voltar |
| 68 | (-2927, 1245) | raw a Trimideum |
| 69 | (-3417, 1945) | raw a Trimideum |
| 70 | (-4447, 3845) | raw h Talgarite, raw g Ravolute |
| 71 | (-2507, -155) | raw b Talon, raw a Trimideum |
| 72 | (-2977, 705) | raw b Talon, raw a Trimideum |
| 73 | (-1877, -1655) | raw g Ravolute, raw f Voltar |
| 74 | (-3107, 365) | raw a Trimideum |
| 75 | (-3842, 1715) | raw a Trimideum |
| 76 | (-4902, 3815) | raw a Trimideum |
| 77 | (-2892, -525) | raw f Voltar |
| 78 | (-1427, -3495) | raw h Talgarite, raw g Ravolute |
| 79 | (-4312, 2275) | raw d Orchanum |
| 80 | (-1212, -3945) | raw i Liberite, raw h Talgarite |
| 81 | (-3547, 505) | raw d Orchanum |
| 82 | (-2107, -2415) | raw h Talgarite |
| 83 | (-4312, 1595) | raw b Talon |
| 84 | (-2037, -3115) | raw i Liberite, raw h Talgarite |
| 85 | (-5067, 2625) | raw f Voltar |
| 86 | (-5527, 3425) | raw b Talon, raw a Trimideum |
| 87 | (-3737, -215) | raw a Trimideum |
| 88 | (-4477, 1005) | raw d Orchanum |
Because of the 1:1 mapping of weight to ore and the additive overlap of areas, coverage of all nine ores within any 1000-L1 radius is intentional — the
Resourcestest prospects a grid and asserts exactly 9 distinct ore types are reachable, and thatraw a(Trimideum) is found in more regions thanraw i(Liberite) (prospecting_tests.cpp:189-221).
2.8 Ancient artefacts (bonus drop on prospect)#
After resource detection, MaybeFindArtefact (prospecting.cpp:80-122) may grant
an artefact based on the ore type just detected. The mapping is in
resource_dist.possible_artefacts (resourcedist.pb.text:4-88). Each ore has an
ordered list of {artefact, probability} entries; they are evaluated in order
and the first one that succeeds is awarded — at most one artefact per prospect
(prospecting.cpp:93-121, the loop breaks on success).
Each entry's roll is
rnd.ProbabilityRoll(1, probability)→ success chance =1 / probability(prospecting.cpp:95).The four artefacts (display names from the game UI):
Code Display name art cAncient Artefact (common) art ucAncient Artefact (uncommon) art rAncient Artefact (rare) art urAncient Artefact (ultra-rare)
Mainnet artefact tables by ore (resourcedist.pb.text:4-88); chance = 1/N:
| Ore (code → name) | Entries evaluated in order (artefact @ 1/N) |
|---|---|
raw a Trimideum |
art c @ 1/20 |
raw b Talon |
art c @ 1/20 |
raw c Henoix |
art c @ 1/20, art uc @ 1/50 |
raw d Orchanum |
art c @ 1/20, art uc @ 1/50 |
raw e Kalanite |
art c @ 1/20, art uc @ 1/50 |
raw f Voltar |
art c @ 1/20, art uc @ 1/50, art r @ 1/100 |
raw g Ravolute |
art c @ 1/20, art uc @ 1/50, art r @ 1/100 |
raw h Talgarite |
art c @ 1/20, art uc @ 1/50, art r @ 1/100, art ur @ 1/200 |
raw i Liberite |
art c @ 1/20, art uc @ 1/50, art r @ 1/100, art ur @ 1/200 |
So rarer ores can drop rarer artefacts, but the common artefact is always rolled
first; e.g. on raw i (Liberite) the overall chance of any artefact is
1 − (19/20)(49/50)(99/100)(199/200) ≈ 8.29%, with art ur (Ancient Artefact,
ultra-rare) only possible if all earlier rolls fail.
If no artefact map entry exists for the ore (cannot happen for the nine real ores)
a warning is logged and nothing is given (prospecting.cpp:86-90).
Cargo handling (prospecting.cpp:104-119): if the artefact's space (1 for
all four artefacts, artefacts.pb.text) fits in free cargo, it is added to the
character's inventory; otherwise it is dropped on the ground at the
character's position into the GroundLootTable. Test CargoFull shows 8
prospects with room for 5 → 5 carried, 3 on the ground
(prospecting_tests.cpp:353-374).
Regtest difference.
test_resourcedist.pb.textoverrides theraw aandraw fartefact tables (proto map keys override on merge):raw a→art r@1/1 thenart c@1/1 (used to test "always first in order" → alwaysart r);raw f→art c@1/2 thenart r@1/2 (used to test randomisation, ~50%art c, ~25%art r, ~25% nothing). Seeprospecting_tests.cpp:287-351.
2.9 The prize lottery (competition)#
On every completed prospect, before returning, the code walks the configured
prize tiers and may award one prize (prospecting.cpp:155-178). This was the
launch competition; prizes are finite and global.
Mechanics (prospecting.cpp:156-178):
- Determine if the position is a low-prize zone:
IsLowPrizeZone(pos)(prospecting.cpp:156). - For each prize tier
p, in config order:found = ItemCounts.GetFound("<name> prize")— how many of that prize have already been won globally (prospecting.cpp:160-161).- If
found == p.number(the tier is exhausted), skip it (prospecting.cpp:162-164). - Roll:
rnd.ProbabilityRoll(lowChance ? 55 : 100, 100 * p.probability).- Normal zone: success chance =
100 / (100 · probability)=1 / probabilityper prospect. - Low-prize zone: chance =
55 / (100 · probability)= 55% of normal (a 45% reduction) (prospecting.cpp:166-168).
- Normal zone: success chance =
- On success: increment the global found count, add
"<name> prize"to the character inventory, and stop (only one prize per prospect) (prospecting.cpp:170-177).
Prizes are stored in the character's inventory under the item code
"<tier name> prize" (e.g. "cash prize").
Low-prize zone (Params::IsLowPrizeZone, src/params.cpp:30-49): any position
within L1 distance 1250 of a faction safe-zone centre (the three starter
spawns). Faction safe zones (safezones.pb.text:1-22):
| Faction | Centre (x, y) | Spawn building |
|---|---|---|
Red (r) |
(1960, -2601) | building 6 (params.pb.text:35-37) |
Green (g) |
(-3472, 1824) | building 4 |
Blue (b) |
(547, 2497) | building 5 |
Only safe_zones entries that have a faction count toward the low-prize
zone; the small radius-30 neutral/partner no-combat zones do not
(params.cpp:38-41). On regtest the prize roll still uses IsLowPrizeZone, but
regtest clears all safe zones on merge (roconfig.cpp:135), so no low-prize
zone exists there unless re-added (the test re-checks with explicit positions,
prospecting_tests.cpp:39-41, 227, 269).
Mainnet prize table (params.pb.text:54-110) — 55 tiers total. The first
tier cash lives inside the params block (params.pb.text:54); the remaining 54
were exported from a spreadsheet (params.pb.text:57-110).
Each tier: number = total available globally, probability = N where per-prospect
chance is 1/N (normal zone). Sorted here by rarity (largest N = rarest):
Tier name (<name> prize) |
number (total) |
probability (1/N) |
Approx normal chance |
|---|---|---|---|
| cash | 10 | 350,000 | 1 in 350,000 |
| Lastlight Mission Card | 1 | 130,289 | 1 in 130,289 |
| U.S.S. Enterprise NCC-1701-D + 3 Founders Supply Crates + Steam key | 1 | 130,289 | 1 in 130,289 |
| U.S.S. Voyager NCC-74656 + 3 Founders Supply Crates + Steam key | 1 | 130,289 | 1 in 130,289 |
| I.K.S. Rotarran + 3 Founders Supply Crates + Steam key | 1 | 130,289 | 1 in 130,289 |
| Silver Tier P1 Pistol | 1 | 130,289 | 1 in 130,289 |
| Bronze CDA-2 Armor | 1 | 130,289 | 1 in 130,289 |
| Mar 4 SMG | 1 | 130,289 | 1 in 130,289 |
| 3 Spaceships | 1 | 130,289 | 1 in 130,289 |
| F1 Legendary Crate | 1 | 130,289 | 1 in 130,289 |
| Death Knight Head | 1 | 130,289 | 1 in 130,289 |
| Death Knight Body | 1 | 130,289 | 1 in 130,289 |
| Death Knight Arms | 1 | 130,289 | 1 in 130,289 |
| Death Knight Legs | 1 | 130,289 | 1 in 130,289 |
| 347 E-Den | 2 | 77,127 | 1 in 77,127 |
| F1 Epic Crate | 2 | 77,127 | 1 in 77,127 |
| F1 Rare Crate | 3 | 56,367 | 1 in 56,367 |
| Gamerhash Coins | 3 | 56,367 | 1 in 56,367 |
| Gamerhash Store | 3 | 56,367 | 1 in 56,367 |
| PGFK Voucher | 3 | 56,367 | 1 in 56,367 |
| Legendary Crystals | 3 | 56,367 | 1 in 56,367 |
| Assassin's Creed | 3 | 56,367 | 1 in 56,367 |
| F1 Common Crate | 4 | 44,905 | 1 in 44,905 |
| Thermal Implant | 50 | 5,063 | 1 in 5,063 |
| 200 DIO | 50 | 5,063 | 1 in 5,063 |
| Medium Transport | 50 | 5,063 | 1 in 5,063 |
| Silver Crate | 5 | 37,530 | 1 in 37,530 |
| 5,000 TKT | 5 | 37,530 | 1 in 37,530 |
| 10,000 1UP | 5 | 37,530 | 1 in 37,530 |
| Age of Rust MFT Token | 25 | 9,499 | 1 in 9,499 |
| Runner Mission Card | 10 | 21,118 | 1 in 21,118 |
| Cartesi Tokens | 10 | 21,118 | 1 in 21,118 |
| Dissolution DFT | 10 | 21,118 | 1 in 21,118 |
| 5,000 DWD | 10 | 21,118 | 1 in 21,118 |
| Clown Weapon | 10 | 21,118 | 1 in 21,118 |
| Epic Keys | 10 | 21,118 | 1 in 21,118 |
| 500 ZNZ | 10 | 21,118 | 1 in 21,118 |
| TBD Legendary | 16 | 14,090 | 1 in 14,090 |
| Dungeon Key | 20 | 11,582 | 1 in 11,582 |
| Starter Pack | 20 | 11,582 | 1 in 11,582 |
| Summoner Spellbook | 20 | 11,582 | 1 in 11,582 |
| Rare Keys | 20 | 11,582 | 1 in 11,582 |
| 100 DAI | 12 | 18,075 | 1 in 18,075 |
| TBD Ultra Rare | 35 | 7,015 | 1 in 7,015 |
| TBD Rare | 45 | 5,578 | 1 in 5,578 |
| Prison Key Card | 100 | 2,655 | 1 in 2,655 |
| Package of 10 Training Tickets + 1 Trinity Boost | 100 | 2,655 | 1 in 2,655 |
| 1% Player Shares | 100 | 2,655 | 1 in 2,655 |
| TBD Uncommon | 75 | 3,477 | 1 in 3,477 |
| TBD Common | 125 | 2,150 | 1 in 2,150 |
| 100 TKT | 250 | 1,109 | 1 in 1,109 |
| Temporal Disrupter | 200 | 1,374 | 1 in 1,374 |
| Syphon | 200 | 1,374 | 1 in 1,374 |
| Graviton Spectrometer | 200 | 1,374 | 1 in 1,374 |
| Tritanian Drill | 200 | 1,374 | 1 in 1,374 |
Display names for these prizes (with the partner-game suffix) are the ones shown
in the game UI, e.g. "Prison Key Card" → "Prison Key Card (Age of Rust)".
Because tiers are checked in config order and the loop stops at the first success, a rarer-listed tier earlier in the list is evaluated first. Note the last four Taurion items (
Temporal Disrupter,Syphon,Graviton Spectrometer,Tritanian Drill) are the most common prizes (1/1374) and are also obtainable as craftable fitments.
Regtest prize table (used by unit tests; test_params.pb.text:25-27):
| Tier | number | probability (1/N) |
|---|---|---|
| gold | 3 | 100 |
| silver | 1000 | 10 |
| bronze | 1 | 1 |
The Prizes test confirms exact behavior: over 10,000 prospects in a normal zone
it finds all 3 gold, the 1 bronze, and ~1000 silver (expected value 1000); in a
low-prize zone silver expectation drops to ~550 (55%)
(prospecting_tests.cpp:223-283).
3. Mining#
Mining extracts the prospected resource from a region over many blocks, filling the character's cargo.
3.1 Mining rate per vehicle, and fitment modifiers#
Each vehicle has a mining_rate { min, max } stat
(vehicles_*.pb.text). Per processed block, the amount attempted is a uniform
integer in [min, max] (§3.4). On all real vehicles min = 0 and max scales
with hull size and role; see the table in §2.4. Scouts (… sc) have
mining_rate { min:0 max:0 } and cannot mine at all
(vehicles_others.pb.text:113-114).
The Tritanian Drill (lf pick) fitment applies mining_rate { percent: 20 }
(fitments.pb.text:889-905), increasing both min and max by 20%
(fitments.cpp:249-254). Modifiers are summed across fitments and applied once
to each of min and max.
A character with no mining field (e.g. after InitCharacterStats for a vehicle
without mining_rate) simply cannot mine (fitments.cpp:141-144).
3.2 Who can mine, and entry conditions#
The mine move starts mining; accepted only if
(BaseMoveProcessor::ParseCharacterMining, moveprocessor.cpp:881-954):
| Requirement | Source |
|---|---|
mine value is an empty JSON object {} |
moveprocessor.cpp:887-897 |
Vehicle has a mining stat (can mine) |
moveprocessor.cpp:899-903 |
| Character not busy (no ongoing op) | moveprocessor.cpp:905-910 |
| Character not inside a building | moveprocessor.cpp:912-917 |
Character is not moving (!has_movement) |
moveprocessor.cpp:925-930 |
Region under the character is prospected (has_prospection) |
moveprocessor.cpp:932-940 |
Region has resource_left > 0 |
moveprocessor.cpp:942-951 |
3.3 Starting mining (state transition)#
MoveProcessor::MaybeStartMining (moveprocessor.cpp:1546-1557) simply sets
character.mining.active = true after validation. Mining does not create an
ongoing operation; it is processed every block while active (§3.4).
Ordering in a combined move: mining is started after prospecting and before
waypoints, so that "set waypoints + mine" does not result in moving and mining at
once (moveprocessor.cpp:1862-1866). Setting waypoints (StopCharacter →
StopMining) cancels mining (moveprocessor.cpp:1465).
The mine move:
{
"c": {
"5": { "mine": {} }
}
}
mine MUST be {} (moveprocessor.cpp:891-897). Pending view
(pending.cpp:231-258): { "mining": <regionId> }. A pending mine is rejected if
the same character has a pending prospect, and setting waypoints clears a pending
mine (pending.cpp:139-144, 235-247).
3.4 Per-block mining processing#
ProcessAllMining (src/mining.cpp:40-131) runs every block over all characters
with mining.active (characters.QueryMining()):
For each active miner:
- Find the region under the character's position
(
mining.cpp:50-55). - Region no longer prospected? If the region's
prospectionwas cleared (e.g. exhausted last block and immediately re-prospected by someone), gracefully stop: clearmining.activeand continue (mining.cpp:60-73). - Roll the amount to mine:
mined = rate.min + rnd.NextInt(rate.max − rate.min + 1)— uniform in[min, max](mining.cpp:75-80). - Rolled 0? Continue to the next character without running the stop
logic — i.e. a 0-roll does not stop mining
(
mining.cpp:82-86; testZeroRolls,mining_tests.cpp:174-195). - Clamp to region's
resource_left: ifmined > left, setmined = left(mining.cpp:88-95). - Clamp to free cargo:
maxForSpace = free_cargo / item_space(item space is 1 for ores), somined = min(mined, maxForSpace)(mining.cpp:97-111). - If
mined > 0: subtract from regionresource_left, addminedof the resource type to the character inventory (mining.cpp:113-122). - Else (
mined == 0after clamping, i.e. region empty OR cargo full): clearmining.active— the operation auto-stops (mining.cpp:123-129).
So mining auto-stops when the region is exhausted or the vehicle's cargo is full, but a random 0 roll keeps it active.
Basic example (mining_tests.cpp:112-118): rate fixed at 10, region has 100,
cargo 1000 → after one block: inventory +10, region 90, still active.
3.5 Cargo and depletion edge cases#
- Cargo full.
FreeCargoSpace = cargo_space − used(database/character.cpp:243-249). When free cargo can't hold even one unit, the clamp drivesminedto 0 and mining stops. TestCargoFull(mining_tests.cpp:159-172): starting with 95/100 cargo used and a region holding 100, block 1 mines 5 (cargo 95→100, region 100→95) and stays active; block 2 finds cargo full, clampsminedto 0 and stops (cargo stays 100, region stays 95). - Resource used up. Test
ResourceUsedUp: region 5, rate 10 → mines 5, region 0, still active that block; next blockleft == 0→minedclamps to 0 → mining stops (mining_tests.cpp:144-157). - Multiple miners on one region. All active miners are processed in the same
block in DB order; each draws from the same shared
resource_left. TestMultipleMiners: region 25, two miners at rate 10 — block 1 both take 10 (region→5); block 2 the first takes the remaining 5 and the second gets 0 and stops (mining_tests.cpp:234-263). Mining is first-come-first-served within a block, not split fairly. - Region de-prospected mid-mining → graceful stop (§3.4 step 2; test
RegionNotProspected,mining_tests.cpp:136-142).
3.6 When a region empties, and re-prospecting#
- A region is exhausted when
resource_leftreaches 0 via mining (mining.cpp:113-117). - An exhausted region keeps its
prospectionrecord (name, height, resource type) — it just has nothing left to mine. The region JSON still showsresource.amount = 0. - To make it productive again it must be re-prospected, which requires
(§2.2): the region is exhausted (
resource_left == 0) and at leastprospection_expiry_blocks(5000 mainnet / 100 regtest) have passed since the last prospect finished. - Re-prospecting immediately clears the old
prospection(moveprocessor.cpp:1530-1532), then re-runsDetectResourceon completion — the new resource type and amount are rolled fresh and may differ from before. - Edge case: if a region is exhausted and re-prospected in adjacent blocks, a
character that was mining it can find its
prospectionalready cleared on the next mining pass and stops gracefully (mining.cpp:60-73).
4. Client-facing JSON (what the UI reads)#
The official client consumes the GSP game-state JSON. Relevant fields:
Character (gamestatejson.cpp)
prospectingblocks: effectiveprospecting_blocksof the vehicle (only present if the vehicle can prospect) (gamestatejson.cpp:291-292).mining: present if the vehicle can mine (gamestatejson.cpp:213-233):{ "rate": { "min": 0, "max": 4000000 }, "active": true, "region": 12345 }regiononly appears whenactiveis true.inventory.fungible: includes mined ores (raw a…raw i), artefacts (art *) and prizes ("<name> prize") (gamestatejson.cpp:237-249).
Ongoing operation (a prospect in progress; gamestatejson.cpp:480-569):
{
"operation": "prospecting",
"start_height": 1000,
"end_height": 1010,
"characterid": 5
}
end_height is the finish height (op.height + endDelta, with endDelta = 0 for
prospecting) (gamestatejson.cpp:496-567).
Region (gamestatejson.cpp:572-603): see §2.6. Either
prospection.inprogress (a character ID, while running) or
prospection.{name,height} + resource.{type,amount} (after completion).
5. Quick rule summary#
- Granularity: prospect/mine a region (a cluster of hexes), not a tile. Region ID from the embedded map; IDs sparse, start at 0.
- Prospect entry:
prospect:{}, vehicle hasprospecting_blocks, not busy, not in a building, region not in-progress, and either never-prospected or (exhausted AND past expiry). - Duration: vehicle
prospecting_blocks(10 down to 3), minus 20% per Graviton Spectrometer (lf scanner), floored at 1. - Found resource: weighted by nearby distribution areas (fall-off 400→1000), amount uniform in 2,000,000…100,000,000 (mainnet) then scaled by distance.
- Bonus: ancient artefact (1/20…1/200 by ore), dropped to ground if cargo full; one global prize from a finite lottery (1/probability, ×0.55 in low-prize zones within 1250 of a faction spawn).
- Mine entry:
mine:{}, vehicle can mine, not busy/in building/moving, region prospected withresource_left > 0. - Mine rate: uniform
[min,max]per block (min always 0; max 0…14,000,000 by vehicle), +20% per Tritanian Drill (lf pick). - Mining auto-stops when the region empties or cargo fills (but not on a 0 roll). Multiple miners share the pool first-come-first-served per block.
- Re-prospect an exhausted region after
prospection_expiry_blocks(5000 mainnet) blocks; the new ore type/amount is rolled fresh.
Open questions#
- Exact mainnet region count. The number of regions is embedded in
regiondata.dat(logged atprocmap.cpp:598), not a source constant. The doc states "hundreds of thousands" but the precise figure would require building and running the map-generation/region tooling against the shipped data blob. - Average region size in hexes / region shape.
GetRegionShapereturns the tile set per region, but no aggregate (min/avg/max tiles per region) is encoded as a constant;regionmap_bench.cpp:82-87logs per-region sizes only when run. The exact per-region hex footprint and any guaranteed shape (e.g. roughly hexagonal blobs of N tiles) is not asserted in the reviewed source. - Real block time → wall-clock duration.
prospecting_blocksand mining are in blocks; the wall-clock conversion depends on Polygon block cadence and the GSP's confirmation depth, which are outside the reviewed GSP rule source. cashprize redemption. Thecashprize tier (10 available, 1/350,000) is awarded as the inventory item"cash prize"like any other; how/whether it is redeemed for value is not defined in the prospecting/mining code reviewed here.