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

Back to top

Galaxy — Kubernetes-based multiplayer space game

This site uses Just the Docs, a documentation theme for Jekyll.