Per-System Physics Integration

Overview

Bodies are grouped by system_id during physics integration so each star system is integrated independently. Ships, stations, and jump gates only experience gravity from bodies in their own system. This prevents gravitational cross-talk between systems that are light-years apart.

Currently only the Sol system exists, so these changes are transparent — behavior is identical to the flat-list approach.

Design

Physics service (simulation.py)

In process_tick(), bodies are grouped into a dict[str, list[CelestialBody]] keyed by system_id (defaulting to "sol" when absent).

  1. N-body integrationupdate_bodies_compute() is called once per system group. Energy and angular momentum are summed across systems.
  2. Reference body snapshotsref_bodies (pre-update position snapshots) are built per system for attitude reference lookups.
  3. Ship integration — Each ship’s system_id selects its system’s bodies and ref_bodies for gravity computation and collision checks.
  4. Station/jump gate integration — Same per-system scoping.

Tick-engine (automation_helpers.py)

_find_reference_body() accepts an optional system_id parameter. When provided, bodies whose system_id field does not match are skipped during both the Hill-sphere SOI search and the M/r² fallback.

Tick-engine (automation.py) — per-system body cache

evaluate_all_ships() fetches body positions per system rather than globally. For each ship with automation rules, get_ship_system_id() (a single hget on the ship hash) determines which system the ship belongs to. Unique system IDs are collected and get_system_body_positions() is called once per system. The resulting dicts are cached in a system_bodies: dict[str, dict] map so that ships in the same system share a single body-position dict.

This replaces the previous get_all_body_positions() call (global SCAN body:*) which passed every body in the universe to every ship.

_build_context() and _evaluate_ship() pass the ship’s system_id through to _find_reference_body().

Tick-engine (state.py) — per-system queries

Redis index sets (system:{id}:bodies, system:{id}:ships, etc.) written by the physics service are consumed by new query methods:

  • get_ship_system_id(ship_id) — single hget for a ship’s system_id.
  • get_system_body_positions(system_id) — pipeline-fetch bodies in a system.
  • get_system_ship_ids(system_id) — ship IDs in a system.
  • get_system_station_ids(system_id) — station IDs in a system.
  • get_system_jumpgate_ids(system_id) — jump gate IDs in a system.
  • get_available_systems() — discover systems by scanning system:*:bodies.

Tick-engine (state.py) — tick.completed broadcast

publish_tick_completed() accepts an optional systems list. When provided, the tick.completed event includes a comma-separated systems field listing all active star systems.

Tick-engine (tick_loop.py) — system discovery

Available systems are discovered once during initialization via get_available_systems() and cached in _available_systems. The list is passed to publish_tick_completed() each tick.

Tick-engine (automation_helpers.py) — context enrichment

_build_context() includes _ship_system_id in the returned dict so that maneuver modules can pass it to _find_reference_body() without re-reading the ship hash.

Tick-engine maneuver modules — system-scoped reference body

maneuver_transfer.py and maneuver_approach.py extract _ship_system_id from the context dict and pass it to _find_reference_body() at all call sites. This ensures cross-SOI detection only considers bodies within the ship’s star system.

Galaxy service (service.py)

_load_fallback_ephemeris() loads the primary ephemeris-j2000.json and then scans for additional ephemeris-{system_id}-j2000.json files, merging their bodies. Each body already carries a system_id field in the JSON.

API gateway (websocket.py) — enriched galaxy:systems response

The request_systems WebSocket handler calls ListSystems via gRPC, then enriches each system with dynamic counts from Redis:

  • body_count: SCARD system:{id}:bodies
  • player_count: SCARD system:{id}:ships

Response shape:

{
  "type": "galaxy:systems",
  "systems": [
    {
      "id": "sol",
      "name": "Sol",
      "star_body": "Sun",
      "position": {"x": 0, "y": 0, "z": 0},
      "body_count": 12,
      "player_count": 3
    }
  ]
}

API gateway (websocket.py) — switch_system placeholder

The switch_system message handler returns an error until inter-system travel mechanics are implemented (#736). Response:

{
  "type": "error",
  "code": "NOT_IMPLEMENTED",
  "message": "Inter-system travel not yet implemented (#736)"
}

API gateway (admin.py) — enriched GET /api/admin/systems

Same enrichment as the WebSocket galaxy:systems response: each system includes body_count and player_count from Redis index sets.

API gateway (admin.py) — POST /api/admin/ship/{ship_id}/system

Admin endpoint to move a ship between star systems (for testing multi-system infrastructure). Request body:

{"system_id": "alpha_centauri"}

Steps:

  1. Validate ship exists (EXISTS ship:{ship_id})
  2. Read current system_id from ship hash (default "sol")
  3. Remove ship from old system index (SREM system:{old}:ships)
  4. Update ship hash system_id field (HSET ship:{ship_id} system_id)
  5. Add ship to new system index (SADD system:{new}:ships)

Returns {"success": true, "previous_system": "sol", "new_system": "alpha_centauri"}.

Invariants

  • A body with no system_id field defaults to "sol".
  • A ship/station/jumpgate with no system_id defaults to "sol".
  • With only Sol bodies present, output is bit-identical to the previous flat-list integration.
  • Cross-system gravity is never computed.

Back to top

Galaxy — Kubernetes-based multiplayer space game

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