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
creditswallet (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_productiontoward 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_idnullable FK — the canonical NPC attribution column. Future actor types add their own nullable FK; theplayer_idindex 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 ofplayer_id/npc_idis 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 onWarpTunnel(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
notorietyscruples axis (models/npc_character.py:notoriety), the ≥ 50 lawful-target threshold, the band definitions and spawn distribution, and theattack_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¶
./trading.md— station classes, dynamic pricing, and the transaction flow shared with players../port-ownership.md— station tariffs and port-takeover hostility that apply to trader trades../docking-slips.md— the finite-slip model traders compete for.../../SYSTEMS/region-lifecycle.md— region liquidation handling for cross-region routes.../gameplay/factions-and-teams.md— faction reputation affected by trader kills and warp-gate access grants.../gameplay/police-forces.md— theattack_innocentoffense trigger fired on killing a below-threshold trader.../../SYSTEMS/npc-lifecycle.md— the TRADER archetype and lifecycle.../../SYSTEMS/npc-scheduler.md— roster spawning and route stepping.../../DATA_MODELS/—npc_characters,enhanced_market_transactions, andWarpTunnelschema.- ADR-0042 — police engagement and the
attack_innocentoffense trigger. - ADR-0062 — economy tariffs and contracts.
- ADR-0063 — NPC scheduler lifecycle.