Skip to content

Ships

Ship lifecycle from acquisition through maintenance, upgrades, insurance, and destruction. Detailed type-by-type stats live in ./ship-roster.md.

Acquisition

Method Notes
First-login claim New players get one of 4–6 starter ships based on persuasion outcome. See first-login.md.
Shipyard purchase Most ships available for credits at any port with shipyard facilities.
Salvage Recovered from abandoned ships in contested sectors.
Faction reward Six unique ships, one per faction, awarded at the Exalted reputation tier. See Faction reward ships below — 📐 Design-only, none in code yet.
Crafting (TradeDock) Player-built ships via the shipyard system. See economy/trading.md.
Genesis side-effect Advanced Genesis sacrifices a Colony Ship — that's a destruction path, not acquisition.

The only craft-only ship is the Warp Jumper: 1,000,000 cr all-in, 14-day TradeDock slip rental (see ../economy/tradedock-shipyard.md), requires Quantum Shards + Lumen Crystals among other materials, limit 1 per player. 📐 Design-only — the Warp Jumper hull is consumed at Phase 3 of warp gate creation (destination focus anchor); the pilot ejects to escape pod in the destination sector. The Warp Jumper is non-insurable — the 1,000,000 cr is the gate-builder's risk capital, not a hedged premium (see ./ship-insurance.md#non-insurable-ships). See ../galaxy/warp-gates.md for the full sequence.

