Ship Registry¶
Status: 📐 Design-only — Nothing of the Ship Registry exists: no ownership/possession columns on Ship, no ShipRegistry model, no registry service … (impl audit 2026-06-16)
Purpose¶
The Ship Registry is the universal, persistent record of who legally owns each ship in the universe. It separates possession (who's flying the ship right now) from ownership (whose name is on the registration). This separation enables a rich social/legal layer:
- Borrowing — anyone can pilot an abandoned ship without consequence; you don't need permission to climb into a parked vehicle.
- Theft — the registered owner can flip a ship's status to
STOLEN, at which point anyone piloting it is in serious legal trouble. - Salvage — paying a registration-transfer fee at a port legally moves the ship from one owner to another; replaces the old "claim an abandoned ship" model.
- Insurance integrity — payouts always go to the registered owner, regardless of who was flying when the ship went down.
The registry is append-mostly — registrations are never deleted, even after the ship is destroyed. Historical records support investigations, faction reputation calculations, and cross-region travel verification.
Inputs¶
The registry reads (canonical column names per ../DATA_MODELS/ships.md):
- Ship.id (UUID FK to the live ship row).
- Ship.registration_number (player-visible stable string, lives on Ship not ShipRegistry).
- Ship.registered_owner_id (current legal owner; lives on Ship).
- Ship.stolen_status boolean and Ship.stolen_reported_at timestamp (live state on Ship).
- Ship.current_pilot_id (whoever's at the controls right now; can differ from registered owner).
- ShipRegistry.original_owner_id (immutable; first registered owner — recorded once in the initial registry event row).
- ShipRegistry event-log rows (event_type, previous_owner_id, new_owner_id, acting_party_id, transfer_fee_paid, port_id, created_at, metadata) form the append-only audit trail.
The registry fires on:
- Ship purchase / construction — new registry entry created; original_owner_id and registered_owner_id both set to the buyer.
- Manual eject — Ship.current_pilot_id = NULL; ownership unchanged. Ship enters Drifting state.
- Boarding — Ship.current_pilot_id = boarder.id; ownership unchanged. Ship enters Borrowed state if boarder ≠ registered owner.
- Stolen report filed — stolen_status = True, stolen_reported_at = now(). Ship now in Stolen state.
- Stolen report retracted — stolen_status = False, log retraction.
- Registration transfer — multi-step contested transfer flow (see below); on success, registered_owner_id updated, stolen_status cleared.
- Ship destruction — Ship.status = DESTROYED; registry retains the entry as historical record. CargoWreck spawns separately (see ../FEATURES/gameplay/ships.md#cargo-wreck). The Ship.destruction_cause enum (📐 Design-only) records why: COMBAT, HAZARD, SELF_DESTRUCT, ABANDONMENT_EXPIRED, and WARP_GATE_ANCHOR (the Warp Jumper hull consumed at Phase 3 of warp gate creation per ADR-0029 — does not spawn a CargoWreck since the dismantle is planned, not explosive; does not fire an insurance payout because Warp Jumpers are non-insurable per ../FEATURES/gameplay/ship-insurance.md#non-insurable-ships).
Process¶
Registration number format¶
Each ship gets a stable, player-visible registration_number of the form REG-XXXX-YYYY:
XXXX— 4-character alphanumeric block, randomly generated at registration time, uppercase A-Z + 0-9 (excluding ambiguous I/O/0/1).YYYY— 4-digit registration year (game-time, not real-time; matches the in-universe calendar).
Examples: REG-A47B-2103, REG-XQF9-2104.
Registration numbers are immutable — they never change, even when ownership transfers. If you see REG-A47B-2103 flying around, it's the same hull that was first registered in year 2103, regardless of who's currently piloting or owns it.
Six ownership-affecting events¶
Possession (who's flying) and ownership (whose name is on the registration) move through six distinct events:
| 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 | Yes (buyer takes possession) | Yes (sale) | Owner + buyer both at 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 |
These compose freely — e.g., a stranger can salvage-break a Drifting ship → enter (Borrow) → take the ship to a port and Salvage-claim it; or the owner could come back and Borrow it back (since they have the pin), or report-Steal from a port if they prefer.
Ship state machine¶
[purchase or construction]
│
▼
Owner aboard ──── manual eject ────► Drifting (locked) ◄────────┐
▲ ▲ │ │
│ │ │ pin entered │
│ │ │ OR salvage break │
│ │ ▼ │
│ │ Borrowed ◄──────────────────┤
│ └────── owner re-boards ──┤ │ │
│ │ │ owner reports stolen │
│ │ ▼ │
│ │ STOLEN ────────────►──────┤
│ │ │ │
│ trade at port (sale) │ │ retract report │
├──────────────────────────────┘ │ │
│ ▼ │
│ salvage-claim at port (24h) Borrowed/Drifting/Owner ─────┘
├──────────────────────────────────│
│ │ destroyed in combat
│ abandoned at port (giveaway) ▼
├────────────────────────► (registry archived,
│ CargoWreck spawns)
│
▼
Owner aboard (new owner)
State definitions¶
- Owner aboard —
current_pilot_id == registered_owner_id; the registered owner is flying it. - Drifting —
current_pilot_id IS NULL; nobody's flying. The ship sits in its sector, visible to all players. The hatch is locked by the owner's pin; entry requires either knowing the pin or completing a salvage break. - Borrowed —
current_pilot_id != NULLandcurrent_pilot_id != registered_owner_id; someone other than the legal owner is flying. Not a crime by default. The borrower can change the pin (locking the original owner out, but exposing themselves to a Stolen report). - Abandoned —
is_abandoned = True,current_pilot_id IS NULL,registered_owner_idretained for history. Free to claim by anyone at a port; first claimant becomes the new registered owner without paying a transfer fee. Auto-archives if unclaimed for 1 week. - For Sale —
for_sale_price IS NOT NULLANDfor_sale_listed_by_idset; ship is listed at a port for peer-to-peer purchase. Owner can de-list at any time. Buyer at the same port pays the price; transfer is instant. - Stolen —
stolen_status = True; the registered owner has filed a stolen report. Whoever's piloting is now a thief and enters Wanted Status.
Eject and board (the canonical piloting flow)¶
A player can own any number of ships but can only pilot one at a time — the one whose current_pilot_id matches the player's id, also pointed to by Player.current_ship_id. Changing which ship you're flying is always two underlying operations:
POST /api/v1/players/me/eject
current_pilot_id cleared; ship transitions to Drifting in its current sector; Player.current_ship_id cleared (player is briefly in escape-pod state).
POST /api/v1/ships/{ship_id}/board
{ "pin": "...optional, required if not registered owner..." }
Ship.current_pilot_id = player.id; sets Player.current_ship_id = ship.id; ship transitions to Owner-aboard, Borrowed, or Stolen-piloted (the last if the ship has stolen_status = True).
Clients typically chain these as a single "switch ship" UX action — eject + board in one click. There is no separate "swap" endpoint; the registry doesn't need one.
Turn cost for the eject + board pair:
| Where | Turn cost | Reason |
|---|---|---|
| Both ships docked at the same port | 0 turns | Safe, planned switching at a single location. Most common case. |
| Both ships in the same sector but in space | 1 turn | One-turn friction discourages loadout-cycling mid-fight without making routine in-space switches painful. |
| Ships in different sectors | n/a | Cannot eject + board across sectors; you must travel to the target ship first. |
Eligibility for board:
- Registered owner of the target → always eligible (no pin required).
- Pin known by the boarder → eligible regardless of ownership (this is regular Borrow).
- Post-salvage-break target (hatch pin cleared) → eligible to anyone in the sector.
- Wanted-trigger — boarding a stolen_status = True ship while you are not the registered owner immediately enters Wanted Status (see above).
Failed boarding (no pin and ship still locked) returns 403 Forbidden with the boarding-rejected reason.
Hatch pin lock¶
Every ship has a pin code that gates boarding:
Ship.hatch_pin_code— string, 4–8 alphanumeric characters, owner-settable.- Default at registration: a randomly-generated pin is set at ship creation. The owner sees it in their ship panel from minute one. To let a friend or team-mate borrow, the owner shares the string out-of-band (chat, voice — the system doesn't track who knows it).
- Boarding flow: when a non-owner attempts to board a Drifting ship, the client prompts for the pin. Wrong pin → boarding rejected. Correct pin → ship enters Borrowed state.
- Pin changes: the current pilot — owner OR borrower — can change the pin while aboard. A borrower who changes the pin locks the owner out of their own ship; the owner's recourse is to file a stolen report.
- Pin recovery: the registered owner can always reset the pin via a port admin action (1-hour real-time delay before the new pin takes effect; gives any current borrower a window to extract themselves before being locked out).
- No pin tracking: the system does not maintain an allowlist of who knows the pin. If it leaks, the owner changes it.
Salvage break¶
A stranger without the pin can force entry to a Drifting ship via a salvage break — a slow, sector-bound operation that simulates physically cutting through the hatch.
Duration scales with ship class:
| Ship class | Salvage break time |
|---|---|
| Scout, Fast Courier, Light Freighter | 1 hour real-time |
| Cargo Hauler, Defender, Colony Ship | 4 hours |
| Carrier, Warp Jumper | 12 hours |
| Escape Pod | n/a — escape pods cannot be salvage-broken |
Mechanics:
- Salvager dispatches a
POST /api/v1/ships/{ship_id}/salvage-breakrequest while in the same sector as the Drifting target. - Server acquires a row-level lock on the target
Shiprow (SELECT ... FOR UPDATE NOWAITper ADR-0049). Concurrent attempts on the same ship reject immediately withERR_SALVAGE_BREAK_IN_PROGRESScarrying the in-progress salvager's id and ETA — no queue, no silent serialization. - Server records
Ship.salvage_break_in_progress_by_id = salvager.idandsalvage_break_started_at = now(). - Salvager must remain in the sector until the timer completes. Leaving the sector (warp, dock, anything that changes
Player.current_sector_id) cancels the operation and clears the row lock; the timer resets to zero and a new salvager can start a fresh break. - Combat involving the salvager interrupts the operation; the timer resets to zero, lock clears.
- Salvager disconnect: a watchdog at
salvage_break_started_at + salvage_break_durationauto-clears the lock at duration expiry. A separate periodic sweep handles stuck locks where the salvager went idle (defaults to 2× the duration before forced clear). - The original owner returning to the sector does NOT auto-cancel — but the owner can attack the salvager (interrupting via combat) or simply re-board their ship using the pin (instantly aborting the break by occupying the ship).
- On timer completion,
Ship.hatch_pin_codeis cleared (set to NULL); the ship is now unlocked. The salvager — and anyone else in the sector — can board freely. The lock clears. - After boarding, the new pilot can set their own pin (which locks subsequent borrowers out, including the original owner).
The break operation is visible in sector-presence: while in progress, all players in the sector see "salvage break on REG-XXXX-YYYY, ETA: X hours" — making it a contestable event. Other players can interfere with combat or by racing to complete their own break.
📐 Design-only — Ship.salvage_break_in_progress_by_id and salvage_break_started_at are launch-target columns; not in code today.
Reporting a ship stolen¶
The registered owner can file a stolen report at any time via:
POST /api/v1/ships/{ship_id}/report-stolen
Body: { "recovery_mode": "with_bounty" | "no_bounty" }
Recovery mode choice (per ADR-0052 SK37): the owner picks one of two recovery vehicles. The choice is preserved on the ship row and propagates to the destruction handler:
| Mode | Auto-bounty placed? | Insurance fires on destruction? | Trade-off |
|---|---|---|---|
with_bounty (default for insurable = true hulls) |
Yes — 50% of ship value | No | Owner uses the bounty as their chosen resolution. Insurance is forfeit because the owner self-paid for the destruction. Closes the insurance × auto-bounty owner-collusion exploit. |
no_bounty (default for insurable = false hulls) |
No | Yes (when applicable) | No bounty hunter incentive; the ship may stay stolen longer. Insurance fires normally if the ship is later destroyed by external hostiles — moot for non-insurable hulls (no payout exists), but recovery-preferred is the better default since destruction is total loss for those owners. |
The rule: one recovery method per stolen-ship event, not both. Switching modes is permitted by retracting and re-filing (paying the posting fee each time).
Default by ShipSpecification.insurable (per ADR-0055 S-F4): when recovery_mode is omitted from the request body, the default is with_bounty for insurable hulls and no_bounty for non-insurable hulls. The owner can override either way; ARIA narrates the trade-off when the filer is on a non-insurable hull.
Effects:
1. Ship.stolen_status = True, stolen_reported_at = now(), Ship.stolen_recovery_mode = <chosen>.
2. The ship's registration number flags as STOLEN in any registry lookup.
3. The current pilot (if any) immediately enters Wanted Status (see ranking.md).
4. Realtime alert broadcast to nearby NPC patrols (📐 design-only — patrol AI hooks).
5. If recovery_mode = with_bounty: the owner is debited 50% of the ship's last appraised value from their wallet, and that amount is placed as a player-funded bounty on the bounty board (see bounties.md). The auto-stolen-report bounty rides the same player-paid escrow path as a manually placed bounty — it is not system-minted; the owner's credits are the fund. The standard 10% bounty placement fee is waived for auto-stolen-report bounties (the owner has already lost the use of their ship; the registry doesn't double-tax). If the owner has insufficient credits, the report is rejected up-front with ERR_INSUFFICIENT_CREDITS_FOR_AUTO_BOUNTY and the stolen flag is not set — the owner can either earn the credits and re-file, or file with recovery_mode = no_bounty (no bounty placed; insurance preserved on destruction per SK37).
6. Realtime UI: the ship appears in red on the owner's tracker; if it's in a sector the owner has presence in, an alert fires.
The owner can retract the stolen report at any time:
POST /api/v1/ships/{ship_id}/retract-stolen-report
Retracting clears the stolen_status flag and removes the auto-placed bounty. The bounty refund schedule depends on retract timing: retract within 24 hours of report = 75% refund (25% retention covers the bounty-board posting cost, false-report damping, and processing); retract after 24 hours = no refund (the bounty has been on the board long enough that other players may have made decisions based on its presence). No reputation penalty for false reports — the owner is presumed to know who they lent to — but the credits are non-trivial, which discourages frivolous filing.
Anti-collusion lock (per ADR-0049): the retract handler runs an atomic check inside the same transaction as the refund. If any BountyClaim row exists with target_player_id = ship.current_pilot_id (the thief), placed_at >= ship.stolen_reported_at, and collected_at IS NOT NULL, the retract rejects with ERR_BOUNTY_ALREADY_COLLECTED. This closes the file → kill → retract → refund collusion cycle at the most direct point — once a kill has fired the bounty, the report can no longer be retracted. The 24h grace remains for legitimate "false-alarm" cases where no kill happened.
Auto-bounty target (per ADR-0054 X-V2): when a stolen-report fires the auto-bounty, the bounty is placed on the thief's Player.id, not the stolen ship. Schema: BountyClaim.target_player_id (FK Player.id). The bounty stays attached to the thief regardless of what happens to the ship — if the thief destroys the ship to escape consequences, the bounty persists on their player and pays out when they're killed by any hunter. Multiple distinct owners filing stolen reports on the same thief stack bounties on the thief's player, creating cumulative pressure (intentional). Closes the trapped-escrow case where a destroyed ship row would otherwise strand the bounty escrow.
Bounty uniqueness constraint (per ADR-0055 S-V3): partial unique index BountyClaim UNIQUE (placer_player_id, target_player_id) WHERE collected_at IS NULL — one active bounty per (placer, target) pair. The same placer cannot stack multiple bounties on the same target; multiple distinct placers still stack. The "one active stolen-report per ship" half of the original concern is already covered by Ship.stolen_status being a single-valued boolean on the ship row.
Same-team collusion block (per ADR-0055 S-F1). Two rejection rules close the third-party-laundering loop:
- Filing rejected with
ERR_THIEF_IS_TEAM_MATEifship.current_pilot_id(the alleged thief) shares a team with the placer at file-time. Filing against own team is not a real theft. - Bounty collection rejected with
ERR_COLLECTOR_SAME_TEAM_AS_PLACERifcollector_player_idshares a team withplacer_player_idat collection-time. Cross-team bounty hunting still works; same-team laundering is blocked.
Both checks query live team membership (team_members join), not a snapshot — players changing teams to dodge the rule are blocked the moment the gated action fires. The collection-side check, when it rejects, leaves the BountyClaim row untouched (escrow still held); the placer can retract or wait for a non-team-mate kill.
Retract-grace expiry ticker (per ADR-0053 WR10). A periodic service runs every 60 seconds scanning Ship rows where stolen_status = True AND stolen_reported_at + 24h <= now() AND retract_grace_processed = false. For each: sets retract_grace_processed = true. After the flag is set, retract attempts no longer get the 75% refund — they fall through to the standard "no refund after 24 hours" rule. The flag is set-once and never cleared on the same report; a retract + refile produces a new stolen_reported_at and a fresh grace window with retract_grace_processed = false again. New schema: Ship.retract_grace_processed BOOLEAN DEFAULT false.
Wanted Status (pilot of a stolen ship)¶
When a player pilots a ship flagged stolen_status = True, they enter Wanted Status — a stronger flag than Suspect Status. While Wanted:
Player.wanted_status = True,Player.wanted_until = NULL(lasts as long as they're piloting the stolen ship).- Name color renders red in all UI surfaces (overrides personal reputation tier color).
- Federation-zone immunity is suspended — anyone may attack a Wanted player in fed-space without normal policing penalties.
- NPC patrols actively hunt — sector-presence broadcasts trigger NPC pursuit (📐 design-only).
- Automatic bounty placed by the owner (see above) — typically 50% of ship value.
- Personal reputation hit —
−100 / daywhile still in possession of the stolen ship; cumulative. - Port-docking impound — at any port, attempting to dock with a stolen ship results in:
- Ship impounded; returned to registered owner (held at the port for the owner to collect).
- Pilot fined: 25% of their credits (clamped to 100k cr max).
- Pilot personal reputation hit: −100.
- Pilot ejected to escape pod.
- Wanted Status auto-clears the moment the pilot is no longer in the stolen ship (e.g., ejects, ship is destroyed, ship is impounded).
Wanted is mechanically distinct from Suspect Status (which fires from grace-window CargoWreck salvage). They can co-occur — but Wanted's effects dominate.
Legal ownership transfer¶
The new repo's "Salvage" acquisition method is registered as a contested transfer at a port:
POST /api/v1/ports/{port_id}/ship-registry/transfer
{ "ship_id": "...", "claimant_id": "..." }
Flow:
- Eligibility check — the ship must be Drifting (no current pilot) and not flagged stolen, OR Borrowed by the claimant for at least 1 hour. (Boarding doesn't grant transfer rights immediately; you can't board and instantly claim.)
- Fee assessment — claimant pays 30% of the ship's appraised market value (current value, accounting for damage / age / upgrades).
- Original-owner notification — registry pings the registered owner via realtime: "A registration transfer has been requested for
REG-A47B-2103. You have 24 real-time hours to dispute." - Dispute window — during the 24h window:
- Owner takes no action → transfer completes automatically at the deadline.
- Owner files stolen report → transfer is canceled, ship enters Stolen state, claimant is now Wanted (see above).
- Owner explicitly approves → transfer completes immediately.
- On completion —
registered_owner_idupdated to the claimant; the registry logs the transfer with timestamps, fee paid, and the original-owner-notification trail. Insurance from the previous owner voids.
The 24h window is what makes the system work: it gives the original owner a real chance to respond. If they're inactive, the ship transfers — protecting against indefinite squat. If they're active, the system surfaces the issue clearly.
Repair handling. Per legacy "may require repairs" — the ship may be transferred with damage. The new owner pays repair costs at standard rates after taking ownership. The transfer fee covers the registration; repair is separate.
Rare finds. 5% of contested-transfer ships have a small bonus on completion: residual cargo, an installed upgrade not the owner had on record, or a unique cosmetic modifier. 📐 Design-only — RNG table TBD.
Trading (peer-to-peer sale)¶
Two players can transact directly while both docked at the same port:
POST /api/v1/ports/{port_id}/ship-registry/sell
{ "ship_id": "...", "buyer_id": "...", "price": 250000 }
Flow:
- Mutual presence check — both players must be docked at the same port; the seller must be the registered owner; the ship must be in their inventory at the same port (i.e., not Drifting in space). Buyer must have credits ≥ price.
- Buyer confirmation — buyer receives a realtime confirmation request showing ship registration, price, and condition (hull, shields, upgrades, cargo). Buyer accepts or declines.
- On accept — credits transfer (buyer → seller); registry update is instant (no 24h dispute window because both consented); insurance from the previous owner voids.
- On decline / timeout (5 minute response window) — listing expires; no transfer, no fee.
Trading is the consensual cousin of contested salvage transfer: same registry-update mechanism, but mutual agreement bypasses the dispute window. Suitable for negotiated hand-offs to friends, team-mates, or buyers found through external coordination. The full trade protocol — including credits, cargo, and equipment alongside ships — is governed by ADR-0089.
No marketplace listings at launch. Ships cannot be listed publicly for general sale — both seller and buyer must be present at the port simultaneously. Discovery happens through external channels (chat, team coordination, etc.). If marketplace browsing becomes desirable later, it'd be a future ADR.
Abandonment¶
The registered owner can voluntarily relinquish ownership at a port:
POST /api/v1/ports/{port_id}/ship-registry/abandon
{ "ship_id": "..." }
Effects:
- Owner must be docked at the port and the ship must be at the same port (i.e., not in space and not currently Borrowed by someone else).
- Owner is ejected to escape pod; ship enters Abandoned state.
Ship.is_abandoned = True,Ship.abandoned_at = now(),Ship.registered_owner_idretained for history but flagged as no-longer-active.- The ship sits at the port (or in the sector if it was undocked); the first player to dock at the port and claim it becomes the new registered owner — no transfer fee, no dispute window, no contestable transfer. It's a true giveaway.
- 1-week auto-archive: if the ship is unclaimed for 7 real-time days, the registry auto-archives it; the ship row enters DESTROYED status with
cause = ABANDONMENT_EXPIREDand a CargoWreck spawns containing whatever cargo was aboard.
Abandonment is irreversible — once the action is filed, the previous owner cannot un-abandon. They could re-claim the ship like anyone else if it's still unclaimed, but they don't get priority.
📐 Design-only — Ship.is_abandoned and Ship.abandoned_at are launch-target columns.
Insurance interaction¶
Insurance follows the registered owner, not the current pilot:
- Drifting / Borrowed ship destroyed in combat — payout goes to
registered_owner_id, not the borrower-pilot. The borrower gets nothing (and probably faces the registered owner's wrath socially). - Owner aboard ship destroyed — standard payout to owner.
- Stolen ship destroyed — payout still goes to the registered owner. Theft does not void insurance.
- Ownership transfer — insurance voids on transfer. New owner buys fresh coverage.
This makes insurance an asset-tracking policy, not a pilot-tracking policy. Aligns with how vehicle insurance works in real life.
Outputs / state changes¶
Per any registry action:
- New ShipRegistry row inserted (immutable history) when ownership changes or stolen status flips.
- Ship.registered_owner_id, Ship.current_pilot_id, Ship.stolen_status, Ship.stolen_reported_at mutated as needed.
- Realtime events on the bus:
- ship.registry_transfer_requested — to the registered owner; payload {ship_id, registration_number, claimant_id, dispute_deadline}.
- ship.stolen_reported — to the current pilot (if any); becomes Wanted.
- ship.stolen_retracted — to the current pilot; Wanted clears.
- ship.registry_transferred — to the new owner and previous owner.
Invariants¶
- Every ship has exactly one
registration_number, immutable for the lifetime of the hull. ShipRegistry.original_owner_idis set at creation and never changes.Ship.registered_owner_idis the current legal owner; updates only via registration transfer.Ship.current_pilot_idcan be NULL (Drifting),registered_owner_id(Owner aboard), or any other player UUID (Borrowed). It can also be the pilot of a Stolen ship — independent of ownership.Ship.stolen_status = Truerequires astolen_reported_attimestamp. Cleared on retraction or successful transfer.- Insurance payouts always credit
registered_owner_id, never the pilot. - After ship destruction the
Shiprow enters DESTROYED, butShipRegistryrows remain for historical lookup.
Failure modes¶
| Mode | Target handling |
|---|---|
| Original owner doesn't respond to transfer notification | After 24h, transfer completes automatically. |
| Original owner files stolen report mid-transfer | Transfer canceled; claimant becomes Wanted. Fee refunded. |
| Pilot of a Stolen ship enters fed-space | Federation patrols pursue. Player-attackers don't suffer normal fed-space penalties. |
| Pilot of a Stolen ship docks at a port | Ship impounded; pilot fined and ejected to escape pod. |
| Concurrent transfer requests on the same ship | First request locks the ship until resolved (24h dispute or earlier outcome). Second request returns 409 Conflict. |
| Owner reports a ship they don't own | Registry rejects the request (FK enforcement on registered_owner_id). |
| Insurance + Stolen + destroyed | Owner collects payout; pilot's Wanted status clears (ship gone). Owner can also collect any auto-placed bounty if a third party kills the pilot. |
| Player tries to board their own destroyed ship | Reject; the ship row is DESTROYED, not Drifting. CargoWreck mechanics apply instead. |
Source map¶
| Concern | Path (target) |
|---|---|
| ShipRegistry model | services/gameserver/src/models/ship_registry.py (target) |
| Ship state additions | services/gameserver/src/models/ship.py (registered_owner_id, current_pilot_id, stolen_status, stolen_reported_at) |
| Registry service | services/gameserver/src/services/ship_registry_service.py (target — handles report, retract, transfer flow) |
| Wanted status logic | services/gameserver/src/services/wanted_service.py (target) |
| Combat hook (Wanted suspends fed-space immunity) | services/gameserver/src/services/combat_service.py (extension) |
| Port impound logic | services/gameserver/src/services/port_service.py (extension) |
| Auto-bounty on stolen-report | services/gameserver/src/services/bounty_service.py:place_auto_bounty (extension) |
| Realtime registry events | services/gameserver/src/services/websocket_service.py (event taxonomy in ./realtime-bus.md) |
Related¶
- DATA_MODELS:
../DATA_MODELS/ships.md#shipregistry,../DATA_MODELS/ships.md#ship. - FEATURES:
../FEATURES/gameplay/ships.md#manual-eject-and-the-ship-registry,../FEATURES/gameplay/ranking.md#wanted-status,../FEATURES/gameplay/bounties.md. - SYSTEMS:
./realtime-bus.mdfor the registry event channel.