Multi-System Data Model
Issue: #728
Overview
Galaxy currently assumes a single star system (Sol). This spec adds system_id plumbing to the data model — proto definitions, Redis state, PostgreSQL schema, and galaxy service RPCs — so all other multi-system work (#729–#736) can build on it.
Scope
In scope:
system_idfield on all entity types (bodies, ships, stations, jump gates)- Per-system index sets in Redis
- StarSystem data structure and configuration
- ListSystems / GetSystemBodies RPCs
- PostgreSQL migration for ships.system_id
- Backward compatibility (lazy defaults to “sol”)
Out of scope:
- Per-system physics simulation
- Per-system WebSocket filtering
- Jump gate transit logic
- New star system ephemeris data
- Web-client changes
- Tick-engine behavior changes
StarSystem Data Structure
{
"id": "sol",
"name": "Sol",
"star_body": "Sun",
"position": [0, 0, 0]
}
| Field | Type | Description |
|---|---|---|
| id | string | Unique system identifier (lowercase, e.g. “sol”) |
| name | string | Display name (e.g. “Sol”) |
| star_body | string | Name of the primary star body |
| position | [x,y,z] | Galactic position in meters (for inter-system navigation) |
Systems are defined in services/shared/galaxy_config/shared_data.json under the SYSTEMS key.
Redis Namespacing
Decision: Flat keys with system_id field + index sets.
Entity keys remain flat (body:{name}, ship:{uuid}, station:{uuid}, jumpgate:{uuid}). Each entity hash gains a system_id field. Per-system index sets enable efficient lookups.
Index Sets
| Key Pattern | Type | Description |
|---|---|---|
system:{system_id}:bodies |
Set | Body names in this system |
system:{system_id}:ships |
Set | Ship UUIDs in this system |
system:{system_id}:stations |
Set | Station UUIDs in this system |
system:{system_id}:jumpgates |
Set | Jump gate UUIDs in this system |
Index sets are maintained by the physics service on entity create/delete/clear, and rebuilt by tick-engine on snapshot restore.
Backward Compatibility
Missing system_id field on any entity hash defaults to "sol". All reads use .get("system_id", "sol") or equivalent.
Proto Changes
galaxy.proto
| Message | Field | Number |
|---|---|---|
| CelestialBody | string system_id |
12 |
| GetBodiesRequest | string system_id |
1 (optional filter) |
New messages:
StarSystem { string id = 1; string name = 2; Vec3 position = 3; string star_body = 4; }ListSystemsRequest {}/ListSystemsResponse { repeated StarSystem systems = 1; }GetSystemBodiesRequest { string system_id = 1; }— reusesGetBodiesResponse
New RPCs:
ListSystems(ListSystemsRequest) returns (ListSystemsResponse)GetSystemBodies(GetSystemBodiesRequest) returns (GetBodiesResponse)
physics.proto
| Message | Field | Number |
|---|---|---|
| BodyInit | string system_id |
11 |
| BodyState | string system_id |
11 |
| ShipState | string system_id |
22 |
| StationState | string system_id |
9 |
| JumpGateState | string system_id |
9 |
| SpawnShipRequest | string system_id |
7 |
| SpawnStationRequest | string system_id |
6 |
| SpawnJumpGateRequest | string system_id |
5 |
players.proto
| Message | Field | Number |
|---|---|---|
| SpawnNewShipRequest | string system_id |
5 |
| ShipInfo | string system_id |
6 |
PostgreSQL Migration
ALTER TABLE ships ADD COLUMN IF NOT EXISTS system_id VARCHAR(32) NOT NULL DEFAULT 'sol';
CREATE INDEX IF NOT EXISTS idx_ships_system_id ON ships (system_id);
Migration file: db/alembic/versions/009_multi_system.py
Shared Configuration
services/shared/galaxy_config/shared_data.json gains a SYSTEMS key:
"SYSTEMS": {
"sol": { "name": "Sol", "star_body": "Sun", "position": [0, 0, 0] }
}
Exported as SYSTEMS dict from galaxy_config.__init__.
Service Changes
Galaxy Service
CelestialBodymodel gainssystem_id: str = "sol"- Bodies initialized from ephemeris are assigned
system_id="sol" - New
ListSystemsRPC returns systems from shared config - New
GetSystemBodiesRPC filters bodies by system_id GetBodiessupports optional system_id filter
Physics Service
- All entity models (
CelestialBody,Ship,Station,JumpGate) gainsystem_id: str = "sol" - Redis state:
system_idwritten to all entity hashes, read with.get("system_id", "sol") - Index sets (
system:{id}:bodies/ships/stations/jumpgates) maintained on set/delete/clear - gRPC: system_id read from proto requests, included in responses
Players Service
Shipmodel gainssystem_id: str = "sol"- Database: system_id included in ship INSERT/SELECT, default “sol” for old rows
- gRPC: system_id in ShipInfo response, read from SpawnNewShipRequest
Tick-Engine
- Passes system_id through BodyInit, SpawnStationRequest, SpawnJumpGateRequest
- Rebuilds system index sets on snapshot restore
- Cleans up system index sets on reset
API Gateway
- Includes system_id in WebSocket broadcast dicts (forward compatibility)