Skip to content

Player Domain

Status: 🚧 Partial — Player, User, Team, Reputation, FirstLogin model family are committed; 10 design-only Player fields (ARIA trust/abuse, suspect/wanted flags … (impl audit 2026-06-16)

User accounts, the in-game Player row, the first-login onboarding flow, teams, reputation, and messaging.

Schema status

Per ADR-0066 D-V1, schema-level implementation status is consolidated here. Field descriptions describe the target schema.

Design-only columns — present in the spec, not yet committed:

  • Player: nickname, aria_trust_score, aria_blocked_until, aria_violation_count, suspect_status, suspect_until, suspect_team_snapshot, wanted_status, wanted_until, daily_faction_rep_consumed, daily_faction_rep_consumed_date.
  • PlayerTradingProfile, AIRecommendation: model classes exist; columns above are the launch target.
  • PlayerCentralBankAccount, TakeoverIntent, PlayerWarpKnowledge: entire models are design-only — schema below describes the launch target.

All other columns on this page are committed.


User

Source: services/gameserver/src/models/user.py

Purpose: Authentication-layer account. One User may own one Player (game state) and/or AdminCredentials. Subscription / billing flags live here.

Fields:

name type constraints notes
id UUID PK
username String(50) unique, not null
email String(255) unique, nullable optional
is_active Boolean default true
is_admin Boolean default false
last_login DateTime nullable
deleted Boolean default false soft delete
paypal_subscription_id, subscription_tier, subscription_status String nullable galactic_citizen etc.
subscription_started_at, subscription_expires_at TIMESTAMP nullable

Relationships: - player (1:1, cascade), admin_credentials (1:1, cascade), player_credentials (1:1, cascade), mfa_secret (1:1, cascade). - oauth_accounts, refresh_tokens, mfa_attempts (1:many). - owned_regionsRegion.


Player

Source: services/gameserver/src/models/player.py

Purpose: In-game state for a user — credits, turns, reputation snapshot, location, ship pointer, ARIA AI counters, and regional citizenship.

Fields:

name type constraints notes
id UUID PK
user_id UUID FK users.id unique, not null, CASCADE 1:1 with User
nickname String(50) nullable, unique index Optional in-game callsign. Set via the first-login confirmation step (see ../SYSTEMS/first-login.md#completion-side-effects-complete_first_login) — gated on player Yes/No after extraction-and-validation; 3–20 chars; alphanumeric + _ + - + single internal space; profanity- and impersonation-filtered; unique across all Players. NULL means no callsign set; UI falls back to User.username.
credits Integer default 10000
turns Integer default 1000
reputation JSONB default {} per-faction reputation snapshot
personal_reputation Integer default 0 -1000..+1000 alignment (migration fe22441146b1)
reputation_tier String(50) default Neutral cached tier name
name_color String(20) default #FFFFFF UI color cache
military_rank String(50) default Recruit rank progression
rank_points Integer default 0
aria_bonus_multiplier Float default 1.0 1.0-1.5 (migration f4a5b6c7d8e9)
aria_consciousness_level Integer default 1 1-5
aria_relationship_score Integer default 25 0-100
aria_total_interactions Integer default 0
aria_trust_score Float default 1.0 0.0–1.0; player-facing trust counter that ARIA's content-policy and abuse-detection layer reads. Drops on policy violations (jailbreak attempts, prompt injection, abusive content). Recovers slowly with sustained legitimate interaction. See ../OPERATIONS/aria.md, ../SYSTEMS/aria-dialogue.md.
aria_blocked_until DateTime nullable Auto-block timer set when trust hits a floor; ARIA refuses interaction until expiry. Admin can clear via the trust-reset endpoint.
aria_violation_count Integer default 0 Cumulative count of policy violations triggering trust drops. Resets on admin trust-reset; drives escalation thresholds (warn → soft-block → hard-block).
current_ship_id UUID FK ships.id nullable, SET NULL
home_sector_id, current_sector_id Integer default 1 integer sector numbers
is_docked Boolean default false
current_port_id UUID FK stations.id nullable, SET NULL station player is docked at (migration 5f5a988bdbb1); FK targets stations
is_landed Boolean default false
current_planet_id UUID FK planets.id nullable, SET NULL
team_id UUID FK teams.id nullable, SET NULL
attack_drones, defense_drones, mines, genesis_devices Integer defaults 0 aggregated counters
insurance JSONB nullable
last_game_login DateTime nullable distinct from User.last_login
turn_reset_at DateTime nullable
settings JSONB default {} UI / language / audio / accessibility / privacy / notifications / active bounties — full shape in jsonb-schema.md#playersettings
first_login JSONB default quick flag, separate from FirstLoginSession trail
is_active Boolean default true in-game deactivation
home_region_id, current_region_id UUID FK regions.id nullable
is_galactic_citizen Boolean default false unlocks cross-region travel
suspect_status Boolean default false temporary "gray" flag (separate from persistent personal_reputation); fires from early-window Cargo Wreck salvage and similar antisocial-but-not-criminal actions. While true, fed-space immunity is suspended and name color renders gray/yellow. See ../FEATURES/gameplay/ranking.md#suspect-status-temporary-flag.
suspect_until DateTime nullable timestamp when suspect_status auto-clears; max cumulative 4 hours from first triggering event.
suspect_team_snapshot UUID[] nullable Per ADR-0061 S-V4 — frozen team-mate set captured at suspect-status acquisition. Team-mate exemptions during the grace window evaluate against this snapshot, not live team membership. Mid-grace team changes don't affect existing exemptions; the snapshot is taken once at first acquisition and not refreshed by extension events. Cleared when suspect_status clears. NULL when not flagged; empty array when the player had no team.
wanted_status Boolean default false persistent "criminal" flag fired by piloting a ship reported stolen. While true, name renders red, fed-space immunity suspended, NPC patrols hunt, automatic bounty placed, daily personal-reputation drain. Clears when the pilot leaves the stolen ship. See ../FEATURES/gameplay/ranking.md#wanted-status, ../SYSTEMS/ship-registry.md.
wanted_until DateTime nullable optional auto-clear timestamp; usually NULL (Wanted persists for the duration the player pilots the stolen ship). Set non-NULL only if a future design adds a fixed-duration Wanted state for non-stolen-ship triggers. Parallel field to suspect_until.
daily_faction_rep_consumed Integer default 0 Per ADR-0056 N-V1. Sum of positive faction-rep deltas applied today across all factions. Compared against a single global daily pool (Launch: 100 rep / day) by apply_emergent_action before the rep delta lands. Reset to 0 by the throttle-rollover service per ADR-0053. The two cascade-redemption actions per ADR-0056 N-F2 bypass this counter.
daily_faction_rep_consumed_date Date nullable The local date the daily_faction_rep_consumed counter applies to. Rollover service uses (consumed > 0 AND consumed_date < player_local_today) as the reset predicate.

Relationships: - userUser. - shipsShip (1:many, FK Ship.registered_owner_id per ADR-0008); current_shipShip (FK current_ship_id, post-update). - teamTeam (FK team_id); team_membershipTeamMember (1:1). - faction_reputationsReputation (1:many). - planets (many-to-many via player_planets), stations (many-to-many via player_stations). - discovered_sectorsSector. - genesis_devicesGenesisDevice (1:many — note: this shadows the integer counter genesis_devices defined on the same class earlier; the relationship wins at runtime). - combat_logs_as_attacker, combat_logs_as_defenderCombatLog. - created_warp_tunnelsWarpTunnel. - market_transactionsresource.MarketTransaction (resource-enum table); enhanced_market_transactionsmarket_transaction.MarketTransaction (commodity-string table). - first_login_sessions (1:many), first_login_state (1:1). - regional_membershipsRegionalMembership; inter_regional_travelsInterRegionalTravel. - AI: trading_profile, ai_recommendations, aria_memories, aria_market_intelligence, aria_exploration_map, aria_trading_observations, ai_assistant. - commanded_fleetsFleet; fleet_membershipsFleetMember. - dronesDrone; drone_deploymentsDroneDeployment.

Note: the genesis_devices name is overloaded (integer column + relationship). In practice the relationship overwrites the column attribute. The integer counter is still backed by the database column; access via Player.__table__.c.genesis_devices if you need it.


PlayerTradingProfile

Source: services/gameserver/src/models/ai_trading.py (PlayerTradingProfile)

Purpose: Per-player trading profile that aggregates ARIA's view of how the player trades — risk tolerance, commodity preferences, performance metrics. Distinct from ARIATradingPattern (Trade DNA per-pattern); this is the player-level summary.

name type constraints notes
id UUID PK
player_id UUID FK players.id unique, CASCADE 1:1 with Player
risk_tolerance Float default 0.5 0.0 = ultra-conservative; 1.0 = high-risk
preferred_commodities JSONB default [] array of commodity names ordered by player preference
avoided_sectors JSONB default [] array of sector compound IDs the player consistently avoids
ai_assistance_level String(10) default medium minimal / medium / full (see ../FEATURES/gameplay/aria-companion.md#player-controlled-assistance-level)
notification_preferences JSONB default {} per-event notification toggles (route_alerts, market_alerts, combat_alerts, genesis_alerts)
last_active_sector UUID FK sectors.id nullable most recent sector the player traded from
average_profit_per_trade Float default 0.0 rolling average across the trading history
total_trades_analyzed Integer default 0 denominator for the rolling averages
created_at, updated_at DateTime defaults

Relationships: player (1:1).

The model class exists; the schema fields above are the launch target. Currently unused beyond the relationship reference. (See "Schema status" at the top of this file.)


AIRecommendation

Source: services/gameserver/src/models/ai_trading.py (AIRecommendation)

Purpose: Persistent record of a recommendation ARIA surfaced to the player, with feedback-loop fields the trading pattern fitness loop reads.

name type constraints notes
id UUID PK
player_id UUID FK players.id indexed, CASCADE
recommendation_type String(20) not null buy / sell / route / avoid
recommendation_data JSONB not null service-private opaque shape (see ./jsonb-schema.md for redaction rules)
confidence_score Float default 0.5 0.0–1.0
expected_profit Numeric nullable predicted profit in credits
risk_assessment String(10) default medium low / medium / high
reasoning Text nullable human-readable explanation surfaced in the UI
priority_level Integer default 3 1 (lowest) – 5 (highest); drives surface-priority in companion panel
created_at DateTime indexed, default utcnow
expires_at DateTime nullable, indexed recommendation auto-purges past this point
accepted Boolean nullable NULL = pending; True = player accepted; False = player explicitly dismissed
accepted_at DateTime nullable
outcome_profit Numeric nullable actual profit captured post-hoc; feeds Trade DNA fitness
outcome_timestamp DateTime nullable
feedback_score Integer nullable 1–5 player rating
feedback_text Text nullable optional player commentary

Relationships: player (FK).

The model class exists; columns above are the launch target. Recommendation generation, acceptance tracking, outcome attribution, and the feedback loop into Trade DNA are all design-stage. Anti-gaming on the feedback loop is documented in ../OPERATIONS/aria.md#recommendation-anti-gaming. (See "Schema status" at the top of this file.)


FirstLoginSession + Friends

Source: services/gameserver/src/models/first_login.py

Purpose: Captures the AI-driven onboarding dialog where the player tries to claim a starter ship from a guard; logs negotiation outcomes and drives starting state.

FirstLoginSession

name type constraints notes
id UUID PK
player_id UUID FK players.id not null, CASCADE
started_at DateTime server default now
completed_at DateTime nullable
ai_service_used Boolean default false
fallback_to_rules Boolean default false
guard_name, guard_title, guard_trait, guard_description String nullable personality bundle (migration ec92f8afd44a)
guard_base_suspicion Float nullable
ship_claimed Enum ship_choice nullable what player tried to claim
extracted_player_name String nullable
negotiation_skill Enum negotiation_skill_level nullable WEAK/AVERAGE/STRONG
final_persuasion_score Float nullable
outcome Enum dialogue_outcome nullable SUCCESS/PARTIAL_SUCCESS/FAILURE
awarded_ship Enum awarded_ship_type nullable
starting_credits Integer nullable
negotiation_bonus_flag, notoriety_penalty Boolean nullable
client_info, performance_metrics JSONB nullable

Relationships: player, dialogue_exchanges (1:many cascade), ship_options (1:1 cascade).

DialogueExchange

Per-turn record of the negotiation. Key columns: session_id (FK), sequence_number, npc_prompt, player_response, topic, persuasiveness, confidence, consistency, key_extracted_info JSONB, detected_contradictions ARRAY(String). AI logging fields (migration 6acc65ee7a72): ai_provider, ai_system_prompt, ai_user_prompt, ai_raw_response, believability, current_suspicion, response_time_ms, estimated_cost_usd, tokens_used.

ShipPresentationOptions

Per-session ship offering. Columns: session_id FK, available_ships ARRAY(String), escape_pod_present (default true), rarity_roll (0-100), special_event_active, seed_value.

ShipRarityConfig

Static config: ship_type (unique), rarity_tier (1-5), spawn_chance (0-100), base_credits, and three persuasion thresholds (weak_threshold, average_threshold, strong_threshold). Several migrations rebalance these (2e78250f47bc, 6b1d95a38c98, c5e32c313020).

PlayerFirstLoginState

Tracks completion across sessions. Columns: player_id (unique FK), has_completed_first_login, current_session_id (FK), attempts, last_attempt_at, completion bools (claimed_ship, answered_questions, received_resources, tutorial_started), and history arrays previous_ship_claims, previous_dialogue_strategies.


Team

Source: services/gameserver/src/models/team.py

Purpose: Player alliance with shared treasury, sector claims, and reputation aggregation policy.

Fields:

name type constraints notes
id UUID PK
name String(80) unique, not null
description Text nullable
leader_id UUID nullable not a FK constraint; resolved via Player.team_id and the leader check
reputation_calculation_method String(20) default AVERAGE AVERAGE/LOWEST/LEADER
tag String(10) nullable display tag
logo String nullable URL
is_public Boolean default true
max_members Integer default 4
sector_claims ARRAY(Integer) default [] claimed sector numbers
home_sector_id Integer nullable
recruitment_status String(20) default OPEN OPEN/INVITE_ONLY/CLOSED
treasury_credits, treasury_fuel, treasury_organics, treasury_equipment, treasury_technology, treasury_luxury_items, treasury_precious_metals, treasury_raw_materials, treasury_plasma, treasury_bio_samples, treasury_dark_matter, treasury_quantum_crystals Integer default 0 shared treasury per resource
total_credits, total_planets Integer default 0 aggregates
combat_rating, trade_rating Float default 0.0
join_requirements, member_roles, resource_sharing, invitation_codes JSONB defaults

Relationships: - membersPlayer (1:many via Player.team_id). - team_membersTeamMember (1:many cascade) — adds roles/permissions on top of plain membership. - reputationTeamReputation (1:1 cascade). - controlled_sectorsSector. - drones, fleets, messages.

TeamMember

Per-member roles/permissions. Columns: team_id FK, player_id FK, role (LEADER/OFFICER/MEMBER/RECRUIT), joined_at, permissions JSONB, individual booleans can_invite, can_kick, can_manage_treasury, can_manage_alliances, last_active, contribution_credits JSONB.


Reputation

Source: services/gameserver/src/models/reputation.py

Purpose: Per-(player, faction) reputation row with a 17-tier enum and effects flags.

Fields:

name type constraints notes
id UUID PK
player_id UUID FK players.id not null, CASCADE
faction_id UUID FK factions.id not null, CASCADE
current_value Integer default 0 numeric score
current_level Enum reputation_level default NEUTRAL 17 tiers from PUBLIC_ENEMY (-8) to EXALTED (+8)
title String(50) default Neutral display
decay_paused Boolean default false
history JSONB default []
trade_modifier Float default 0
port_access_level Integer default 0
combat_response String(50) default neutral one of friendly (Honored+), neutral (default), cautious (mid-negative), hostile (Hostile/Hated band), attack_on_sight (Public Enemy / Criminal). Drives NPC patrol behavior toward the player at faction-flagged stations and in faction-controlled sectors.
is_locked Boolean default false admin lock
lock_reason, lock_expires, special_status mixed nullable

Relationships: player, faction.

Indexes (per ADR-0051 SK32): - (player_id, faction_id) UNIQUE composite — primary access pattern: "what's my rep with faction X." - (faction_id, current_value DESC) — leaderboard queries ("top reputation with faction X"). - (player_id) — full scan over a player's faction rows.

Canonical query patterns documented to keep new query authors aware. New patterns require an index review.

TeamReputation

Aggregated team reputation. Columns: team_id (unique FK), calculation_method (AVERAGE/LOWEST/LEADER), faction_reputation JSONB, history JSONB, last_recalculated, next_recalculation, pending_notifications JSONB.


PlayerCentralBankAccount

Per ADR-0050. Region-independent depository at the Central Nexus, operated by the Galactic Concord. One account per player. Stores credits and commodities indefinitely. Receives planet-safe transfers and station credit-compensation fallbacks during region-termination cascades; players withdraw at any Starport Prime dock in the Central Nexus. Cross-link surfaces (per ADR-0065 R-O2): the cascade-deposit flow lives in ../SYSTEMS/region-lifecycle.md; the subscription-tier-driven Bank-access semantics live in ../OPERATIONS/monetization.md.

name type constraints notes
player_id UUID FK players.id PK, not null One account per player
credits Integer not null, default 0 Liquid; transfer to wallet free at any Starport Prime
commodities JSONB not null, default {} {ore: 5000, organics: 2000, ...} per the canonical commodity enum
ledger JSONB not null, default [] Append-only entries describing deposits/withdrawals for player audit. Each entry: {timestamp, type, amount, source, notes, access_override}. access_override BOOLEAN (per ADR-0054 X-I3) flags cascade-deposit entries that grant Bank withdrawal access regardless of current GC subscription status.
created_at, updated_at DateTime server defaults

Operations (per ../SYSTEMS/region-lifecycle.md Bank service):

  • Deposit — only by the cascade orchestrator (planet-safe transfer, station credit-compensation fallback). No normal player-side deposit; the Bank is a one-way recipient from cascade events.
  • Withdraw credits — instant, free, at any Starport Prime dock.
  • Withdraw commodities — 1 turn per 100 units (rounded up); to player's ship cargo; at any Starport Prime dock; subject to ship cargo space.

The ledger entries persist for the player's lifetime as audit history. Common entry types: cascade_safe_transfer (with 20% loss flag if Path A), cascade_station_compensation (50%-acquisition fallback), withdraw_credits, withdraw_commodity.

Bank access during GC lapse (per ADR-0054 X-I3): Bank operations normally require an active Galactic Citizen subscription. If a player's GC subscription has lapsed but their account holds credits/commodities deposited via cascade compensation (i.e., the ledger has at least one prior entry with access_override = true), withdrawals remain enabled FIFO until those access-override balances are drawn down. Logic: deposits made by the cascade orchestrator carry access_override = true; withdrawals consume the oldest access-override entries first; once the cumulative access-override balance reaches zero, normal GC-required gating resumes. This prevents the regress where a region terminates, the player's assets land in the Bank, then GC lapses and traps the assets.


TakeoverIntent

Per ADR-0050. Records a player's intent to take over a Suspended/Grace region while the PayPal payment flow runs. Cleaned up by a periodic sweep on expiry.

name type constraints notes
id UUID PK
region_id UUID FK regions.id not null The region being taken over
caller_user_id UUID FK users.id not null The player making the offer
approval_url String not null PayPal flow approval URL returned to client
status Enum (pending, won, lost, transferred, failed, expired) default pending Per ADR-0058 A-F3. State machine: pending → won (claim accepted, region transfer follows), pending → lost (claim rejected by serializer; escrow refunded in same tx), won → transferred (transfer succeeded), won → failed (transfer failed; escrow refunded; region stays with old owner), pending → expired (PayPal flow window timed out). Every transition is atomic.
created_at DateTime server default
expires_at DateTime not null created_at + 1 hour (the PayPal flow window)
completed_at DateTime nullable Set when commit_takeover() runs

Indexes: (region_id, status) for "pending intents on this region"; (expires_at) for the periodic-sweep cleanup; (region_id) WHERE status = 'pending' for the SELECT FOR UPDATE serializer per ADR-0058 A-F3.

Concurrent-claim serialization (per ADR-0058 A-F3): when multiple takeover claims race on the same region, the takeover handler opens a transaction and runs SELECT * FROM regions WHERE id = :region_id FOR UPDATE before evaluating the claim. First commit wins (status = 'won'); concurrent claims see the won state inside their own lock acquisition and reject (status = 'lost') with the offer escrow refunded inside the same transaction. No temporal window where money is held without a corresponding ownership state.


PlayerWarpKnowledge

Per ADR-0045 and ADR-0065 R-I3. Per-player warp discovery state — the source of truth for "what does this player know about the warp graph." Map-visibility queries read against it; ARIA reads it for hint generation per ADR-0045.

Column Type Constraints Notes
player_id UUID FK players.id not null, CASCADE composite PK part 1
warp_id UUID not null composite PK part 2; references either sector_warps or WarpTunnel (polymorphic per warp_type).
warp_type Enum (sector_warp, warp_tunnel) not null Disambiguates the polymorphic FK target.
discovered_at DateTime not null First time this player became aware of the warp (any source).
discovery_source Enum (personal_visit, warp_jumper_scan, corp_share, aria_hint) not null How the discovery happened. corp_share propagation may upgrade to personal_visit later (record updates, not duplicates).
corp_shared_at DateTime nullable Set when a corp-mate's discovery propagates to this player. Distinct from discovered_at so the system can distinguish first-hand from second-hand knowledge.

Composite PK: (player_id, warp_id).

Indexes: - (player_id) — "what does this player know" (the dominant query). - (warp_id) — "who knows about this warp" (rare; for ops/audit queries).

The Nexus warp visibility filter per ADR-0064 R-V3 reads this table — free-tier players who have a corp-mate's discovered_at set on the Nexus warp are still excluded from the visible-on-map set, because the filter intersects this table with the player's subscription tier.


Message

Source: services/gameserver/src/models/message.py

Purpose: Player-to-player and team messaging with threading and moderation.

Fields:

name type constraints notes
id UUID PK
sender_id UUID FK players.id not null, indexed
recipient_id UUID FK players.id nullable, indexed null for team messages
team_id UUID FK teams.id nullable, indexed null for direct messages
subject String(255) nullable
content Text not null
sent_at DateTime default utcnow, indexed
read_at DateTime nullable
deleted_by_sender, deleted_by_recipient Boolean defaults false soft delete per side
thread_id UUID indexed conversation grouping
reply_to_id UUID FK messages.id nullable direct reply
message_type String(20) default player player/team/system
priority String(10) default normal low/normal/high/urgent
flagged Boolean default false
flagged_reason String(255) nullable
moderated_at DateTime nullable
moderated_by UUID FK users.id nullable admin user

Relationships: sender, recipient, team, reply_to (self-ref), moderator (User).

Composite indexes: (recipient_id, read_at), (team_id, sent_at), (thread_id, sent_at).


Auth side-tables (referenced)

PlayerCredentials, AdminCredentials (see ./admin.md), OAuthAccount, RefreshToken, MFASecret, MFAAttempt all hang off User 1:1 or 1:many. They are mechanical auth plumbing — no game logic — and not expanded here.