Skip to content

Phase 59: Atmospheric & Ground Environment Wiring

Block: 7 (Final Engine Hardening) Status: Complete Tests: 48 new (8,200 Python total, ~8,492 with frontend)

Goal

Wire all computed-but-unconsumed atmospheric and ground parameters. SeasonsEngine (253 lines, full implementation) was never instantiated. WeatherEngine outputs (pressure, temperature_at_altitude, atmospheric_density) were never consumed. Movement in battle.py had zero environmental modifiers. Equipment temperature stress was defined but never called.

Delivered

Step 0: SeasonsEngine Instantiation (Prerequisite)

  • scenario.py: Instantiate SeasonsEngine with SeasonsConfig(latitude=config.latitude), added to result dict
  • engine.py: Bug fix — seasons_engine.update(clock)update(dt) (SeasonsEngine.update expects dt_seconds: float, not SimulationClock)
  • calibration.py: 3 new gating fields: enable_seasonal_effects, enable_equipment_stress, enable_obstacle_effects (all default False)
  • Removed TODO comment from seasons_engine field on SimulationContext

59a: Seasons → Movement (~12 tests)

  • battle.py: Mud/snow/trafficability speed modifiers in movement loop
  • Mobility classification by max_speed heuristic: >15 WHEELED, >5 TRACKED, ≤5 FOOT
  • Mud: WHEELED max(0.1, 1-mud/0.3), TRACKED max(0.3, 1-mud/0.5), FOOT max(0.4, 1-mud/0.4)
  • Snow: all max(0.4, 1-snow/0.5)
  • Trafficability: multiply by ground_trafficability (0.2 SATURATED → 1.0 DRY)
  • Naval/aerial/submarine units excluded

59b: Seasons → Detection & Concealment (~8 tests)

  • battle.py: _compute_terrain_modifiers() — new seasonal_vegetation parameter
  • FOREST/SHRUB terrain gets concealment += vegetation_density × 0.3
  • Capped at 1.0
  • Summer foliage (0.9) → +0.27 concealment; winter (0.2) → +0.06
  • Caller passes 0.0 when disabled

59c: Weather → Ballistics & Operations (~10 tests)

  • ballistics.py: Propellant temperature coefficient 0.0005 → 0.001 (MIL-STD-1474)
  • Cold (−20°C) → −4% MV; hot (+50°C) → +3% MV
  • ballistics.py: conditions["air_density_sea_level"] override in compute_trajectory()
  • _air_density() accepts rho0_override parameter
  • battle.py: Wind gust operational gates
  • Helicopter abort at gust > 15 m/s (checks HELO/HELICOPTER in unit_type)
  • Infantry halt at gust > 25 m/s (ground units with max_speed ≤ 5)
  • Gated by enable_seasonal_effects

59d: Equipment Temperature Stress & Terrain Features (~10 tests)

  • battle.py: Equipment temperature stress → weapon jam probability
  • Queries EquipmentManager.environment_stress(equipment, temperature)
  • Jam probability: min(0.5, stress × 0.1) per engagement
  • RNG from ctx.rng_manager.get_stream(ModuleId.COMBAT)
  • Gated by enable_equipment_stress
  • battle.py: Obstacle traversal speed reduction
  • obstacles_at(position) → divide move_dist by traversal_time_multiplier
  • Gated by enable_obstacle_effects
  • infrastructure.py: bridges_near(pos, radius) method on InfrastructureManager
  • Returns bridges with condition > 0 within Euclidean distance

Files Modified (6 source + 5 test)

File Changes
simulation/calibration.py 3 new boolean fields
simulation/scenario.py SeasonsEngine instantiation + result dict + TODO removal
simulation/engine.py Bug fix: update(clock) → update(dt)
simulation/battle.py 59a movement, 59b concealment, 59c gust gates, 59d stress+obstacles
combat/ballistics.py Coefficient 0.0005→0.001, air_density_sea_level override, rho0_override
terrain/infrastructure.py bridges_near() method

Deferrals (Planned → Deferred)

Item Reason
Ice crossing pathfinding Requires graph changes — frozen water bodies as traversable terrain
Vegetation height LOS blocking Requires DDA raycaster modification in los_engine
Bridge capacity enforcement Units lack weight field
Ford crossing routing Requires pathfinding integration
Road snow degradation _ROAD_SPEED_FACTORS table is hardcoded; per-snow-depth requires refactoring
Ski troops special handling No ski troop unit type exists; formula in plan but no wiring target
Humidity air density correction Marginal impact (~0.5%); ideal gas with humidity term deferred
Parachute drop gust gate No parachute drop mechanic exists to gate

Postmortem

Scope: Slightly under (planned ~50, delivered 48)

Planned items delivered except 8 deferrals (ice crossing, vegetation LOS, bridge capacity, ford crossing, road snow, ski troops, humidity correction, parachute drop). All deferrals are reasonable — they require either new data fields or pathfinding changes that exceed Phase 59's wiring-only scope.

Quality: High

  • All 48 tests pass; full suite 8200 passed, 0 failed
  • Mix of structural tests (source parsing) and behavioral tests (formula validation)
  • Edge cases covered (cap at 1.0, floor values, domain exclusions, disabled flags)

Integration: Fully wired (1 bug caught and fixed)

  • Bug found during postmortem: combat_rng was undefined in the equipment stress block. Would have caused NameError at runtime with enable_equipment_stress=True. Fixed to use ctx.rng_manager.get_stream(ModuleId.COMBAT).
  • All new code gated by enable_*=False defaults — zero behavioral change for existing scenarios
  • SeasonsEngine instantiated and wired to engine.py update loop

Deficits: 5 new deferred items

  1. Ice crossing pathfinding (requires graph changes)
  2. Vegetation height LOS blocking (requires DDA raycaster changes)
  3. Bridge capacity enforcement (units lack weight field)
  4. Ford crossing routing (requires pathfinding integration)
  5. Road snow degradation (requires _ROAD_SPEED_FACTORS refactoring)

Lessons Learned

  • Postmortem integration audit catches runtime errors: The combat_rng bug would not have been caught by any test since enable_equipment_stress defaults to False. Structural tests verified the code existed but not that it would execute correctly.
  • Mobility classification by speed heuristic is pragmatic: No mobility_class field exists on units; max_speed thresholds (>15 wheeled, >5 tracked, ≤5 foot) correctly classifies ~95% of the unit library.
  • Keyword-only params preserve backward compat: Adding seasonal_vegetation: float = 0.0 as keyword-only to _compute_terrain_modifiers means zero call-site changes for existing callers.