Skip to content

MigrationContract — Schema

Status: ✅ Shipped — the pioneer migration-contract ledger is live: broker a cohort at a population hub's Pioneer Office, ferry pioneers into ship cargo in batches, settle them on frontier worlds. Distinct from the general trade-contract system (see ../FEATURES/economy/contracts.md) and from the planet-surplus pioneer sale (see ../FEATURES/economy/lifecycle.md §1.4) — this page covers only the tracked-cohort ferry ledger.

Tracks a player's brokered cohort of pioneers from the population-hub Pioneer Office through delivery to a frontier world. Companion docs: ../FEATURES/planets/colonization.md (the narrative flow), ../FEATURES/economy/lifecycle.md (the separate planet-surplus sale faucet).


MigrationContract

Source: services/gameserver/src/models/migration_contract.py; services/gameserver/src/services/pioneer_service.py; services/gameserver/src/api/routes/pioneer.py.

Canon deviation (deliberate): canon places the Pioneer Office at the Capital Sector's Class-0 station (reached by docking); this contract layer instead surfaces it on the population-hub planet (landed). The station's colonist-commodity buy flow is untouched and still works independently.

Purpose: A contract is a tracked cohort — the player brokers N pioneers at a locked per-pioneer fee, ferries them from the hub into ship cargo in cargo-limited batches (loaded), and settles them on frontier worlds over multiple trips (delivered) via the existing claim/disembark flow. The contract ledger watches settlement and advances delivered; it never moves colonists itself. Cargo is fungible (cargo.contents.colonists is a single integer, no per-pod tagging) — settlement is attributed across a player's open contracts FIFO by created_at.

Fields:

name type constraints notes
id UUID PK
player_id UUID FK players.id not null, CASCADE the brokering player
source_planet_id UUID FK planets.id not null, CASCADE the hub planet the contract was brokered at; loading is gated to returning here
source_sector_id Integer not null denormalized global sector id, for "return to {sector}" messaging without a join
cohort_total Integer not null pioneers brokered in this cohort (1–10,000, MAX_COHORT in pioneer.py)
loaded Integer not null, default 0 pioneers currently riding in cargo against this contract (not yet settled)
delivered Integer not null, default 0 pioneers settled at frontier worlds
fee_per_pioneer_locked Integer not null the 30–80 cr per-pioneer fee (COMMODITY_PRICE_RANGES["colonists"]), snapshotted at broker time — later fee-quote changes don't affect an open contract
status Enum migration_contract_status not null, default BROKERED BROKERED / IN_PROGRESS / FULFILLED / VOID
created_at DateTime not null brokered time; FIFO settlement-attribution ordering key
updated_at DateTime not null most recent mutation

Invariant: delivered + loaded <= cohort_total and delivered <= cohort_total.

States: - BROKERED — created, fee locked, nothing loaded yet. - IN_PROGRESS — at least one pioneer loaded into cargo and/or settled. - FULFILLEDdelivered == cohort_total. - VOID — cancelled by the player (only while loaded == 0), or reabsorbed when the carrying ship was destroyed with nothing yet delivered.

Indexes: - (player_id, status) — the "list my active contracts" query and the fulfillment match. - (player_id, source_planet_id) — the load-batch lookup (contract at the hub you're landed on).

Relationships: none declared on the model (player_id / source_planet_id are plain FKs, no ORM relationship()).


Lifecycle

  1. Broker (POST /pioneer/contracts, landed on a population hub) — creates a BROKERED contract with the fee quoted live (pioneer_service.quote_fee, clamped 30–80 cr) and locked onto the row. No credits charged yet.
  2. Load (POST /pioneer/contracts/{id}/load, landed at the same hub the contract was brokered at) — charges fee_per_pioneer_locked × quantity, clamped to whichever is tightest of: pioneers remaining on the cohort, ship's free cargo space, and affordable credits (400 with the specific binding reason — never silently truncated). Moves the contract to IN_PROGRESS on first load.
  3. Settle — happens through the existing claim/disembark flow, not a Pioneer Office endpoint. pioneer_service.attribute_settlement runs whenever colonists settle on a frontier world: it advances delivered on the player's open IN_PROGRESS contracts FIFO by created_at, moving min(remaining, loaded) from loaded to delivered per contract until the settled quantity is exhausted. A contract flips to FULFILLED the moment delivered >= cohort_total. Settled colonists not carried against any contract (e.g. station-bought, or embarked off another owned planet) are simply not attributed to anything.
  4. Cancel (POST /pioneer/contracts/{id}/cancel) — only while loaded == 0 (you cannot void cryosleep pods physically in your hold; settle or disembark them first). The brokerage fee is not refunded — it was a fee, not escrow.
  5. Reabsorb on ship loss (pioneer_service.reabsorb_on_ship_loss, best-effort, wrapped so a ledger hiccup never blocks combat resolution) — cryosleep pods die with the hull (canon). Zeroes loaded on the player's open contracts; VOIDs any that have delivered nothing yet.

Routes (api/routes/pioneer.py, mounted under /pioneer): - GET /pioneer/office — one-round-trip venue payload: live fee quote, ship's colonist cargo + free space, and the player's open contracts. - POST /pioneer/contracts — broker. - POST /pioneer/contracts/{id}/load — load a batch. - GET /pioneer/contracts — list (open by default, include_closed=true for all); reachable from anywhere, not just the hub. - POST /pioneer/contracts/{id}/cancel — void. - POST /pioneer/surplus/sellnot part of this ledger — the separate planet-surplus pioneer sale (docked at a Class-0 station, sells a planet's production-tick-accrued surplus; see lifecycle.md §1.4). Included in this router only because it shares the Pioneer Office venue conceptually.

Not covered here: the planet-surplus pioneer sale's own ledger (Planet.active_events['surplus_pioneers']) — that's a separate faucet with no MigrationContract row involved; see ../FEATURES/economy/lifecycle.md.