Skip to content

NPC Traders

TRADER-archetype NPCs are full economic actors, not flavour. They carry real credit wallets, buy and sell real goods at stations, pay the same tariffs and trigger the same hostility metrics as players, occupy real docking slips, and run cross-region routes through the galaxy. The guiding principle: the economy is driven by real product moved by real actors, not by invisible auto-supplement. When a station restocks, it is because a trader hauled goods in and sold them — not because a regen tick conjured stock from nothing.

This doc covers the trader wallet and cargo model, the restock-by-delivery loop, market participation (tariffs, hostility, docking, attribution), cross-region routing and warp-gate access, the notoriety system that decides which traders are lawful targets, and which numbers are canon vs. gameserver-tunable.

The closely-related docs: - ./trading.md — station classes, dynamic pricing, and the transaction flow traders share with players. - ./port-ownership.md — station owner tariffs and port-takeover hostility, both of which apply to trader trades. - ./docking-slips.md — the finite-slip model traders compete for alongside players. - ../../SYSTEMS/npc-lifecycle.md — the TRADER archetype, succession, and lifecycle this doc gives economic teeth. - ../../SYSTEMS/npc-scheduler.md — the scheduler that spawns and steps trader rosters.

Trader wallet and cargo

Every trader carries a real credits wallet, seeded variably by ship profile — a larger, more valuable trading hull spawns with proportionally more credits on hand, so a Freighter captain is richer than a Clipper courier. A trader spends real currency to buy goods and is stalled when broke until a profitable leg refills the wallet. Accumulated profit advances the canon 100-route career metric.

The cargo a trader holds is real goods it actually purchased at a station — never spawned-from-nothing cargo. Because both the wallet and any undelivered cargo are real, they are lootable on KIA: kill a trader carrying credits or goods and that value drops.

✅ Shipped. Traders carry a real credits wallet (models/npc_character.py:credits) seeded by ship profile at spawn (services/npc_spawn_service.py), and buy/sell real goods at station stops, spending and refilling the wallet leg by leg (services/npc_trading_service.py).

Restock by delivery

Station stock movement is hybrid, real-transaction-dominant. Passive tick_production regen survives only as a thin baseline floor; the great majority of station stock movement is real product moved by NPC and player transactions. The auto-supplement weight is dialed down so the economy reflects actual product flow.

A station's restock is realized as a visible NPC supply-delivery. When a station runs low, a supply trader spawns in an empty sector carrying goods and delivers them to the station to sell, rather than the station conjuring stock from a regen tick. The scheduler still spawns the trader, but the goods arrive by a visible haul — the abstraction is one step further back, where players can see and intercept it.

Trader roster size scales to a region's trading-station count, with randomness; an operator override via NPCRoster.target_count is always available.

🚧 Partial. Roster sizing and the buy/sell trade loop are shipped; the visible supply-delivery restock (low-stock station triggers a goods-carrying spawn that hauls in) and the dial-down of passive tick_production toward a thin floor are the launch target.

Market participation

Traders are first-class market actors. A trader trade is treated identically to a player transaction wherever it touches the shared market:

  • Tariffs. Station-owner tariffs apply to trader buys and sells, just as they do to players (see ./port-ownership.md).
  • Hostility. Port-takeover hostility metrics accumulate from trader trades. Anti-exploit guard: hostility accounting distinguishes NPC- from player-driven contribution, so an NPC route can never trigger a takeover cascade a player never caused.
  • Docking slips. Traders occupy real docking slips like players — a busy station can be full because traders are berthed there (see ./docking-slips.md). Anti-camp guard: a slip-tenure limit forces traders to release slips promptly so they cannot be used to grief player docking.
  • Attribution. Every trader trade is attributed via the enhanced_market_transactions.npc_id nullable FK — the canonical NPC attribution column. Future actor types add their own nullable FK; the player_id index stays hot for player trade-history reads.

✅ Shipped — attribution. enhanced_market_transactions.npc_id (nullable FK with its own index, models/market_transaction.py) attributes TRADER trades; exactly one of player_id / npc_id is set per row.

📐 Design-only — tariffs, hostility, slips. Applying station tariffs and port-takeover hostility to trader trades (with NPC-vs-player contribution isolation), and giving traders real slip occupancy with an anti-camp tenure limit, are the launch target.

Cross-region routing and warp gates

Trader routes may cross region boundaries freely within the route hop budget, exactly like players. Regional starvation is monitored in tandem with roster sizing; a region in liquidation (see ../../SYSTEMS/region-lifecycle.md) still receives explicit handling.

NPC movement may traverse a player-built warp gate only when the gate owner has granted access to the relevant faction. This is a per-faction permission surface on WarpTunnel, and the default is no access — player infrastructure stays a player advantage unless the owner deliberately opens it.

🚧 Partial. Cross-region routing within the hop budget is shipped (services/npc_movement_service.py); the per-faction warp-gate access-grant model on WarpTunnel (default-deny) is the launch target.

Notoriety

Each trader carries a scruples axis, notoriety (0–100), that decides whether attacking it is a crime or a public service.

Band Notoriety Reputation Lawful target?
Reputable 0–24 reputable merchant No
Standard 25–49 standard trader No
Unscrupulous 50–74 unscrupulous operator Yes
Notorious 75–100 notorious Yes

The lawful-target threshold is notoriety ≥ 50. Spawn distribution across the bands is approximately 50 / 25 / 17 / 8 (reputable / standard / unscrupulous / notorious).

Consequences of a kill fire on the kill, not on the act (matching PvP):

  • Killing a reputable or standard trader (notoriety < 50) fires attack_innocent — a −100 personal reputation penalty per ../gameplay/police-forces.md (offense triggers, ADR-0042).
  • Killing a trader also carries faction consequences: killing a faction-affiliated or station-buyer-aligned trader costs standing with that faction. Lawful targets are exempt.
  • Killing a notorious trader (notoriety ≥ 50) yields a positive incentive — a kill-notorious reward of +25 reputation (bounty / faction approval), not merely the absence of penalty.

Notoriety drifts dynamically: a trader caught smuggling rises toward notorious, while honest trade decays it back toward reputable. A trader's standing is therefore earned by behaviour, not fixed at spawn.

✅ Shipped — base notoriety. The notoriety scruples axis (models/npc_character.py:notoriety), the ≥ 50 lawful-target threshold, the band definitions and spawn distribution, and the attack_innocent −100 penalty on killing a below-threshold trader are in place.

📐 Design-only — drift + reward. Dynamic notoriety drift (smuggling raises, honest trade decays) and the +25 kill-notorious reward are the launch target.

Tunables: canon vs. gameserver

Player-visible numbers are canon. Anything a player experiences directly — notably the pirate same-identity respawn cooldown (players see the re-appearance) and the runtime tuning values surfaced in-client — is canon and player-visible.

Internal route and demand math is gameserver-tunable, not canon and not region-owner-tunable. This includes ROUTE_HOP_BUDGET, SURPLUS_RATIO / DEFICIT_RATIO, the DEMAND_SCORE bounds, the trader wallet seed amount, and the supply-delivery thresholds. Keeping these off-canon avoids difficulty-cheese (region owners cannot tune the economy in their favour) and lets the still-evolving delivery model be re-tuned without a canon edit.

🚧 Partial. The internal route/demand/seed/threshold constants are gameserver-tunable today (services/npc_trading_service.py, services/npc_spawn_service.py); surfacing the player-visible runtime tuning values in-client is the launch target.

Cross-references