Phase 63: Cross-Module Feedback Loops¶
Status: Complete Tests: 74 new (6 test files) Source files modified: 6 (zero new source files)
Overview¶
Wires four cross-module integration gaps — feedback loops where one system's output should drive another system's behavior. Closes the systemic build-then-defer-wiring pattern identified in the Block 7 brainstorm.
Implementation¶
Step 0: CalibrationSchema Infrastructure¶
Added 4 new fields to CalibrationSchema:
- enable_event_feedback: bool = False — gates medical/maintenance event consumption
- enable_missile_routing: bool = False — gates MISSILE type inference for guided launchers
- enable_c2_friction: bool = False — gates comms loss → order freeze
- degraded_equipment_threshold: float = 0.3 — fraction broken equipment → degraded logging
All default False for zero behavioral change to existing scenarios.
63a: Detection → AI Assessment¶
FOW sensor/signature wiring — replaced hardcoded empty data in battle.py's FOW update block:
- "sensors": [] → ctx.unit_sensors.get(_u.entity_id, []) — real sensor data flows to detection
- "signature": None → _get_unit_signature(ctx, _eu) — real signatures from sig_loader
- Added _get_unit_signature() helper with graceful fallback to None on any error
- Assessment path already reads FOW contacts when enable_fog_of_war=True — now detection actually produces real contacts
Deviation from plan: Plan called for modifying c2/ai/assessment.py and enabling FOW in modern scenarios. Assessment.py already consumed FOW contacts (Phase 53a); the actual gap was upstream — FOW update block passed empty data. No scenario YAML changes needed since all flags default False.
63b: Medical → Strength & Maintenance → Readiness¶
Event subscription in engine.py — when enable_event_feedback=True:
- Subscribes to ReturnToDutyEvent → calls unit.restore_crew_member(member_id) (to MINOR_WOUND, KIA permanent)
- Subscribes to EquipmentBreakdownEvent → marks equipment.operational = False
- Subscribes to MaintenanceCompletedEvent → restores equipment.operational = True
- Equipment breakdown handler checks degraded_equipment_threshold and logs when exceeded
Unit.restore_crew_member() added to entities/base.py:
- Restores injury state (default MINOR_WOUND, not HEALTHY)
- Returns False for KIA (permanent) or unknown member_id
Deviation from plan: Plan called for formal DEGRADED unit status with Pk/movement/detection penalties. Implemented as threshold-based logging; formal enum value deferred.
63c: Checkpoint State Completeness¶
Added 4 previously missing engines to both get_state() and set_state() engine lists in scenario.py:
- comms_engine
- detection_engine
- movement_engine
- conditions_engine
Deviation from plan: Plan described registering with CheckpointManager. Actual pattern is the engine tuple list in get_state()/set_state() — same mechanism used by all other engines.
63d: MISSILE Routing & Comms → C2¶
MissileEngine instantiation in scenario.py _create_engines():
- MissileEngine(dmg_engine, bus, combat_rng) — follows same pattern as all combat engines
- Added missile_engine field to SimulationContext and result dict
MISSILE type inference in battle.py engagement routing:
- When enable_missile_routing=True: weapon category MISSILE_LAUNCHER + guidance != NONE → EngagementType.MISSILE
- missile_engine passed to route_engagement() call
MISSILE handler in engagement.py route_engagement():
- Calls missile_engine.launch_missile() with proper args (launcher_id, missile_id, positions, ammo, timestamp)
- Returns not-engaged with aborted_reason="no_missile_engine" if engine absent
C2 friction gate in battle.py OODA DECIDE handler:
- When enable_c2_friction=True: checks _compute_c2_effectiveness(ctx, unit_id, side)
- If effectiveness < c2_min_effectiveness (default 0.3): skips DECIDE phase, unit operates on last orders
Files Modified¶
| File | Changes |
|---|---|
stochastic_warfare/simulation/calibration.py |
4 new fields (3 enable flags + 1 threshold) |
stochastic_warfare/simulation/battle.py |
FOW sensor/signature fix, _get_unit_signature helper, MISSILE type inference, missile_engine in route_engagement call, C2 friction gate |
stochastic_warfare/simulation/engine.py |
_register_event_handlers(), 3 handler methods, unit lookup helper, degraded equipment threshold check |
stochastic_warfare/simulation/scenario.py |
missile_engine field + instantiation, 4 engines added to get_state/set_state |
stochastic_warfare/combat/engagement.py |
MISSILE type handler in route_engagement() |
stochastic_warfare/entities/base.py |
restore_crew_member() method |
Test Files¶
| File | Tests |
|---|---|
tests/unit/test_phase_63_infra.py |
13 |
tests/unit/test_phase_63a_detection_ai.py |
12 |
tests/unit/test_phase_63b_event_feedback.py |
14 |
tests/unit/test_phase_63c_checkpoint.py |
10 |
tests/unit/test_phase_63d_missile_comms.py |
13 |
tests/unit/test_phase_63_structural.py |
12 |
Deferrals¶
- MissileEngine per-tick update —
update_missiles_in_flight()advances in-flight missiles. Launch ships; flight-to-impact resolution deferred. - MissileDefenseEngine intercept — Intercepting in-flight missiles requires missile-as-contact detection. Deferred.
- Formal DEGRADED unit status — Equipment breakdown logs threshold but no formal enum value or automatic Pk/movement penalties.
- FOW confidence-weighted assessment — Assessment uses simple contact count. Sophisticated confidence mapping deferred.
- RTD to HEALTHY — RTD restores to MINOR_WOUND. Full rehabilitation deferred.
- Logistics event feedback — SupplyDeliveredEvent/RouteInterdictedEvent/ConvoyDestroyedEvent remain unsubscribed.
Postmortem¶
Scope: Over (74 tests vs planned ~45)¶
More tests than planned due to additional structural verification and edge case coverage. All 4 planned subsections delivered. Three scope reductions: no assessment.py modification needed (gap was upstream), no formal DEGRADED enum, no scenario YAML changes.
Quality: High¶
- All 74 Phase 63 tests pass
- Full suite 8483 passed, 0 failed
- Calibration coverage test caught
degraded_equipment_thresholdas unconsumed — fixed immediately - Three test quality issues found and fixed during postmortem (logic error in structural test, empty test body, fragile .index() call)
Integration: Fully wired¶
- All 10 integration checks pass (missile_engine end-to-end, all 3 flags consumed, event subscription verified, checkpoint lists complete, helper called, C2 friction wired)
- No orphaned code, no dead imports
- Zero new source files — all changes in existing modules
Test quality notes¶
- Mix of behavioral (unit tests with real objects) and structural (source string assertions)
- Structural tests are inherently fragile to refactoring but catch regressions fast (Block 7 pattern)
- Edge cases well-covered: KIA permanence, unknown IDs, missing engines, empty equipment
Deficits: 6 deferrals (see above)¶
All are planned future work, not bugs. MissileEngine flight resolution is the most impactful deferral.
Lessons Learned¶
- FOW gap was upstream (battle.py passed empty data), not downstream (assessment.py) — always trace the data flow, not just the consumer
- Calibration coverage test is an excellent safety net — caught unconsumed field before commit
- MissileEngine constructor signature differs from how COASTAL_DEFENSE/AIR_LAUNCHED_ASHM call it (pre-existing bug in engagement.py lines 412-432: missing launcher_id/missile_id args)