Test strategy and coverage requirements for Galaxy.
Coverage Goals
Category
Target
Rationale
Physics/simulation
95%
Critical correctness, bugs cause cascading errors
API endpoints
90%
Core functionality, user-facing
Data persistence
90%
Data integrity critical
Web client logic
80%
UI can tolerate minor bugs
Overall
90%
High confidence before deployment
Coverage Tooling
Python Services
All Python services use pytest-cov (already a dev dependency) with branch coverage enabled.
Setting
Value
Notes
Tool
pytest-cov >= 4.1.0
Installed in dev dependencies
Source
src
Measures coverage of src/ package only
Branch coverage
Enabled
Detects untested conditional paths
Report format
term-missing
Shows uncovered line numbers in terminal
fail_under
Not set initially
Baselines measured first, thresholds added in follow-up
Configuration lives in each service’s pyproject.toml:
[tool.pytest.ini_options]addopts="--cov=src--cov-report=term-missing"[tool.coverage.run]source=["src"]branch=true[tool.coverage.report]show_missing=trueexclude_lines=["pragma: no cover","if __name__ == .__main__.","pass",]
Coverage runs automatically via addopts — no extra flags needed on pytest invocations.
CI installs pytest-cov in the test image and copies pyproject.toml so the config takes effect. The CI test command (python -m pytest tests/ -v --tb=short) is unchanged.
Web Client
The web-client enforces 88% line coverage via vitest (npx vitest run --coverage).
Test Framework
Component
Framework
Python services
pytest
Web client
Jest + Testing Library
E2E
Playwright
API
pytest + httpx
Protobuf Testing Pattern
gRPC service tests use real compiled protobuf modules from the Docker image — proto modules are NOT mocked. This avoids the anti-pattern where PhysicsServicer(physics_pb2_grpc.PhysicsServicer) inherits from a MagicMock base class, which causes all method access to return MagicMock instead of real async methods.
Dependencies (redis_state, simulation, service) are still mocked
Request objects use MagicMock() with explicit attribute values (e.g., request.dt = 1.0)
Assertions check return value fields directly (e.g., response.success, response.bodies)
Tests run in Docker where proto compilation happens during image build
Never use importlib.reload with proto mocking — it pollutes sys.modules for subsequent tests
Per-Service Unit Test Cases
Physics Service
Models (test_models.py)
Test Case
Expected Behavior
Vec3 creation
Construct from (x, y, z); default to zero vector
Vec3 magnitude
(3, 4, 0) → 5.0
Vec3 normalization
Result has magnitude 1.0; zero vector returns zero
Vec3 dot product
(1,0,0) · (0,1,0) → 0; (1,0,0) · (1,0,0) → 1
Vec3 cross product
(1,0,0) × (0,1,0) → (0,0,1)
Quaternion creation
Construct from (w, x, y, z)
Quaternion identity
(1, 0, 0, 0) is identity
Quaternion normalization
Result has magnitude 1.0
Quaternion rotation
Rotating (1,0,0) by 90° about Z → (0,1,0)
CelestialBody creation
Construct with position, velocity, mass, radius
Ship creation
Construct with position, velocity, attitude, fuel
Ship mass calculation
Dry mass + remaining fuel mass
Ship inertia calculation
Moment of inertia from mass distribution
Simulation (test_simulation.py)
Gravity:
Test Case
Expected Behavior
Single body no acceleration
Lone body has zero gravitational acceleration
Two bodies mutual attraction
Bodies attract with F = GMm/r²
Two bodies known acceleration
Acceleration matches analytical calculation
Three bodies superposition
Net gravity = vector sum of pairwise attractions
Very close bodies
Large acceleration, no division by zero
Coincident bodies skipped
Zero-distance pairs skipped with warning logged
Empty bodies list
No crash, zero acceleration
Inverse square law
Doubling distance → quarter acceleration
Earth surface gravity
9.8 m/s² at Earth’s surface ± 1%
LEO orbital velocity
~7.7 km/s at 400 km altitude
Ship gravity:
Test Case
Expected Behavior
No bodies
Ship has zero gravitational acceleration
Single body attraction
Ship attracted toward body center
Ship at body position
Handled gracefully with warning
Multiple bodies superposition
Net gravity on ship = vector sum
Thrust:
Test Case
Expected Behavior
No thrust requested
Zero thrust level → zero force, no fuel consumed
No fuel
Zero fuel → zero force regardless of thrust level
Negative thrust level
Clamped to zero
Full thrust
Maximum force along ship’s forward axis
Partial thrust
Force proportional to thrust level
Fuel limits thrust
Limited fuel caps available impulse
Long time step
Thrust impulse scales linearly with dt
Fuel consumption scales with dt
Fuel consumed ∝ thrust × dt
Attitude control (inertia-compensated PD controller):
Test Case
Expected Behavior
Zero angular velocity
Zero damping torque
Positive angular velocity
Damping torque opposes rotation
Negative angular velocity
Damping torque opposes rotation
Torque not clamped
Output in N·m, not normalized [-1,1]
All axes
Torque computed independently per axis
Z-axis lower inertia
Z-axis has lower moment of inertia
No torque no change
Zero torque → attitude unchanged
Torque changes angular velocity
Non-zero torque → angular velocity changes
Angular velocity rotates attitude
Non-zero ω → quaternion evolves
Attitude remains normalized
Quaternion stays unit length after integration
Inertia affects angular acceleration
α = τ/I
No input no torque
No rotation input and no attitude hold → zero torque
Rotation input applies torque
Manual rotation input → torque on corresponding axis
Wheel momentum accumulates
Continuous torque → wheel momentum increases
Saturated wheels use RCS
Wheels at capacity → RCS thrusters fire
No fuel no RCS
RCS requires fuel
Desaturation when idle
Wheels desaturate when no torque demanded
Desaturation during attitude hold
Desaturation runs concurrently with attitude hold
No desaturation below threshold
Small wheel momentum not desaturated
Concurrent torque and desaturation
Both operate simultaneously
Attitude reference frames:
Test Case
Expected Behavior
Prograde via helper
Quaternion aligns +Y with velocity direction
Retrograde via helper
Quaternion aligns +Y against velocity direction
Normal (circular orbit XY plane)
Normal perpendicular to orbital plane
Antinormal
Opposite of normal direction
Radial (circular orbit)
Points away from central body
Antiradial
Points toward central body
Local horizontal (mixed velocity)
Horizontal component of velocity direction
Local vertical
Points radially from central body
Degenerate: normal for radial trajectory
Falls back gracefully
Degenerate: horizontal for purely radial motion
Falls back gracefully
Degenerate: prograde at low velocity
Falls back to current orientation
Degenerate: vertical at body center
Falls back gracefully
Degenerate: radial at low angular momentum
Falls back gracefully
Normal with body offset
Works when reference body is not at origin
Integration (Leapfrog):
Test Case
Expected Behavior
Position displacement scales with dt
Δx ∝ dt
Velocity change scales with dt
Δv ∝ dt
Wheel momentum scales with dt
ΔL ∝ dt
Angular velocity change scales with dt
Δω ∝ dt
Position-velocity relationship
Leapfrog half-step ordering preserved
Constant acceleration
Exact solution for constant F
State restoration:
Test Case
Expected Behavior
Restore bodies from Redis
RestoreBodies loads evolved positions/velocities
Does not write to Redis
Restore is read-only
Clears previous bodies
Old in-memory state replaced
Empty Redis returns zero
No bodies in Redis → zero count, no crash
RefBody snapshots:
Test Case
Expected Behavior
Preserves pre-update positions
Snapshot captures positions before tick update
Fields accessible
All RefBody fields readable
Works with find_reference_body
RefBody used in reference body lookup
Works with compute_ship_gravity
RefBody used in gravity calculations
Station Physics (test_simulation.py)
Test Case
Expected Behavior
Station gravity only
Station updated with gravity, no thrust or attitude changes
Station Leapfrog integration
Position and velocity evolve correctly under gravity
Station attitude fixed
Attitude quaternion unchanged after tick
Station batch write
All stations written via pipeline
Spawn station equatorial orbit
Station placed at correct altitude with orbital velocity
Spawn station Lagrange L5
Station placed at −60° from secondary body
Spawn station Lagrange L4
Station placed at +60° from secondary body
Spawn station equatorial tilt
Position/velocity rotated to body’s equatorial plane
Station gravity matches ship gravity
Same acceleration at same position
Ship Classes (test_simulation.py)
Test Case
Expected Behavior
Spawn fast_frigate default
Ship created with fast_frigate parameters
Spawn cargo hauler
Ship created with cargo hauler parameters
Unknown class defaults to fast_frigate
Invalid class name falls back to fast_frigate
Inertia tensor at full fuel
Returns inertia_full diagonal values
Inertia tensor at empty fuel
Returns inertia_dry diagonal values
Inertia tensor interpolation
Linear interpolation at 50% fuel
Ship class persists in Redis
ship_class field roundtrips through Redis
Legacy ship without class
Defaults to fast_frigate on deserialization
gRPC Server (test_grpc_server.py)
Test Case
Expected Behavior
InitializeBodies
Accepts body list, initializes simulation
RestoreBodies
Loads body state from Redis
ProcessTick
Advances simulation by one tick
GetBody
Returns single body state by ID
GetBodies
Returns all body states
CreateShip
Creates ship at specified position/velocity
GetShip
Returns single ship state by ID
GetShips
Returns all ship states
SetThrust
Updates ship thrust level
SetRotation
Updates ship rotation input
DeleteShip
Removes ship from simulation
SpawnStation
Creates station in equatorial orbit
SpawnStation Lagrange
Creates station at L4/L5 point
RemoveStation
Deletes station, publishes event
GetAllStations
Returns all station states
ClearAllStations
Removes all stations, returns count
Redis State (test_redis_state.py)
Test Case
Expected Behavior
Redis connection
Connect, disconnect, reconnect
Get body
Retrieve body by key
Set body
Store body with correct key pattern
Get all bodies
Retrieve all bodies
Set bodies batch
Atomic batch store
Get ship
Retrieve ship by ID
Set ship
Store ship state
Get all ships
Retrieve all ships
Delete ship
Remove ship from Redis
Clear ships
Remove all ships
Get station
Retrieve station by ID
Set station
Store station with correct key pattern
Get all stations
Retrieve all stations
Set stations batch
Atomic batch store via pipeline
Delete station
Remove station from Redis
Clear all stations
Remove all stations
Publish station spawned
Event published to galaxy:stations stream
Publish station removed
Event published to galaxy:stations stream
Health (test_health.py)
Test Case
Expected Behavior
Readiness OK
Returns 200 when initialized
Readiness not ready
Returns 503 when not initialized
Readiness shutting down
Returns 503 during shutdown
Liveness
Always returns 200
API Gateway Service
Auth (test_auth.py)
Test Case
Expected Behavior
Verify valid JWT
Returns decoded payload
Verify expired JWT
Raises authentication error
Verify invalid JWT
Raises authentication error
Hash password
Returns bcrypt hash
Verify correct password
Returns true
Verify incorrect password
Returns false
Admin Auth (test_admin_auth.py)
Test Case
Expected Behavior
Admin required with valid token
Dependency passes, returns admin identity
Admin required without token
Returns 401
Admin required with invalid token
Returns 401
Admin required with expired token
Returns 401
Chat Rate Limiter (test_chat.py)
Test Case
Expected Behavior
Allow messages within limit
5 messages in 1 second all allowed
Block excess messages
6th message in same window returns False
Window reset allows new messages
Messages allowed after window expires
Per-player isolation
One player’s limit doesn’t affect another
Cleanup removes player state
cleanup_player() frees memory
Monotonic timing
Uses time.monotonic(), not wall clock
Config (test_config.py)
Test Case
Expected Behavior
JWT secret required
Missing secret raises validation error
Sensitive field masking
Secrets masked in log output
Request Validation (test_validation.py)
Test Case
Expected Behavior
Valid thrust level
0.0–1.0 accepted
Invalid thrust level
Negative or > 1.0 rejected
Valid rotation input
3D vector within bounds accepted
Invalid rotation input
Out-of-range values rejected
Valid tick rate
Within configured bounds accepted
Invalid tick rate
Out-of-range values rejected
Rate Limiting (test_rate_limit.py)
Test Case
Expected Behavior
Requests within limit
Allowed through
Excess requests blocked
Returns 429 after limit exceeded
Resets after window
Requests allowed after window expires
Per-user isolation
One user’s limit doesn’t affect another
Admin exemption
Admin requests bypass rate limits
Metrics (test_metrics.py)
Test Case
Expected Behavior
Middleware records requests
Counter incremented per request
Metrics endpoint
Returns Prometheus-formatted metrics
WebSocket Manager (test_websocket_manager.py)
Test Case
Expected Behavior
Connection established
Client connects, added to connection pool
Disconnection
Client removed from pool on disconnect
Subscribe to events
Client receives only subscribed event types
Unsubscribe from events
Client stops receiving unsubscribed events
Broadcast
All connected clients receive broadcast messages
User-specific message
Only targeted user receives message
Authentication
Unauthenticated WebSocket connections rejected
Health (test_health.py)
Test Case
Expected Behavior
Readiness OK
Returns 200 when ready
Readiness not ready
Returns 503 when not ready
Readiness shutting down
Returns 503 during shutdown
Liveness
Always returns 200
Tick Engine Service
State (test_state.py)
Test Case
Expected Behavior
Get tick
Returns current tick number from Redis
Increment tick
Atomically increments tick counter
Get game time
Returns current game time
Set game time
Updates game time in Redis
Get tick rate
Returns configured ticks per second
Set tick rate
Updates tick rate in Redis
Is paused
Returns current pause state
Set paused
Updates pause state in Redis
Tick Loop (test_tick_loop.py)
Initialization:
Test Case
Expected Behavior
Calls galaxy service first
Galaxy InitializeBodies called before physics
Passes bodies to physics
Bodies from galaxy forwarded to physics InitializeBodies
Fails when galaxy init fails
Returns error, does not proceed to physics
Fails when galaxy raises exception
Exception caught, initialization fails
Fails when GetBodies raises exception
Exception caught, initialization fails
Fails when physics init fails
Returns error despite galaxy success
Sets initialized flag on success
Flag set only after both services succeed
Logs fallback warning
Warning logged when galaxy uses fallback ephemeris
Tick processing:
Test Case
Expected Behavior
Process tick success
Calls physics ProcessTick, increments counter
Retries on failure
Transient failure retried up to configured limit
Retries on RPC error
gRPC errors trigger retry
Unhealthy after all retries fail
Service marked unhealthy when retries exhausted
Passes correct tick number
Current tick number sent to physics
Uses configured timeout
gRPC timeout from configuration
Fast-fails when circuit open
Skips physics call, records failure
Records success closes circuit
Successful tick closes open circuit
Records failure on retries exhausted
Circuit breaker failure recorded
Health check loop:
Test Case
Expected Behavior
Recovers physics service
Health check detects physics recovery
Resets on failure
Failed health check maintains unhealthy state
Auto-resumes after recovery
Game unpaused after physics recovers
Skips when already healthy
No action when physics is healthy
Resets circuit on recovery
Circuit breaker reset on health recovery
Pause/Resume:
Test Case
Expected Behavior
Pause publishes event
tick.paused event broadcast via WebSocket
Resume publishes event
tick.resumed event broadcast via WebSocket
Resume clears auto-paused flag
Auto-pause flag cleared on manual resume
Resume resets last tick time
Prevents time jump after long pause
Run loop:
Test Case
Expected Behavior
Processes ticks when not paused
Ticks advance at configured rate
Sets game time incrementally
Game time advanced by dt each tick
Skips processing when paused
No tick processing while paused
Uses effective time scale for dt
Time scale applied to dt calculation
Auto-pauses when physics unhealthy
Game paused when physics service unreachable
Time synchronization:
Test Case
Expected Behavior
Positive drift speeds up
Positive drift → time scale > 1.0
Negative drift slows down
Negative drift → time scale < 1.0
Deadband no correction
Drift within ±10s → no correction
Cap at ±5%
Time scale clamped to [0.95, 1.05]
Disabled uses admin scale
Sync disabled → admin time scale used
Non-unit admin scale bypasses sync
Custom admin scale disables sync
Deadband edge at 10s
Exactly 10s drift → no correction
Game reset:
Test Case
Expected Behavior
Pauses first
Game paused before reset operations
Clears all ships
All ships removed from physics and Redis
Clears state before reinitialize
State cleared before fresh initialization
Resumes after completion
Game unpaused after reset
Resets last tick time
Prevents time jump after reset
Handles clear ships failure
Gracefully handles failure during ship clear
Returns correct values
Returns new tick number and body count
Snapshots:
Test Case
Expected Behavior
Acquires tick lock
Snapshot waits for current tick to complete
Returns snapshot result
Snapshot metadata returned
Waits for tick processing
Snapshot taken between ticks, not during
Snapshot loop creates periodic snapshots
Snapshots created at configured interval
Snapshot loop continues on error
Failed snapshot does not stop loop
Snapshot loop stops on shutdown
Loop exits on shutdown signal
Connection management:
Test Case
Expected Behavior
Connect creates gRPC channels
Channels created to galaxy and physics
Close closes gRPC channels
Channels closed on shutdown
Close handles None channels
No error if channels not initialized
Tick rate:
Test Case
Expected Behavior
Set valid rate
Rate updated in Redis
Minimum bound
Rate ≥ configured minimum
Maximum bound
Rate ≤ configured maximum
Below minimum clamped
Value below minimum clamped to minimum
Above maximum clamped
Value above maximum clamped to maximum
Automation Engine (test_automation.py)
Rule Evaluation:
Test Case
Expected Behavior
Evaluate simple condition
ship.fuel < 0.5 triggers when fuel low
Evaluate orbital condition
orbit.apoapsis > 500000 triggers correctly
Evaluate distance condition
ship.distance_to with body arg computes distance
AND logic all conditions
All conditions must be true to trigger
AND logic partial match
Some false conditions prevent trigger
Immediate field
immediate condition always triggers
Unknown field
Unknown condition field treated as false
Once mode disables after trigger
Rule set to enabled=false after firing
Continuous mode keeps firing
Rule stays enabled after trigger
Actions:
Test Case
Expected Behavior
Set thrust action
Thrust level applied via physics gRPC
Set attitude prograde
Attitude mode set to prograde
Set attitude hold
Attitude hold enabled
Alert action
Message logged, no gRPC call
Start circularize
Maneuver hash created, prograde attitude, full thrust
Start set_inclination
Maneuver hash created with target inclination
Start rendezvous
Maneuver hash created with target_id, phase=transfer
Circularize Maneuver:
Test Case
Expected Behavior
Prograde burn raises orbit
Attitude set to prograde, thrust applied
Retrograde burn lowers orbit
Attitude set to retrograde when apoapsis needs lowering
Completion at low eccentricity
Maneuver completes when e < 0.005
Set Inclination Maneuver:
Test Case
Expected Behavior
Normal burn at ascending node
Burns normal near ascending node
Antinormal burn at descending node
Burns antinormal near descending node
Completion at target inclination
Maneuver completes when
incl − target
< 0.5°
Rendezvous Maneuver:
Test Case
Expected Behavior
Maneuver creation
Maneuver stored with phase “plane_change” and strategy “manual”
Approach throttle-down
Thrust proportional to relative velocity
Approach completion
distance < 1 km AND rel_vel < 1 m/s completes maneuver
Target not found
Missing target aborts maneuver
Maneuver abort
abort flag detected, thrust zeroed, maneuver cleared
Brachistochrone Edge Cases:
Test Case
Expected Behavior
Fuel depleted gRPC failure
ApplyControl/SetAttitudeMode exceptions during fuel=0 don’t crash maneuver
Coast phase gRPC failure
Physics command exceptions during coast don’t crash maneuver
Burn phase gRPC failure
Physics command exceptions during burn don’t crash maneuver
Direction hold — first tick
No previous direction → use computed direction, reset age
Direction hold — spin freeze
High angular velocity → hold previous direction
Direction hold — not due
Between updates → hold previous direction
Direction hold — due for update
Past update interval → use fresh direction, reset age
Prograde escape bypass
During prograde escape → dir_age forced to 0
Cross-body missing parent
Common parent body not in positions → fallback to local frame
Zero command magnitude
a_cmd_mag ≈ 0 → use position-based direction fallback
Periapsis safety status
status_text includes “[periapsis safety]” when active
Dynamic braking trigger fires
dv_remaining ≤ rel_vel × 1.15 during coast → burn2
Dynamic braking trigger holds
dv_remaining > rel_vel × 1.15 during coast → stays coast
Fuel exactly 1.0
fuel=1.0 → NOT fuel-depleted, uses normal guidance
Sub-phase boundary burn1→coast
elapsed == burn_time → transitions to coast
Sub-phase boundary coast→burn2 (fallback)
elapsed == burn_time + coast_time → transitions to burn2
Maneuver execute gRPC error
AioRpcError during maneuver tick → logged, not crash
Missing ref body
Maneuver ref_body not in body_positions → early return
Integration tests that run against a real Redis instance to verify serialization roundtrips, pipeline behavior, stream consumer groups, and cross-service data contracts.
Infrastructure
Tests run via Docker Compose (tests/redis/docker-compose.yml):
redis: Ephemeral Redis 7.x (no AOF, no RDB saves)
test-runner: Python 3.12 container with service source code and test dependencies
Resource limits: --cpus=2 --memory=512m per container.
Run command:
sudo docker compose -f tests/redis/docker-compose.yml up --build\--abort-on-container-exit--exit-code-from test-runner
Test Isolation
Each test gets a clean database via flushdb() before and after
Stream consumer groups use UUID-based names per test to avoid collisions
Service state objects (RedisState, TickEngineState) injected with real Redis client, bypassing connect()
TickEngineState._db_pool mocked (PostgreSQL not needed for Redis-only tests)
1.496e11 survives roundtrip within float64 precision
Float precision at small scale
0.001 survives roundtrip exactly
Scan isolation
body:, ship:, and station:* keys don’t cross-contaminate
Station set/get roundtrip
All 14 fields preserved including attitude and parent_body
Station batch pipeline write
Multiple stations stored atomically via pipeline
Station batch pipeline read
All stations retrieved via pipeline scan
Station delete
Station removed, get returns None
Clear all stations
All stations removed, count returned
Tick Engine State Roundtrips (test_tick_engine_state.py)
Test Case
Expected Behavior
Tick get/set roundtrip
Integer tick value preserved
Tick increment
Atomically increments and returns new value
Game time get/set roundtrip
ISO datetime preserved with timezone
Tick rate get/set roundtrip
Float rate preserved
Time scale get/set roundtrip
Float scale preserved
Paused get/set roundtrip
Boolean serialized as “true”/”false”
Time sync get/set roundtrip
Boolean serialized as “true”/”false”
Time drift get/set roundtrip
Float drift preserved
Initialize game state (fresh)
All keys created, returns True
Initialize game state (existing)
No overwrite, returns False
Reset game state
All game keys deleted
Conserved quantities set/get
Energy and momentum values preserved
Clear conserved quantities
All conservation keys deleted
Default values when empty
Returns defaults (0, current time, settings defaults)
Stream Publish/Consume (test_streams.py)
Test Case
Expected Behavior
Publish ship.spawned
Message appears in galaxy:ships stream with correct fields
Publish ship.removed
Message appears in galaxy:ships stream with correct fields
Publish tick.completed
Message appears in galaxy:tick stream with all fields
Publish tick.paused
Message appears in galaxy:tick stream
Publish tick.resumed
Message appears in galaxy:tick stream
Publish station.spawned
Message appears in galaxy:stations stream with correct fields
Publish station.removed
Message appears in galaxy:stations stream with correct fields
Publish automation.triggered
Message appears in galaxy:automations stream with correct fields
Consumer group creation
Group created successfully, BUSYGROUP handled idempotently
Consumer group reads new messages
Reading with “>” returns only new messages
Acknowledge and pending recovery
Unacked messages appear in pending; acked messages don’t
Message ordering
Messages read in publication order
Stream field types
All field values are strings (Redis stream requirement)
Cross-Service Data Contracts (test_cross_service.py)
Test Case
Expected Behavior
Physics body → tick-engine snapshot read
Body written by physics RedisState readable by pipeline hgetall
Physics ship → tick-engine snapshot read
Ship written by physics RedisState readable by pipeline hgetall
Physics ship event → gateway consumer
ship.spawned fields match gateway expectations
Tick-engine tick event → gateway consumer
tick.completed fields match gateway expectations
Tick-engine game config → gateway read
Game config keys readable by any service
Key naming: body:{name}
Bodies stored at body:{name} keys
Key naming: ship:{id}
Ships stored at ship:{id} keys
Key naming: game:*
Game state stored at game:* keys
Key naming: station:{id}
Stations stored at station:{id} keys
Key naming: maneuver:{id}
Active maneuvers stored at maneuver:{ship_id} keys
Key naming: automation:{id}:*
Automation rules at automation:{ship_id}:{rule_id} keys
Physics station → gateway broadcast
Station written by physics readable by gateway
Physics station event → gateway consumer
station.spawned fields match gateway expectations
Tick-engine automation event → gateway
automation.triggered fields match gateway expectations
Pipeline Behavior (test_pipeline.py)
Test Case
Expected Behavior
Batch write atomicity
10 bodies in pipeline all exist after execute
Batch read via pipeline
Write individually, read all via pipeline
Large pipeline (100 items)
100 items written and read back correctly
Concurrent coroutine access
Two async writers don’t interfere
Empty pipeline
No error on empty pipeline execute
Rendezvous Verification Test Suite
Live integration tests that run rendezvous maneuvers inside the running game cluster. Located at scripts/run-test.py, executed inside the tick-engine pod.
Ship Pool
Each test gets a dedicated chaser + target ship pair with deterministic UUIDs (uuid5(NAMESPACE_DNS, "galaxy-test.{test_name}.{role}")). Ship class properties (dry_mass, fuel_capacity) are written to Redis so the automation engine reads correct values.
Test
Chaser Class
Target Class
Scenario
t1_hohmann
fast_frigate
fast_frigate
Earth coplanar 400km, Hohmann
t1_fast
fast_frigate
fast_frigate
Earth coplanar 400km, fast
t1_express
fast_frigate
fast_frigate
Earth coplanar 400km, express
t2_cargo
cargo_hauler
fast_frigate
Earth cargo hauler 400km
t3_altitude
fast_frigate
fast_frigate
Earth 400km to 35000km
t5_inclined
fast_frigate
fast_frigate
Earth 15deg plane change
t6_luna
fast_frigate
fast_frigate
Luna 100km to 500km
t7_mars
cargo_hauler
fast_frigate
Mars cargo hauler descent
t8_jupiter
fast_frigate
fast_frigate
Jupiter 100Mm to 120Mm
Commands
Command
Description
run-test.py list
List all tests with ship IDs
run-test.py setup <name>
Set up single test (game must be paused)
run-test.py monitor <name>
Monitor single test until complete/timeout
run-test.py run <name>
Setup + monitor single test
run-test.py run-all
Setup all tests, one RestoreBodies call, monitor all in parallel with threaded monitoring, print summary table
Parallel Execution (run-all)
Sets up all 9 tests with skip_restore=True (no per-test RestoreBodies)
Single RestoreBodies call syncs all 18 ships at once
Spawns one monitoring thread per test (each with its own Redis connection)
Prints interleaved progress with [test_name] prefix
Prints summary table with PASS/FAIL, elapsed time, fuel remaining, and phases
Continuous Integration
All tests run on every pull request
Coverage report generated and must meet targets
Physics validation tests run nightly (longer duration)
Performance benchmarks run weekly with regression alerts