Skip to content

Phase 17 — Space & Satellite Domain

Status: COMPLETE Date: 2026-03-04 Tests: 149 new (4,763 total)

Summary

Full Space & Satellite domain covering orbital mechanics, GPS constellation management, space-based ISR, missile early warning, SATCOM dependency, and anti-satellite warfare. New space/ package with 9 source modules. Space assets modulate existing navigation, detection, communication, and missile defense systems through their existing parameter interfaces — no parallel resolution systems. All effects backward-compatible via enable_space config flag defaulting to False and default parameter values preserving existing behavior.

Deliverables

17a: Orbital Mechanics & Constellation Management (tests)

  • space/__init__.py — Package init
  • space/events.py — Space domain events (SatelliteOverpassEvent, GPSDegradedEvent, SATCOMWindowEvent, ASATEngagementEvent, ConstellationDegradedEvent)
  • space/orbits.py — Simplified Keplerian orbital mechanics: period T = 2pi*sqrt(a^3/mu), ground track computation, J2 nodal precession for sun-synchronous orbits, Kepler equation solver (Newton-Raphson)
  • space/constellations.py — Constellation manager: satellite group definitions, coverage windows over theater bounding box, constellation health tracking, EventBus integration

17b: GPS Dependency & Navigation Warfare (tests)

  • space/gps.py — GPS accuracy model: visible satellite count over theater, DOP (dilution of precision) computation, position error sigma = DOP * sigma_range. INS drift model for GPS-denied: sigma(t) = sigma_0 + drift_rate * t. CEP scaling for GPS-guided weapons (JDAM-class: ~13m CEP GPS, ~30m+ INS-only)

17c: Space-Based ISR & Early Warning (tests)

  • space/isr.py — Space-based ISR: imaging satellites generate detection events during overpass windows. Resolution determines minimum detectable unit size. Revisit time from orbital period + ground track drift. Cloud cover blocks optical satellites (not SAR)
  • space/early_warning.py — Missile early warning: GEO/HEO IR satellites detect missile launches (IR bloom). Detection time = coverage check + processing delay (30-90s). Wired into combat/missile_defense.py early warning time parameter. No coverage = no early warning (fall back to ground radar)

17d: SATCOM Dependency & Anti-Satellite Warfare (tests)

  • space/satcom.py — SATCOM dependency model: satellite coverage windows determine SATCOM availability for beyond-LOS communications. Bandwidth capacity limits per theater. Degradation feeds into c2/communications.py reliability for SATCOM-type equipment
  • space/asat.py — Anti-satellite warfare: direct-ascent kinetic kill vehicle (Pk from intercept geometry), ground-based laser dazzle (temporary blinding) and laser destruct (permanent). Poisson debris generation with cascade model (Kessler syndrome risk). Satellite loss cascades to constellation degradation

17e: Integration (tests)

  • core/types.py — Added ModuleId.SPACE
  • environment/electromagnetic.py — Added constellation_accuracy_m for dynamic GPS accuracy from orbital model
  • combat/missile_defense.py — Added early_warning_time_s parameter for space-based early warning integration
  • combat/missiles.py — Added gps_accuracy_m parameter for GPS-guided weapon CEP scaling
  • c2/communications.py — Added satcom_reliability_factor for SATCOM availability modulation
  • simulation/scenario.py — Added space_engine field to SimulationContext
  • simulation/engine.py — Added space_engine.update() call in tick loop

17f: YAML Data & Validation (tests)

  • 9 constellation YAMLs: GPS NAVSTAR (24-slot MEO), GLONASS (24-slot MEO), Milstar SATCOM, WGS SATCOM, Keyhole optical, Lacrosse SAR, SBIRS early warning, Molniya early warning, SIGINT LEO
  • 3 ASAT weapon YAMLs: SM-3 Block IIA, Nudol ASAT, ground-based laser
  • 3 validation scenarios: space_gps_denial (PGM accuracy comparison), space_isr_gap (exploit satellite overpass gap), space_asat_escalation (kinetic ASAT cascading DOP increase)

Key Design Decisions

  1. Simplified Keplerian, not SGP4: Campaign-scale simulation needs "when does satellite see theater?" — full SGP4/TLE propagation is unnecessary complexity. J2 precession captures the most important perturbation (sun-synchronous orbit drift).
  2. DOP-based GPS accuracy: Visible satellite count drives geometric DOP, which scales position error. This captures constellation degradation effects naturally — losing satellites increases DOP.
  3. INS drift as linear model: GPS-denied navigation degrades linearly with time. Simple but captures the key dynamic — longer GPS denial means worse navigation.
  4. Space→detection via parameter injection: constellation_accuracy_m, early_warning_time_s, satcom_reliability_factor, gps_accuracy_m parameters added to existing modules — zero-impact defaults mean all existing callers unaffected.
  5. Poisson debris with cascade: Each kinetic ASAT kill generates Poisson-distributed fragments. Each fragment has per-orbit collision probability for satellites at similar altitude. Captures Kessler cascade risk without full orbital debris simulation.

