Skip to content

Ranking & Reputation

Status: ๐Ÿšง Partial โ€” military rank and personal reputation tracks shipped; team reputation pricing integration pending. (re-verified 2026-08-21 vs Sectorwars2102 46bce720.)

Two parallel progression tracks:

  1. Military Rank โ€” what you've done. Achievement-based, never lost.
  2. Personal Reputation โ€” how you've behaved. Alignment scale that swings with combat actions.

Plus a separate per-faction reputation (covered in factions-and-teams.md).


Military Rank

Tiers and thresholds

18 ranks across five tiers (services/ranking_service.py:RANK_DEFINITIONS).

Level Tier Rank Points required Trading bonus Combat bonus Max-turns bonus
0 Enlisted Recruit 0 0% 0 0
1 Enlisted Spacer 50 +2% +1 +5
2 Enlisted Corporal 150 +3% +2 +10
3 NCO Sergeant 300 +5% +4 +15
4 NCO Staff Sergeant 500 +6% +5 +20
5 NCO Master Sergeant 800 +7% +6 +25
6 Warrant Warrant Officer 1,200 +8% +8 +30
7 Warrant Chief Warrant Officer 1,800 +10% +10 +35
8 Officer Ensign 2,500 +12% +12 +40
9 Officer Lieutenant 3,500 +15% +14 +45
10 Officer Commander 5,000 +18% +16 +50
11 Officer Captain 7,000 +20% +18 +55
12 Officer Senior Captain 10,000 +22% +20 +60
13 Flag Commodore 14,000 +25% +22 +70
14 Flag Rear Admiral 20,000 +30% +25 +80
15 Flag Vice Admiral 28,000 +35% +28 +90
16 Flag Admiral 40,000 +40% +32 +100
17 Flag Fleet Admiral 60,000 +50% +40 +120

Achievement gates beyond points

Some ranks require minimum stat thresholds in addition to points (RANK_REQUIREMENTS):

Rank Extra requirement
Sergeant 25 trades, 50 sectors visited
Staff Sergeant 50 trades, 10 combat victories
Master Sergeant 100 trades, 25 combat victories, 100 sectors
Warrant Officer 50 combat victories, 200 trades
Ensign 100 combat victories, 1 planet owned
Lieutenant 500 trades, 200 combat victories
Commander 3 planets owned, 500 combat victories
Captain 1,000 trades, 1,000 combat victories, 5 planets owned

Earning rank points

Valid award reasons (VALID_REASONS): - combat_victory โ€” points scaled by relative rank of attacker vs defender (RankingService.calculate_combat_points). - trading_volume โ€” proportional to trade revenue. - exploration โ€” first-discovery of sectors. - colony_establishment โ€” claiming a planet. - admin_grant โ€” manual.

The combat hook in combat_service.attack_player calls:

ranking_service.award_rank_points(winner_id, points, "combat_victory")

Ranks are permanent

Once promoted, a player stays at that rank even if their stats fluctuate. Promotion is checked on every point award.

Old-save rank-name compatibility

LEGACY_RANK_MAP translates older save data to current rank IDs: - Private โ†’ Recruit - General โ†’ Admiral - Major โ†’ Commander - Colonel โ†’ Commodore

Special medals

Medals are the horizontal achievement layer that sits alongside the linear rank progression. The full catalog (combat / economic / exploration / diplomatic / special), award lifecycle, the Orange Cat Society lore, and the privacy model live in dedicated docs:

