Skip to content

Colonization & Colony Management

Status: 🚧 Partial — colonization lifecycle from pioneer transit through outpost partially shipped; endgame colony management incomplete. (re-verified 2026-08-21 vs Sectorwars2102 HEAD 46bce720.)

Covers the lifecycle from contracting pioneer transit at the region's Capital Sector through running a Planetary Capital.

Two paths to a colony

1. Traditional colonization

Requires: - 10,000 pioneers in cargo (1 cargo space each, riding in cryosleep transit pods). - 10,000 credits investment (the founding-grant fee paid to the destination claim). - An uncolonized planet you can land on.

Result: Outpost (Phase 1, citadel level 1) with 100–1,000 starting population.

2. Genesis Device terraforming

See galaxy/genesis-devices.md.

  • Basic Genesis (1 device) → Outpost.
  • Enhanced Genesis (3 devices) → Outpost (better planet-type roll).
  • Advanced Genesis (Colony Ship sacrifice) → instant Settlement (Phase 2, citadel level 2) with 5,000 settled pioneers, +10% production, 4 turrets, basic shields.

Pioneer migration contracts

A planet doesn't begin with population — it begins with a transit contract. Volunteer pioneers continuously enrol with the regional Migration Authority for resettlement on frontier worlds, and each region's Capital Sector hosts the Pioneer Office that registers and dispatches them. Players broker pioneer transit by contracting at the Office, ferrying the cohort in cryosleep transit pods, and fulfilling the migration contract on landing. The cargo commodity colonists represents one cohort of pioneers in transit (one pioneer per cargo unit).

Securing a contract

  • Pioneer migration contracts are issued only at the Capital Sector of every region (sector 1 in Terran Space; the random Capital in player regions; Central Nexus Starport Prime in the Gateway Plaza cluster of the Nexus).
  • Class-0 station, special inventory.
  • Always available — pioneer demand far exceeds available transit capacity.

Per-pioneer fee (✅ Shipped)

The per-pioneer fee is dynamically priced. Source: services/gameserver/src/services/trading_service.pyCOMMODITY_PRICE_RANGES["colonists"] = {min: 30, max: 80}, calculate_dynamic_price().

Per-pioneer fee at the Pioneer Office is computed as:

supply_ratio = station.commodity.quantity / capacity   # 0.0 – 1.0
midpoint     = base_price × (1.5 - supply_ratio)
fee          = midpoint × 1.15   (SELL_SPREAD)
final        = clamp(fee, 30, 80)

Drivers: - Pioneer roster depth at the Office is the only live signal — heavy contracting drains the available roster, raising the per-pioneer fee toward 80; periodic intake refreshes it back toward 30. - The 15% spread is the same one used for every commodity (SELL_SPREAD = 1.15). - Federation tax / region control do not currently feed into the formula.

Effective range a player will see: ~30–80 cr per pioneer, dependent on roster depth at contracting time. Bulk colonization runs (10,000 pioneers) deplete the roster enough to noticeably push the fee up mid-contract.

Transit

  • 1 cargo space per pioneer (cryosleep transit pod).
  • For 10,000 pioneers you need 10,000 cargo space — multiple trips for any non-Cargo Hauler ship.
  • Standard cargo loss rules apply: pioneers lost on ship destruction; the contracts void and the regional roster reabsorbs replacements.

Fulfilling the contract

When you colonize: - Cargo colonists decremented by 1,000 (pioneers disembark on the destination world). - Credits decremented by 10,000 (founding-grant fee paid to the destination claim). - Planet.colonists += 1,000 (matches the L1 Outpost cap; growth fills toward higher caps as the citadel upgrades). - Planet.max_colonists initialized to the L1 cap (1,000); Planet.max_population initialized to habitability_score × 1,000. - Player added to player_planets association table — recognized as the founder of record.

Planet types