Backward Compatibility

  • SpaceConfig.enable_space = False — space engines not instantiated unless enabled
  • SimulationContext.space_engine = None — consuming code checks None before querying
  • constellation_accuracy_m defaults preserve existing GPS accuracy when no space engine active
  • early_warning_time_s default preserves existing missile defense behavior
  • satcom_reliability_factor = 1.0 — comms reliability unchanged when no space engine
  • gps_accuracy_m default preserves existing weapon delivery behavior

Files Changed

  • 9 new source files in space/
  • 7 modified existing files (types, electromagnetic, missile_defense, missiles, communications, scenario, engine)
  • 12 YAML data files (9 constellations, 3 ASAT weapons)
  • 3 validation scenarios
  • 1 existing test file modified (test_types.py — added SPACE to ModuleId set)

No New Dependencies

All orbital mechanics and space domain physics implemented with existing numpy/scipy. No new package requirements.

Known Limitations / Future Work

  • Simplified Keplerian orbits (no SGP4/TLE, no atmospheric drag for LEO decay)
  • No detailed satellite bus modeling (power, thermal, attitude control)
  • No space-based SIGINT integration with Phase 16 SIGINT engine
  • Debris cascade model is statistical (no individual fragment tracking)
  • No satellite maneuvering or station-keeping fuel limits
  • No space weather effects (solar flares, radiation belt variations)

Lessons Learned

  • J/S ratio lesson from Phase 16 applies here too: parameter injection through existing interfaces keeps integration clean
  • Space engine wired into engine tick loop (unlike Phase 16 EW which deferred this)
  • Constellation YAML data follows same pydantic-loader pattern established in Phase 2
  • Per-side GPS accuracy needs worst-case aggregation when feeding a shared (non-per-side) EM environment — caught in postmortem
  • Geometry-dependent visibility tests need careful fixture design — equatorial LEO is visible from surprising distances due to orbital altitude

Postmortem

Scope: On target

  • Plan: ~150 tests across 6 sub-phases. Delivered: 149.
  • Plan: 9 source + 7 modified + ~14 YAML + 3 scenarios. Delivered: 9 + 7 + 12 + 3.
  • All planned items delivered. Nothing dropped or deferred beyond the documented known limitations.
  • One unplanned change: test_types.py modification to add SPACE to expected ModuleId set.

Quality: High

  • All 7 non-init modules use get_logger(__name__). No bare print().
  • No PRNG violations. No TODOs/FIXMEs. No set() for determinism-sensitive iteration.
  • Type hints on all public functions. DI pattern throughout.
  • All engines implement get_state()/set_state() with roundtrip tests.
  • All 7 event types are frozen dataclasses inheriting from Event base class.
  • Test distribution well-balanced: 35/25/25/30/14/20 across 6 files.
  • No @pytest.mark.slow needed — all 149 tests run in 0.49s.
  • Some geometry-dependent tests use weak assertions (isinstance(list)) with comments explaining why — orbital visibility is hard to guarantee without fixing exact positions.

Integration: Mostly wired, 2 gaps fixed/documented

  • Fixed: Per-side GPS accuracy overwrite in SpaceEngine.update() — was calling set_constellation_accuracy() in a loop where red's value overwrote blue's. Fixed to use max() (worst-case) since EMEnvironment is shared state.
  • Documented gap: ScenarioLoader doesn't auto-wire SpaceEngine from scenario YAML. Same pattern as Phase 16 EW. Both need a future wiring pass.
  • By design: No event subscribers outside space/ and tests. Space events are captured by SimulationRecorder via Event MRO dispatch. Effects flow via parameter injection, not event reaction.
  • By design: except Exception: pass wrapping space_engine.update() in engine.py. Consistent with existing EW pattern — prevents space domain errors from crashing the sim.

Deficits: 2 new (+ 6 already documented)

  1. EMEnvironment GPS accuracy is not per-side — single _constellation_accuracy_m value. Mitigated by using worst-case (max) aggregation. Per-side EM would require architectural changes.
  2. ScenarioLoader doesn't auto-wire SpaceEngine or EWEngine — both Phase 16 and Phase 17 engines require manual wiring. Future integration pass needed.

(6 pre-existing limitations already in devlog index: simplified Keplerian, no satellite bus, no SIGINT integration, statistical debris, no maneuvering, no space weather)

Performance: No impact

  • Full suite: 4,763 tests in ~90s (consistent with Phase 16 baseline).
  • Phase 17 tests alone: 0.49s — negligible.

Action items completed

  1. Fixed per-side GPS accuracy overwrite bug.
  2. Added 2 new deficits to devlog index and development-phases-post-mvp.md.
  3. Wrote this postmortem.