Skip to content

Medals

Status: 🚧 Partial — 33-entry medal catalog and auto-award baseline shipped; remaining catalog triggers stay admin-grant-only until counters land. ✅ Admin player collection GET tip-shipped via PR #710 / LEG-264 @ tip 96c968ad (GET …/admin/players/{player_id}/collection + view_hidden_medal audit). ✅ Admin UI collection consumer tip-PRESENT via PR #794 / LEG-1643 (MedalAdmin.tsx Player collection tab + GET caller — see Display surfaces). (re-verified 2026-08-21 vs Sectorwars2102 96c968ad.)

Medals are the horizontal achievement layer that sits on top of the linear military rank progression. Where rank measures how much a player has done (point thresholds → trading bonus, max-turns bonus, combat bonus), medals commemorate what specific things a player has done — ten thousand combat kills, a successful diplomatic treaty, a deep-nebula shard run, mentioning the right cat to the right guard.

The award lifecycle (storage, dispatcher, realtime events, admin tools) lives in ../../SYSTEMS/medal-service.md. This doc covers the catalog — what medals exist, what they commemorate, what they grant, and the lore that grounds them.

🚧 Partial. The medal system is shipped — storage, the award dispatcher + realtime events, and the bespoke per-medal effect layer (§ Effects). Re-audited 2026-08-04 (WO-BUILD-MEDAL-AUTO-AWARD-BATCH verify-first — the prior "24 admin-grant-only" figure was stale): auto-award now covers combat.first_blood/bronze_star/silver_star/quantum_cross/fleet_commander/untouchable, economic.traders_merit/merchant_prince/port_baron/quartermaster, exploration.explorers_badge/colonizer/genesis_award/pathfinder, diplomatic.ambassadors_star/lawgiver/peacemaker/team_founder/beacon_keeper, combat.bounty_hunter/drone_reaper/siege_master, special.orange_cat_society/honorary_tabby/arias_favor, economic.cartel_breaker, exploration.void_walker. combat.untouchable's counter is an unbroken STREAK (2026-08-04 orchestrator ruling), not a lifetime cumulative count like every other wired counter — reset to 0 on any ship loss across every PvP/NPC/drone/planet/port combat context, incremented on every combat resolved without one. cartel_breaker's regional_commodity_sales is query-derived (no durable counter needed) — the player's best single-region lifetime SELL total, joined enhanced_market_transactions.sector_uuid → Sector.region_id. void_walker's "uncharted void" (2026-08-04 orchestrator ruling: wire it for real, don't reuse sectors_visited) is now the galaxy-wide Sector.discovered_by_id field — previously schema-only, first-wins-marked on arrival in BOTH quantum_service.jump() and normal-warp movement (movement_service._execute_movement, mirrors discovery_service.mark_planet_discovered), since the column is galaxy-wide-first-discovery, not jump-specific. Only a quantum jump (not a normal warp) landing on a sector NOBODY has ever discovered before increments the durable void_jumps medal counter — the counting trigger stays jump-specific even though the underlying discovery mark doesn't. Wired same-day (80dbf6ae, 2026-08-04): diplomatic.first_citizen (governance_votes >= 1, fired from check_and_award_first_citizen_medal via run_sync from cast_election_vote/cast_policy_vote — the AsyncSession incompatibility was closed) and special.pioneer_office_pillar (colonists_transported_lifetime >= 10000, counter now live). Wired (commit 994e91f5, 2026-08-09): economic.spread_hunter — DECISIONS.md's spread-hunter-round-trip-definition ruling (a completed BUY MarketTransaction followed by a co-located SELL) is now implemented in medal_service.py/medal_catalog.py. Still admin-grant-only: special.ghost_in_the_static (NO-CANON — no "dark-territory discovery" system exists, routed to Max).


Categories

Five categories at launch, expandable:

Category What it commemorates
Combat Kills, victories, survival under fire
Economic Trade volume, port ownership, market dominance
Exploration Sectors discovered, gates built, deep-nebula runs
Diplomatic Treaties, multi-faction reputation, council service
Special / Hidden Easter eggs, lore beats, one-off achievements

The schema enum (medal_category) is closed at launch but extensible — additional categories like GENESIS, GOVERNANCE, SOCIAL, or SEASONAL can be added when their gameplay loops mature.


Tiers

Tier Visual treatment Where it appears
BRONZE Bronze metallic First-tier achievements
SILVER Silver metallic Mid-tier achievements
GOLD Gold metallic High-tier achievements
PLATINUM Platinum / iridescent Capstone achievements
UNIQUE One-of-a-kind / lore-themed Special and one-off medals; no tier escalation

📐 Design-only (not on tip 96c968ad). Planned shape tier values (circle / hexagon / star / shield / cross) would be a colorblind-safe second axis beyond tier color — live Medal has no shape_tier column (Sectorwars2102/services/gameserver/src/models/medal.py:33-55: id, name, description, category, tier, criteria, effect only; git grep shape_tier empty on tip).


Effects philosophy

✅ Shipped — medal_service.get_active_medal_bonuses() folds held-medal effects into the combat-damage / trade-rate / turn-regen / haggle-band hooks under the hard per-hook caps; one-time grants are idempotent on award; 33 medals are seeded with effects (WO-CG c1a0333). The full per-medal effect table + magnitudes live in the design spec (audit/design-briefs/medal-effects-spec.md). Auto-award dispatchers cover nine medals (the three combat-tier checks plus six more, WO-CG2 7a069f4); the remaining catalog is admin-grant-only until its counters land.

Medals carry bespoke, per-medal effects. Each medal grants a small gameplay effect tied to the loop it celebrates — a combat medal buffs combat, an explorer medal eases exploration, a trade medal sharpens trade. The effect must be diegetically grounded (the lore makes the effect, not the other way around), and a medal whose celebrated loop exposes no per-player stat to buff stays prestige-only rather than inventing a mechanic. Rank remains the primary progression layer; medal effects are a deliberately small celebration sprinkle on top.

Application model. Each catalog entry carries an effect; medal_service.get_active_medal_bonuses() reads held medals and folds their effects into the three bonus stacks the resolvers already consume — the rank-bonus dict (combat damage / trade rate), the Player.aria_bonus_multiplier (turn-regen rate, never the ADR-0004 turn cap), and the haggle band multiplier. Effects are passive-while-held or one-time-on-award (idempotent on the medal-award insert).

Per-hook caps — the hard ceiling. Total held-medal bonus is clamped per hook: combat_damage ≤ +3%, trading_discount ≤ −2%, turn_regen ≤ +0.05 (within the 1.0–1.5 aria clamp), haggle_band ≤ +0.08 — all tiny against rank's reach (+40% combat / −50% trade). The flat per-hook cap is the real stacking guarantee. No medal effect may mint credits outside a bounded faucet or perturb a canon invariant.

One-time grants — a bounded celebration faucet, fired once per medal per account (lifetime total ≈ 6,050 cr + 25 turns, well under a single month of the Galactic Citizen perk; a sprinkle, not an income channel):

Medal Tier Grant
First Blood bronze +150 cr, +10 turns
Colonizer bronze +200 cr
Quartermaster bronze +700 cr
Pathfinder bronze +15 turns
Cartel Breaker silver +1,000 cr
Bounty Hunter silver +1,000 cr
Genesis Award gold +1,000 cr + a passive +0.02 turn-regen while held
Worldsmith platinum +2,000 cr

Special-case perks — exempt from the medal caps. A few medals record perks sized by their lore moment, not the medal cap:

Medal Effect
Orange Cat Society The +15% persuasion bonus granted during first-login dialogue (see first-login.md). The medal records it; the bonus is the lore beat, exempt from the haggle cap.
Honorary Tabby +1 starting reputation with Mercantile Guild, applied at first Mercantile interaction (one-time).
Pillar of the Pioneer Office −2% per-pioneer migration fee at any Capital Sector's Class-0 station (clamp range unchanged).

Catalog

Combat

Medal Tier Trigger Commemorates
Bronze Cluster Bronze 100 combat victories Survival of the first hundred fights — the threshold past which a recruit becomes a veteran.
Silver Cluster Silver 1,000 combat victories A career soldier's record; the back of the navy is built on Silver Clusters.
Quantum Cross Gold 10,000 combat victories The vanishingly few who have made killing other ships their life's work.
First Blood Unique First successful PvP victory against another player The transition from NPC scuffles to player-vs-player combat — the moment the universe gets real.
Carrier-Killer Platinum Destroy a Carrier-class ship in single-pilot combat Pulling down a capital hull with a single ship — feat, not statistic.
The Longest Hour Unique Survive a 5-on-1 engagement where you were the 1, and finish with hull > 0 Coming out the other side of being outnumbered five-to-one.
Cabal-Breaker Unique Participate in a successful raid on the Cabal HQ (per Cabal lore) The endgame trophy — only one award per Cabal HQ clear cycle.

Economic

Medal Tier Trigger Commemorates
Trader's Merit Bronze 500 completed trades and lifetime trade revenue ≥ 1,000,000 cr The first real merchant — past the early-game grind, into actual commerce.
Guild Order of the Manifest Silver Lifetime trade revenue ≥ 100,000,000 cr and Mercantile Guild reputation ≥ Allied Recognition by the Guild itself — the ledger is your ledger.
Pillar of the Pioneer Office Silver Transport ≥ 10,000 colonists in cryosleep transit, lifetime The frontier moves on the backs of colonist haulers; this medal says you carried more than your share. (Effect allowlist — see above.)
Port Sovereign Gold Own ≥ 5 ports simultaneously for 30 consecutive real-time days Holding a network of ports long enough that the sector's commerce flows through your gates.
The Cornered Market Platinum Hold ≥ 70% of total active galactic supply of a single core commodity for 24 consecutive real-time hours The whales-only medal — a brief, demonstrable monopoly. Quietly disliked by the Mercantile Guild.

Exploration

Medal Tier Trigger Commemorates
Pathfinder Bronze First-discover 100 sectors The early cartographer's badge — before there were maps, you made them.
Cartographer's Compass Silver First-discover 500 sectors across at least 3 distinct regions A surveyor who's worked more than one region's stars.
Quantum Harvest Silver Personally harvest ≥ 500 Quantum Shards from nebulae (not bought, not pooled) The shard-runner's medal — earned shard by shard in the deep nebulae.
Gate Architect Gold Construct ≥ 3 player-built warp gates Reshaping the topology of the galaxy itself, not just mapping it.
The Lost Cluster Platinum First-discover an isolated cluster (no natural-tunnel link to the main network — reachable only via Quantum Jump) True frontier — a cluster nobody had ever been to until you jumped blind.

Diplomatic

Medal Tier Trigger Commemorates
Concordat Seal Silver Be the negotiating signatory on ≥ 1 inter-region treaty (trade, defense, non-aggression, or cultural) that survives ≥ 30 real-time days Diplomacy that holds — a treaty that didn't collapse the next week.
Ambassador of Two Banners Gold Reach Honored or higher reputation with ≥ 2 mutually-rivalrous factions simultaneously Threading the needle between rivals — the diplomat-spy archetype.
The Peacemaker's Star Gold Mediate the resolution of a declared inter-region war Ending a war you weren't fighting. (📐 depends on launch-target war declaration system.)
Voice of the Council Silver Serve a full term on a Regional Council via election Civic service — quiet medal, earned by showing up. (📐 depends on regional governance launch.)

Special / Hidden

These medals are not shown in the public catalog until earned (Medal.is_hidden = true).

✅ Shipped (Orange Cat Society + Honorary Tabby award path)check_and_award_first_login_special_medals (medal_service.py:998) from complete_first_login (first_login_service.py:1873); catalog special.orange_cat_society / special.honorary_tabby (medal_catalog.py:241 / :388). Detection + first-login +15% persuasion: CatBoostDetector (enhanced_manual_provider.py:97 / :119). Orange Cat haggle leniency while held: _orange_cat_band_factor (haggle_service.py:317). 📐 Residuals: Pumpkin's Own and the other rows in this table remain unwired / launch-target-gated.

Medal Tier Trigger Commemorates
Orange Cat Society Unique Mention the cat (per detection rules below) during first-login dialogue Recognition that you've walked the docks before — see The Orange Cat Society below. (Effect allowlist — see above.)
Honorary Tabby Unique Complete first-login at negotiation_skill = strong, claim a rarity_tier ≥ 3 ship, and trigger the cat-mention in the same session The rare combination — you talked your way into a real ship and you noticed Pumpkin. Pumpkin remembers. (Effect allowlist — see above.)
Pumpkin's Own Unique Trigger the cat-mention while playing as a Region Owner subscriber, in a session where the guard personality rolled Tired Night-Shifter Wildly specific lore-trivia medal — the kind of thing the playerbase will catalogue on a wiki within 48 hours of launch. Pure flavor.
Ghost in the Static Unique Discover a Lost World — land inside a graph-disconnected dark-territory formation reached only by Quantum Jump (see ../galaxy/special-formations.md#lost-worlds-quantum-jump-only-content) First to step into territory that touches no warp on the chart — a ghost on the long-range scans, real only to the one who jumped blind to reach it. (📐 depends on the launch-target Lost-World discovery system.)
Static on the Wire Unique Discover a Lost Sector — the smallest dark-territory formation: a lone sector off the warp graph, reachable only by Quantum Jump (see ../galaxy/special-formations.md#lost-worlds-quantum-jump-only-content) A single pinprick of dark territory found at the end of a blind jump — no warp leads there, and almost none lead back. (📐 depends on the launch-target Lost-Sector discovery system.)
The Sol Pilgrim Unique Visit Earth orbit (the Sol-system Easter egg sector) A pilgrimage medal for finding the lore corner most players never reach. (📐 depends on Sol Easter egg landing.)
Survived the HQ Unique Enter the Cabal HQ sector and successfully escape without being destroyed Distinct from Cabal-Breaker (which requires the kill) — this is for the scout who came back alive.

The Orange Cat Society

Pumpkin

Pumpkin is an orange tabby tom, one ear notched, who lives in the Callisto Colony shipyard. Officially he belongs to nobody. Unofficially he belongs to everyone who has ever passed through the Pioneer Office — a stray that the night-shift dockworkers started feeding decades ago, kept alive long past any cat's natural span by an open secret nobody at the colony will admit on the record: the Nova Scientific Institute's medical wing on Callisto runs an experimental geriatric-feline regenerative protocol, and Pumpkin has been their unofficial test subject for three generations of dockhands. He is, depending on which mechanic you ask, somewhere between thirty and seventy years old, and he is exactly the same cat their grandfather knew.

Pumpkin patrols the shipyard like he owns it — because in every way that matters, he does. He sleeps on the warm thrust-coupling housings of ships scheduled for departure (sailors say a ship Pumpkin sleeps on comes home; sailors say a lot of things). During the security guard's interrogation in first-login, Pumpkin will do one of three things, deterministically rolled from a small table per session: weave between the player's ankles, bat at a piece of dust caught in the holographic ID scanner, or hop onto the guard's desk and sit on whatever paperwork is open. The guards pretend to shoo him. They never quite do.

The Society

The Orange Cat Society is a real thing the way most real things in shipyards are real — never written down, never officially acknowledged, but understood. It's the loose informal fraternity of veteran spacers, dockhands, customs officers, and Federation enlisted personnel who have all, at some point, stood where the player is standing and noticed the cat. Mentioning Pumpkin during an ID check is a sign — to a Callisto guard, to a Mercantile customs agent at any major Sol-system port, to a Frontier hauler comparing notes at a refueling depot — that you've been around. You're not a paper-trail traveler. You're someone who has actually walked the docks. The +15% persuasion bonus during the first-login dialogue isn't magic. It's the guard relaxing because, whoever you are, you're not new.

The Society has no membership roster. It has only one ritual: when an orange tabby crosses your path on the docks, you nod.

Detection patterns

The first-login manual provider (per SYSTEMS/first-login.md) detects cat-mentions via whole-word regex matches against the player's raw input. Whole-word boundaries are required — substring matches on words like "scattered" or "category" must not trigger.

English (en) base patterns

cat, cats, kitty, kitties, kitten, tabby, feline, orange cat, the cat, that cat, pumpkin (the proper-name reference — full bonus), meow, purr, purring, whiskers, mouser, furball, furry friend.

Locale-specific aliases

Locale Aliases
es (Spanish) gato, gata, gatito, michi, minino, atigrado
fr (French) chat, chatte, chaton, matou, minou, tigré
zh (Chinese Simplified) , 猫咪, 小猫, 橘猫, 橘子
pt (Portuguese, Brazilian) gato, gata, gatinho, bichano, felino, rajado
de (Partial — see OPERATIONS/i18n.md) katze, kater, kätzchen, mieze, stubentiger

Detector contract: case-insensitive, whole-word match against the player's raw input before sanitization. Always match the player's active-locale alias list plus the English list (a Spanish-speaking player who types "cat" still triggers). One match per session is sufficient — repeated mentions don't stack.


Player-facing affordances

  • Service Record medal grid (Trophy Room)MedalShowcase.tsx mounted in ServiceRecordTab.tsx (StatusBar dossier); loads collection via medalsAPI.getMe (api.ts) (origin/feat 96c968ad).
  • Realtime award toastMedalToast.tsx in GameLayout listens for WS medal_awarded (WebSocketContext.tsx).
  • Unviewed-award splashMedalUnviewedSplash.tsx in GameLayout via medalsAPI.getUnviewed / GET /api/v1/medals/unviewed (cross-session queue; not the designed pre-GameDashboard ~2s centerpiece).
  • 🚧 Pinned-medal chrome elsewhere — chat nameplate / sector marker / leaderboard medal column remain tip-pending on origin/feat 96c968ad (git grep PlayerNamePlate under player-client/src empty; PR #597 OPEN not tip-ancestor; PC READY Fibril #1179); Leaderboard.tsx has medal-category filter tabs only, not a pinned-medal icon + count column (Fibril #1207).
  • Admin catalog browse — grant/revoke/bulk-grant + Catalog tab GET …/admin/catalog ✅ on tip via MedalAdmin.tsx + GS LEG-355 / PR #683 MERGED → tip 96c968ad — see Admin actions; do not duplicate admin grant scope here.
  • 📐 Privacy / curated visibility controlsmedal_privacy knobs documented in Privacy controls; no player settings panel for visibility or pinning on tip.
  • 📐 Team rosters, combat log, discovery banner medal pins — no medal-icon render on tip (designed surfaces only).

Player-facing surfaces

Display surfaces — where medals appear

Status: Consolidated ✅/🚧/📐 markers live in Player-facing affordances above. This section retains the per-surface design table; do not read unmarked chrome as player-live.

The Trophy Room is the only "see all" surface on tip. Every other designed surface shows at most one medal (the player's pinned medal) plus a count — most of those pins are not player-live yet.

Surface Status What renders Density Interaction
Trophy Room (MedalShowcase.tsx on player profile) ✅ Shipped Full collection, categorized; locked silhouettes for unearned All medals Click → medal detail modal (lore, earned-at date, conditions)
Player nameplate in chat 🚧 tip-pending PR #597 / #1179 @ 96c968ad Pinned medal icon 1 Hover → tooltip (medal name + 1-line flavor)
Sector view 🚧 tip-pending PR #597 / #1179 @ 96c968ad Pinned medal icon next to player marker label 1 Hover-tooltip + click-through to public profile
Leaderboards 🚧 Partial Fibril #1207 Tip: category filter tabs (medal-categories). Designed: "Medals" count column + pinned medal icon 1 + count Click → that player's public Trophy Room
Team rosters (TeamManager.tsx) 📐 Design-only Per-member: pinned medal icon + total count badge 1 + count Click row → public profile
Combat log 📐 Design-only Combat-tier medal icon next to combatant names 1 (combat-medals only) Tooltip only
Player-discovery banner (first-encounter in a sector) 📐 Design-only Pinned medal icon under player name; "(N medals)" suffix if 5+ 1 + count None — banner is transient
Login splash (post-auth, pre-GameDashboard) 🚧 Partial Tip: MedalUnviewedSplash inside GameLayout. Designed: ~2s pre-dashboard centerpiece 1 (offline-earned) Click anywhere to dismiss
Admin player view ✅ tip-PRESENT — Catalog tab ✅; GS collection GET ✅; Admin UI collection consumer ✅ via #794 Catalog GET …/admin/catalog tip-shipped (#683). ✅ GS GET …/admin/players/{player_id}/collection tip-shipped (PR #710 MERGED → tip 96c968ad; view_hidden_medal audit). ✅ Admin UI Player collection tab tip-PRESENT (PR #794 MERGED → tip 96c968ad) Catalog all; GS collection tip-shipped; UI consumer tip-PRESENT Catalog browse live; GS collection API live; Admin UI Player collection tab live (#794). PC nameplate/leaderboard Soft-HOLDs stay tip-pending

Pinning model

Each player can pin one medal as their public face (Player.settings.medal_privacy.pinned_medal_id). Defaults to the player's highest-prestige earned medal per the catalog's prestige ordering. Hidden medals can be pinned but only render publicly if medal_privacy.show_hidden = true.

Award notification flow

Status (verified vs origin/feat 96c968ad): In-session toast ✅ MedalToast mounted in GameLayout (WS medal_awarded personal-unicast). Cross-session unviewed splash 🚧 Partial (MedalUnviewedSplash in the same layout). GS team/sector medal_awarded fan-out 🚧 tip-pending (PR #758 / LEG-939) — tip _dispatch_medal_awarded_event remains personal-unicast; do not invent fan-out as tip-shipped (GS Soft-HOLD #1310 owns tip-land). The ~600 ms shimmer / 8 s banner / 20 s bottom-right toast / tier fanfare chrome, hidden-medal 1.2 s spoiler unveil, and mute preference remain 📐 Design-only until those exact timings and surfaces exist. Do not read the designed chrome as shipped.

In-session toast / banner: a non-modal banner card slides into the top-center of the dashboard chrome. The medal icon shimmer-wipes into the slot over ~600 ms; banner persists ~8 seconds, then auto-dismisses to a small bottom-right toast for ~20 s. A single-shot fanfare cue plays (gated by Player.settings.audio.sfx_volume); three cue variants by tier (combat / economic / hidden). Animations respect Player.settings.accessibility.reduced_motion (collapses to a fade). Notification copy follows the tone guide below.

Cross-session (offline-earned): the award is queued as a persistent system message (Message.message_type = "system", priority = "high") plus a flag in Player.settings.medal_privacy.unviewed_awards: [medal_id...]. At next login, the splash shows the most recent unviewed award (or "N new commendations" if multiple). A "NEW" pip persists on each unviewed medal in the Trophy Room until first viewed.

Hidden-medal handling (spoiler protection): hidden medals' toast is one-shot reveal — obscured icon for the first 1.2 seconds, then unveils with hint-shaped copy ("Something noticed you. The Orange Cat Society has welcomed you in"). Subsequent views (Trophy Room, profile) show the medal in plain text — the spoiler protection is one-shot only.

Player-to-player visibility:

Audience Default Setting
Earner's own team Realtime team toast medal_privacy.broadcast_to_team (default ON)
Earner's current sector Routine medals: silent. Prestige tier (tier ≥ GOLD or category = UNIQUE): sector-wide notification Hard-coded by catalog tier, not user-tunable
Global feed None at launch (Reserved for future galaxy-tier medals)

Suppression: players can mute their own medal toasts via Player.settings.notification_preferences.medals (default: notifications on). When muted, awards still persist to inbox and Trophy Room — just no in-session banner or sound.

Award notification copy — first-pass

Tone reference: faction-lore.md (gritty space-opera, faction-flavored where applicable). All entries post-pended with "— View in Trophy Room.":

Trigger Copy
Bronze Cluster "One hundred kills logged. The brass have noticed. Bronze Cluster awarded."
Silver Cluster "A thousand ships dead in your wake. Silver Cluster awarded."
Quantum Cross "Ten thousand confirmed kills. The Federation declassified your record long enough to pin this on you. Quantum Cross awarded."
Trader's Merit "The Mercantile Guild has reviewed your trade volume and finds it sufficient to acknowledge. Trader's Merit awarded."
Pathfinder "First into the dark, first onto the charts. Pathfinder awarded — twenty unmapped sectors logged in your name."
Orange Cat Society (hidden) "Something noticed you. The Orange Cat Society has welcomed you in. They do not explain themselves."
Concordat Seal "You walked between three flags and left none of them angry. Concordat Seal awarded."

Style notes: no exclamation marks (reads MMO-cheerful, off-brand); faction-issued medals use a faction voice; hidden-medal copy hints at being observed without spelling out the trigger; combat copy is matter-of-fact (the kill counts speak for themselves).


Privacy controls

Player.settings.medal_privacy JSONB shape (see ../../DATA_MODELS/jsonb-schema.md#playersettings):

{
  "medal_privacy": {
    "visibility": "all | curated | none",
    "pinned_medal_id": "str | null",
    "curated_medal_ids": ["str"],
    "show_hidden": false,
    "show_count_publicly": true,
    "broadcast_to_team": true,
    "unviewed_awards": ["str"]
  }
}

Knob semantics:

  • visibility = "all" (default): every earned medal renders on public profile / Trophy Room (subject to show_hidden).
  • visibility = "curated": only medals listed in curated_medal_ids render publicly.
  • visibility = "none": public profile shows only count (or nothing if show_count_publicly = false); Trophy Room is owner-only.
  • show_hidden (default false): hidden-tier medals never render publicly even at visibility = "all". Intentional opt-in — earning a hidden medal is a private moment by default.

Default policy: opt-in publicity for normal medals, opt-out for hidden. Most players want to show off a Bronze Cluster; hidden medals (Orange Cat Society, easter eggs) should default private and require an explicit "yeah, share that one" toggle.

Admin override: admins always see the full collection (including hidden) via the admin player view, regardless of player privacy settings. Each view of a privacy-overridden hidden medal writes an audit_logs entry (action view_hidden_medal).


Admin actions

Admin medal tooling on tip (96c968ad):

  • Gameserver grant / revoke — ✅ ShippedPOST /api/v1/medals/admin/grant and POST /api/v1/medals/admin/revoke (medals.py). Server records grant via MedalService.admin_grant, writes audit_logs entry with {action: "medal_grant", actor_user_id, target_player_id, medal_id, reason}. Player gets the standard award notification flow.
  • Gameserver bulk grant — ✅ Shipped (PR #611 MERGED → 74db9aaf, ancestor of tip): POST /api/v1/medals/admin/bulk-grant with dry_run defaulting true (validate up to 1,000 recipients; summary of valid/invalid) then commit via dry_run=false. Commit shares one grant_batch_id; personal toasts suppressed when grantable batch size > 50 (players see medals at next-login splash).
  • Admin UI (MedalAdmin.tsx / /medals) — ✅ Shipped (PR #677 MERGED → tip): page + /medals route + Sidebar on tip (MedalAdmin.tsx, MedalAdmin.test.tsx). Grant/revoke/bulk-grant tabs use the shipped admin APIs above. Catalog tab uses GET /api/v1/medals/admin/catalog — ✅ tip-live after LEG-355 / PR #683 MERGED. ✅ Collection consumer for GET …/admin/players/{player_id}/collection tip-PRESENT via PR #794 / LEG-1643 — Player collection tab + GET caller + Vitest 403/429 on tip 96c968ad.
  • Catalog GET — ✅ Shipped (LEG-355 / PR #683 MERGED → tip): GET /api/v1/medals/admin/catalog in medals.py (admin_list_medal_catalog), gated require_scope(PLAYERS_VIEW); returns {items, total} from MEDAL_CATALOG (id/name/category/tier/description/criteria). Unit pins: test_admin_medal_catalog.py.
  • Admin player collection GET — ✅ Shipped (PR #710 MERGED → tip 96c968ad / LEG-264): GET /api/v1/medals/admin/players/{player_id}/collection (admin_get_player_medal_collection in medals.py), gated require_scope(PLAYERS_VIEW); always includes hidden-catalog medals; privacy-overridden hidden views write audit_logs action view_hidden_medal. Unit pins: test_admin_medal_collection.py. ✅ Admin UI consumer tip-PRESENT via PR #794.

Revocation (mistaken grants, tournament disqualifications): every revocation requires a mandatory reason text. Writes audit_logs entry; emits a personal system message to the player ("Your Medal Name has been rescinded. Reason: …"). Bulk-revoke by grant_batch_id (📐 Design-only) undoes a tournament's grants in one button (with confirmation).

Auth gate: per ADR-0058 (supersedes ADR-0027), grant/revoke/bulk-grant routes require require_scope(PLAYERS_ADJUST_REP) (admin.players.adjust_rep). Catalog browse (GET …/admin/catalog) requires require_scope(PLAYERS_VIEW) on tip after LEG-355 / PR #683. See OPERATIONS/rbac-admin-scopes.md.


Naming conventions

A short style guide for future medal additions:

  1. Tier escalation borrows from era progression. Bronze/Silver/Gold are pre-spaceflight materials; Quantum/Stellar/Singularity are the SF-prestige tier. Use material-name floors for early medals; escalate to physics terms at the top.
  2. Category vocabulary is faction-flavored. Combat = military ("Star", "Cross", "Order"). Economic = guild-mercantile ("Order of the Manifest", "Guild...", "Pillar of...", "Sovereign"). Exploration = surveyor / cartographer ("Pathfinder", "Compass", "Architect"). Diplomatic = formal-civic ("Concordat", "Council", "Ambassador", "Peacemaker's"). Special = lore-noun ("Orange Cat Society", "Sol Pilgrim").
  3. Possessive titles signal earned rank, not gift. "Trader's Merit" / "Cartographer's Compass" / "Peacemaker's Star" — apostrophe-s reads as "this is what they call you now".
  4. One proper noun per name maximum. "Quantum Cross" is fine. "Federation Quantum Cross of the Sol Frontier" is too much.
  5. Avoid generic verbs ("Master Trader", "Elite Pilot"). Reads as MMO completionist filler. Prefer artifact-noun constructions that imply a physical token someone bestowed on you.

Source map

Concern Path
Catalog seed data services/gameserver/src/services/medal_catalog.py
Service & lifecycle ../../SYSTEMS/medal-service.md
Player UI: Trophy Room services/player-client/src/components/ranking/MedalShowcase.tsx
Player UI: dossier mount services/player-client/src/components/layouts/ServiceRecordTab.tsx
Player UI: award toast services/player-client/src/components/ranking/MedalToast.tsx
Player UI: unviewed splash services/player-client/src/components/ranking/MedalUnviewedSplash.tsx
Player UI: pinned medal nameplate services/player-client/src/components/common/PlayerNamePlate.tsx (+ TeamManager / TacticalTarget wiring) — 🚧 tip-pending on origin/feat 96c968ad (git grep PlayerNamePlate empty; land path PC READY Fibril #1179 / PR #597 OPEN MERGEABLE, not tip-ancestor — re-verified 2026-08-21)
Gameserver pin reader GET /api/v1/medals/mepinned_medal_id — ✅ Shipped (PR #613 MERGED → f8277b2c, ancestor of tip 96c968ad)
Admin UI MedalAdmin.tsx / /medals — ✅ Shipped (PR #677 MERGED → ancestor of tip 96c968ad); Catalog tab tip-live via GET …/admin/catalog after PR #683
Gameserver bulk-grant API POST /api/v1/medals/admin/bulk-grant (dry_run default true / commit) — ✅ Shipped (PR #611 MERGED → 74db9aaf, ancestor of tip 96c968ad)
Gameserver catalog GET GET /api/v1/medals/admin/catalog — ✅ Shipped (LEG-355 / PR #683 MERGED → tip 96c968ad; require_scope(PLAYERS_VIEW); {items, total} from MEDAL_CATALOG)
Schema ../../DATA_MODELS/player.md Player section + new Medal/PlayerMedal entries
JSONB privacy block ../../DATA_MODELS/jsonb-schema.md Player.settings.medal_privacy