Canonical planet types (models/planet.py:PlanetType). 5 are Genesis-rollable (OCEANIC, DESERT, ICE, VOLCANIC, MOUNTAINOUS — see ../galaxy/genesis-devices.md#planet-type-weights and ADR-0014). The rest appear only via natural sources — bang-imported planets, special encounters, operator-seeded sectors — never as Genesis output. nexus_generation_service generates JUNGLE and TROPICAL as colonizable worlds alongside the Genesis-rollable types; GAS_GIANT is a real body but cannot be landed on or claimed.

JUNGLE, TROPICAL, ARCTIC, and GAS_GIANT carry neutral 1.0 production multipliers (services/gameserver/src/services/planetary_service.py NEUTRAL_TYPE_EFFICIENCY).

Type Fuel mult Organics mult Equip mult Fuel cap Org cap Equip cap Growth/day Habitability % Native life chance
TERRAN (Capital welcome) 0 0 0 100
MOUNTAINOUS 0.6 0.4 1.5 8,000 5,000 15,000 +0.3% 60–75 40%
OCEANIC 1.5 0.4 0.6 15,000 5,000 8,000 +0.4% 60–75 75%
DESERT 0.4 1.5 0.6 5,000 15,000 8,000 +0.2% 30–45 25%
VOLCANIC 1.0 0.0 2.0 8,000 0 15,000 +0.1% 10–25 10%
BARREN 0.0 0.0 1.5 0 0 20,000 −0.1% 0 5%
ICE 0.8 1.2 0.5 10,000 15,000 8,000 −0.2% 35–50 30%
TROPICAL 1.0 1.0 1.0 80–100
JUNGLE 1.0 1.0 1.0 60–90
ARCTIC 1.0 1.0 1.0
GAS_GIANT (uncolonizable) 1.0 1.0 1.0

BARREN and ICE planets have negative natural growth — without continuous immigration or terraforming, the colony shrinks.

TROPICAL, JUNGLE, ARCTIC, and GAS_GIANT carry neutral 1.0 production multipliers; per-type stockpile caps, growth rates, and native-life chances are not yet defined for them. 📐 Design-only — these per-type tuning numbers await canonization; the neutral fallback applies in the interim.

Provisional numbers, drafted for canonization symmetry with the ratified table above (extrapolated from each type's habitability-generation band in nexus_generation_service.py and its neutral 1.0/1.0/1.0 efficiency triad — none of this row is ratified; see the colonization-neutral-biome-numbers Pending Decision below):

Type Fuel mult Organics mult Equip mult Fuel cap Org cap Equip cap Growth/day Habitability % Native life chance
TROPICAL (📐 provisional) 1.0 1.0 1.0 12,000 12,000 10,000 +0.3% 80–100 60%
JUNGLE (📐 provisional) 1.0 1.0 1.0 10,000 10,000 10,000 +0.2% 60–90 50%
ARCTIC (📐 provisional) 1.0 1.0 1.0 8,000 8,000 10,000 0.0% 20–45 15%
GAS_GIANT (📐 provisional, uncolonizable) 1.0 1.0 1.0 0 0%

GAS_GIANT is not landable/claimable (api/routes/planets.py:386,1504 reject it outright, admin_colonization.py:650 excludes it from the colonizable set) and is never rolled by nexus_generation_service's planet-type generator, so its row is a placeholder for schema completeness only, not a reachable gameplay state. ARCTIC is likewise never rolled by that generator's random.choice list today (only TERRAN/TROPICAL/JUNGLE/OCEANIC/MOUNTAINOUS/DESERT/BARREN/ICE are) — it exists in the PlanetType enum and the neutral-efficiency table but has no live spawn path, so its provisional row is speculative pending either a spawn-path decision or removal from the roll-eligible taxonomy.

Colony lifecycle phases

Colonies progress through five phases, each tied to a citadel level (see citadels.md for the structural side):

Phase Pop range Citadel level Notes
1 — Outpost 0–1,000 1 Survival focus, minimal defense
2 — Settlement 1,001–5,000 2 Basic shield, moderate production
3 — Colony 5,001–15,000 3 Specialization, defense grid
4 — Major Colony 15,001–50,000 4 Industrial scale, orbital platforms unlock
5 — Planetary Capital 50,001–200,000 5 Fortress, max upgrades

Promotion between phases requires meeting population, resource, and prerequisite thresholds — see citadels.md for the full upgrade matrix.

Production growth is multiplicative across phases: - Outpost → Settlement: ~5×. - Settlement → Colony: ~3×. - Colony → Major Colony: ~4×. - Major Colony → Planetary Capital: ~5×.

Population capacity

Two independent ceilings interact, and the runtime invariants must hold all of them simultaneously:

  • Planet.max_colonistscitadel-tier workforce ceiling. Driven by citadel level: L1 Outpost = 1,000, L2 Settlement = 5,000, L3 Colony = 15,000, L4 Major Colony = 50,000, L5 Planetary Capital = 200,000 (see citadels.md). Bumped automatically when the citadel upgrades. Caps the workforce-side production input.
  • Planet.max_populationhabitability-derived demographic ceiling. Total inhabitants (colonists + dependents — children, retirees, non-contributors).

Canonical formula:

max_population = habitability_score × 1,000

Examples: - Habitability 100 (TERRAN) → 100,000 max population. - Habitability 60 (MOUNTAINOUS / OCEANIC) → 60,000. - Habitability 25 (VOLCANIC) → 25,000. - Habitability 0 (BARREN) → 0 — orbital habitats only.

The two ceilings work together — citadel caps the workforce, habitability caps total demographic — and never substitute for each other. A high-habitability planet at L1 still caps colonists at 1,000 (citadel-bound), but its population may grow toward the habitability ceiling as families and dependents accumulate. A low-habitability planet at L5 still caps total inhabitants at habitability × 1000 regardless of citadel tier.

Runtime invariants: - colonists ≤ max_colonists (citadel cap). - population ≤ max_population (habitability cap). - colonists ≤ population (working-age subset of total demographic).

When habitability changes (terraforming completes, hazard mutates the score, etc.), max_population recomputes from the formula above — the recompute is a trigger fired by the habitability mutation, not a separate rule. Citadel upgrades fire the same recompute on max_colonists.

✅ Shipped — world generation derives max_population = habitability_score × 1,000 at colony seeding (services/gameserver/src/services/genesis_service.py:542, services/gameserver/src/services/nexus_generation_service.py:544); in-game habitability mutations recompute from the same formula.

The runtime habitability-effects helper (services/planetary_service.py:946 get_habitability_effects) reports effectiveMaxColonists = max_colonists × (habitability/100) — keep this view; it's the workforce-side limiter that pairs with max_population as the demographic-side limiter.

Population growth

Source: services/gameserver/src/services/planetary_service.py:914 (_calculate_production_rates).

Daily formula:

colonist_rate = colonists × 0.01 × (habitability_score / 100)

i.e. base growth = 1% per day, scaled linearly by habitability. With a specialization bonus applied:

colonist_rate ×= specialization.production.colonists   # e.g. agricultural

Per-planet-type effective daily growth (base 1% × habitability midpoint):

Type Habitability midpoint Effective daily growth
TERRAN 100 ~1.0%
MOUNTAINOUS 67 ~0.67%
OCEANIC 67 ~0.67%
DESERT 37 ~0.37%
ICE 42 ~0.42%
VOLCANIC 17 ~0.17%
BARREN 0 0%

These formula-derived numbers are the canonical effective growth rates at the habitability midpoint of each type. The simpler "Growth/day" column in the planet-types table above (+0.3% / +0.4% / +0.2% / +0.1% / −0.1% / −0.2% across MOUNTAINOUS / OCEANIC / DESERT / VOLCANIC / BARREN / ICE; TERRAN is uncapped) is a player-facing summary that abstracts the formula — players see the summary value in UI; the production tick uses the formula. The two views diverge because the summary uses simplified per-type baselines while the formula scales by actual current habitability.

Other growth modifiers

  • Siege: colonist_rate = 0 while Planet.under_siege is true (planetary_service.py:937). ✅ Shipped.
  • Specialization: agricultural / balanced configurations multiply colonist growth via _calculate_specialization_bonuses. ✅ Shipped.
  • Specialization identity (per ADR-0087): a colony's specialization also shapes its non-economic strengths — Military gives ×1.5 planet-defense effectiveness in siege/combat, Research gives ×1.5 research-point output, and Balanced gives +10% all-round (production / defense / research). ✅ Shipped.
  • Terraforming: raises habitability_score, which directly raises colonist_rate; see terraforming.md. Terraforming completion also floors population_growth to 1.0 / 1.5 / 2.0 depending on tier (services/terraforming_service.py:481).
  • Negative natural growth for BARREN / ICE in the planet-type table: ✅ Shipped (WO-AH, habitability zero-crossing). apply_population_growth (services/gameserver/src/services/planetary_service.py:652-774) now owns a full below-threshold decline branch: below HABITABILITY_GROWTH_THRESHOLD = 20 (:372), colonists are lost at -colonists × DAILY_DECLINE_BASE × (THRESHOLD − habitability)/100 per day (DAILY_DECLINE_BASE = 0.01 at :373, decline math at :737-774), floored at 0 colonists with the loss mirrored into population. At/above the threshold the unchanged canon growth formula still applies. Terraforming a harsh world past the threshold flips the colony from decline back to growth on the next read (same code path, per its docstring at :659-667).

Overcrowding

Shipped — demographic overcrowding cap is live in services/gameserver/src/services/planetary_service.py (verified against tip):

  • Ceiling formulamax_population_for(habitability_score) returns habitability_score × 1,000 (:534–541).
  • Growth hard-stopapply_population_growth stops colonist accrual at min(workforce_cap, max_population_for(...)) (:785–797).
  • Production-rate taper_calculate_production_rates keys on population vs max_population (demographic ceiling, not the workforce max_colonists cap): at population ≥ max_populationcolonist_rate = 0; across the top 10% band (population > 0.9 × max_population) → linear taper 100% → 0%; no shrink below the cap (:2649–2661).

Gourmet food luxury bonus

Status: ✅ Shipped (WO-G14) — planetary_service.py:960-978 (production) and :2742-2751 (colonist growth), gated on a positive gourmet_food stockpile (re-verified 2026-08-04).

A planet stockpile carrying any nonzero amount of gourmet_food receives a flat luxury bonus while supplies last:

  • +5% to colonist growth rate (multiplies colonist_rate after habitability scaling).
  • +5% to production tick output (multiplies the per-bucket production amount before specialization bonuses).

The bonus consumes gourmet_food at 1 unit per 1,000 colonists per day (proportional to the population it's feeding). When the stockpile depletes, the bonuses cease until the planet is resupplied. Gourmet food is sourced from Class-10 stations (the canonical gourmet_food buyer/seller per economy/trading.md:30) or via interregional trade.

Why this is in the spec: gourmet food sits a tier above organics in the commodity catalog. Organics keeps the colony alive; gourmet food is the luxury input that makes a colony thrive.

Low-habitability resource cost penalty

Shipped, ratified 2026-08-04. habitability_score in [20, 30) nets commodity output down by a flat 20% (life support / environmental control cost), applied in the production tick alongside the existing siege multiplier (LOW_HABITABILITY_THRESHOLD/LOW_HABITABILITY_PRODUCTION_PENALTY, planetary_service.py). Mutually exclusive with population decline — below habitability 20 (HABITABILITY_GROWTH_THRESHOLD) the colony's population declines instead; the output penalty does not additionally stack on a world that's already shrinking. A hab-20-to-29 world nets ~20% less output at stable population; a hab-below-20 world shrinks but pays no extra output tax on top.

Colonist allocation

Planet.fuel_allocation, organics_allocation, equipment_allocation — three buckets, each a head-count of colonists assigned to that role (Integer columns, not percentages). Sum may be ≤ Planet.colonists; any unallocated colonists are idle (no production, no consumption penalty). Adjustable any time, instantly, no cost. The production-tick formula multiplies these head-counts directly into the per-commodity rate (see ../../SYSTEMS/planetary-production-tick.md#production-rates).

Production: see production.md.

Optimal allocation depends on planet type. Example with 10,000 colonists: - Oceanic (1.5× fuel): 7,000 fuel / 1,500 organics / 1,500 equipment. - Mountainous (1.5× equipment): 1,500 / 1,500 / 7,000. - Other types: allocate based on the type's multiplier profile and empire needs (e.g., DESERT favors organics, VOLCANIC favors equipment, ICE has no strong dominant axis).

The percentage-style examples some legacy notes used (e.g., "70% fuel") are presentation-layer only — the player UI may render allocations as sliders that map percentages to head-counts, but the persisted columns and the production-tick math are head-counts.

Colony management actions

Daily

  • Withdraw produced resources (production stops at storage cap).
  • Adjust allocation as market prices shift.
  • Monitor drone count and shield strength.

Strategic

  • Citadel upgrades (resources + time + prerequisites; see citadels.md).
  • Production upgrades (+10% per level, max level 10 per resource).
  • Defensive investments (drones, shields, orbital platforms, rail guns; see defense.md).
  • Terraforming (see terraforming.md).

Ownership controls

  • Transfer ownership to another player (5% transfer fee, recipient must accept). ✅ Gameserver shippedplanet_ownership_transfer_service.py + POST/GET /planets/{planet_id}/ownership-transfer (+ /accept, /cancel) on PR #658 / LEG-159·LEG-200 (merge a8d1f953). 🚧 Player-client UI tip-pending — no OwnershipTransferControl on origin/feat 46bce720; in-flight branch wo/LEG-514-ownership-transfer @ ec553ad0 adds the control + Vitest (head_contains_tip=YES, tip_HAS_land=NO — not player-reachable until merge). PC INI-44/#507 and LEG-514/#563 track the land path. Do not treat the 5% flow as player-reachable until it lands on tip.
  • Abandonment & inactivity reclamation (PL4b — abandonment_service.py, the single writer): two paths revert a planet to unowned while structures / citadel / population / resources persist on the row (that developed-world inheritance is the reclaim premium).
  • Voluntary abandon (POST /planets/{planet_id}/abandon, owner-only): forfeiture, 0 compensation, blocked while the planet is under siege. ✅ Shipped.
  • Involuntary inactivity reclaim: a daily sweep flags a planet whose owner's last_game_login is older than INACTIVITY_DAYS (90 days); a RECLAIM_GRACE_DAYS (7-day) window after the flag must pass before any other player may reclaim. POST /planets/{planet_id}/reclaim charges the reclaimer a flat 50,000 cr + 5,000 each of ore/organics/equipment, atomic with the ownership flip. GET /planets/{planet_id}/reclaim-status exposes the flag / grace-end / eligibility for the UI. ✅ Shipped.
  • Displaced-owner compensation: see Displaced-owner compensation below.
  • Landing rights (Planet.landing_rights JSONB): one of five modes — public (anyone), team_only (owner's team-mates only), private (owner only), whitelist (specific player UUIDs in landing_rights.whitelist[]), denylist (anyone except UUIDs in landing_rights.denylist[]). Mode changes apply immediately to subsequent landing attempts; ships already on-planet are not evicted by a mode change. ✅ Shipped — _check_landing_allowed enforces all five modes fail-closed; the owner-only PUT /planets/{planet_id}/landing-rights setter writes the mode. ✅ Owner UILandingRightsControl (services/player-client/src/components/planetary/LandingRightsControl.tsx) is ✅ Shipped on tip (LEG-175 / PR #660 / 7bb248a8) for the three simple modes (public / team_only / private). 🚧 tip-pending — whitelist/denylist UUID list editor (Fibril #1097 / LEG-INI-31 / PR #698 @ 8dd3531d, not tip ancestor); tip still disables those modes with "not in this release" reasons. Do not claim tip already enables whitelist/denylist editors.
  • Tax rate (Planet.tax_rate, Float 0.00–0.20): owner can charge a production tax on team-mates landing on the planet (extracts a percentage of resources withdrawn from planet stockpiles to the team-mate's cargo, deposits to the owner's safe). Defaults to 0.0 (no tax) for owner-team and friendlies; non-team landings always pay 0% (no tax mechanism for non-team since they shouldn't be landing in the first place under most landing-rights modes). ✅ ShippedPOST /planets/{planetId}/stockpile/withdraw (commit 37f6309b, PR #565) charges team-mates clamp_tax_rate(Planet.tax_rate) skim on resource egress, deposited to the owner's citadel safe.

Displaced-owner compensation

When an inactive owner is displaced by an involuntary reclaim, they are paid COMPENSATION_FRACTION = 0.4 × sunk cost, where sunk cost is claim_fee + Σ CITADEL_LEVELS[n].upgrade_cost for n = 2..level (anchored to the real in-code citadel upgrade ladder: L2 = 50k, L3 = 150k, L4 = 500k, L5 = 2M). The payout is capped at the sunk cost so the flow can never be a money pump, and is paid only if the displaced owner's tenure was ≥ TENURE_FLOOR_DAYS (7 days). For an L0/L1 planet (upgrade_cost 0) the payout is 0.4 × 10,000 = 4,000 against a 10,000 claim fee — a guaranteed loss, so abandon/reclaim is never net-positive. Source: abandonment_service.py:39-49. ✅ Shipped.

Gameserver shipped — voluntary ownership transfer (5% fee, recipient accept) is live via planet_ownership_transfer_service.py + planets ownership-transfer routes (PR #658 MERGED → a8d1f953). 🚧 Player-client UI tip-pending — no OwnershipTransferControl on origin/feat 46bce720; in-flight branch wo/LEG-514-ownership-transfer @ ec553ad0 (not tip-land). The 5% flow is not player-reachable (PC INI-44/#507, LEG-514/#563). The tax-rate skim is ✅ Shipped on stockpile withdraw (see Tax rate above). Abandonment, reclamation, and the five-mode landing-rights ACL are shipped.

Player-facing affordances

Verified vs Sectorwars2102 origin/feat 46bce720.

  • 🚧 Planet management UI: phase, population, growth rate, allocation sliders, and storage levels on the landed colony console (CockpitColonyManagement / ColonistAllocator / ProductionPanel — see production.md). A canon 0–100 defense rating is not live (qualitative labels on DefenseConfiguration only — defense.md).
  • 🚧 Voluntary ownership transfer (5% fee, recipient accept): gameserver routes are ✅ Shipped; player UI is 🚧 tip-pending on wo/LEG-514-ownership-transfer @ ec553ad0 (see Ownership controls above).
  • 🚧 Resource collection: ProductionPanel stockpile readouts + Store→Safe are shipped. A dedicated panel that transfers stockpile to docked ship cargo is not claimed live on tip 46bce720.
  • ✅ Citadel upgrade timer with construction countdown (CitadelManager.tsx upgrading-countdown / construction progress — see citadels.md).
  • 📐 Map overlay showing all your owned planets across regions — Design-only. ColoniesRosterTab is a read-only name/sector/pop roster ("Travel there to manage."), not a map overlay.
  • 📐 Income feed showing passive credit/resource gains — Design-only.
  • 📐 Population graphs — Design-only. 🚧 "Days until full" is computed in GameDashboard.tsx but not rendered (same Partial as production.md § Player-facing affordances); there is no days-until-citadel-cap projection on tip 46bce720.

Colonist profession system

📐 Design-only. A profession layer sits over the three-bucket allocation model: generic colonists are trained (irreversibly) into twelve specialized roles (Engineering, Scientific, Military, Economic) that stack percentage bonuses on top of base production, defense, research, and terraforming. Specialization cap scales with citadel phase from 0% at Outpost to 75% at Planetary Capital, and training requires citadel L3+. No code path exists today — Planet has no profession column and there is no colonist_professions table.

Full mechanics, the per-profession bonus table, training and retraining flow, cross-system effects, and target source-file map live in professions.md.

Source map

Topic Path
Planetary service services/gameserver/src/services/planetary_service.py
Growth + production tick services/gameserver/src/services/planetary_service.py:_calculate_production_rates
Habitability effects helper services/gameserver/src/services/planetary_service.py:get_habitability_effects
Planet model services/gameserver/src/models/planet.py
Player ↔ planet association services/gameserver/src/models/planet.py:player_planets
Planet API routes services/gameserver/src/api/routes/planets.py
Ownership transfer services/gameserver/src/services/planet_ownership_transfer_service.py
Admin colonization tools services/gameserver/src/api/routes/admin_colonization.py
Genesis-based creation services/gameserver/src/services/genesis_service.py
Citadel upgrades services/gameserver/src/services/citadel_service.py
Terraforming services/gameserver/src/services/terraforming_service.py
Population commodity pricing services/gameserver/src/services/trading_service.py (COMMODITY_PRICE_RANGES["colonists"], calculate_dynamic_price)