JSONB schemas¶
Status: 🚧 Partial — Galaxy, Cluster, WarpTunnel, and SpecialFormation JSONB shapes fully match code; Ship JSONB shapes are documented as launch targets diverging from the live legacy shape … · ⚠︎ contains code↔spec divergence (impl audit 2026-06-16)
Most domain models lean on PostgreSQL JSONB columns rather than fully-relational sub-tables. This page documents the launch-target shape of each JSONB column so consumers can write to it without guessing.
Launch-target schemas — code is migrating. Several ship-related JSONB columns (
Ship.maintenance,Ship.cargo,Ship.combat,Ship.upgrades,Ship.equipment_slots, and theShipSpecificationshapes forspecial_abilities,acquisition_methods,faction_requirements) are currently written by the gameserver in a simpler legacy shape. The shapes below represent the launch design thatship_service.pyandship_upgrade_service.pyshould converge to; until that convergence ships, integrators reading or writing these columns directly should consult the model files for what's currently on the wire and treat the shapes here as the migration target. All other JSONB columns on this page are accurate to what code writes today.
Conventions used below:
- Source. The model file that owns the column.
- Shape. Canonical JSON skeleton. Where a key's value is a primitive, the type is annotated as
int,str,float,bool, oriso8601. Where it's a collection, the inner element shape follows. - Defaults. The literal default a freshly-inserted row receives.
{}means "empty object";[]means "empty array";nullmeans SQL NULL. - Constraints. Application-level validation. The database does not enforce these; the gameserver does.
The contracts in this page apply to both JSONB (the explicit Postgres type) and the handful of columns declared as plain JSON (e.g. Fleet.battle_log).
Player¶
Source: services/gameserver/src/models/player.py
Player.reputation¶
{
"<faction_code>": {
"score": int,
"tier": str,
"last_updated": iso8601
}
}
Defaults: {} (empty; populated lazily on first interaction with a faction).
Constraints:
- Keys must match Faction.code.
- score clamped to [-1000, 1000].
- tier is the cached tier name derived from score; the source of truth is Reputation rows in reputations. The JSONB cache exists for fast reads on the player record.
Player.settings¶
{
"ui": {
"theme": str,
"compact_layout": bool,
"minimap_position": str
},
"language": {
"code": str,
"manual_override": bool
},
"audio": {
"master_volume": float,
"music_volume": float,
"sfx_volume": float,
"voice_volume": float,
"muted": bool
},
"accessibility": {
"colorblind_mode": str,
"reduced_motion": bool,
"screen_reader_hints": bool,
"subtitle_size": str
},
"gameplay": {
"auto_refuel": bool,
"confirm_destructive_actions": bool,
"default_trade_quantity": int,
"show_aria_suggestions": bool
},
"privacy": {
"show_on_leaderboards": bool,
"show_personal_reputation_publicly": bool,
"allow_team_invites": bool,
"allow_dms": str
},
"notification_preferences": {
"combat": bool,
"trade": bool,
"social": bool,
"system": bool,
"medals": bool
},
"medal_privacy": {
"visibility": str,
"pinned_medal_id": "str | null",
"curated_medal_ids": [str],
"show_hidden": bool,
"show_count_publicly": bool,
"broadcast_to_team": bool,
"unviewed_awards": [str]
},
"bounties": [
{
"id": str,
"placed_by": str,
"placed_by_name": str,
"amount": int,
"placed_at": iso8601,
"type": str
}
]
}
Defaults: {} — keys are populated lazily as the player interacts with the relevant feature. The gameserver supplies sane defaults at read time when a key is missing (e.g. ui.theme = "dark", language.code = inherit-from-region, audio.master_volume = 0.8, notification_preferences.medals = true, medal_privacy.visibility = "all", medal_privacy.show_hidden = false, medal_privacy.broadcast_to_team = true, medal_privacy.show_count_publicly = true).
Constraints:
- bounties is appended to by the bounty service (services/gameserver/src/services/bounty_service.py); each entry is one active bounty placed against this player.
- notification_preferences must contain only known channel keys (combat, trade, social, system, medals).
- accessibility.colorblind_mode ∈ {"off", "deuteranopia", "protanopia", "tritanopia"}.
- accessibility.subtitle_size ∈ {"small", "medium", "large", "x-large"}.
- privacy.allow_dms ∈ {"everyone", "team_only", "none"}.
- medal_privacy.visibility ∈ {"all", "curated", "none"}.
- medal_privacy.pinned_medal_id and entries in curated_medal_ids / unviewed_awards reference Medal.id (string PK on the medals table — see ../FEATURES/gameplay/medals.md).
- audio.*_volume clamped to [0.0, 1.0].
Status: —
ui/audio/accessibility/privacy/gameplayblocks are the target schema; the live model only enforcesnotification_preferencesandbountiestoday.
Player.first_login¶
{
"completed": bool,
"session_id": str,
"ship_chosen": str,
"completed_at": iso8601
}
Defaults: {"completed": false}.
Constraints: Once completed flips to true it must not flip back. Admin reset goes through DELETE /first-login/session, which resets the entire row, not this column.
Player.insurance¶
{
"type": "NONE | BASIC | STANDARD | PREMIUM",
"purchased_at": iso8601,
"expires_at": iso8601,
"claims_remaining": int
}
Defaults: null — the column is nullable; an absent row means no insurance.
Constraints: type must match the InsuranceType enum in services/gameserver/src/models/ship.py.
Ship¶
Source: services/gameserver/src/models/ship.py
Ship.maintenance¶
{
"current_integrity": float,
"max_integrity": float,
"last_serviced_at": iso8601,
"decay_rate_per_turn": float,
"next_failure_check_at": iso8601,
"failure_history": [
{"type": "MINOR | MAJOR | CATASTROPHIC", "at": iso8601, "repaired_at": iso8601}
]
}
Defaults: Set on ship creation by services/gameserver/src/services/ship_service.py from the matching ShipSpecification.
Constraints: current_integrity must be in [0, max_integrity]. When it hits 0, the ship is destroyed (is_destroyed = true).
Ship.cargo¶
{
"ore": int,
"organics": int,
"gourmet_food": int,
"fuel": int,
"equipment": int,
"exotic_technology": int,
"luxury_goods": int,
"colonists": int,
"precious_metals": int,
"lumen_crystals": int,
"quantum_shards": int,
"max_capacity": int,
"used_capacity": int
}
Defaults: Set on creation from ShipSpecification.max_cargo. All commodity slots default to 0.
Constraints: used_capacity ≤ max_capacity. The trading service is responsible for keeping the sum of commodity quantities equal to used_capacity. Commodity keys match the canonical commodity catalog at ../FEATURES/definitions.md#resource-types plus colonists as the cryosleep-pod cargo slot.
Mining-drop slots (per ADR-0065 M-E1): lumen_crystals and quantum_shards join the canonical cargo shape. Both are integer counts; default 0; produced as mining drops per ../FEATURES/economy/mining.md; extracted at TradeDocks for refining (per ADR-0009). precious_metals was already part of the price-ranges table per ADR-0062 E-D1; this entry confirms the cargo-slot parity.
Ship.combat¶
{
"attack_rating": int,
"defense_rating": int,
"shields_current": int,
"shields_max": int,
"shields_recharge_rate": float,
"shield_resistance": float,
"armor_rating": float,
"evasion": int,
"weapons": [
{"type": str, "damage": int, "range": int, "ammo": int}
]
}
Defaults: Populated from ShipSpecification at creation time. shield_resistance and armor_rating default to 0.0 (no damage absorbed); ship class and upgrades raise them — see the canonical damage stack in ../SYSTEMS/combat-resolver.md#damage-stack-order-of-operations.
Constraints:
- shields_current ≤ shields_max.
- shield_resistance ∈ [0.0, 1.0] — fraction of shield-damage absorbed (0.0 = no resistance, 1.0 = total absorption).
- armor_rating ∈ [0.0, 1.0] — fraction of hull-damage absorbed (post-shield residual). Same units as shield_resistance for symmetry.
Ship.upgrades¶
[
{
"type": "ENGINE | CARGO_HOLD | SHIELD | HULL | SENSOR | DRONE_BAY | GENESIS_CONTAINMENT | MAINTENANCE_SYSTEM",
"level": int,
"installed_at": iso8601,
"credits_spent": int
}
]
Defaults: [].
Constraints: At most one entry per type. level must be ≤ ShipSpecification.max_upgrade_levels[type].
Ship.equipment_slots¶
{
"<slot_name>": {
"equipment_id": str,
"installed_at": iso8601,
"condition": float
}
}
Defaults: {}.
Constraints: Slot names are validated against ShipSpecification.special_abilities.
Ship.insurance¶
Same shape as Player.insurance. null when uninsured.
ShipSpecification.max_upgrade_levels¶
{
"ENGINE": int,
"CARGO_HOLD": int,
"SHIELD": int,
"HULL": int,
"SENSOR": int,
"DRONE_BAY": int,
"GENESIS_CONTAINMENT": int,
"MAINTENANCE_SYSTEM": int
}
Defaults: Required; populated from the ship-spec seed data.
Constraints: Keys must match the UpgradeType enum.
ShipSpecification.special_abilities¶
[
{"name": str, "description": str, "slot": str, "compatible_with": [str]}
]
Defaults: [].
ShipSpecification.acquisition_methods¶
[
{"method": "purchase | quest_reward | faction_unlock | event_drop", "cost": int, "requirement": str}
]
Defaults: [].
ShipSpecification.faction_requirements¶
{
"<faction_code>": {"min_reputation": int, "tier": str}
}
Defaults: null (no requirement).
Sector¶
Source: services/gameserver/src/models/sector.py
Sector.resources¶
{
"has_asteroids": bool,
"asteroid_yield": {
"ore": int,
"precious_metals": int,
"radioactives": int
},
"gas_clouds": [
{"type": str, "density": int}
],
"has_scanned": bool
}
Defaults:
{
"has_asteroids": false,
"asteroid_yield": {"ore": 0, "precious_metals": 0, "radioactives": 0},
"gas_clouds": [],
"has_scanned": false
}
Constraints: asteroid_yield values are non-negative.
Sector.players_present¶
[
{
"player_id": str,
"ship_id": str,
"ship_type": str,
"joined_at": iso8601,
"last_seen": iso8601,
"is_cloaked": bool
}
]
Defaults: [].
Constraints: Maintained by the movement service; player_id must be a valid Player.id UUID. The fat-dict shape supports presence-aware features (sector-presence encounter checks per SYSTEMS/sector-presence.md). Stale entries are cleaned up when a player moves out or last_seen exceeds the presence-decay window.
Sector.ships_present¶
[
{"ship_id": str, "owner_id": str, "type": str, "is_cloaked": bool}
]
Defaults: [].
Constraints: Mirrors Ships rows whose sector_id equals this sector's sector_id. The JSONB is the read-fast denormalization; the relational column is canonical.
Sector.defenses¶
{
"drone_blocks": [
{
"count": int,
"owner_player_id": str,
"owner_team_id": "str | null",
"faction_code": "str | null",
"deployed_at": iso8601,
"is_hostile_to_passers": bool,
"weapon_profile": "attack | defense"
}
],
"dominant_owner_id": "uuid | null",
"dominant_owner_name": "str | null",
"dominant_team_id": "uuid | null",
"mines": int,
"mine_owner_id": "uuid | null",
"patrol_ships": [
{
"patrol_id": str,
"faction_code": str,
"squad_kind": "frontier_militia | corporate_security | terran_navy | cabal_skirmisher | mercenary_company | federation_marshal | nexus_sentinel",
"npc_character_ids": [str],
"ship_count": int,
"wanted_threshold": int,
"deployed_at": iso8601,
"scheduled_clear_at": "iso8601 | null",
"shift_handoff_state": {
"outgoing_npc_id": "str | null",
"incoming_npc_id": "str | null",
"overlap_started_at": "iso8601 | null",
"threat_notes": [{"player_id": str, "sector_id": int, "note": str, "timestamp": iso8601}],
"engaged_player_ids": [str],
"coverage_gap_started_at": "iso8601 | null"
}
}
],
"docked_npc_ships": [
{
"npc_id": str,
"ship_id": str,
"status": "docked_off_duty | docked_maintenance | docked_standby",
"docked_at": iso8601
}
]
}
Defaults:
{
"drone_blocks": [],
"dominant_owner_id": null,
"dominant_owner_name": null,
"dominant_team_id": null,
"mines": 0,
"mine_owner_id": null,
"patrol_ships": [],
"docked_npc_ships": []
}
docked_npc_ships semantics. Off-duty NPC ships parked at this sector (typically the host sector of an NPCBarracks or OutlawBase). The combat-resolver checks this array during attack-validation: ships listed with status = "docked_off_duty" at a sector flagged as the NPC's home_barracks_id host are shielded from player attack (ERR_NPC_SHIP_AT_BARRACKS). Ships at docked_standby (e.g., a pirate captain's ship parked at a public Fringe-Alliance port that isn't his outlaw base) are not shielded — players who infiltrate the port can attack them. docked_maintenance is a middle state — auto-repair queued during the NPC's sleep block; ships in this state are also shielded as a side effect of being at a barracks.
dominant_* fields are denormalized read-side conveniences for the UI — derived from the largest drone_blocks entry. Multiple players can stack defenses in the same sector via separate drone_blocks entries; each block tracks its own owner, team, faction (if NPC patrol), deploy timestamp, hostility flag (drives whether it engages passers-by or only attackers), and weapon profile.
patrol_ships is NPC-only per SD2 — these are faction patrol squads spawned by the world generator and zone-policing system, never player-deployable. Each entry carries faction_code and squad_kind so sector-presence._check_for_encounters can compare a passing ship's Wanted status (Player.personal_reputation and any active stolen-ship reports) against the patrol's wanted_threshold to decide whether to fire pursuit. scheduled_clear_at is non-NULL on temporary surge patrols (e.g., zone-event spawned squads); NULL means the patrol persists until the sector's policing rules say otherwise. Player-deployable defenses live in drone_blocks, not here.
Constraints:
- When any drone_blocks[].count > 0 or mines > 0, the corresponding owner field must not be null.
- patrol_ships[].faction_code must reference a valid faction (terran_federation, cabal, corporate, frontier, etc.); never NULL.
- patrol_ships[].owner_player_id is disallowed — the schema enforces NPC-only by omitting the field entirely.
Sector.active_events¶
[
{
"event_id": str,
"type": str,
"started_at": iso8601,
"expires_at": iso8601,
"payload": {}
}
]
Defaults: [].
Sector.nav_hazards¶
{
"<hazard_type>": {
"severity": int,
"duration_remaining": int
}
}
Defaults: {}.
Sector.nav_beacons¶
[
{"beacon_id": str, "owner_id": str, "message": str, "placed_at": iso8601}
]
Defaults: [].
Planet¶
Source: services/gameserver/src/models/planet.py
Planet.resources¶
{
"fuel_ore": int,
"organics": int,
"equipment": int,
"rare_minerals": int,
"extraction_modifier": float
}
Defaults: {}.
Constraints: Quantities are non-negative. Specialization can boost extraction_modifier.
Planet.economy¶
{
"trade_balance": int,
"tax_rate": float,
"currency_reserves": int,
"active_treaties": [str],
"trade_routes": [
{"partner_planet_id": str, "volume": int, "established_at": iso8601}
]
}
Defaults: {}.
Constraints: tax_rate in [0.0, 1.0].
Planet.production¶
{
"fuel": int,
"organics": int,
"equipment": int,
"research": int
}
Defaults: {"fuel": 0, "organics": 0, "equipment": 0, "research": 0}.
Constraints: Each value is non-negative; the per-tick rate is computed by _calculate_production_rates in services/gameserver/src/services/planetary_service.py.
Planet.active_events¶
Same shape as Sector.active_events.
Galaxy¶
Source: services/gameserver/src/models/galaxy.py
Galaxy.statistics¶
{
"total_sectors": int,
"discovered_sectors": int,
"station_count": int,
"planet_count": int,
"player_count": int,
"team_count": int,
"warp_tunnel_count": int,
"genesis_count": int
}
Defaults: All zeros.
Constraints: Counts are non-negative; discovered_sectors ≤ total_sectors. Maintained by Galaxy.update_statistics().
Galaxy.density¶
{
"station_density": int,
"planet_density": int,
"one_way_warp_percentage": int,
"resource_distribution": {
"ore": int,
"organics": int,
"equipment": int,
"luxury_goods": int,
"medical_supplies": int,
"technology": int
}
}
Defaults: station_density=10, planet_density=3, one_way_warp_percentage=5, distribution shown in source.
Constraints: Density percentages are validated against the canonical ranges (5–15 for stations, 10–25 for planets, ~10 for one-way warps). resource_distribution values should sum to 100.
Galaxy.faction_influence¶
{
"terran_federation": int,
"mercantile_guild": int,
"frontier_coalition": int,
"astral_mining_consortium": int,
"nova_scientific_institute": int,
"fringe_alliance": int,
"player_controlled": int,
"contested": int
}
Defaults: Shown in source.
Constraints: Values sum to 100.
Galaxy.state¶
{
"age_in_days": int,
"resource_depletion": int,
"economic_health": int,
"exploration_percentage": int,
"player_wealth_distribution": {
"top_10_percent": float,
"middle_40_percent": float,
"bottom_50_percent": float
}
}
Defaults: Shown in source.
Constraints: Wealth distribution percentages sum to 100.
Galaxy.events¶
{
"active_events": [
{"event_id": str, "started_at": iso8601, "expires_at": iso8601}
],
"scheduled_events": [
{"event_id": str, "scheduled_for": iso8601}
]
}
Defaults: {"active_events": [], "scheduled_events": []}.
Galaxy.combat_penalties¶
{
"federation": "high | medium | low | none",
"border": "high | medium | low | none",
"frontier": "high | medium | low | none"
}
Defaults: {"federation": "high", "border": "medium", "frontier": "none"}.
Galaxy.economic_modifiers¶
{
"global_price_multiplier": float,
"<commodity_key>": {"multiplier": float, "expires_at": iso8601}
}
Defaults: {}.
Cluster¶
Source: services/gameserver/src/models/cluster.py
Cluster.stats¶
{
"total_sectors": int,
"populated_sectors": int,
"empty_sectors": int,
"resource_value": int,
"danger_level": int,
"development_index": int,
"exploration_percentage": int
}
Defaults: Shown in source. All numeric fields are 0-100.
Cluster.resources¶
{
"primary_resources": [str],
"resource_distribution": {
"ore": int,
"organics": int,
"equipment": int,
"luxury_goods": int,
"medical_supplies": int,
"technology": int
},
"special_resources": [str]
}
Defaults: Shown in source. resource_distribution sums to 100.
Cluster.faction_influence¶
{
"terran_federation": int,
"mercantile_guild": int,
"frontier_coalition": int,
"astral_mining_consortium": int,
"nova_scientific_institute": int,
"fringe_alliance": int,
"dominant_faction": str
}
Defaults: Shown in source.
Constraints: Numeric values sum to 100; dominant_faction is the key with the highest value. Roster: only the six "territorial" allyable factions appear in cluster influence — Terran Federation, Mercantile Guild, Frontier Coalition, Astral Mining Consortium, Nova Scientific Institute, Fringe Alliance. Shadow Syndicate is intentionally excluded because per FEATURES/gameplay/faction-lore.md Syndicate territory is operational (it hides inside other factions' space) rather than geographical, so cluster-level influence percentages don't apply. Pirates and Cabal are hostile-only and similarly absent — Pirates have no fixed territory; Cabal occupies a single Terran-Space HQ sector (Border Zone, randomized 101–200 per ../FEATURES/gameplay/faction-lore.md#the-cabal) without cluster-level influence.
Cluster.resource_modifiers¶
{
"<resource_key>": float
}
Defaults: {}. Each value is a multiplier applied to extraction in this cluster.
Cluster.discovery_requirement¶
{
"min_player_rank": str,
"min_sectors_explored": int,
"required_quest": str
}
Defaults: null (no requirement, cluster starts visible).
WarpTunnel¶
Source: services/gameserver/src/models/warp_tunnel.py
WarpTunnel.properties¶
{
"length": float,
"stability_rating": int,
"expected_lifetime": "iso8601 | null",
"age": int,
"traversal_cost": int,
"cool_down": int,
"discovered": bool,
"discoverer_id": "uuid | null",
"discovery_date": "iso8601 | null",
"affected_by_storms": bool
}
Defaults: Shown in source. Natural tunnels have expected_lifetime: null; artificial tunnels carry an explicit collapse time.
WarpTunnel.tunnel_status¶
{
"is_active": bool,
"disruption": "null | { reason: str, until: iso8601 }",
"traffic_level": int,
"last_traversal": "iso8601 | null",
"maintenance_status": "null | { state: str, scheduled_for: iso8601 }"
}
Defaults: Shown in source.
WarpTunnel.source_endpoint / destination_endpoint¶
Same shape on both:
{
"sector_id": "uuid | null",
"cluster_id": "uuid | null",
"region_id": "uuid | null",
"coordinates": {"x": int, "y": int, "z": int},
"controlling_faction": "str | null",
"is_secured": bool,
"access_requirements": "null | { min_rank: str, min_reputation: int }"
}
Defaults: Shown in source.
Constraints: sector_id, cluster_id, region_id should be populated when the endpoint is real. access_requirements is null on public tunnels.
WarpTunnel.artificial_data¶
{
"creator_id": str,
"created_at": iso8601,
"fuel_cost": int,
"decay_per_day": float,
"anchor_method": str,
"owner_team_id": "uuid | null"
}
Defaults: null (only populated for type = ARTIFICIAL).
WarpTunnel.traversal_history¶
[
{"ship_id": str, "owner_id": str, "at": iso8601, "direction": "outgoing | incoming"}
]
Defaults: []. Capped to the last 100 entries; older entries are evicted by the traversal handler.
WarpTunnel.access_requirements¶
{
"min_rank": str,
"min_reputation": int,
"required_faction": str,
"required_quest": str
}
Defaults: null.
WarpTunnel.special_effects¶
{
"<effect_name>": {"magnitude": float, "duration_turns": int}
}
Defaults: {}.
SpecialFormation¶
Source: services/gameserver/src/models/special_formation.py
SpecialFormation.properties¶
Type-specific generation parameters. The shape varies by the row's SpecialFormationType; keys present in a row must match the row's type. A BUBBLE row never carries target_formation_id; a BACKDOOR row never carries interior_size.
{
// BUBBLE / DEAD_END_BUBBLE
"interior_size": int,
"link_tunnel_depth": int,
"branching": "chain | tree | cyclic",
// GOLD_BUBBLE
"gateway_count": int,
"interior_size_min": int,
// TUNNEL
"length": int,
"one_way_bias": float,
// DEAD_END
"parent_kind": "open | tunnel | bubble",
"is_unfigged_only": bool,
// WARP_SINK
"entry_count": int,
"recovery_method": "slipdrive | distress | scheduled_clear | none",
// BACKDOOR
"target_formation_id": str,
"entry_distance": int,
// BLISTER
"interior_size": int,
"bypass_distance": int,
// ESCAPE_HATCH
"surprise_source_distance": int,
// LOST_SECTOR
"exitWarp": null | { "destinationSectorId": int },
// LOST_CLUSTER
"interiorClusterId": str,
"exitWarp": null | { "sourceSectorId": int, "destinationSectorId": int },
// ARCHIPELAGO
"memberClusterIds": [str],
"crossWarpCount": int,
"exitWarps": [{ "sourceSectorId": int, "destinationSectorId": int }]
}
Defaults: {} — only the keys relevant to the row's type are populated by the generator.
Constraints:
- interior_size, length, entry_count, gateway_count, interior_size_min, entry_distance, bypass_distance, surprise_source_distance are positive integers.
- link_tunnel_depth ≥ 0.
- one_way_bias ∈ [0.0, 1.0].
- branching ∈ {"chain", "tree", "cyclic"}.
- parent_kind ∈ {"open", "tunnel", "bubble"}.
- recovery_method ∈ {"slipdrive", "distress", "scheduled_clear", "none"}. WARP_SINK rows set this to a non-"none" value unless they are deliberate traps.
- target_formation_id for BACKDOOR rows references a SpecialFormation.id that exists in the same region.
- entry_distance and surprise_source_distance are graph hops; min 1, max 32 (typical region diameter).
- gateway_count for GOLD_BUBBLE ∈ [1, 3].
- exitWarp on LOST_SECTOR and LOST_CLUSTER is either null or an object — never an array. destinationSectorId must reference a sector in the main connected sub-graph (not inside any lost formation and not in fedspace).
- exitWarps on ARCHIPELAGO is an array (may be empty); each entry's sourceSectorId must belong to a different member cluster (per ADR-0070).
- interiorClusterId (LOST_CLUSTER) references a Cluster row whose sector set equals the formation's interior_sector_ids. memberClusterIds (ARCHIPELAGO) references 2–6 Cluster rows whose union of sector sets equals the formation's interior_sector_ids.
SpecialFormation.discovery_requirement¶
{
"ship_class": str,
"min_reputation": {"<faction_code>": int},
"warp_jumper_required": bool,
"min_player_rank": str
}
Defaults: null — most formations are discoverable on first visit and scan with no gating.
Constraints: All keys optional. The discovery service evaluates each present key against the visiting player; a missing key means the requirement does not apply. Used for GOLD_BUBBLE story sites and other operator-placed rare formations.
Station¶
Source: services/gameserver/src/models/station.py
Station.commodities¶
{
"<commodity_key>": {
"quantity": int,
"capacity": int,
"base_price": int,
"current_price": int,
"production_rate": int,
"price_variance": int,
"buys": bool,
"sells": bool
}
}
Defaults: Shown in source — eight commodity keys populated with class-appropriate defaults.
Constraints: quantity ≤ capacity. current_price is recomputed by the market service on each price-update tick.
Station.trader_personality¶
Drives haggling behavior for the station's NPC trader: difficulty, preferred persuasion approach, memory of past interactions, and per-player trust.
{
"type": "Federation | Border | Frontier | Luxury | Black Market",
"haggling_difficulty": int,
"preferred_appeal_types": [str],
"memory_duration_days": int,
"trust_level": int,
"quirks": [str]
}
Defaults: Populated at station creation from the personality archetype keyed off the station's class. Archetype defaults:
type |
haggling_difficulty |
preferred_appeal_types |
memory_duration_days |
|---|---|---|---|
| Federation | 3 | procedural, compliance |
14 |
| Border | 5 | economic, personal |
30 |
| Frontier | 7 | personal, risk |
60 |
| Luxury | 8 | cultural, aesthetic |
21 |
| Black Market | 9 | risk, discretion |
90 |
trust_level defaults to 0; quirks defaults to [].
Constraints:
- type must be one of the five archetypes above.
- haggling_difficulty is in [1, 10]; higher values are harder to haggle against.
- preferred_appeal_types entries are drawn from procedural, compliance, economic, personal, cultural, aesthetic, risk, discretion. A player using a preferred appeal type receives a +20% success modifier (see ../FEATURES/economy/haggling.md).
- memory_duration_days is in [7, 90] and controls how long the station remembers a given player's haggling history.
- trust_level is in [-1000, 1000], accumulated per player via repeated successful trades and eroded by failed haggling or hostile actions; high trust eases haggling difficulty.
- quirks are free-form behavior modifiers consumed by the haggling resolver. Examples: "always_haggles_under_500_units", "responds_to_humor", "refuses_pirates", "prefers_carrier_owners".
Station.services¶
{
"ship_dealer": bool,
"ship_repair": bool,
"ship_maintenance": bool,
"ship_upgrades": bool,
"insurance": bool,
"drone_shop": bool,
"genesis_dealer": bool,
"mine_dealer": bool,
"diplomatic_services": bool,
"storage_rental": bool,
"market_intelligence": bool,
"refining_facility": bool,
"luxury_amenities": bool
}
Defaults: Shown in source.
Station.defenses¶
{
"defense_drones": int,
"max_defense_drones": int,
"auto_turrets": bool,
"defense_grid": bool,
"shield_strength": int,
"patrol_ships": int,
"military_contract": bool
}
Defaults: Shown in source.
Station.ownership¶
{
"owner_id": str,
"owner_type": "player | team | syndicate | faction",
"acquired_at": iso8601,
"acquisition_price": int,
"acquisition_method": "purchase | trade_volume | faction_standing | takeover",
"tariff_rate": float,
"docking_fee": int,
"service_charge_multiplier": float,
"price_adjustment_pct": float,
"preferred_traders": [str],
"shareholders": [
{
"player_id": str,
"stake_pct": float,
"joined_at": iso8601,
"voting_weight": float
}
],
"treasury_balance": int,
"treasury_pending_payouts": int,
"withdrawal_schedule": "monthly | quarterly | manual",
"upgrade_levels": {
"extended_storage": int,
"market_intelligence": int,
"automated_trading": int,
"shipyard": int,
"refining": int,
"luxury_amenities": int,
"defense_grid": int,
"security_patrol": bool,
"military_contract": iso8601
},
"lifetime_revenue": int,
"monthly_revenue_history": [
{
"period": "YYYY-MM",
"tariff_revenue": int,
"docking_revenue": int,
"service_revenue": int,
"storage_revenue": int,
"info_revenue": int,
"maintenance_costs": int,
"net_profit": int
}
]
}
Defaults: null until first acquired. Field semantics:
tariff_rate— 0.02–0.08 (range 2%–8%); applied to every player buy/sell at the station.docking_fee— 50–500 credits per docking; flat charge, owner-set.service_charge_multiplier— 0.8–2.0 (×0.8 loss-leader to ×2.0 premium); applied to repair/refueling/drone/refining services.price_adjustment_pct— −0.10 to +0.10 (−10% to +10%); owner can nudge buy and sell prices within this band, applied after the standard supply/demand formula.shareholders— forowner_type = syndicate; max 10 stakeholders; sum ofstake_pct≤ 100; voting threshold 50% by default. See../FEATURES/economy/port-ownership.md#syndicate.withdrawal_schedule— when treasury auto-distributes profits to owner / shareholders.upgrade_levels— integer level per upgrade type (Extended Storage L1-3, Market Intelligence L1-3, Auto Trading L1-3, Shipyard L1-3, Refining L1-3, Luxury Amenities L1-3, Defense Grid L1-3); booleans for Security Patrol (active subscription) and Military Contract (binding-until timestamp).monthly_revenue_history— rolling 12-month accounting; older periods rolled intolifetime_revenue.
Status: — Station ownership ships only the 5-field stub at launch (owner_id, owner_type, acquired_at, acquisition_price, tariff_rate). The full shape above is the launch-target home for syndicate stakes, treasury accounting, upgrade levels, and price adjustments described in ../FEATURES/economy/port-ownership.md.
Station.acquisition_requirements¶
{
"min_trade_volume": int,
"min_faction_standing": str,
"base_price": int
}
Defaults: Shown in source.
Station.price_modifiers, service_prices, active_events¶
price_modifiers and service_prices are owner-set {<key>: int} maps; default {}. active_events follows the same shape as Sector.active_events.
Reputation (Faction reputation cache)¶
Source: services/gameserver/src/models/reputation.py
Reputation.history (player-faction)¶
[
{"delta": int, "reason": str, "at": iso8601, "source_event": str, "sector_id": "uuid | null"}
]
Defaults: []. Capped at the most-recent 100 entries. sector_id is the Sector.id where the triggering event occurred (combat, port transaction, sector-defense event); null for non-spatial events (admin grant, decay tick, server-initiated).
TeamReputation.faction_reputation¶
{"<faction_code>": int}
Defaults: {}.
TeamReputation.history¶
Same shape as Reputation.history.
TeamReputation.pending_notifications¶
[
{"id": str, "type": str, "message": str, "created_at": iso8601}
]
Defaults: [].
Team¶
Source: services/gameserver/src/models/team.py
Team.join_requirements¶
{
"min_player_rank": str,
"min_personal_reputation": int,
"required_faction_standing": {"<faction_code>": int},
"invitation_only": bool
}
Defaults: {}.
Team.member_roles¶
{
"<player_uuid>": {"role": str, "since": iso8601}
}
Defaults: {}.
Team.resource_sharing¶
{
"auto_share_credits": bool,
"auto_share_cargo": bool,
"share_percent": int
}
Defaults: {}.
Team.invitation_codes¶
[
{"code": str, "created_by": str, "created_at": iso8601, "expires_at": iso8601, "max_uses": int, "uses": int}
]
Defaults: [].
TeamMember.permissions¶
TeamMember has explicit boolean columns (can_invite, can_kick, can_manage_treasury, can_manage_alliances) for the canonical permission set; the permissions JSONB is a free-form extension surface for per-team overrides and future flags. Recommended shape mirrors the column names so role-resolution can fall back uniformly:
{
"can_invite": bool,
"can_kick": bool,
"can_manage_treasury": bool,
"can_manage_alliances": bool,
"<custom_flag>": bool
}
Defaults: {}. Custom flags are scoped to the owning team and have no game-side meaning unless team-specific code reads them.
TeamMember.contribution_credits¶
{
"credits_donated": int,
"resources_donated": {"<commodity_key>": int},
"ships_destroyed": int,
"contracts_completed": int
}
Defaults: {}.
Other notable JSONB columns¶
DroneandDroneDeployment— seeservices/gameserver/src/models/drone.py. No JSONB columns; deployment state is fully relational.Fleet.battle_log(JSON) — append-only log of round-by-round battle events.[]default.Fleet.resources_looted(JSON) —{<commodity_key>: int}, default{}.MarketTransaction— relational, no JSONB.AIRecommendation.recommendation_data,AIRecommendation.contextual_data— opaque payload owned by the AI service. Seeservices/gameserver/src/services/ai_trading_service.pyfor shape; treat as service-private.ARIA*models —services/gameserver/src/models/aria_personal_intelligence.py. MultipleJSONcolumns documented inline in the model file; treat their shape as private to the ARIA service.Region.language_pack,Region.aesthetic_theme,Region.traditions,Region.social_hierarchy,Region.trade_bonuses— seeOPERATIONS/multi-regional.md. Each is owner-controlled metadata used to differentiate regions; the gameserver does not validate inner shapes.RegionalElection.candidates,RegionalElection.results— election scaffolding documented inOPERATIONS/multi-regional.md.
Migrations¶
JSONB shape changes go through Alembic migrations under services/gameserver/alembic/versions/. Adding a key is non-breaking; renaming or removing a key requires a backfill step. Reads that depend on a key existing should default-fall-back rather than assume the row was migrated.