gRPC Service Definitions
Protocol Buffer definitions for internal service communication.
Proto Compilation Notes
When importing types from common.proto in other proto files, use absolute package references with a leading dot:
// Correct - absolute reference
message MyMessage {
.galaxy.Vec3 position = 1;
}
// Incorrect - may cause namespace conflicts
message MyMessage {
galaxy.Vec3 position = 1; // Can fail if there's a local 'galaxy' field
}
The leading dot (.galaxy.Vec3) ensures the compiler looks up from the root namespace, avoiding ambiguity with local field names.
Common Types
// common.proto
syntax = "proto3";
package galaxy;
message Vec3 {
double x = 1;
double y = 2;
double z = 3;
}
message Quaternion {
double w = 1;
double x = 2;
double y = 3;
double z = 4;
}
message Timestamp {
int64 seconds = 1; // Unix seconds
int32 nanos = 2; // Nanoseconds
}
Physics Service
// physics.proto
syntax = "proto3";
package galaxy.physics;
import "common.proto";
service Physics {
// Process one tick of physics simulation
rpc ProcessTick(ProcessTickRequest) returns (ProcessTickResponse);
// Initialize celestial bodies (called once at startup)
rpc InitializeBodies(InitializeBodiesRequest) returns (InitializeBodiesResponse);
// Apply player control input to a ship
rpc ApplyControl(ApplyControlRequest) returns (ApplyControlResponse);
// Request a service (fuel, reset)
rpc RequestService(ServiceRequest) returns (ServiceResponse);
// Get current state of a ship
rpc GetShipState(GetShipStateRequest) returns (ShipState);
// Get all active ships
rpc GetAllShips(GetAllShipsRequest) returns (GetAllShipsResponse);
// Get all celestial bodies (current simulation state)
rpc GetAllBodies(GetAllBodiesRequest) returns (GetAllBodiesResponse);
// Spawn a new ship for a player
rpc SpawnShip(SpawnShipRequest) returns (SpawnShipResponse);
// Remove a ship (player deleted)
rpc RemoveShip(RemoveShipRequest) returns (RemoveShipResponse);
// Remove all ships (admin reset)
rpc ClearAllShips(ClearAllShipsRequest) returns (ClearAllShipsResponse);
// Restore bodies from Redis into physics memory (restart recovery)
rpc RestoreBodies(RestoreBodiesRequest) returns (RestoreBodiesResponse);
// Spawn a station in orbit or at a Lagrange point
rpc SpawnStation(SpawnStationRequest) returns (SpawnStationResponse);
// Remove a station
rpc RemoveStation(RemoveStationRequest) returns (RemoveStationResponse);
// Get all stations
rpc GetAllStations(GetAllStationsRequest) returns (GetAllStationsResponse);
// Remove all stations (admin reset)
rpc ClearAllStations(ClearAllStationsRequest) returns (ClearAllStationsResponse);
}
message ProcessTickRequest {
int64 tick_number = 1;
}
message ProcessTickResponse {
bool success = 1;
int64 tick_number = 2;
int64 duration_ms = 3; // Processing time
string error = 4; // Empty if success
}
// Note: ProcessTick returns E017 "Not initialized" if called before InitializeBodies.
// Physics service tracks initialization state internally and rejects tick processing
// until celestial bodies have been loaded.
message InitializeBodiesRequest {
repeated BodyInit bodies = 1; // Initial body states from galaxy service
}
message BodyInit {
string name = 1;
string type = 2; // "star", "planet", "moon"
Vec3 position = 3;
Vec3 velocity = 4;
double mass = 5;
double radius = 6;
string system_id = 11; // Star system (default: "sol")
}
message InitializeBodiesResponse {
bool success = 1;
string error = 2;
}
message ApplyControlRequest {
string ship_id = 1;
optional Vec3 rotation = 2; // -1 to 1 per axis (omit to leave unchanged)
optional double thrust_level = 3; // 0 to 1 (omit to leave unchanged)
}
// Note: At least one of rotation or thrust_level must be provided.
// If neither is provided, returns E001 error ("Invalid control: no fields provided").
// Omitted fields retain their current value in Redis.
// To explicitly set rotation to zero, send [0, 0, 0].
message ApplyControlResponse {
bool success = 1;
string error = 2; // Error code if failed (E001, E002, etc.)
}
message ServiceRequest {
string ship_id = 1;
ServiceType service = 2;
}
enum ServiceType {
SERVICE_UNKNOWN = 0;
SERVICE_FUEL = 1;
SERVICE_RESET = 2;
}
message ServiceResponse {
bool success = 1;
string error = 2; // Error code if failed
}
message GetShipStateRequest {
string ship_id = 1;
}
// Note: Returns gRPC NOT_FOUND status if ship_id doesn't exist.
// Error message: "Ship not found: {ship_id}"
message GetAllShipsRequest {
// Empty - returns all active ships
}
message GetAllShipsResponse {
repeated ShipState ships = 1;
}
message GetAllBodiesRequest {
// Empty - returns all celestial bodies with current simulation state
}
message GetAllBodiesResponse {
repeated BodyState bodies = 1;
}
message BodyState {
string name = 1;
string type = 2; // "star", "planet", "moon"
Vec3 position = 3; // Current position (meters)
Vec3 velocity = 4; // Current velocity (m/s)
double radius = 5; // meters
double rotation_period = 6; // Sidereal rotation period in seconds (negative = retrograde)
double axial_tilt = 7; // Obliquity in degrees
double mass = 8; // kg (for orbital calculations)
string system_id = 11; // Star system (default: "sol")
}
message ShipState {
string ship_id = 1;
string player_id = 2;
string name = 3;
Vec3 position = 4;
Vec3 velocity = 5;
Quaternion attitude = 6;
Vec3 angular_velocity = 7; // rad/s
double thrust_level = 8;
double fuel = 9;
double fuel_capacity = 10;
double mass = 11;
Vec3 wheel_momentum = 12; // N·m·s per axis
Vec3 rotation_input = 13; // Current player input (-1 to 1 per axis)
bool attitude_hold = 14;
Vec3 inertia_tensor = 15; // Diagonal [I_x, I_y, I_z] kg·m²
AttitudeMode attitude_mode = 16;
string ship_class = 17; // "fast_frigate" or "cargo_hauler"
string system_id = 22; // Star system (default: "sol")
}
enum AttitudeMode {
ATTITUDE_NONE = 0;
ATTITUDE_HOLD = 1;
ATTITUDE_PROGRADE = 2;
ATTITUDE_RETROGRADE = 3;
ATTITUDE_NORMAL = 4;
ATTITUDE_ANTINORMAL = 5;
ATTITUDE_RADIAL = 6;
ATTITUDE_ANTIRADIAL = 7;
ATTITUDE_TARGET = 8;
ATTITUDE_TARGET_RETROGRADE = 9;
}
message SpawnShipRequest {
string ship_id = 1; // UUID from players service
string player_id = 2;
string name = 3; // Player's username
string spawn_near_station = 4; // Optional station ID to spawn near
string ship_class = 5; // Optional: "fast_frigate" (default) or "cargo_hauler"
string system_id = 7; // Star system (default: "sol")
}
message SpawnShipResponse {
bool success = 1;
ShipState ship = 2; // Initial state (LEO position)
string error = 3;
}
message RemoveShipRequest {
string ship_id = 1;
}
message RemoveShipResponse {
bool success = 1;
string error = 2;
}
message ClearAllShipsRequest {
// Empty - removes all ships from Redis
}
message ClearAllShipsResponse {
bool success = 1;
int32 ships_removed = 2; // Count of ships deleted
string error = 3;
}
// Note: ClearAllShips is called by tick-engine during admin reset.
// It deletes all ship:* keys from Redis, resets game:total_spawns to 0,
// and publishes ship.removed event for each deleted ship.
message RestoreBodiesRequest {
// Empty - restores all bodies from Redis into physics memory
}
message RestoreBodiesResponse {
bool success = 1;
string error = 2;
int32 body_count = 3; // Number of bodies restored
}
// Note: RestoreBodies is called by tick-engine after physics restart.
// It loads evolved body positions/velocities from Redis into physics memory,
// avoiding an ephemeris reset that would displace bodies.
message StationState {
string station_id = 1;
string name = 2;
Vec3 position = 3;
Vec3 velocity = 4;
Quaternion attitude = 5; // Fixed, never changes (identity)
double mass = 6; // kg (default: 420,000)
double radius = 7; // meters (default: 50)
string parent_body = 8; // Reference body name
string system_id = 9; // Star system (default: "sol")
}
message SpawnStationRequest {
string name = 1; // Station name
string parent_body = 2; // "Earth", "Mars", etc.
double altitude = 3; // Altitude above surface (meters)
string secondary_body = 4; // For Lagrange point spawn (e.g., "Luna")
int32 lagrange_point = 5; // 4 or 5 for L-point spawn, 0 = altitude spawn
string system_id = 6; // Star system (default: "sol")
}
message SpawnStationResponse {
bool success = 1;
string station_id = 2; // Generated UUID
StationState station = 3;
string error = 4;
}
message RemoveStationRequest {
string station_id = 1;
}
message RemoveStationResponse {
bool success = 1;
string error = 2;
}
message GetAllStationsRequest {
// Empty - returns all active stations
}
message GetAllStationsResponse {
repeated StationState stations = 1;
}
message ClearAllStationsRequest {
// Empty - removes all stations from Redis
}
message ClearAllStationsResponse {
bool success = 1;
int32 stations_removed = 2;
string error = 3;
}
Players Service
// players.proto
syntax = "proto3";
package galaxy.players;
import "common.proto";
service Players {
// Authenticate player credentials
rpc Authenticate(AuthRequest) returns (AuthResponse);
// Register new player account
rpc Register(RegisterRequest) returns (RegisterResponse);
// Get player info
rpc GetPlayer(GetPlayerRequest) returns (PlayerInfo);
// List all players (admin)
rpc ListPlayers(ListPlayersRequest) returns (ListPlayersResponse);
// Delete player account
rpc DeleteAccount(DeleteAccountRequest) returns (DeleteAccountResponse);
// Reset player password (admin)
rpc ResetPassword(ResetPasswordRequest) returns (ResetPasswordResponse);
// Refresh player token
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
// Create admin account (admin-only)
rpc CreateAdminAccount(CreateAdminAccountRequest) returns (CreateAdminAccountResponse);
// Set admin role flag on a player (admin-only)
rpc SetAdminRole(SetAdminRoleRequest) returns (SetAdminRoleResponse);
}
// Note: Ship state is retrieved via physics.GetShipState, not from players service.
// api-gateway calls physics directly for ship state using ship_id from JWT or player info.
message AuthRequest {
string username = 1;
string password = 2;
}
message AuthResponse {
bool success = 1;
string player_id = 2;
string username = 3;
string ship_id = 4;
string token = 5; // JWT token
string error = 6; // Error if failed
bool is_admin = 7; // True if player has admin role
}
message RegisterRequest {
string username = 1;
string password = 2;
}
message RegisterResponse {
bool success = 1;
string player_id = 2;
string username = 3;
string ship_id = 4;
string token = 5; // JWT token (generated by players service)
string error = 6; // Error if failed (username taken, invalid format, etc.)
bool is_admin = 7; // Always false for regular registration
}
message GetPlayerRequest {
string player_id = 1;
}
// Note: Returns gRPC NOT_FOUND status if player_id doesn't exist.
// Error message: "Player not found: {player_id}"
message PlayerInfo {
string player_id = 1;
string username = 2;
string ship_id = 3;
Timestamp created_at = 4;
bool is_admin = 6; // True if player has admin role (field 5 skipped for compat)
}
message DeleteAccountRequest {
string player_id = 1;
}
message DeleteAccountResponse {
bool success = 1;
string error = 2;
}
message ListPlayersRequest {
// Empty - returns all players
}
message ListPlayersResponse {
repeated PlayerSummary players = 1;
}
message PlayerSummary {
string player_id = 1;
string username = 2;
string ship_id = 3;
Timestamp created_at = 4;
bool online = 5; // True if player has active WebSocket
bool is_admin = 6; // True if player has admin role
}
// Note: players service determines `online` status by reading Redis `connections:online` set.
// This is read-only access to data owned by api-gateway. Players service uses SISMEMBER
// for each player_id when building the response.
message ResetPasswordRequest {
string player_id = 1;
string new_password = 2;
}
message ResetPasswordResponse {
bool success = 1;
string error = 2; // E009 if player not found, E012 if password too short
}
message RefreshTokenRequest {
string player_id = 1;
}
message RefreshTokenResponse {
bool success = 1;
string token = 2; // New JWT token
string error = 3; // E009 if player not found
}
message CreateAdminAccountRequest {
string username = 1;
string password = 2;
}
message CreateAdminAccountResponse {
bool success = 1;
string player_id = 2;
string error = 3;
}
message SetAdminRoleRequest {
string player_id = 1;
bool is_admin = 2;
}
message SetAdminRoleResponse {
bool success = 1;
string error = 2;
}
Galaxy Service
// galaxy.proto
syntax = "proto3";
package galaxy.galaxy;
import "common.proto";
service Galaxy {
// Get all celestial bodies
rpc GetBodies(GetBodiesRequest) returns (GetBodiesResponse);
// Get a specific body by name
rpc GetBody(GetBodyRequest) returns (CelestialBody);
// Initialize bodies from ephemeris
rpc InitializeBodies(InitializeBodiesRequest) returns (InitializeBodiesResponse);
// List all star systems
rpc ListSystems(ListSystemsRequest) returns (ListSystemsResponse);
// Get bodies in a specific star system
rpc GetSystemBodies(GetSystemBodiesRequest) returns (GetBodiesResponse);
}
message GetBodiesRequest {
string system_id = 1; // Optional: filter by star system (empty = all)
}
message GetBodiesResponse {
repeated CelestialBody bodies = 1;
}
message GetBodyRequest {
string name = 1;
}
// Note: Returns gRPC NOT_FOUND status if body name doesn't exist.
// Error message: "Body not found: {name}"
message CelestialBody {
string name = 1;
BodyType type = 2;
Vec3 position = 3;
Vec3 velocity = 4;
double mass = 5;
double radius = 6;
string color = 7; // Hex color
string parent = 8; // Parent body name (empty for Sun)
string system_id = 12; // Star system (default: "sol")
}
message StarSystem {
string id = 1; // System identifier (e.g., "sol")
string name = 2; // Display name (e.g., "Sol")
Vec3 position = 3; // Galactic position in meters
string star_body = 4; // Name of primary star body
}
message ListSystemsRequest {
// Empty - returns all systems
}
message ListSystemsResponse {
repeated StarSystem systems = 1;
}
message GetSystemBodiesRequest {
string system_id = 1; // Star system to query
}
enum BodyType {
BODY_UNKNOWN = 0;
BODY_STAR = 1;
BODY_PLANET = 2;
BODY_MOON = 3;
BODY_ASTEROID = 4;
}
message InitializeBodiesRequest {
Timestamp start_date = 1;
}
message InitializeBodiesResponse {
bool success = 1;
bool used_fallback = 2; // True if bundled ephemeris was used
string error = 3;
}
Tick Engine Service
// tick_engine.proto
syntax = "proto3";
package galaxy.tick_engine;
import "common.proto";
service TickEngine {
// Get current tick status
rpc GetStatus(GetStatusRequest) returns (TickStatus);
// Pause tick processing
rpc Pause(PauseRequest) returns (PauseResponse);
// Resume tick processing
rpc Resume(ResumeRequest) returns (ResumeResponse);
// Set tick rate
rpc SetTickRate(SetTickRateRequest) returns (SetTickRateResponse);
// Create a snapshot
rpc CreateSnapshot(CreateSnapshotRequest) returns (CreateSnapshotResponse);
// List available snapshots
rpc ListSnapshots(ListSnapshotsRequest) returns (ListSnapshotsResponse);
// Restore from a snapshot
rpc RestoreSnapshot(RestoreSnapshotRequest) returns (RestoreSnapshotResponse);
// Factory reset the game (destructive)
rpc ResetGame(ResetGameRequest) returns (ResetGameResponse);
}
message GetStatusRequest {
// Empty
}
message TickStatus {
int64 current_tick = 1;
double tick_rate = 2; // Configured ticks per second
double actual_rate = 3; // Actual achieved rate
bool paused = 4;
int64 ticks_behind = 5; // 0 if caught up
Timestamp game_time = 6;
}
message PauseRequest {
// Empty
}
message PauseResponse {
bool success = 1;
int64 paused_at_tick = 2;
}
message ResumeRequest {
// Empty
}
message ResumeResponse {
bool success = 1;
int64 resumed_at_tick = 2;
}
message SetTickRateRequest {
double tick_rate = 1;
}
message SetTickRateResponse {
bool success = 1;
double previous_rate = 2;
double new_rate = 3;
string error = 4;
}
message CreateSnapshotRequest {
// Empty - creates snapshot of current state
}
message CreateSnapshotResponse {
bool success = 1;
int64 snapshot_id = 2;
int64 tick_number = 3;
Timestamp created_at = 4;
string error = 5;
}
message ListSnapshotsRequest {
// Empty - returns all available snapshots
}
message ListSnapshotsResponse {
repeated SnapshotInfo snapshots = 1;
}
message SnapshotInfo {
int64 id = 1;
int64 tick_number = 2;
Timestamp game_time = 3;
Timestamp created_at = 4;
}
message RestoreSnapshotRequest {
int64 snapshot_id = 1;
}
message RestoreSnapshotResponse {
bool success = 1;
int64 tick_number = 2; // Tick restored to
Timestamp game_time = 3; // Game time restored to
string error = 4; // E.g., "Snapshot not found"
}
message ResetGameRequest {
// Empty - resets game to initial state
}
message ResetGameResponse {
bool success = 1;
Timestamp game_time = 2; // Reset to this time (start_date)
string error = 3;
}
Error Codes
gRPC methods return errors in the error field using these codes:
| Code | Description |
|---|---|
| E001 | Invalid rotation value |
| E002 | Invalid thrust value |
| E003 | Invalid service type |
| E004 | Rate limit exceeded |
| E005 | Authentication failed |
| E006 | Ship not found |
| E007 | Service unavailable |
| E008 | Server error |
| E009 | Player not found |
| E010 | Username already taken |
| E011 | Invalid username format |
| E012 | Invalid password (too short) |
| E013 | Server full (max connections) |
| E014 | Account deleted by administrator |
| E015 | Invalid tick rate |
| E016 | Snapshot not found |
| E017 | Not initialized (physics called before InitializeBodies) |
| E022 | Target not found (attitude target entity does not exist) |
| E023 | Relative velocity too low (target prograde/retrograde degenerate) |
| E024 | Too close to target (target/anti-target distance degenerate) |
| E025 | Rule limit reached (max 10 automation rules per ship) |
| E026 | Invalid automation rule (validation failed) |
| E027 | Automation rule not found |