The Warp Jumper carries two complementary FTL primitives — Quantum Jump is the primary tool for discovering lost formations (Lost Sectors, Lost Clusters, and Archipelagos sitting off the warp graph; see ../galaxy/special-formations.md#lost-worlds-quantum-jump-only-content and ADR-0070), and the Slipdrive (renamed from TransWarp) handles stranded recovery from WARP_SINK traps and other one-way dead-ends. The two are cross-referenced from ./movement.md and the Quantum Jump spec in ../galaxy/sectors.md#quantum-jump-warp-jumper.

Faction reward ships

📐 Design-only. Six unique ships, one per faction, available exclusively to players who reach Exalted reputation with that faction. None exist in code today — these are the design target for the faction-progression Exalted tier. Stats / class / cost are anchored to the faction's identity (combat / trade / colonization / mining / science / stealth) and need a balance pass before implementation.

Faction Reward ship Role
Terran Federation Federation Battlecruiser Combat-focused; heavy armor; military-grade weapons and shields. Built around frontline engagement and faction-zone enforcement.
Mercantile Guild Guild Freighter Massive cargo capacity; passive trade bonuses; merchant-grade sensors and route optimization. Premium freighter for deep-haul commerce.
Frontier Coalition Colony Ark Improved colonization (faster Genesis sequence, larger initial colonist payload); doubles as a mobile base.
Astral Mining Consortium Mining Barge Enhanced asteroid mining (industrial mining laser bay); high cargo for ore haul; extended asteroid-field operations.
Nova Scientific Institute Research Vessel Quantum harvester bonuses; advanced sensor suite; nebula-field optimization for shard gathering.
Fringe Alliance Shadow Runner Stealth systems; smuggling compartments; contraband scanners and false transponders. The smuggler's premium ride.

Each ship is restricted at the model level: ShipSpecification.faction_requirements for these vessels gates on the corresponding faction reaching Exalted. They cannot be purchased pre-fab, salvaged, or crafted — the only acquisition path is claim from the faction once Exalted reputation unlocks. Loss in combat does not erase eligibility — the player can claim again, but the ship itself isn't returned.

Stats are TBD pending balance pass; the design rule of thumb is "premium variant of an existing ship class" (e.g., Federation Battlecruiser ≈ Defender + 30% combat modifiers; Guild Freighter ≈ Cargo Hauler + 50% capacity; Mining Barge ≈ Cargo Hauler + mining bay; etc.). Specific numbers will land alongside the faction-progression spec.

Ownership and piloting

A player can own any number of ships (each ship has a registered_owner_id per the Ship Registry) but can only pilot one at a time. The ship the player is currently flying is pointed to by Player.current_ship_id; on the ship side, Ship.current_pilot_id == player.id.

To change which ship you're piloting, you eject from the one you're in (it becomes Drifting in your sector) and board another (you become its pilot). At a port this is friction-free — 0 turns total — so a player can quickly hop between ships they own that are docked at the same port. In space, eject + board costs 1 turn (one for the act of switching) — discourages combat-loadout-cycling while preserving the smooth "I'm at port and I want my mining ship today" experience. See SYSTEMS/ship-registry.md for the full eject + board flow.

Ships destroyed in combat go to ShipStatus.DESTROYED; the player auto-transfers to their escape pod.

Code: models/player.py, models/ship.py, services/ship_service.py.

Maintenance system

Implemented in models/ship.py:maintenance JSONB and services/ship_service.py:

  • Each ship has a Maintenance Rating 0–100% (starts 100% on purchase).
  • Decay rate per real day varies by ship class:
Ship Decay/day
Light Freighter, Fast Courier, Scout Ship −1%
Cargo Hauler, Colony Ship, Defender −2%
Carrier, Warp Jumper −3%

Performance bands

Rating Speed Combat effectiveness Fuel use Failure risk
90–100% +5% +5% −5% 0
75–89% 0 0 0 0
50–74% −5% −5% +5% 0
25–49% −15% −20% +20% 5%/jump minor
10–24% −30% −40% +50% 15%/jump major
0–9% −50% −75% +100% 30%/jump catastrophic

Failure types (models/ship.py:FailureType): MINOR / MAJOR / CATASTROPHIC. - Minor → temporary system loss (sensors, etc.). - Major → ship immobilized until repaired. - Catastrophic → 20% destruction chance, otherwise hull dropped to ~1%.

Repair options

  • Basic (any shipyard) — 5% of ship value per +10% rating, 6h.
  • Emergency (any shipyard, even mid-failure) — 10% of ship value per +10%, 2h.
  • Premium (Class I / Military) — 15% of ship value per +10%, 1h, +2% temporary speed/combat for 48h.
  • Self-repair — Maintenance Kit (5,000 cr, 1 cargo). Restores up to 25% rating per kit; 12h game time; 15% chance of error reducing to 15% effectiveness.

🚧 Partial — ✅ live (2026-06-14, see FINDINGS.md): lazy condition decay by hull class (advance-on-read, maintenance_service), the performance-band combat-effectiveness penalty consumed in combat, tiered shipyard servicing (GET/POST /ships/{id}/maintenance, basic/emergency/premium per the cost table; premium gated to a SpaceDock as a Class-I/Military approximation), and the speed band modifier biting in the move-cost path (movement penalty ✅ Shipped). 📐 Still design-stage: the fuel band modifier, the per-jump failure roll (Minor/Major/Catastrophic — the catastrophic path touches the destruction handler), repair timers, and the self-repair Maintenance Kit. NB: ShipSpecification.maintenance_rate (seeded 0.05–0.30) is unused and does not match the canon %/day decay table — the decay uses the canon table by hull class.

Upgrades

Eight upgrade types (models/ship.py:UpgradeType); detailed upgrade and equipment loadouts in ship-systems.md:

Type Effect (max)
ENGINE +0.5 sectors/turn speed
CARGO_HOLD +30% storage
SHIELD +200 shield points
HULL +300 hull points
SENSOR +15% encounter avoidance
DRONE_BAY +2 bays
GENESIS_CONTAINMENT +2 Genesis capacity (only for Genesis-compatible ships)
MAINTENANCE_SYSTEM Auto-deploys maintenance kits when rating < 65%

Upgrades stored in Ship.upgrades JSONB. Purchased at shipyards; price varies by sector tech level. Upgrades are 0-turn actions.

Service: services/ship_upgrade_service.py.

Insurance

One-time purchase at a friendly port, attached to the hull for its lifetime; payout is immediate on destruction. Tiers (BASIC / STANDARD / PREMIUM), premium pricing, deductible math, what is and isn't covered, the payout flow, and worked examples live in ./ship-insurance.md.

Destruction & escape pod

When ship.hull <= 0:

  1. Ship status set to DESTROYED.
  2. Player auto-ejected to escape pod (Player.current_ship_id updated).
  3. Cargo scatters into the sector as a salvageable Cargo Wreck (see Cargo Wreck below). Credits retained (they live on Player, not the ship).
  4. Insurance payout calculated and credited if applicable. Insurance covers ship value only — not cargo or installed upgrades.
  5. Combat log entry written.
  6. Personal-reputation hooks fire (see combat.md).

Escape pods: - Truly indestructible (per ADR-0061 S-V1). The combat resolver rejects escape pods as valid targets at the validation step — pods cannot be attacked, period. Hull (200) and shield (150) values on EscapePod are flavor-only and not consulted by combat math. The kick-them-while-they're-down attack vector is closed at the validator. - Movement turn cost is much higher than any standard ship. - Can dock with a teammate's ship for free transport, subject to three preconditions: (a) both players are in the same sector; (b) the host ship has at least 1 free cargo space (the pod consumes 1 cargo unit while docked, like a colonist); (c) while embarked, the pod player consumes none of their own turns during the host's movement — the host pays movement turns as normal.

Carrier hangar cascade (per ADR-0061 S-I2). When a Carrier with at least one docked escape pod is destroyed, the destruction handler auto-ejects each docked pod to the sector at the Carrier's last position. The pod retains its state — turns, inventory, pilot identity — and arrives in drifting posture at the Carrier's sector. Realtime event escape_pod.auto_ejected fires per docked pod with host_carrier_id + sector_id payload. The Carrier blew up around the pilots; they survived.

Manual eject and the ship registry

When a player manually ejects from an intact ship, the ship is not destroyed — it sits in its sector as a Drifting ship and remains the property of the registered owner. Possession is separate from ownership; six distinct events move a ship through the registry:

Event Pilot changes? Ownership changes? Triggered by
Drift Owner ejects → no pilot No Owner manual-eject from intact ship
Borrow Stranger boards → new pilot No Boarder enters with the pin (or after a salvage break)
Trade Buyer takes possession Yes (sale) Owner + buyer at the same port; price agreed; credits transfer
Abandon NULL → first taker Yes (relinquished, no fee) Owner explicit "abandon" action at a port
Salvage Claimant takes possession Yes (contested transfer) Claimant pays 30% transfer fee at port; 24h owner-dispute window
Steal Pilot != owner; owner reports stolen No (still legally owner's) Owner files stolen report on a Borrowed ship

The full mechanics — including the hatch pin lock, salvage break (force entry over 1h–12h depending on ship class), trading, abandonment, and stolen report → Wanted Status flows — live in ../../SYSTEMS/ship-registry.md. See ranking.md → Wanted Status for the criminal-pilot effects. The full registry-overhaul design rationale lives in ADR-0008.

Cargo Wreck

📐 Design-only. The Cargo Wreck mechanic is canonical Launch design. Today's services/ship_service.py:_transfer_emergency_cargo (which proportionally transferred 10% of cargo to the escape pod) is to be replaced by Cargo Wreck creation; that legacy path should be removed in the same code change.

When a ship is destroyed in combat, a CargoWreck row is created in the host sector containing a damage-type-modulated, randomly-rolled fraction of the destroyed ship's cargo. The wreck is salvageable by anyone with cargo space. Full design rationale in ADR-0007.

Wreck-creation matrix per destruction_cause (per ADR-0052 SK36):

destruction_cause Wreck spawned? Recovery damage band Rationale
COMBAT Yes per killing weapon's damage type (per ADR-0007) Canonical case.
HAZARD (black hole, radiation zone, warp storm) Yes kinetic band (50–70%) Environmental destruction; cargo plausibly survives in scattered debris.
SELF_DESTRUCT No n/a Intentional cargo denial. Self-destruct is the player's deliberate "deny enemies the cargo" act; spawning a wreck would defeat the mechanic.
ABANDONMENT_EXPIRED Yes kinetic band (50–70%) A ship un-claimed for 7 days past abandonment per ADR-0008; cargo preserved by inactivity.
WARP_GATE_ANCHOR No n/a Hull consumed as gate focus per ADR-0029 and ADR-0036. The hull is a planned structural dismantle, not an explosion.

The destruction handler short-circuits wreck creation for WARP_GATE_ANCHOR and SELF_DESTRUCT; all other causes proceed through the standard wreck-creation flow with the appropriate recovery damage band.

Destruction-handler sequencing

The destruction path lives in combat_service._handle_ship_destruction(ship, killing_damage_type, destruction_cause). The legacy ship_service._transfer_emergency_cargo is removed in the same change — the two paths cannot coexist (cargo can't both transfer to the escape pod and scatter into a wreck). The canonical sequence:

def _handle_ship_destruction(ship, killing_damage_type, destruction_cause):
    # 1. Mark ship destroyed (status, registry, audit log)
    ship.status = DESTROYED
    ship.destruction_cause = destruction_cause
    ship.destroyed_at = utcnow()

    # 2. Auto-eject pilot to escape pod (preserves Player.current_ship_id continuity)
    if ship.pilot_id:
        spawn_escape_pod(ship.pilot_id, ship.current_sector_id)

    # 3. Wreck-suppression check — short-circuit before any cargo handling
    #    (per ADR-0052 SK36 matrix)
    if destruction_cause == WARP_GATE_ANCHOR:
        # Hull is consumed as gate-focus structure. Cargo on the WJ at Phase 3
        # is documented as zero by ADR-0029 (the WJ is non-cargo by design at
        # gate-build commit). If non-zero is observed, log a structural-invariant
        # warning and discard — do NOT spawn a wreck.
        if ship.cargo_total_units > 0:
            log.warn("WARP_GATE_ANCHOR with non-zero cargo", ship.id, ship.cargo)
        return  # no wreck, no insurance, ship.destruction_cause already set
    if destruction_cause == SELF_DESTRUCT:
        # Intentional cargo denial. The pilot triggered the scuttle; cargo
        # destruction is the point of the mechanic. No wreck. Insurance still
        # fires (the ship is destroyed) — see SK37 for stolen-ship variation.
        pass  # fall through to insurance below; skip wreck creation

    # 4. Insurance payout (covers ship value only; cargo is wreck-bound)
    insurance_payout(ship)

    # 5. Cargo Wreck creation (replaces legacy _transfer_emergency_cargo)
    #    Skipped if destruction_cause is SELF_DESTRUCT (returned above).
    #    For HAZARD and ABANDONMENT_EXPIRED, treat as `kinetic` damage band.
    band_damage_type = killing_damage_type if destruction_cause == COMBAT else 'kinetic'
    wreck_cargo = roll_recovery_per_commodity(ship.cargo, band_damage_type)
    if wreck_cargo:  # at least one commodity rolled > 0
        cargo_wreck_service.create(
            sector_id = ship.current_sector_id,
            original_owner_id = ship.owner_id,
            original_team_id = ship.owner.team_id,
            killing_blow_pilot_id = killing_blow_pilot_id,  # per ADR-0055 S-F2
            destroyed_ship_type = ship.ship_type,
            cause = wreck_cause_from(destruction_cause),
            cargo = wreck_cargo,
        )

    # 6. Bounty collection (per ADR-0055 X-D2 + ADR-0054 X-V2)
    #    Pay out any active BountyClaim rows targeting the destroyed ship's
    #    pilot. SELECT FOR UPDATE inside this same tx; killing_blow_pilot_id
    #    becomes collector_player_id. Same-team collusion check (S-F1)
    #    rejects collection if collector and placer share a team at this
    #    instant — those rows are skipped (escrow stays held; the placer
    #    can refile later or retract).
    bounty_service.collect_for_destruction(
        target_player_id = ship.current_pilot_id,
        collector_player_id = killing_blow_pilot_id,
    )

    # 7. Stolen-report resolution (per ADR-0055 X-D2)
    if ship.stolen_status:
        stolen_report_service.resolve(ship.id, cause='destroyed')

    # 8. Kill-log insert (PirateKillLog if pirate; combat audit row otherwise)
    kill_log_service.record(ship, killing_blow_pilot_id, killing_damage_type)

    # 9. Combat log + reputation hooks
    write_combat_log(ship, killing_damage_type)
    apply_destruction_rep_hooks(ship)

Atomicity (per ADR-0055 X-D2): all nine steps share a single DB transaction. Realtime events (ship.destroyed, bounty.collected, wreck.spawned, stolen_report.resolved) are queued via the transactional outbox per ADR-0054 and flushed post-commit, never broadcast mid-flight. Concurrent attackers are serialized upstream by the combat resolver's per-target tick aggregation, which picks killing_blow_pilot_id before this handler is invoked — there is no second-collector race.

Legacy-code co-landing — removal sequence. The single PR that introduces CargoWreck must also remove _transfer_emergency_cargo and its callers, or the codebase momentarily double-grants cargo:

  1. Land the cargo_wrecks table migration first (forward-only).
  2. In the same PR, add cargo_wreck_service.create() and the wreck-creation call inside _handle_ship_destruction.
  3. Remove ship_service._transfer_emergency_cargo (the function body) and its single call site inside combat_service.
  4. Remove any escape-pod cargo-receiving logic that exists to receive the 10% transfer (escape_pod_service.accept_emergency_cargo if it exists; verify against the live tree).
  5. Delete the test fixture that exercised the 10% transfer path; replace with a wreck-creation assertion.
  6. Audit Ship.destruction_cause enum: ensure WARP_GATE_ANCHOR exists (per ADR-0029); add it if missing in the same migration.

The order matters because _transfer_emergency_cargo and wreck creation are both invoked from the destruction-handler — leaving both paths active means cargo gets duplicated.

Recovery fraction

Each commodity in the destroyed ship's cargo is independently rolled within the band determined by the killing weapon's damage type. The band represents how much of that commodity survived the explosion's heat / shock / radiation; the actual percentage is uniform(band_min, band_max).

Killing damage type Recovery band per commodity Flavor
emp 80–100% Cargo intact; ship systems fried
laser 60–80% Precise hits; partial damage to holds
kinetic (autocannon, future) 50–70% Mid-tier; impact damage
plasma 40–60% High heat; substantial vaporization
missile 20–40% Explosive; mostly destroyed

Example: a missile-killed Cargo Hauler with {ore: 1000, organics: 500, exotic_tech: 100} might leave a wreck with {ore: 280, organics: 140, exotic_tech: 28} — each rolled independently in the 20–40% band.

Wreck visibility & lifecycle

  • Visible to every player currently in the sector (sector-presence broadcast).
  • Visible from adjacent sectors (1 warp-tunnel hop) only when the observing ship's scanner_range ≥ 5 — Scout (5) and Warp Jumper (8) qualify; Carrier (4), Defender (3), Fast Courier (3), Cargo Hauler / Light Freighter / Colony Ship (2) do not. "Adjacent" follows the warp-tunnel graph, not Euclidean distance — a wreck two sectors removed by tunnel hop is invisible regardless of scanner range, and a wreck "physically near" by 3D coordinates but not warp-connected is similarly invisible.
  • Persists indefinitely until salvaged. No decay timer; the wreck stays as long as it has cargo.
  • Wrecks display original-owner identity, destroyed-ship type, damage type, age, and remaining cargo manifest in the sector view.

Salvage mechanics

Approach the wreck → choose commodities to salvage → confirm. Salvage is time-cost gated: 1 turn per 100 cargo units retrieved (rounded up). A 1,000-unit wreck takes ~10 turns to fully clean. The salvager's available cargo space caps how much can be taken in one pass.

The turn-cost is intentional: it makes contested wrecks meaningful PvP moments, since salvaging is interruptible by combat. Pirates can ambush salvagers mid-recovery.

Ownership grace period — the suspect flag

For the first 1 hour after a wreck is created, three parties can salvage without consequence:

  • the original owner of the destroyed ship,
  • members of the original owner's team at destruction time, and
  • the killing-blow pilot (the individual whose hit destroyed the ship — per ADR-0055 S-F2). The killer's team is not extended grace by this rule, only the individual pilot.

Any other player who salvages during the 1-hour grace window is hit with two consequences:

  • Temporary Suspect StatusPlayer.suspect_status = True, Player.suspect_until = now + 1 hour, Player.suspect_team_snapshot = team_member_ids_at_acquisition. Multiple early-salvage events extend the timer (capped at 4 hours total) but do not refresh the team snapshot — the snapshot is taken once at first acquisition. Effects:
  • Name color shifts to gray/yellow in all UI surfaces.
  • Federation-zone immunity is suspended — anyone can attack the flagged player in fed-space without normal policing penalties; the flagged player can fight back without fed-space combat-cost penalties.
  • Team-mate exemptions during the grace window evaluate against suspect_team_snapshot, not live team membership (per ADR-0061 S-V4). A player joining the suspect's team mid-grace is not added to the snapshot and does not gain the exemption; a player leaving stays exempt for the remainder of the window. Closes the mid-grace-join exploit symmetric to the pirate-holding raid lock per ADR-0060 G-F2.
  • Auto-clears at suspect_untilsuspect_team_snapshot clears at the same time.
  • Permanent personal-reputation hit−25 to Player.personal_reputation per early-salvage event. Repeat offenders slowly slide toward Suspicious / Outlaw / Criminal tiers (see ranking.md → Personal Reputation).

After the 1-hour grace period (wreck.created_at < now − 1h), salvage is free and clear — no flag, no reputation hit. The wreck is fair game for anyone.

Edge cases

  • Owner returns to their own wreck: no flag, no reputation hit, no time-cost preferential treatment — they salvage at the same rate as anyone else.
  • Self-destruction in fed-space: same wreck mechanic; same grace period and flag rules.
  • Hazard / environmental destruction (e.g., black hole, radiation zone): no original-owner attacker, but the wreck still has the original ship owner. Grace period and team exemption still apply.
  • Multiple destructions in same sector: each destroyed ship creates its own wreck. Wrecks don't merge; salvagers approach them individually.
  • Stolen ship destruction: the wreck's original_owner_id is the player who owned the ship at destruction, not the player who originally built / purchased it. (Stolen-ship reclamation is a separate concept.)

Cargo & special equipment

Ship.cargo JSONB tracks per-commodity quantities. Hard cap = ship type's cargo_capacity modified by upgrades and the Cargo Hauler's +15% bonus.

Special equipment slots (Ship.equipment_slots JSONB): - Quantum Field Harvester — 50,000 cr, only on Scout / Fast Courier / Defender / Warp Jumper. - Cloaking — has_cloaking boolean. - Genesis containment — genesis_devices count vs max_genesis_devices. - Mines — mines count vs max_mines. - Auto-maintenance — has_automated_maintenance. - Tractor Beam — 40,000 cr, only on Cargo Hauler / Defender / Carrier / Warp Jumper. Dual-use weapon + tow rig — see Tractor Beam tow operations below and the weapon-mode entry in combat.md. ✅ Shipped — the Warp Jumper's own Tractor is the only Tractor that operates through Quantum Jump (size cap medium max, one ship per jump, +5 turns flat surcharge); see ../galaxy/sectors.md#quantum-jump-warp-jumper for the QJ mechanic.

Ship size axis

Shipped. Every ship spec carries a ship_size enum on ShipSpecification. The size axis is consumed by two systems: the Carrier's ship-hangar capacity (below) and the Tractor Beam tow per-move turn surcharge.

Size Ships Hangar units Tractor tow surcharge (per move)
tiny Escape Pod 1 +1 turn
small Scout Ship, Fast Courier 2 +2 turns
medium Light Freighter, Defender 4 +3 turns
large Cargo Hauler, Colony Ship, Warp Jumper 8 +5 turns
capital Carrier — (cannot be docked or towed) — (not towable)

The Carrier is the only capital-size ship at launch; it cannot be hangared by another Carrier and cannot be tractor-towed (its mass exceeds the equipment's structural rating). All other ships are tow-eligible by mass; whether a tow is actually allowed at runtime is a consent flow on the towed pilot's side.

Carrier hangar

Shipped. The Carrier's existing 12-drone bay (combat-class drones) is preserved unchanged; the ship-hangar is a separate capacity unique to the Carrier that holds entire player ships in transit.

  • Capacity: 8 hangar size-units, allocated per the size axis above (so the same Carrier hangar can hold 1 large ship like a Warp Jumper, OR 2 medium ships, OR 4 small ships, OR 8 tiny ships, OR any size-unit-summed combination up to 8).
  • Storage shape: Ship.hangar JSONB on the Carrier — see ../../DATA_MODELS/ships.md#carrier-ship-hangar for the field shape.
  • Dock: the docking ship and the Carrier must be in the same sector. The Carrier captain accepts a dock request from the docking pilot (consent flow). 1 turn for the docking ship; the Carrier pays 0 turns. Once docked, the docking ship is inert in transit — its pilot becomes a passenger.
  • Undock: the docked pilot pays 1 turn to undock and resume control of their ship in the Carrier's current sector. No Carrier consent needed for undock (passengers can always disembark).
  • Carrier travel with hangar load: the Carrier traverses warp tunnels, player warp gates, and (once the destination's gate is built) cross-region the way it normally does. The hangared ships ride along; their pilots pay 0 turns for the Carrier's movement.
  • Combat: docked ships are inert. They cannot attack; they cannot be individually targeted. Damage to the Carrier does not damage docked ships. If the Carrier hull goes to 0, all docked ships are jettisoned intact into the destruction sector as Drifting (per the Ship Registry); the docked pilots auto-eject to Escape Pods. The Carrier itself spawns a Cargo Wreck per the standard destruction flow, but the docked ships are not destroyed and do not spawn wrecks — their cargo and insurance are unaffected. Dock initiation is blocked while either ship is IN_COMBAT (no mid-fight escapes via hangar).
  • Capacity vs drone bay: the 8-unit ship-hangar is a separate capacity from the Carrier's 12-drone bay. They share no underlying budget — a fully-loaded hangar does not reduce drone capacity, and a full drone bay does not reduce hangar capacity.
  • Port docking: a Carrier with hangar load can dock at any station as normal. Docked passengers stay aboard but may disembark to the port at 0 turns (a port-side variant of the standard 1-turn undock). Port services (repair, refuel, trade, upgrades) apply only to ships actually docked at the port — passengers must disembark to use them.
  • Tractor + Hangar interaction: a tractor-towed ship cannot dock to a Carrier mid-tow; the towing operation must be detached first.

The intended use case is logistics — moving a Warp Jumper to a destination region for warp-gate construction, or ferrying a teammate's specialised ship through a hostile sector. The 8-unit cap means the Carrier-as-transport is a planned operation, not a fleet-mover; combat fleets continue to coordinate via fleet-tactics.md.

Tractor Beam tow operations

📐 Design-only. The Tractor Beam equipment slot enables ship-towing (in addition to its planned weapon-mode role — see combat.md → Weapons).

  • Equipped on: Cargo Hauler / Defender / Carrier / Warp Jumper (40,000 cr install cost).
  • Lock-on: hauler and target ship in the same sector. The tow request fires to the target pilot, who must accept (consent flow).
  • Engaged: hauler pays full movement turn cost plus the size-axis surcharge (see the table above). Towed pilot pays 0 turns and is along for the ride.
  • Travel restrictions:
  • Natural warp tunnels: allowed at the modified turn cost.
  • Player warp gates: allowed at +2 turns flat surcharge to the gate transit (regardless of towed size). Gates are stable two-way bridges; one tow per transit.
  • Quantum Jump: allowed only when the hauler is a Warp Jumper using its own Tractor Beam. Other ships' Tractors do not transit QJ. Constraints: towed size cap medium max (size_units ≤ 4 — tiny / small / medium eligible; large / capital excluded), one towed ship per jump, and a +5 turns flat surcharge to the QJ commit cost (size-based scaling does not apply for QJ — the quantum link carries the load). The towed pilot stays in their own ship (not a Carrier-hangar passenger) and sees the same resonance-band scan reading as the WJ pilot during the bearing/scan phase.
  • Detach: the hauler or the towed pilot can break the tow at any sector for 0 turns. Tow lock-on requires both ships to be in IN_SPACE state (not IN_COMBAT); detach is unrestricted — including from IN_COMBAT state. Detach takes priority over combat lock (per ADR-0067 S-F3): the towed pilot can break the tow at any moment, including while in combat; the attacker's combat lock does not pin the towed ship to the tow. The hauler can also break the tow from IN_COMBAT. Combat events do not auto-break a tow — only destruction does (per the Combat sub-section below). Attackers cannot directly break a tow they aren't part of; only the hauler pilot or the towed pilot can detach voluntarily. A Warp Jumper pilot can abort an in-progress QJ pre-commit; the tow remains intact and both ships stay at the source sector.
  • Combat:
  • The hauler+towed pair appears in sector presence as a single combined contact.
  • If the hauler is destroyed mid-tow (combat, including a Warp Jumper destroyed mid-QJ Phase 2), the tow auto-detaches; the towed ship enters Drifting state in the destruction sector. The towed pilot stays aboard the now-Drifting towed ship — no auto-eject — because the towed ship is intact (no Cargo Wreck, no insurance payout, cargo preserved). The pilot can fly out under their own power once they regain control. This matches the Warp Jumper Phase 3 sacrifice case below: if the towed ship survives intact, the towed pilot remains aboard it.
  • If the towed ship is destroyed mid-tow (e.g., taken out by NPCs in the destination sector), the tow auto-detaches; the hauler continues at base turn cost; standard Cargo Wreck mechanics apply at the destruction sector.
  • Warp Jumper Phase 3 anchor sacrifice while towing: the tow auto-detaches at Phase 3 hull consumption; the towed ship arrives intact in the destination sector with the new gate available. The WJ pilot ejects to escape pod in the destination sector per the gate-creation flow.
  • The hauler's Tractor Beam slot is mutually exclusive with weapon-mode firing while a tow is active — a hauler cannot tow and tractor-attack simultaneously.
  • Pilot disconnect: if either pilot's session disconnects, the tow auto-detaches at the next sector boundary; both ships sit Drifting at that sector if the disconnect persists past the move.
  • Consent expiry: tow requests sent to a target pilot expire 60 seconds after issuance if not accepted. Carriers and haulers can pre-authorise team-mates (📐 future setting on TeamMember.permissions).
  • Capital exclusion: a capital-size ship (Carrier) cannot be tractor-towed — its mass exceeds the equipment's structural rating.
  • No nesting: a hauler that is itself being towed cannot tow a third ship.
  • Hangar exclusion: a ship currently docked inside a Carrier hangar cannot be the source or target of a tow lock-on.

Player-facing affordances

  • Ship dashboard shows hull / shields / fuel / cargo bars.
  • Maintenance warnings at 75% / 50% / 25% / 10% thresholds.
  • Upgrade interface at shipyards with before/after stat preview.
  • Insurance purchase UI at friendly ports.
  • Quick eject-and-board between owned ships when docked at the same port (0 turns).

Source map

Topic Path
Ship model + enums services/gameserver/src/models/ship.py
Ship specifications seed services/gameserver/src/core/ship_specifications_seeder.py
Ship service services/gameserver/src/services/ship_service.py
Upgrade service services/gameserver/src/services/ship_upgrade_service.py
API routes services/gameserver/src/api/routes/ship_upgrades.py, admin_ships.py
Movement (turn cost lookup) services/gameserver/src/services/movement_service.py
Combat (destruction handling) services/gameserver/src/services/combat_service.py:_handle_ship_destruction