Auto-award dispatchers cover the three combat-tier medals via MedalService.check_combat_medals plus first-login specials (Orange Cat Society / Honorary Tabby via check_and_award_first_login_special_medals โ€” see ./first-login.md#easter-egg-mechanic-pumpkin-and-the-orange-cat-society) and the other WO-CG2 wired medals per ./medals.md page banner (nine auto-award paths total). The remaining catalog is admin-grant-only / ๐Ÿ“ Design-only until their counters land.

Player-facing display

See Player-facing affordances below for consolidated โœ…/๐Ÿšง/๐Ÿ“ markers. Mid-combat rank promotion deferral (per ADR-0061 S-I4): when rank_points crosses a rank threshold during an active combat, the promotion is queued and fires at combat end. The combat_bonus value used throughout the fight is the pre-combat rank's bonus (snapshot at combat init). No mid-combat stat changes; combat math stays internally consistent round-to-round; ARIA narrates the rank-up after the fight resolves.

Source map

Topic Path
Rank tables, promotion logic services/gameserver/src/services/ranking_service.py
Player rank fields services/gameserver/src/models/player.py (military_rank, rank_points)
Ranking REST routes services/gameserver/src/api/routes/ranking.py
Medal awards services/gameserver/src/services/medal_service.py
Awarding hook services/gameserver/src/services/combat_service.py
Player UI: dossier mount services/player-client/src/components/layouts/ServiceRecordTab.tsx
Player UI: rank progress services/player-client/src/components/ranking/RankProgress.tsx
Player UI: leaderboard services/player-client/src/components/ranking/Leaderboard.tsx
Player UI: rank display services/player-client/src/components/ranking/RankDisplay.tsx

Status: โœ… Shipped โ€” Player.military_rank and Player.rank_points columns exist, the player-client ranking/ UI (RankDisplay, RankProgress, Leaderboard, MedalShowcase) renders against them, ranking_service.py is live behind routes/ranking.py (rank / leaderboard / progress / medals / reputation / bounties), and trading.py awards rank points on buy and sell. The lifetime-stats table is also shipped: PlayerLifetimeStats (services/gameserver/src/models/player_lifetime_stats.py) is a per-player row (total_trades, combat_victories, sectors_visited, planets_owned) incrementally updated at the existing rank-point award call sites (trading.py buy/sell, combat_service.py kill resolver) rather than recomputed ad hoc, with a one-time backfill for players who predate the table.


Personal Reputation

Tracks moral alignment โ€” separate from faction standing. Eight tiers across the โˆ’1000 to +1000 range. Used to drive name color, bounty system, and station-pricing modifiers.

Tiers (personal_reputation_service.py:REPUTATION_TIERS)

Score range Tier Name color (hex)
โˆ’1000 to โˆ’750 Villain #FF0000
โˆ’749 to โˆ’500 Criminal #FF4400
โˆ’499 to โˆ’250 Outlaw #FF8800
โˆ’249 to โˆ’1 Suspicious #FFCC00
0 Neutral #FFFFFF
+1 to +249 Lawful #88FF88
+250 to +499 Heroic #00FF00
+500 to +1000 Legendary #00FFFF

Triggers (REPUTATION_TRIGGERS)

Action ฮ” Reason key
Attack a player with no bounty โˆ’100 attack_innocent
Kill a player in an escape pod โˆ’500 kill_escape_pod
Salvage another player's Cargo Wreck during the 1-hour grace window (non-team-member) โˆ’25 early_salvage (โœ… shipped 2026-08-04, salvage_service.py:190 โ€” stale marker, the mechanic itself has been live since WO-CMB-SUSPECT-LIFE-1, see line 194 below)
Pilot a ship reported stolen, per real-time day โˆ’100 pilot_stolen_ship (โœ… shipped, corrected 2026-08-07 โ€” _run_stolen_ship_rep_penalty_sweep_sync, services/gameserver/src/services/scheduler/economy_sweeps.py:514-, applies the exact โˆ’100/canonical-day penalty via STOLEN_SHIP_REP_PENALTY_PER_DAY, idempotent per-canonical-day)
Caught at port docking with a stolen ship โˆ’100 stolen_ship_impound (โœ… shipped, corrected 2026-08-07 โ€” _check_stolen_ship_impound, services/gameserver/src/services/docking_service.py:517, wired into the dock flow at line 607)
Successfully defend against an attacker +50 defend_against_attacker
Defeat a player with active bounty +100 defeat_bounty_target
Complete legitimate trade +1 complete_trade
Destroy hostile sector drones +10 destroy_pirate_drones

Adjustments are clamped to [โˆ’1000, +1000]. The service writes both the new score and the cached reputation_tier / name_color columns on Player.

Gameplay effects (get_reputation_info)

Bracket boundaries match the tier table above; the trade-multiplier percentages are a linear interpolation across the two ratified endpoints (Legendary 0.90, Villain 1.20 โ€” ADR-0062), not independently-set flat values. This table defers to ../../SYSTEMS/bounty-and-reputation.md#reputation-tier-table as the single source of truth:

Score range Tier Effect
[โˆ’1000, โˆ’750] Villain +20% station markup, bounty hunters target
[โˆ’749, โˆ’500] Criminal +15% station markup
[โˆ’499, โˆ’250] Outlaw +10% station markup
[โˆ’249, โˆ’1] Suspicious +5% station markup
[0, 0] Neutral (no station effect)
[+1, +249] Lawful โˆ’3% station discount
[+250, +499] Heroic โˆ’5% station discount, +5% faction standing bonus
[+500, +1000] Legendary โˆ’10% station discount, +5% faction standing bonus

Decay

apply_weekly_decay pulls extreme values back toward 0 by 5 points per week (positive scores decrement, negative scores increment). Once a player hits Neutral, decay stops.

Bounty interaction

When an attacker wins combat, BountyService.collect_bounty(attacker_id, defender_id) runs. If bounties exist, attacker collects the pool (minus a system fee) and gains reputation; if not, the attacker is treated as having attacked an innocent and loses reputation. See bounties.md for the full bounty system.

Suspect status (temporary flag)

Distinct from the persistent personal_reputation integer above, Suspect Status is a temporary boolean flag on Player that fires from specific actions and auto-clears on a timer. Triggers: early-window Cargo Wreck salvage (see ships.md โ†’ Cargo Wreck) and Light / Moderate contraband violations at a black-market bust (see ../economy/black-market.md).

When Player.is_suspect = True:

  • Name color shifts to gray/yellow across all UI surfaces (overrides the personal_reputation tier color for the duration of the flag).
  • Federation-zone immunity is suspended โ€” anyone may attack the flagged player in fed-space without the normal policing penalties; the flagged player fights back without fed-space combat-cost penalties.
  • Flag auto-clears at Player.suspect_until.

Stacking rules: - Each suspect-triggering event sets suspect_until = max(suspect_until, now + 1h) โ€” i.e., extends the timer to 1 hour from now if that's later than the current expiry. suspect_declared_at is stamped once, at first acquisition, and anchors the cumulative cap. - Maximum cumulative duration: 4 hours from the first triggering event (suspect_declared_at + 4h). Even chronic offenders cap at 4 hours of flag. - suspect_team_snapshot freezes the flagged player's team roster once, at first acquisition โ€” team-mate exemptions during the window evaluate against that snapshot, not live team membership. - After clear, future events restart the cycle.

โœ… Shipped โ€” is_suspect, suspect_until, suspect_declared_at, and suspect_team_snapshot columns on Player; lifecycle owned by suspect_service.apply_suspect_event / clear_expired_suspects, wired from contraband busts (contraband_service._apply_heat) and early-window Cargo Wreck salvage (salvage_service.py), with a scheduler sweep clearing expired flags (WO-CMB-SUSPECT-LIFE-1, Max ruling, 2026-07-10).

Wanted status

A stronger flag than Suspect. Trigger set (the union of this doc's stolen-ship trigger and ./police-forces.md's reputation-based trigger, per ADR-0093): Player.is_wanted = True fires on either (a) piloting a ship the registered owner has reported stolen (see ../../SYSTEMS/ship-registry.md), or (b) Player.personal_reputation < โˆ’500 (Villain / Criminal tiers). Federation Police / Nexus Sentinel Corps engagement on a Wanted player is governed by ./police-forces.md's engagement rules, not by this section. While Player.is_wanted = True:

  • Name color renders red in all UI surfaces (overrides personal-reputation tier color and Suspect Status if both are active).
  • Federation-zone immunity is suspended โ€” anyone may attack a Wanted player in fed-space without normal policing penalties.
  • NPC patrols actively hunt the player. โœ… Shipped (stale marker corrected 2026-08-04) โ€” the real mechanic is ./police-forces.md's engagement routing: route_engagement dispatches a PendingEngagement with a 2-turn spawn delay on Wanted-status entry into a patrolled sector (movement_service._maybe_dispatch_police_engagement), the squad then arrives in whatever sector the player is CURRENTLY in (not the offense sector โ€” "the police chase you") and attacks first via npc_combat_initiation_service.initiate_npc_combat, pursuing until jurisdiction exit, destruction, or surrender. Not a patrol-AI pathfinding-toward-target mechanic (patrols don't roam looking for targets outside an offense event) โ€” see police-forces.md for the full trigger/dispatch/pursuit design.
  • An automatic bounty equal to 50% of the ship's appraised value is auto-placed on the Wanted player by the registry, when the trigger is the stolen-ship report. See bounties.md.
  • Daily personal-reputation drain โ€” โˆ’100 / day while still piloting the stolen ship.
  • Port-docking impound โ€” at any port, attempting to dock with a stolen ship results in:
  • Ship impounded; returned to registered owner (held at port for collection).
  • Pilot fined 25% of credits (capped at 100k cr).
  • Pilot personal-reputation hit โˆ’100 (separate from the daily drain).
  • Pilot ejected to escape pod.

