14 KiB
Overwatch
Real-world physics simulation with backend-owned state, dynamics, sensing, device timing, random variation, interception events, and statistics. The Vue 3 client is an operator-configuration and visualization layer. Three.js renders backend-provided state and trajectory samples; it does not calculate physics or predictions.
Radar console
Open Radar List next to 3D Simulation for per-site scan status, central-command report freshness, counts and USB connection placeholders. Select a card or a 3D radar marker to open the Python-backed radar GUI. The console includes a range/Doppler map, A-Scope, spectrum, settings, diagnostics, recording and JSON config/data export. The standalone V6 demo and supplied synthetic I/Q replay are available without a scene. See radar_gui_port.md for sources, controls and hardware limits.
Radar tracks now fuse noisy angles, range and radial Doppler using a gated Stone Soup EKF. Raw observations remain visible; displayed forecasts shorten with uncertainty. USB status remains disconnected until a hardware adapter is implemented.
Quick start
From the repository root:
./scripts/setup_environment.sh
./scripts/start_stack.sh
setup_environment.sh creates simulation/.venv and installs the operator UI. start_stack.sh brings up the auth stack, builds the UI, and restarts the backend. The operator view is at http://127.0.0.1:54321. A headless check is simulation/.venv/bin/python -m defense_sim.cli --steps 500 --seed 7.
The backend owns a fixed simulation clock, reduced flight dynamics, noisy device observations, timestamped Stone Soup tracking, chronological swept contacts, protected-area outcomes and per-projectile Monte Carlo statistics. SciPy supplies numerical reference checks and attitude conversions. The three independent radii are the protected circle (10 km), a ring of 10 radar sites at 12 km, and a grounded defender loiter ring at 9 km.
Once a radar regression has a usable altitude, the closest drone on the incoming side climbs toward that estimated height and holds its ring position. The line run waits until the recent samples agree on a heading, then that drone runs the attacker's predicted track at full speed. If a later track puts another drone closer to the line, that drone takes over and the earlier one returns to its ring slot. A threat launches at most three drones. Commands incur the modeled radio delay. The radio proposed for the field is ../docs/drone-command-link.md. The simulator still uses the generic link timing. Prediction assumes constant velocity and uses no hidden attacker mass, drag or true wind. The simulation never promises a successful interception.
The reduced attacker integrates gravity, air-relative drag and bounded optional guidance acceleration. Guidance never overwrites physical velocity. This is an illustrative powered point-mass model, not a calibrated aircraft. Grounded idle defenders remain supported by the ground; activated reduced defenders use an explicitly identified kinematic surrogate with actuator response and speed/climb bounds.
Vehicle libraries and ascent controls
drone_dynamics="reduced"remains the inexpensive default, with a 25 m/s configured speed bound."rotorpy_crazyflie"explicitly selects RotorPy's illustrative 30 g multirotor profile, with attitude, rotor aerodynamics and motor dynamics. Use a requested speed <=10 m/s; this is a configuration bound, not a flight-qualified performance claim. Model mass, motor response and thrust capacity come from the reference profile.attacker_dynamics="jsbsim"requiresprojectile_preset="custom"and an explicitaircraft_model, e.g."c172p". It runs the chosen aircraft model with normalized open-loop controls (aircraft_controls: throttle, aileron, elevator, rudder). It does not apply reduced terminal guidance or reinterpret a Cessna as a named threat profile. Start with aircraft-appropriate conditions, e.g. 50 m/s at 300 m.ascent_curveselects"quintic"(default),"cubic"or"exponential". The smoothstep references use actual accumulated horizontal travelx, an airborne starting altitudeh0, andu=clip(x/L,0,1). Quintic usesh=h0+(H-h0)*(6u^5-15u^4+10u^3); cubic usesh=h0+(H-h0)*(3u^2-2u^3). Both reach the requested altitude atL; actual flight can lag or fail to reach it.ascent_distance_m=nullenables automaticL: at least 500 m, increased to place the nominal peak climb rate at 80% of the configured limit under constant reference speed. ManualLlets users request a shorter or longer ascent. The heuristic does not establish acceleration feasibility. Exponential usesascent_k_per_m(default 0.002 1/m), approaches the altitude asymptotically, and starts with nonzero slope.- IR is a delayed rear-facing bearing-only camera model with range/FOV/lock assumptions. The local EKF retains radar-derived range uncertainty; IR never creates a perfect 3D fix. Visibility loss clears acquisition. These are illustrative device assumptions, not measured hardware specifications.
The frontend exposes model and ascent options, distinguishes red true trails, green radar samples, yellow track estimates and purple inferred paths, and lights radar markers after detections. The altitude exaggeration factor is visible in the scene toolbar. Calibration data, terrain occlusion, association/clutter, certified antenna patterns and blast/debris remain outside the current model.
All coordinates use NED world axes (m, m/s, m/s²; altitude is negative down). Physics uses fixed simulation steps independent of network timing; live execution waits for wall-clock deadlines at 1× speed.
Attack-vehicle presets
projectile_preset accepts shahed_136, shahed_238, cruise_missile, ballistic_reentry, prop_drone, random, or custom. The five named profiles are built from open-source vehicle data (Wikipedia infoboxes, CSIS Missile Threat, manufacturer spec sheets, defense-press reporting): each class carries a mass, reference area, and drag-coefficient range plus a cruise speed and altitude envelope (prop loitering, jet loitering, sea-skimming cruise, ballistic re-entry glide, small UAS). Random mode selects one class uniformly; custom exposes entry speed, altitude, and elevation directly in the UI. Mass/area/Cd values are per-class engineering estimates (documented in simulation/presets.py), not certified vehicle data. RK4 integrates gravity, wind-relative drag and bounded optional guidance thrust; named profiles remain engineering approximations. The standard-atmosphere layers support geometric altitude through 86 km, but reentry aerodynamics, heating and Mach-dependent drag are not modeled. The requested selection, realized parameters, and seed are stored in scenario for replay; the UI renders the realized values.
Device timing defaults
The AERIS-10 scan sequence derives from firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp: 16 chirps in each long/short train, PRI values of 167/175 μs, a 175.4 μs guard, three phase patterns at each of 15 elevation positions, four 1 ms stepper pulses, and 50 azimuth positions. The model yields about 258.133 ms per azimuth frame and 12.90665 s per full azimuth revisit. AERIS-10N and AERIS-10X range limits (3 km and 20 km) follow the repository hardware README. Range/bearing/Doppler uncertainty and the 7.1 ms fixed processing delay are model inputs carried forward from the retired tracking study. They are not bench-validated performance claims.
Drone navigation defaults to 10 Hz, flight control to 50 Hz, and actuator acceleration response to a 0.25 s time constant. The wireless drone link is an explicit, tunable R&D assumption: 20 Hz send slots, 250 kbit/s shared half-duplex channel, 64-byte telemetry and 48-byte command payloads plus 24 bytes framing, 50 ms one-way delay, 5 ms latency jitter, 10 ms command computation, and 0% packet loss. Packets are serialized, with airtime and queueing included. Radar reporting is modeled as wired with the configured 7.1 ms fixed delay. Drone-link defaults are not specifications for a selected radio and should be tuned during field R&D.
Layout and frontend
src/defense_sim contains transport-independent simulation modules. The server package only adapts simulation state and events to Socket.IO. tests focuses on physical invariants and reproducibility. See PROJECT_SPEC.md for model assumptions and limitations.
The Vue 3 + Vite client in frontend/ connects with socket.io-client, renders backend entity positions and trajectory history in Three.js, provides scenario and device configuration, and displays bounded terminal-style telemetry and event scrollback. Sign-in is required before opening the simulator. Socket.IO validates Supabase access tokens on connection and before start/stop commands. Snapshot persistence uses per-user row-level security.
Run the server
The backend requires SUPABASE_URL and SUPABASE_ANON_KEY. scripts/start_stack.sh writes them to deploy/auth.env.local and starts the service. To run the backend in the foreground after that file exists:
set -a
source deploy/auth.env.local
set +a
.venv/bin/python -m defense_sim.server.socketio_server
Socket.IO emits simulation_state, simulation_event, simulation_started, simulation_completed, simulation_stopped and simulation_error. Runs are isolated per authenticated socket connection and canceled on disconnect. Live Socket.IO runs use absolute monotonic deadlines for 1× real-time physics pacing; slow machines catch up without skipping steps. Cooperative yields keep stop responsive, and snapshots publish at up to 20 Hz. CLI and Monte Carlo runs compute without wall-clock pacing. Event cursors index the full event history; snapshot event tails are bounded. It accepts scenario/device configuration, not client-authored entity state. CORS is configurable through SIM_ALLOWED_ORIGINS.
Deployment
The site is served at https://defense.naori.cloud. Traefik in LXC 100 routes all site traffic to Kong at 192.168.1.194:54321. Kong routes /auth/v1/ to Supabase Auth, /rest/v1/ to PostgREST, /socket.io/ to the Python backend, and all other paths to the internal Nginx frontend container. The frontend has no separately exposed port.
Start or rebuild the application and its self-hosted auth stack from the repository root:
./scripts/start_stack.sh
That command runs rebuild_restart.sh in this directory.
Docker Compose starts the Postgres, Supabase Auth, PostgREST, Kong, and Nginx frontend services in deploy/supabase/compose.yml. This uses the components described in Supabase's Docker self-hosting guide. The script generates unique secrets on first run, waits for health checks, applies archive migrations, creates a confirmed demo account, verifies password login, builds the frontend, restarts Kong and Nginx, and updates/restarts the backend user service. The legacy frontend service on port 8088 is disabled. Repeating the command preserves the database, secrets, and demo password. Public signup and anonymous accounts are disabled.
The demo email is demo@defense.naori.cloud. Demo login credentials are recorded in DEMO_USER.txt as requested; the runtime copy is stored in deploy/supabase/.env. This file and the generated backend environment and gateway configuration are gitignored. Do not commit them. Kong exposes port 54321 for Traefik. Nginx and the database have no published ports. Nginx serves /login directly so refreshes work. Database volumes persist independently of frontend rebuilds. No database port is exposed.
For frontend development, configure VITE_SUPABASE_URL=http://localhost:54321 and copy the generated SUPABASE_ANON_KEY into VITE_SUPABASE_ANON_KEY in frontend/.env.local. Keep the service-role key server-side. In production the rebuild script supplies the public anon key and uses the site origin for API requests.
The Traefik route in deploy/traefik-defense-sim-v2.yml serves https://defense.naori.cloud entirely through Kong at 192.168.1.194:54321. Its watched dynamic route is /etc/traefik/conf.d/defense-sim-v2.yml in LXC 100. The Supabase stack restarts automatically with Docker; the backend user service is enabled at boot with systemd lingering.
Outcomes and validation
Physical contact consumes both participants under an explicit contact-as-neutralization game assumption. A defense-drone contact is interception; other drone contacts are collision. Earliest ground/contact events win; a ground tie wins. Protected-region entry is separate from a protected ground impact. Per-projectile outcomes distinguish intercepted, collision, safe_ground_impact, protected_impact and unresolved cutoff. Unresolved trials are censored; aggregate rates report resolved counts and Wilson intervals. Pooling multiple threats from the same run is descriptive because those trials can be correlated.
Run .venv/bin/python -m pytest -q and pnpm typecheck / pnpm build in frontend/. SciPy analytic comparisons, contact chronology, radar cadence, asynchronous measurements, launch evidence/delivery, packet loss, bearing-only IR, reference aircraft motion and smoothstep endpoint tests are included. Library adoption and passing tests do not establish real-vehicle defensive effectiveness. Full event traces remain retained; live actual trajectories are bounded to 1,800 samples per entity and radar histories to 600 samples per threat. Monte Carlo avoids serializing every physics step.
Live visualization keeps faint red attacker traces, persistent green radar samples, yellow age-propagated track estimates and purple estimated attacker forecasts after sufficient velocity evidence. Dashed cyan drone paths preview the delivered command reference over 60 seconds; solid blue traces show actual flight. Intentions omit force/actuator/wind uncertainty and do not promise the vehicle will follow them. Actual trails retain up to 1,800 samples at a default 0.2 s recording interval (about six minutes), and always include the terminal contact point. Radar history retains 600 samples per threat. Screen-sized radar points remain visible over the exaggerated aircraft meshes.