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:
- Military Rank โ what you've done. Achievement-based, never lost.
- 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:
- Catalog + lore + UX surfaces:
./medals.md - Award dispatcher + schema + realtime events:
../../SYSTEMS/medal-service.md - Schema decision (association table over JSONB): ADR-0028
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_rankandPlayer.rank_pointscolumns exist, the player-clientranking/UI (RankDisplay, RankProgress, Leaderboard, MedalShowcase) renders against them,ranking_service.pyis live behindroutes/ranking.py(rank / leaderboard / progress / medals / reputation / bounties), andtrading.pyawards 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.pybuy/sell,combat_service.pykill 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_reputationtier 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_engagementdispatches aPendingEngagementwith 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 vianpc_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 / daywhile 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, andRankDisplay.tsxcallrankingAPI.getProgress/getPublicLeaderboard/getRank; mounted viaServiceRecordTab.tsxin the StatusBar dossier (same views as legacyRankingPage) (origin/feat46bce720). - โ
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 insigniaempty in player-client). - โ
Rank bonuses (backend hooks, visible where noted) โ trading discount at ports (
trading.py+RankingService.get_rank_bonuses); turn-cap bonus inRankDisplay(RankingService.calculate_max_turns); combat damage multiplier incombat_service.attack_playerper ADR-0061 S-D2. - ๐ง Bounty board / placement / cancel UI โ kernel REST + WS on tip; player-client browse/place controls tip-pending Fibril
#1178โ seebounties.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.