Architecture Overview¶
This page explains the core architecture of Stochastic Warfare -- how the engine is structured, how simulation runs execute, and how optional subsystems integrate.
Module Dependency Chain¶
The engine is composed of 12 top-level modules with a strict one-way dependency graph:
core -> coordinates -> terrain -> environment -> entities -> movement
-> detection -> combat -> morale -> c2 -> logistics -> simulation
| Module | Purpose |
|---|---|
| core | Types, logging, PRNG management, clock, event bus, config, checkpointing |
| coordinates | Geodetic/UTM/ENU transforms, magnetic declination |
| terrain | Heightmap generation, terrain classification, bathymetry, LOS raycasting, infrastructure |
| environment | Weather (Markov), astronomy (Meeus), sea state, acoustics, EM propagation |
| entities | Unit definitions, equipment, organization hierarchy, task organization |
| movement | A* pathfinding, fatigue, formations, naval/air/amphibious/submarine movement |
| detection | Unified SNR sensors, Kalman tracking, sonar, intel fusion, fog of war |
| combat | Ballistics (RK4), DeMarre penetration, missiles, naval salvo, air combat, IADS, DEW |
| morale | Continuous-time Markov state transitions, cohesion, stress, psychology, rout |
| c2 | Command authority, communications (Bernoulli), EMCON, ROE, orders, mission command |
| logistics | NATO supply classes, networkx routing, Poisson maintenance, M/M/c medical, engineering |
| simulation | Scenario loading, battle/campaign managers, master engine, recording, metrics |
Key rule: Dependencies flow downward only. Terrain never imports environment. Environment may read terrain (one-way). This prevents circular dependencies and makes the system testable in isolation.
Additional Domain Modules¶
Beyond the core 12, specialized domain modules provide optional capabilities:
| Module | Purpose | Phase |
|---|---|---|
| ai | OODA FSM, commander personalities, doctrine templates, MDMP planning | 8 |
| planning | Mission analysis, COA generation, wargaming, estimates | 8 |
| validation | Historical data, Monte Carlo harness, campaign validation | 7, 10 |
| population | Civilian regions, displacement, collateral, insurgency | 12, 24 |
| ew | Electronic warfare: jamming, spoofing, ECCM, SIGINT, decoys | 16 |
| space | Orbital mechanics, GPS, SATCOM, space ISR, early warning, ASAT | 17 |
| cbrn | Chemical/biological/radiological/nuclear effects and protection | 18 |
| escalation | Escalation ladder, political pressure, war termination | 24 |
| tools | MCP server, analysis utilities, visualization | 14 |
Simulation Loop¶
The engine uses a hybrid tick-based + event-driven architecture:
- Outer loop: Advances discrete ticks at variable resolution depending on scale
- Inner loop: Events fire within ticks for fine-grained interactions
Scale Resolution¶
| Scale | Tick Resolution | Manager | When Active |
|---|---|---|---|
| Strategic | 3,600s (1 hour) | CampaignManager |
Between battles |
| Operational | 300s (5 min) | CampaignManager |
Approaching contact |
| Tactical | 5s | BattleManager |
Active engagement |
The engine automatically switches resolution based on battle state. When forces come into contact, resolution drops from strategic to tactical. When battles resolve, it scales back up.
Tick Processing Order¶
Within each tick, processing follows a fixed order:
- Environment -- weather evolution, time-of-day updates
- Detection -- sensor scans, Kalman updates, fog of war refresh
- AI/C2 -- OODA cycle, commander decisions, order propagation
- Movement -- pathfinding, formation maintenance, fatigue
- Combat -- engagement resolution, damage application (see Engagement Gate Sequence below)
- Morale -- state transitions, cohesion checks, rout cascade, rally
- Logistics -- supply consumption, transport, maintenance
- Victory -- condition evaluation
Engagement Gate Sequence¶
Within combat, each potential engagement passes through a series of gates before resolving. If any gate rejects, the engagement is skipped. This gate sequence was wired in Phases 40--47 (Block 5: Core Combat Fidelity), connecting 40+ previously disconnected subsystems into the battle loop. All 41 scenarios are validated against historical outcomes (Phases 47, 57).
- Domain filtering -- attacker's weapon must target the defender's domain (ground, air, naval)
- Posture/status check -- ROUTED/SURRENDERED units skip; morale accuracy multiplier applied
- Suppression check -- heavily suppressed units may skip engagement
- Fire-on-move check --
requires_deployedweapons skip if attacker is moving - Terrain cover/concealment -- cover reduces hit probability; concealment reduces detection range
- Detection quality -- sensor-derived
id_confidencemodulates engagement effectiveness - Training level -- unit quality multiplies hit probability
- ROE gate --
RoeEngine.check_engagement_authorized()blocks engagements below the current ROE level's confidence threshold (WEAPONS_HOLD blocks all non-self-defense; WEAPONS_TIGHT requires highid_confidence) - Weapon selection -- best weapon chosen by range, ammo, and target type
- Hold-fire discipline -- if enabled via
behavior_rules, defensive units wait until targets are within effective range (default 80% of max range) - Engagement resolution -- ballistics, penetration, and damage applied
After morale transitions, a rout cascade check propagates routing to nearby SHAKEN/BROKEN units. A rally check allows ROUTING units near friendly forces to recover.
Spatial Model¶
The engine uses a layered hybrid spatial representation:
Three Layers¶
| Layer | Type | Scale | Used For |
|---|---|---|---|
| Graph | networkx | Strategic | Supply networks, LOC routing |
| Grid | NumPy raster | Operational/Tactical | Terrain, LOS raycasting, pathfinding |
| Continuous | Float coordinates | Unit-level | Precise engagement geometry, movement |
Grid Convention¶
All raster grids share: Grid[0,0] = SW corner, row increases northward, col increases eastward. This matches geographic convention (south-to-north, west-to-east).
Coordinate System¶
- Internal: ENU (East-North-Up) meters for all computation
- External: Geodetic (lat/lon) only for import, export, and display
- Transforms:
pyprojfor all coordinate conversions
ECS-Like Separation¶
The engine follows an Entity-Component-System inspired pattern:
- Entities hold data (position, health, ammunition, morale state, equipment)
- Modules implement behavior (movement algorithms, detection math, combat resolution)
- Entities are passed to modules as arguments; modules return results or modify entity state
This means you can test combat resolution without a terrain engine, or test detection without a movement engine.
Engine Wiring¶
The ScenarioLoader is the central factory. Given a scenario YAML file, it:
- Parses the YAML into a
CampaignScenarioConfig(pydantic-validated) - Creates terrain, environment, and weather engines
- Instantiates all units with their equipment, weapons, and sensors
- Creates detection, combat, movement, morale, C2, and logistics engines
- Wires optional subsystems based on config presence:
ew_configpresent -> creates EW engines (jamming, spoofing, ECCM, SIGINT)space_configpresent -> creates space engines (GPS, SATCOM, ISR, ASAT)cbrn_configpresent -> creates CBRN engines (dispersal, contamination, protection)school_configpresent -> creates doctrinal school registryescalation_configpresent -> creates escalation enginedew_configpresent -> creates directed energy weapon engineeraspecified -> loads era-specific data and engines- Creates always-on behavioral engines: ROE engine (default WEAPONS_FREE), rout engine
- Returns a
SimulationContextwith everything wired together
Null-Config Gating¶
Every optional subsystem follows the same pattern:
if config.ew_config is not None:
# Create and wire EW engines
else:
ctx.ew_engine = None # Disabled -- zero cost
This means scenarios that don't use electronic warfare pay zero performance cost for it.
Era Framework¶
The engine supports 5 historical eras, each with different available technologies:
| Era | Period | Key Mechanics |
|---|---|---|
| Modern | Cold War--present | Full subsystem access |
| WW2 | 1939--1945 | Naval gunnery, convoy/wolf pack, strategic bombing |
| WW1 | 1914--1918 | Trench systems, creeping barrage, gas warfare |
| Napoleonic | 1792--1815 | Volley fire, melee, cavalry charges, formations, courier C2 |
| Ancient/Medieval | 3000 BC--1500 AD | Massed archery, siege machines, oar-powered naval |
Each era is defined by an EraConfig that specifies:
- Which modules are enabled/disabled (e.g., no radar in WW1)
- Available sensor types
- C2 delay multipliers (Napoleonic courier >> modern radio)
- Era-specific engine extensions
Era data lives in data/eras/{era_name}/ with the same directory structure as modern data.
Determinism & Reproducibility¶
Every simulation run is fully reproducible given the same seed:
PRNG Discipline¶
- All randomness flows through
RNGManager, which creates per-modulenp.random.Generatorstreams - Each module gets its own independent PRNG stream via
RNGManager.get_stream(ModuleId) - No bare
randommodule ornp.randommodule-level calls anywhere in the codebase - Given the same seed, the same scenario produces identical results
Deterministic Iteration¶
- No
set()or unordered dict drives simulation logic - All iteration over collections uses sorted or ordered containers
Web Application Layer¶
The engine is wrapped by a web application stack that provides interactive access without writing Python code.
Architecture¶
Browser (React) -> Vite dev proxy -> FastAPI (api/) -> Simulation Engine (stochastic_warfare/)
|
SQLite (aiosqlite)
API Layer (api/)¶
A FastAPI service sits alongside the engine (not inside it). It provides:
- REST endpoints for browsing scenarios, units, and run history
- WebSocket streaming for live simulation progress
- Async run execution via
asyncio.to_thread()(CPU-bound simulation in thread pool) - SQLite persistence for run results across server restarts
- Batch execution for Monte Carlo statistical analysis
The API layer imports stochastic_warfare as a library. It never modifies engine code.
Frontend (frontend/)¶
A React + TypeScript single-page application built with Vite:
- TanStack Query for API data fetching and caching
- Plotly.js for interactive charts (force strength, engagements, morale, tempo) with cross-chart tick sync
- Canvas 2D for the tactical map (terrain with elevation shading, unit positions, FOW filtering, sensor circles, engagement fade, playback)
- React Router for deep-linkable pages
- Headless UI for accessible modals, dropdowns, and menus
Key pages: Scenario Browser, Unit Catalog, Run Results (charts + narrative + map), Scenario Editor (clone-and-tweak), Analysis (Monte Carlo, A/B comparison, sensitivity sweep).
See the Web UI Guide for usage documentation.
Consequence Enforcement Gates¶
The engine uses an opt-in pattern for behavioral enforcement of resource consumption and C2 friction. Each consequence is gated behind an enable_* flag in CalibrationSchema (default False), so enabling a new behavior never breaks existing scenarios.
Key enforcement gates:
| Flag | Effect | Phase |
|---|---|---|
enable_fuel_consumption |
Units consume fuel proportional to distance moved; immobilized when empty | 68 |
enable_ammo_gate |
Engagements skipped when magazine exhausted | 68 |
enable_command_hierarchy |
Orders must propagate through command chain | 69 |
enable_missile_routing |
Missile flight resolved per-tick with defense intercept | 71 |
enable_carrier_ops |
Carrier CAP/sortie rate limited by sea state | 71 |
enable_ice_crossing |
Units on ice move at 50% speed | 78 |
enable_bridge_capacity |
Bridges enforce weight limits | 78 |
enable_environmental_fatigue |
Heat/cold stress degrades unit performance | 78 |
Additional non-flag consequences include fire zone damage (fire_damage_per_tick), stratagem expiry (stratagem_duration_ticks), guerrilla retreat distance, and order misinterpretation effects.
The enable_all_modern meta-flag activates all 21 non-deferred flags at once for convenience, but individual flag control is preferred in scenario YAMLs for performance (selective flags avoid ~6x overhead from unused subsystems).
Checkpointing¶
All stateful classes implement the checkpoint protocol:
class SomeEngine:
def get_state(self) -> dict:
"""Serialize full internal state."""
...
def set_state(self, state: dict) -> None:
"""Restore from serialized state."""
...
This enables:
- Save/restore mid-simulation
- Branching -- checkpoint, run two different decisions, compare outcomes
- Debugging -- reproduce any simulation state from a checkpoint + seed