Wanted Status auto-clears when its triggering condition no longer holds: for the stolen-ship trigger โ€” eject voluntarily, ship destroyed, ship impounded, or registered owner retracts the stolen report; for the reputation trigger โ€” personal_reputation recovers to โ‰ฅ โˆ’500; for the Severe-severity black-market bust trigger (below) โ€” a fixed Player.wanted_until timer, since neither of those two condition-based clears applies to a bust.

โœ… Shipped โ€” is_wanted, wanted_declared_at, and wanted_until columns on Player. All three documented triggers are wired through wanted_service.recompute_is_wanted (the single source of truth, OR-ing them so is_wanted clears only once every trigger is false): Severe-severity black-market busts (see ../economy/black-market.md) via wanted_service.apply_wanted_event โ€” refreshes wanted_until to a flat 24h window (ratified DECISIONS.md wanted-black-market-bust-duration, matching black-market.md's fence-closure-on-raid precedent) and auto-clears via a scheduler sweep (wanted_service.clear_expired_wanted); the stolen-ship report path via ship_registry_service.report_stolen/retract_stolen_report calling recompute_is_wanted on the pilot; and the personal_reputation < WANTED_REPUTATION_THRESHOLD (โˆ’500) trigger via personal_reputation_service's adjustment hook, with a daily sweep backstop (scheduler/economy_sweeps.py) re-deriving all three for drift safety. Independent of the shared suspect_service lifecycle a Wanted player also runs (implicitly also a Suspect). NPC-patrol active-hunt is a separate, still-unwired mechanic (see the bullet above).

โœ… Shipped โ€” system bounty auto-placement at thresholds (โˆ’500 / โˆ’750 / โˆ’1000) is live: bounty_service.SYSTEM_BOUNTY_TIERS + _get_system_bounties emit virtual bounties consumed by get_bounties_on_player and collect_bounty. ๐Ÿšง Planned โ€” bounty-hunter NPC AI to actively hunt flagged players.

Source map

Topic Path
Tier table, adjustment logic services/gameserver/src/services/personal_reputation_service.py
Player columns services/gameserver/src/models/player.py (personal_reputation, reputation_tier, name_color)
Bounty service services/gameserver/src/services/bounty_service.py
Combat hook services/gameserver/src/services/combat_service.py

Player-facing affordances

  • โœ… Service Record rank progress + galactic leaderboard โ€” RankProgress.tsx, Leaderboard.tsx, and RankDisplay.tsx call rankingAPI.getProgress / getPublicLeaderboard / getRank; mounted via ServiceRecordTab.tsx in the StatusBar dossier (same views as legacy RankingPage) (origin/feat 46bce720).
  • โœ… StatusBar rank/rep badges โ€” partial surface: military rank name text and personal-reputation tier color on StatusBar.tsx (playerState?.military_rank); no rank insignia glyph on tip (git grep insignia empty in player-client).
  • โœ… Rank bonuses (backend hooks, visible where noted) โ€” trading discount at ports (trading.py + RankingService.get_rank_bonuses); turn-cap bonus in RankDisplay (RankingService.calculate_max_turns); combat damage multiplier in combat_service.attack_player per ADR-0061 S-D2.
  • ๐Ÿšง Bounty board / placement / cancel UI โ€” kernel REST + WS on tip; player-client browse/place controls tip-pending Fibril #1178 โ€” see bounties.md ยง Player-facing affordances (PR #653 not tip-ancestor). Do not infer bounty flows are live from this page alone.

How rank, personal reputation, and faction standing interact

These three are independent:

  • Military Rank can rise even while you're a Villain (the Admiral Darkstar pattern).
  • Personal Reputation sets your name color and bounty status โ€” visible to everyone in any sector.
  • Faction Standing (per-faction, see factions-and-teams.md) determines port pricing, patrol response, and territory access for each NPC faction. Movement is purely emergent โ€” see ./factions-and-teams.md#reputation-triggers.

A player with high rank, low personal reputation, and Allied Federation standing is possible โ€” they're feared by other players but still the Federation's top mercenary.