Phase 51: Naval Combat Completeness¶
Summary¶
Phase 51 is the third phase of Block 6. Completed naval engagement routing (DEPTH_CHARGE, ASROC, shore bombardment guard, VLS ammo tracking), naval posture system (NavalPosture enum with speed/engagement effects), DEW disable path (threshold-based destroy/disable), and mine/blockade engine wiring.
- 7 files modified, 1 test file created
- 37 new tests, 0 regressions (7840 passed, 21 skipped)
- 6 deficits resolved: D2, D6, D16, Phase 43 shore bombardment, Phase 6 blockade, Phase 6 VLS
What Was Built¶
51a: Naval Engagement Routing¶
- DEPTH_CHARGE routing:
_route_naval_engagement()now routesDEPTH_CHARGEcategory weapons tonaval_subsurface_engine.depth_charge_attack(). Damage >= 0.6 → DESTROYED, else DISABLED. - ASROC routing:
MISSILE_LAUNCHERtargetingDomain.SUBMARINEroutes tonaval_subsurface_engine.asroc_engagement()before the generic MISSILE_LAUNCHER salvo path. Hit with damage >= 0.6 → DESTROYED, else DISABLED. - Shore bombardment guard: Added attacker domain check — only
Domain.NAVALorDomain.SUBMARINEattackers route to naval gunfire support. Prevents GROUND attacker with CANNON from erroneously entering the shore bombardment path. - VLS ammo tracking:
BattleManager._vls_launches: dict[str, int]tracks missiles launched per unit. Whenweapon.definition.magazine_capacity > 0, checks exhaustion before firing and increments count after. Safeint()conversion handles MagicMock'd weapon definitions in existing tests.
51b: Naval Posture¶
NavalPostureIntEnum: ANCHORED (0), UNDERWAY (1), TRANSIT (2), BATTLE_STATIONS (3). Added tonaval.pybeforeNavalUnitType.naval_posturefield onNavalUnitwith defaultUNDERWAY. State round-trips viaget_state()/set_state().- Spawn default:
loader.pysetsnaval_posture=NavalPosture.UNDERWAYfor all naval/submarine/amphibious units. - Speed multiplier:
_NAVAL_POSTURE_SPEED_MULT— ANCHORED=0.0x, UNDERWAY=1.0x, TRANSIT=1.2x, BATTLE_STATIONS=0.9x. Applied in_execute_movement(). - Engagement gate: ANCHORED units skip offensive engagement (parallel to air posture GROUNDED gate).
- Auto-assignment: In tick loop, modern/WW1/WW2 era naval units auto-transition to BATTLE_STATIONS when enemies within 2x engagement range, back to UNDERWAY when threat clears. Era-gated: ancient/napoleonic eras excluded (oar-powered ships don't have modern battle stations concept).
51c: DEW Disable Path¶
dew_disable_threshold: float = 0.5added toCalibrationSchema. Configurable per-scenario.- Threshold-based damage: DEW hit with
p_hit >= threshold→ DESTROYED, below → DISABLED. Replaces unconditional DESTROYED. - Wavelength-dependent transmittance:
compute_atmospheric_transmittance()now acceptswavelength_nmparameter. Rayleigh+Mie composite scattering correction: shorter wavelengths (532nm green) scatter more than 1064nm (Nd:YAG IR).execute_laser_engagement()readsbeam_wavelength_nmfrom weapon definition.
51d: Mine and Blockade Wiring¶
- Mine encounter check: After movement phase, naval/submarine/amphibious units moving (speed >= 0.1) are checked against all armed mines in
mine_warfare_engine._mines. Trigger radius per mine type. Damage assessed against destruction/disable thresholds. - DisruptionEngine instantiation: Created in
ScenarioLoader._create_engines(), attached toSimulationContext.disruption_engine. State persistence in get_state/set_state engine lists. - Blockade query:
campaign.py._update_supply_network()queriesdisruption_engine.active_blockades()and logs effectiveness per zone. Replacespassstub.
Design Decisions¶
-
ASROC before generic MISSILE_LAUNCHER: Target domain check (
Domain.SUBMARINE) differentiates ASROC from anti-ship missile. Order matters — ASROC block placed before generic MISSILE_LAUNCHER to prevent fallthrough to salvo model. -
VLS safe int conversion:
getattr(wpn_inst.definition, "magazine_capacity", 0)returns MagicMock when definition is mocked. Addedtry/except (TypeError, ValueError)conversion to int to maintain backward compat with existing Phase 43 tests. -
Era-gated naval posture auto-assignment: Ancient/napoleonic naval units (triremes, galleys, ships of the line) don't have modern battle stations mechanics. Without the era gate, Salamis scenario regressed (Greek ships slowed to 0.9x by BATTLE_STATIONS auto-assignment, shifted outcome to Persian victory).
-
Blockade query in campaign.py, not supply_network.py: Plan specified modifying
supply_network.py. Actual: blockade effectiveness queried and logged incampaign.py._update_supply_network(). Full throughput reduction deferred — requires more complex route cost modification that's better suited to Phase 56. -
Mine check O(units x mines) with guard: Only runs when
_mineslist is non-empty. No current scenarios lay mines, so zero overhead in practice. STRtree spatial indexing deferred to Phase 56.
Deviations from Plan¶
| Planned | Actual | Reason |
|---|---|---|
| Modify supply_network.py for blockade throughput | Blockade query in campaign.py only (log, no throughput reduction) | Complexity deferral — full throughput reduction needs route cost integration |
| ANCHORED +50% detection cross-section | Not implemented | Detection modifier requires detection engine integration; speed + engagement gate sufficient for Phase 51 |
| TRANSIT engagement delay | Not implemented | Delay mechanic requires cooldown tracking; simplified to speed-only effect |
| BATTLE_STATIONS +20% detection | Not implemented | Detection modifier deferred with ANCHORED detection |
| ~36 tests | 37 tests | On target |
| — | Era-gated naval posture auto-assignment | Unplanned — required to prevent Salamis regression |
| — | Safe magazine_capacity int conversion | Unplanned — required for MagicMock compat in Phase 43 tests |
Issues & Fixes¶
-
MagicMock magazine_capacity: Phase 43 tests mock
wpn_inst.definitionas MagicMock, causingmagazine_capacity > 0to fail withTypeError. Fixed withtry/exceptint conversion. -
Salamis regression: Naval posture auto-assignment set triremes to BATTLE_STATIONS (0.9x speed), slowing Greek advance. Greeks lost. Fixed by gating auto-assignment to modern/WW1/WW2 eras only.
-
Unit constructor: Test helper initially used
display_name=(nonexistent field). Fixed toname=.
Known Limitations¶
- Naval posture detection modifiers not implemented (ANCHORED cross-section, TRANSIT reduced emissions, BATTLE_STATIONS active sensors) — deferred to Phase 52 or later
- TRANSIT engagement delay not implemented — deferred
- Blockade effectiveness queries logged but not integrated into supply throughput reduction — deferred to Phase 56
- No scenario exercises
magazine_capacity > 0on naval weapons — VLS tracking is structurally wired but untested end-to-end in scenarios - No scenario lays mines programmatically — mine encounter check is structurally wired but untested end-to-end
- Mine check is O(units x mines) — STRtree spatial indexing deferred to Phase 56
Postmortem¶
Scope: On target¶
All 4 substeps delivered. 37 tests (plan: ~36). 6 deficits resolved. Two planned detection modifiers deferred (reasonable — detection integration is more complex than speed/engagement gates).
Quality: High¶
- 37 tests across 7 test classes covering all routing paths, posture values, DEW thresholds, mine encounters, and disruption wiring
- Historical accuracy regression caught and fixed (Salamis era-gating)
- Safe MagicMock handling prevents fragile test failures
- No TODOs/FIXMEs in new code
Integration: Fully wired (with one deferral)¶
- All new routing paths reachable from main engagement loop
- NavalPosture used in loader, battle movement, and engagement gate
- DisruptionEngine instantiated, on context, queried in campaign
- Mine encounter check runs in movement phase
- One deferral: supply_network.py blockade throughput integration
Deficits: 3 new items¶
- Naval posture detection modifiers (ANCHORED/TRANSIT/BATTLE_STATIONS) — Phase 52+
- Blockade throughput reduction in supply_network.py — Phase 56
- No scenarios exercise VLS capacity or mine encounters end-to-end — Phase 55+
Test time: ~1222s (within normal range for historical validation tests)¶
Lessons Learned¶
- Era-gating is essential for posture systems: Modern naval concepts (battle stations speed penalty) don't apply to ancient oar-powered ships. Auto-assignment mechanics must check era before applying.
- MagicMock auto-creates attributes:
getattr(mock, "field", 0)returns a MagicMock, not 0. Safe int conversion needed when existing tests use broad mocks. - Blockade wiring is structural first: Instantiating DisruptionEngine and querying blockades (even if only logging) is the right first step. Full throughput integration requires understanding route cost mechanics.