Mining¶
Active resource extraction from asteroid-field sectors. Mining is the canonical inflow path for raw ore, the secondary path for precious_metals, and a trace inflow for quantum_shards in deep-asteroid sectors. The loop is built around a per-attempt harvest action (5 turns), a Mining Laser equipment requirement, and an Astral Mining Consortium licensing system that gates lawful extraction inside AM-claimed sectors.
This doc covers the mining loop in isolation. For commodity pricing and station-side trading, see trading.md. For the Quantum Shard chain that mining trace-feeds, see ../galaxy/quantum-resources.md.
Status: ✅ Shipped —
services/gameserver/src/services/mining_service.pyimplements the server-authoritative harvest and AM claim-license flow; the harvest and license endpoints are live inservices/gameserver/src/api/routes/mining.py(registered inservices/gameserver/src/api/api.py), and theClaimLicensemodel is inservices/gameserver/src/models/claim_license.py. Theasteroid_richness,depletion_pool, andhas_deep_asteroidskeys live inside the existingSector.resourcesJSONB and are derived on demand fromSector.resource_regenerationfor sectors that predate the mining extensions. The Frontier Coalition unclaimed-asteroid rep hook (see Faction reputation hooks) is 📐 Design-only — it awaits a Frontier Coalition faction row.
Where mining happens¶
Mining is restricted to sectors where Sector.type = ASTEROID_FIELD. No other sector type accepts a mining attempt; the harvest endpoint rejects with not_an_asteroid_field outside of that set.
ASTEROID_FIELD is one of six special types in the SectorType enum (models/sector.py). Per ../galaxy/generation.md, galaxy generation flags 15% of sectors as special types; the remaining 85% are STANDARD. Inside that 15% slice, ASTEROID_FIELD share is cluster-driven:
RESOURCE_RICHclusters: ~60% of special-type rolls land on ASTEROID_FIELD (the dominant special type in resource-rich clusters).- Default clusters: ~25% of special-type rolls (15% / 4 evenly across
ASTEROID_FIELD,RADIATION_ZONE,WARP_STORM,BLACK_HOLE). MILITARY_ZONEandFRONTIER_OUTPOSTclusters: 0% — those cluster profiles do not roll ASTEROID_FIELD.
Galaxy-wide, the realised asteroid-field density lands in the 3–10% of sectors band depending on the regional cluster mix. Players seeking high mining throughput are pushed toward RESOURCE_RICH cluster regions; see ../galaxy/generation.md#step-5--sectors and ../../SYSTEMS/bang-import-pipeline.md for the per-cluster distribution.
Equipment requirement¶
Mining requires a Mining Laser equipment slot (mining_laser key in EQUIPMENT_DEFINITIONS, services/gameserver/src/services/ship_upgrade_service.py). Without a Mining Laser equipped, the harvest endpoint rejects with no_mining_laser.
| Attribute | Value |
|---|---|
| Purchase cost (level 0) | 35,000 cr |
| Compatible ships | Cargo Hauler, Colony Ship, Defender |
| Effect | mining_efficiency: 1.5 (multiplier on raw yield) |
| Slot key | mining_laser |
| Removable | Yes (refunds 25% of base cost) |
Source: ../gameplay/ship-systems.md — equipment table.
Status: ✅ Shipped — the equipment definition lives in
services/gameserver/src/services/ship_upgrade_service.py:EQUIPMENT_DEFINITIONS["mining_laser"], and itsmining_efficiencymultiplier is consumed bymining_service.pyat harvest time via the laser-level efficiency ladder (_laser_efficiency_multiplier). The harvest endpoint rejects withno_mining_laserwhen no laser is fitted.
Mining Laser upgrade ladder¶
The base 35,000 cr Mining Laser ships at level 0. Three upgrade levels are purchasable at any Class 7+ Technology Port. Upgrades are applied to the equipped Mining Laser in place; uninstalling and re-installing the laser preserves the upgrade level.
| Level | Upgrade cost | Cumulative cost | Yield multiplier | Notes |
|---|---|---|---|---|
| 0 | — | 35,000 | 1.0× | Stock laser. |
| 1 | 50,000 | 85,000 | 1.25× | Standard upgrade. |
| 2 | 100,000 | 185,000 | 1.5× | Required for quantum_shards trace drops. |
| 3 | 200,000 | 385,000 | 2.0× | End-game; raises the precious_metals rare-drop rate to its 11% ceiling (5% base + 2% per laser level, see § Output). |
Status: ✅ Shipped — the ladder lives in
services/gameserver/src/services/ship_upgrade_service.py:MINING_LASER_LADDER["mining_laser_level"], kept deliberately separate from theUpgradeType-keyedUPGRADE_DEFINITIONSdict (a string key there would break the upgrade-type iterators). The level is alevel: intkey insideShip.equipment_slots["mining_laser"];mining_service.py:_laser_levelreads it and_laser_efficiency_multipliermaps it to the yield multiplier. ThePOST /mining/laser-upgradeendpoint raises the level viaShipUpgradeService.purchase_mining_laser_upgrade(the standard row-lock + credit-check purchase ritual).
The harvest action¶
A harvest is a single per-attempt action issued from the sector view of an ASTEROID_FIELD sector. Each attempt:
| Cost / output | Value |
|---|---|
| Turn cost | 5 turns |
| Credit cost | 0 cr |
| Real-time cooldown | None (turn cost is the throttle) |
| Output | ore primarily; precious_metals rare; quantum_shards trace |
| Ship state during attempt | Ship.status = MINING (stationary, vulnerable) |
Preconditions¶
- Player ship is in a sector where
Sector.type = ASTEROID_FIELD. - Ship has a Mining Laser equipped (
Ship.equipment_slots["mining_laser"]present). - Ship is undocked (in space, not at a station).
- Player has ≥ 5 turns.
- Cargo has ≥ 1 free unit.
- If the sector is AM-claimed and the player wants to avoid rep loss, a valid
ClaimLicensefor the sector is held (see Astral Mining Consortium claim licenses below).
Resolution¶
- Server reads the sector's
Sector.resources.asteroid_richness✅ Shipped — derived on demand fromSector.resource_regeneration(and persisted into the JSONB) bymining_service.py:_ensure_asteroid_richnesswhen the key is absent. - Set
Ship.status = MININGfor the duration of the resolution. The MINING status holds the ship in the sector and exposes it to PvP interrupts (see PvP interaction below). - Roll the ore band from the yield matrix below using
richness_tier × laser_level. - Apply the Mining Laser's
mining_efficiencymultiplier (1.0× / 1.25× / 1.5× / 2.0× by upgrade level). - Apply the planet-side Mining Engineers profession bonus if the player owns a planet in the same region with active Mining Engineers (+30% on
ore/fuelproduction — see../planets/professions.md). The mining bonus extends the same multiplier to direct asteroid extraction. - Apply the Trade Specialist profession bonus if the player owns a planet with active Trade Specialists (+25% credit-equivalent multiplier on raw ore sold from the same trade run; see
../planets/professions.md#trade-specialists). Cross-link only — the Trade Specialist bonus applies at sale, not at harvest. - Roll
precious_metalsrare drop: 5% per harvest at laser level 0; +2% per laser level (cap 11% at level 3). - Roll
quantum_shardstrace drop: 1% per harvest, only ifSector.resources.has_deep_asteroids = true✅ Shipped and the Mining Laser is at level ≥ 2. - Decrement the sector's
Sector.resources.depletion_pool✅ Shipped by the rolled ore yield (precious metals and quantum shards do not count against the pool). - Add commodities to player cargo, deduct turns, set
Ship.status = IN_SPACE, emit the realtime event. - Apply faction-rep deltas (see Faction reputation hooks below).
Yield matrix¶
Base ore yield per harvest, by sector richness tier × Mining Laser level. Richness tier is read from the sector's asteroid_richness JSONB field; tier 5 sectors are rare and concentrated in RESOURCE_RICH clusters.
| Richness tier | Laser L0 (1.0×) | Laser L1 (1.25×) | Laser L2 (1.5×) | Laser L3 (2.0×) |
|---|---|---|---|---|
| 1 (sparse) | 2–4 ore | 3–5 ore | 3–6 ore | 4–8 ore |
| 2 | 4–8 ore | 5–10 ore | 6–12 ore | 8–16 ore |
| 3 (typical) | 6–12 ore | 8–15 ore | 9–18 ore | 12–24 ore |
| 4 | 10–18 ore | 13–23 ore | 15–27 ore | 20–36 ore |
| 5 (deep core) | 15–25 ore | 19–31 ore | 23–38 ore | 30–50 ore |
The min-of-band is a hard floor — see Asteroid depletion for the per-sector depletion modifier. Even at maximum depletion, the sector returns at least 1 ore per attempt.
Status: ✅ Shipped — the yield matrix is
mining_service.py:_YIELD_MATRIX, indexed by(richness_tier, laser_level)with the bands above copied verbatim; the harvest rolls the band, applies the laser-efficiency and depletion multipliers, and enforces the 1-ore floor.
asteroid_richness derivation¶
Sector.resources.asteroid_richness is computed from Sector.resource_regeneration (a float 0.0–1.0 already on the Sector model) and stored as a JSONB sub-object. mining_service.py:_ensure_asteroid_richness performs this derivation lazily — on the first harvest or license purchase in a sector whose resources lacks the key — and persists the result, so sectors generated before the mining extensions backfill on demand:
resource_regeneration |
Tier | Name | Yield band (L0 laser) | Cooldown |
|---|---|---|---|---|
| ≥ 0.9 | 5 | Abundant | 15–25 | None |
| 0.6–0.89 | 4 | Rich | 10–18 | None |
| 0.3–0.59 | 3 | Moderate | 6–12 | None |
| < 0.3 | 1 | Depleted | 0–1 | 24h between harvests |
Stored shape:
"asteroid_richness": {
"richness": "moderate",
"richness_tier": 3,
"yield_band": [6, 12],
"harvest_cooldown_hours": null
}
Computed at Sector creation (worldgen or import from sw2102-bang); does not change during the game session. The yield-matrix lookup above multiplies richness_tier × laser_level for the harvest-time band; this stored representation is the canonical source of richness_tier.
Commodity output¶
Mining produces only raw materials — the loop never emits finished goods. Equipment, fuel, and other refined commodities are out of scope for direct mining yield.
| Commodity | Drop | Rate | Notes |
|---|---|---|---|
ore |
Primary | 100% | Always rolled per the yield matrix. The canonical commodity sold to Class 1, Class 4, Class 5, Class 6 stations and AM-flagged refineries. Price band 15–45 cr/unit per trading.md. |
precious_metals |
Secondary | 5% per harvest at L0; +2% per laser level | Rare drop on top of the base ore yield. Yield 1–3 units per drop. Price band 80–180 cr/unit (services/gameserver/src/core/commodity_economy.py), slotted between equipment and exotic_technology. |
quantum_shards |
Trace | 1% per harvest | Only in sectors with Sector.resources.has_deep_asteroids = true and a Mining Laser at level ≥ 2. Yield 1 shard per drop. Cross-feed into the ../galaxy/quantum-resources.md chain — note that the primary Quantum Shard source remains nebula harvesting; the asteroid drop is a small secondary inflow that complements the dedicated Quantum Field Harvester loop. |
Status: ✅ Shipped —
mining_service.pyemits all three commodities:oreper the yield matrix,precious_metalson the 5%+2%/level rare roll (cap 11%), andquantum_shardson the 1% trace roll gated byhas_deep_asteroidsand laser level ≥ 2. Each drop is clamped to free cargo space.precious_metalsis priced at 80–180 cr/unit incommodity_economy.py;has_deep_asteroidsis aSector.resourcesJSONB key (defaultfalse, set by worldgen/import for deep-asteroid sectors).
Astral Mining Consortium claim licenses¶
Sectors flagged as AM-claimed (the sector's dominant Faction row has Faction.faction_type == MINING per ADR-0033; Sector.controlling_faction is a String(50) snake-case faction code — canonical at launch. The previously proposed Sector.controlling_faction_id FK alternative is dropped from the schema target; normalization to a foreign key is a post-launch follow-up tracked on the code-side punch list, not a launch-blocking decision) require a claim license to mine without rep penalty. AM controls a portion of RESOURCE_RICH clusters in Federation and Border zones — exact share is regional and tracked in ../gameplay/factions-and-teams.md.
License model¶
| Field | Value |
|---|---|
| Model | ClaimLicense ✅ Shipped (services/gameserver/src/models/claim_license.py) |
| Issuer | Any AM-flagged refinery station (Class 1 mining or Class 5 collection with AM controlling-faction tag) |
| Scope | Single sector, single player |
| Duration | 24 real-time hours per purchase, renewable at 80% of base cost |
| Daily fee | 500 cr × richness_tier |
| Stack | One active license per (player, sector) pair |
| Persistence | Survives logout, expires on the timer |
Schema — services/gameserver/src/models/claim_license.py (table claim_licenses):
| Column | Type | Constraints | Notes |
|---|---|---|---|
id |
UUID | PK | |
player_id |
UUID | FK Player.id, not null |
License holder |
region_id |
UUID | FK regions.id, nullable |
Compound sector identity (region + sector); nullable for region-less sectors, where the unique constraint distinguishes by sector_number alone |
sector_number |
Integer | not null | Region-scoped sector |
faction_code |
String(50) | not null | Issuing faction (always astral_mining_consortium at launch) |
purchased_at |
DateTime | not null | UTC timestamp of purchase |
expires_at |
DateTime | not null | purchased_at + 24h |
cost_paid_cr |
Integer | not null | 500 × richness_tier |
is_active |
Boolean (computed) | — | expires_at > NOW() |
UNIQUE on (player_id, region_id, sector_number) ensures one active license per (player, sector) at a time. Renewal: a fresh row is inserted on each renewal at 80% of base (400 cr × richness_tier); the prior row's expires_at is honoured for any overlap window.
License cost by richness tier¶
| Richness tier | Fee per 24h |
|---|---|
| 1 (sparse) | 500 cr |
| 2 | 1,000 cr |
| 3 (typical) | 1,500 cr |
| 4 | 2,000 cr |
| 5 (deep core) | 2,500 cr |
Licenses are paid up-front; partial-day refunds are not granted on cancellation. The license row is created at any AM-flagged station via a single docked transaction; the player does not need to be in the licensed sector to purchase.
License effects¶
- Licensed mining in an AM-claimed sector: harvest proceeds normally, no rep penalty, +1 AM rep per harvest (stacks with the base mining rep gain — see Faction reputation hooks).
- Unlicensed mining in an AM-claimed sector: harvest proceeds, but −10 AM rep per extraction. Repeated unlicensed extraction trips the AM faction enforcement path (NPC patrol response in
MILITARY_ZONEoverlap clusters). - Mining in unclaimed asteroid sectors: no license required, no AM rep penalty. Frontier-zone unclaimed asteroids additionally grant a small Frontier Coalition rep boost — see hooks below.
Status: ✅ Shipped — the
ClaimLicensemodel and thePOST /mining/licensepurchase/renewal endpoint are live (mining_service.py:purchase_license, charging500 cr × richness_tieron a new license and 80% of that on renewal). Licensed mining grants +2 AM rep, unlicensed extraction in AM space applies the −10 AM rep penalty, and unclaimed asteroids grant the base +1 AM tick — all wired inmining_service.py:harvest. The repeated-unlicensed AM enforcement (NPC patrol response) is 📐 Design-only.
Asteroid depletion¶
Each ASTEROID_FIELD sector carries a Sector.resources.depletion_pool — a counter that decrements per harvest and self-replenishes on a real-time timer. Depletion modulates yield band but never floors it to zero.
| Depletion state | Pool consumed | Yield modifier | Replenishment |
|---|---|---|---|
| Fresh | 0% | 1.0× (full band) | n/a |
| Light | < 5% per attempt | 1.0× (full band) | Auto-replenish in 24h real time after last harvest |
| Moderate | 5–50% | 0.75× | Auto-replenish in 24h real time |
| Heavy | 50–90% | 0.5× | Auto-replenish requires 7 real-time days |
| Exhausted | > 90% | Floor — minimum 1 ore per attempt regardless of band | 7 real-time days to fresh |
The hard floor is enforced server-side: if the rolled band would return zero (after multipliers and depletion), the harvest emits 1 ore and the depletion pool is unchanged for that attempt. This guarantees a non-zero feedback signal and prevents stuck "sector worthless" states.
Pool size scales with richness tier: tier 1 = 100 units, tier 5 = 500 units. The pool is decremented by the rolled ore yield; precious metals and quantum shards do not count against the pool.
Status: 🚧 Partial — the
depletion_poolJSONB key, the tier-scaled pool size (tier × 100), the per-harvest decrement by rolled ore yield, the Fresh/Moderate/Heavy yield modifiers (1.0× / 0.75× / 0.5×), and the 1-ore hard floor are implemented inmining_service.py. The real-time auto-replenishment timer (24h / 7-day recovery) is 📐 Design-only — the pool decrements but does not yet refill on a schedule.
Faction reputation hooks¶
All AM deltas applied via services/gameserver/src/services/faction_service.py:apply_faction_rep_delta against FactionType.MINING, in the same transaction as the harvest, per the contract documented in ../gameplay/factions-and-teams.md#reputation-triggers.
| Action | Faction | Delta | Notes |
|---|---|---|---|
| Mine in any asteroid sector with Mining Laser equipped | Astral Mining Consortium | +1 / harvest | Base AM rep tick — applies in all asteroid sectors regardless of claim status. |
| Mine in AM-claimed sector with valid license | Astral Mining Consortium | +1 / harvest | Stacks with the base tick: licensed mining in AM-claimed space yields +2 AM rep / harvest. |
| Mine in AM-claimed sector without license | Astral Mining Consortium | −10 / extraction | Per-attempt penalty; repeated incidents flag AM enforcement response. |
| Mine in Frontier-zone unclaimed asteroid | Frontier Coalition | +5 / harvest | 📐 Design-only — small Frontier-loyalty signal; only fires in Frontier zone (Zone.type = FRONTIER) and only on unclaimed sectors. Awaits a Frontier Coalition faction row to apply the delta to. |
Sell raw ore to AM-flagged refinery |
Astral Mining Consortium | +2 / 5,000 cr of trade volume | Already in ../gameplay/factions-and-teams.md#reputation-triggers; listed here for completeness. |
Buy a ClaimLicense at any AM-flagged station |
Astral Mining Consortium | +15 / purchase | Single-shot reputation boost on license-fee payment (matches ../gameplay/factions-and-teams.md#reputation-triggers). |
Reputation deltas are written inside the same transaction as the harvest commodity grant; a failed harvest does not move the rep counters.
Status: 🚧 Partial — all Astral Mining Consortium hooks (the +1 base tick, the +2 licensed total, the −10 unlicensed penalty, and the +15 license-purchase boost) are wired in
mining_service.py. The Frontier Coalition +5 unclaimed-asteroid hook is 📐 Design-only — there is no Frontier Coalition faction row to apply it to yet.
PvP interaction¶
While Ship.status = MINING, the ship is stationary and visible in the sector for the duration of the harvest action. Any other player in the sector can attack a mining ship; the mining ship cannot maneuver or flee until the action resolves.
Interrupt behaviour¶
If a mining ship is attacked or otherwise interrupted (sector evacuation, warp gate collapse, etc.) before the harvest resolves:
- The harvest cancels — no commodities are awarded.
- 50% of the 5-turn cost is refunded (2 turns returned to
Player.turns; the other 3 are sunk). - The sector's
depletion_poolis not decremented for the cancelled attempt. - The Mining Laser is undamaged; the player can re-attempt as soon as the threat resolves.
Ship.statusresets toIN_SPACE(orIN_COMBATif the interrupt was a successful PvP attack).
The 50% refund is the design lever that disincentivises grief-interrupting cheap harvests while still penalising the miner for poor situational awareness.
Status: 🚧 Partial —
ShipStatus.MININGis in the canonical enum (per../../DATA_MODELS/ships.md) andmining_service.pysets it on the ship during harvest. The harvest itself resolves atomically in a single transaction, so the interruptible in-progress window and the 50%-turn-refund-on-interrupt logic are 📐 Design-only — they await an asynchronous, multi-tick harvest model.
Player-facing affordances¶
- Sector view in an
ASTEROID_FIELDsector exposes a "Mine" button when a Mining Laser is fitted; greyed out (with reason text) when off-cooldown is not the issue but a precondition fails (no laser, docked, no cargo space, sector AM-claimed without license — the last surfaces a one-click "Buy License" CTA). - Yield preview shows the band for the current sector × laser level × richness tier before the player commits the 5 turns.
- Depletion state is visible on the sector overlay: a four-step indicator (Fresh / Light / Moderate / Heavy) plus a real-time replenishment countdown when the sector is in Heavy.
- ARIA market-intelligence overlay surfaces nearest AM-flagged refinery for ore offload, with current buy price.
- License panel at AM-flagged stations lists the player's active and recently expired licenses with one-click renewal.
- Notifications fire on harvest success (with yield),
precious_metalsrare drop,quantum_shardstrace drop, license expiry warning (1h before), and depletion-state transitions on sectors the player has bookmarked.
Anti-cheat / safety¶
- The harvest endpoint is server-authoritative and atomic; commodity grants and turn deductions occur in the same transaction.
- Yield rolls use a cryptographically secure RNG, matching the standard set in
../galaxy/generation.mdfor Genesis device rolls. - The
MININGstatus row takeswith_for_updateon the ship to serialise concurrent attempt requests from a single ship. - License-validity checks are read-locked against
ClaimLicenserows; expiry timestamps are server-clock authoritative. - Per-sector
depletion_pooldecrements are atomic with the commodity grant; a failed grant rolls back the pool decrement.
Source map¶
| Topic | Path |
|---|---|
| Mining service | services/gameserver/src/services/mining_service.py |
| Sector asteroid extensions | services/gameserver/src/models/sector.py — the Sector.resources JSONB carries asteroid_richness, depletion_pool, and has_deep_asteroids (derived on demand by mining_service.py:_ensure_asteroid_richness) |
| Claim license model | services/gameserver/src/models/claim_license.py |
| Mining Laser equipment | services/gameserver/src/services/ship_upgrade_service.py:EQUIPMENT_DEFINITIONS["mining_laser"] |
| Mining Laser upgrade ladder | services/gameserver/src/services/ship_upgrade_service.py:MINING_LASER_LADDER["mining_laser_level"] |
MINING ship status |
services/gameserver/src/models/ship.py:ShipStatus.MINING |
| Faction rep hook | services/gameserver/src/services/faction_service.py:apply_faction_rep_delta |
| AM-flagged refinery sale | services/gameserver/src/services/trading_service.py |
| Mining API | services/gameserver/src/api/routes/mining.py (registered in services/gameserver/src/api/api.py) |
Related¶
./trading.md— ore pricing, AM-flagged refinery sale path.../galaxy/generation.md—ASTEROID_FIELDdensity and cluster-type distribution.../galaxy/quantum-resources.md— primary Quantum Shard chain; mining is a trace secondary inflow.../gameplay/ship-systems.md— Mining Laser equipment definition and upgrade architecture.../gameplay/factions-and-teams.md— Astral Mining Consortium reputation contract.../planets/professions.md— Mining Engineers and Trade Specialists profession bonuses.../../DATA_MODELS/galaxy.md—Sector.resourcesJSONB schema; the target asteroid-mining keys extend this.