Phase 45 — Mathematical Model Audit & Hardening¶
Status: Complete Tests: 21 new + 7,565 total passing (including 1 modified assertion) Files changed: 11 source modified + 1 existing test modified + 1 new test file
Summary¶
Audited and hardened mathematical models across combat, morale, maintenance, pathfinding, and assessment systems. Added literature citations to key constants, migrated hardcoded thresholds to pydantic configuration, replaced the Gaussian blast model with Hopkinson-Cranz overpressure scaling, added Weibull failure distribution option for maintenance, and introduced a moderate-condition floor for hit probability.
Sub-phases¶
45a: Blast Damage Model (core physics upgrade)¶
Replaced Gaussian blast damage falloff with Hopkinson-Cranz overpressure scaling — the standard military blast model using scaled distance Z = r / W^(1/3):
combat/damage.py: New_compute_overpressure_psi()function with regime-dependent exponents (strong shock alpha=2.65, weak shock alpha=1.4). Continuity enforced at regime boundary via_CONV_BLAST_K_WEAK. NewDamageConfigfields:use_overpressure_blast,blast_radius_to_fill_c,strong_shock_alpha,weak_shock_alpha.combat/ammunition.py: Newexplosive_fill_kgfield onAmmoDefinition. Derived fromblast_radius_mwhen 0 (backward compat with 126 existing YAML files viablast_radius_to_fill_c=26.6).- Legacy Gaussian model available via
use_overpressure_blast=False.
45b: Morale Constant Validation (citations, no value changes)¶
Added literature citations to morale constants — validated that current defaults align with published research:
morale/state.py: Citations onMoraleConfigfields referencing Dupuy (QJMA), Marshall (ratio of fire), Shils & Janowitz (Wehrmacht cohesion), Rowland (Stress of Battle)._MORALE_EFFECTStable entries annotated with source basis.- Constants validated as reasonable; no value changes made.
45c: Maintenance Model Review (Weibull option)¶
Added Weibull failure distribution as opt-in alternative to exponential MTBF:
logistics/maintenance.py: Newuse_weibullandweibull_shape_kconfig fields. Weibull hazard rateh(t) = (k/lambda) * (t/lambda)^(k-1)gives increasing failure probability with equipment age.k=1.0recovers exponential (identical to prior behavior). MIL-HDBK-217F citations added.
45d: Hit Probability Review (floor + citations)¶
Prevented extreme penalty stacking in hit probability computation:
combat/hit_probability.py: Newmoderate_condition_floorfield inHitProbabilityConfig(default 0.03). Prevents combined condition modifiers from driving Pk below floor. Docstring citations for modifier basis (Dupuy CEV, RAND combat modeling).
45e: Constant Sourcing & Configuration (assessment + pathfinding)¶
Migrated hardcoded thresholds to pydantic configuration with citation comments:
c2/ai/assessment.py: NewAssessmentConfigpydantic model with 30+ thresholds (force ratio, ammo, morale, range, terrain weights). Constructor accepts optional config. Defaults match prior hardcoded values — zero behavioral change.movement/pathfinding.py: Exponential threat costexp(alpha * threat)replaces linear scaling. Newthreat_cost_alphaparameter (default 3.0). Exponential more accurately models risk-averse pathfinding behavior.- Citation comments added to 9 source files:
combat/naval_subsurface.py(torpedo Pk sources),combat/naval_gunnery.py(bracket convergence),detection/sonar.py(convergence zone, bearing uncertainty),escalation/ladder.py(Kahn/Schelling escalation theory).
Files Modified¶
| File | Changes |
|---|---|
c2/ai/assessment.py |
AssessmentConfig pydantic model (30+ fields), constructor accepts config |
combat/damage.py |
_compute_overpressure_psi(), DamageConfig fields, Hopkinson-Cranz constants |
combat/ammunition.py |
explosive_fill_kg field on AmmoDefinition |
combat/hit_probability.py |
moderate_condition_floor in HitProbabilityConfig, docstring citations |
combat/naval_subsurface.py |
Citation comments (torpedo Pk sources) |
combat/naval_gunnery.py |
Citation comments (bracket convergence) |
detection/sonar.py |
Citation comments (CZ, bearing uncertainty) |
morale/state.py |
Citations on MoraleConfig and _MORALE_EFFECTS |
logistics/maintenance.py |
Weibull option (use_weibull, weibull_shape_k), MIL-HDBK-217F citations |
escalation/ladder.py |
Kahn/Schelling citations |
movement/pathfinding.py |
Exponential threat cost, threat_cost_alpha param |
tests/unit/test_damage.py |
Updated 1 assertion for overpressure model output |
New Test File¶
tests/unit/test_phase45_models.py — 21 tests
Key Design Decisions¶
- AssessmentConfig defaults match prior hardcoded values: Zero behavioral change on upgrade. All 30+ thresholds default to the values that were previously hardcoded in the assessment logic.
- Hopkinson-Cranz with regime-dependent exponents: Strong shock (alpha=2.65) for near-field, weak shock (alpha=1.4) for far-field. Continuity at the regime boundary enforced via computed
_CONV_BLAST_K_WEAKconstant. - explosive_fill_kg derived when zero: Backward compat with 126 existing YAML files —
fill = (blast_radius_m / blast_radius_to_fill_c)^3. No YAML changes required. - Weibull is opt-in:
use_weibull=Falsedefault. Shape parameterk=1.0is mathematically identical to the prior exponential model. - moderate_condition_floor=0.03: Prevents extreme penalty stacking (weather + night + suppression + movement) from driving hit probability to near-zero. 3% floor ensures even worst-case engagements have non-trivial Pk.
- Exponential threat cost (alpha=3.0): Replaces linear threat scaling in pathfinding.
exp(alpha * threat)more accurately models risk-averse routing — units strongly avoid high-threat areas while tolerating low threat.
Known Limitations¶
blast_radius_to_fill_c=26.6calibrated for 155mm HE: Different weapon types (shaped charges, thermobaric, small-arms grenades) may need different calibration values. Future YAML updates can add explicitexplosive_fill_kgper weapon.- Morale constants validated but not changed: Literature confirms current defaults are reasonable. Fine-tuning against specific engagement data would require the
/calibrateskill. - Assessment config is per-scenario, not per-commander: All commanders in a scenario share the same assessment thresholds. Per-commander config would require YAML schema changes.
- Weibull shape parameter is global: Single
kvalue for all equipment types. Real-world failure distributions vary by component (electronics vs mechanical vs structural).
Postmortem¶
1. Delivered vs Planned¶
All 5 sub-phases delivered as planned (45e → 45d → 45b → 45c → 45a). No items dropped, deferred, or descoped. No unplanned items added. Implementation order matched the plan exactly. Scope: well-calibrated.
2. Integration Audit¶
- AssessmentConfig: Used by
SituationAssessor+ tested intest_phase45_models.py. No dead code. - Overpressure model: Called by
DamageEngine.apply_blast_damage()in combat loop. Tested with 6 dedicated tests. - Weibull option: Gated by
use_weibullflag inMaintenanceEngine.update(). Tested with 4 dedicated tests. - moderate_condition_floor: Applied in
compute_phit(), tested with 3 dedicated tests. - Exponential threat cost: Applied in
Pathfinder._raw_threat_cost(), tested with 2 dedicated tests. - explosive_fill_kg: Field on
AmmoDefinition, consumed byDamageEngine. Backward-compat derivation when 0. - Citation comments: Documentation only — no integration needed.
- No dead modules. All new features are wired and tested.
3. Test Quality Review¶
- 21 new tests across 6 test classes in
test_phase45_models.py. - MC validation tests (3 morale tests with 200-iteration runs) verify stochastic properties.
- Edge cases covered: k=1 Weibull matching exponential, regime boundary continuity, worst-case penalty stacking, config override behavior.
- Realistic data: Tests use actual weapon parameters (155mm HE, blast radii, realistic morale inputs).
- 1 modified existing test:
test_beyond_frag_radius_no_fragrelaxed for overpressure model. - No tests on implementation details — all behavioral.
4. API Surface Check¶
- Type hints present on all public functions.
- New config classes (
AssessmentConfig, config fields on existing models) follow pydantic BaseModel pattern. _compute_overpressure_psi()correctly prefixed with_(private helper).- DI pattern maintained:
SituationAssessor(config=...),Pathfinder(threat_cost_alpha=...). get_logger(__name__)used throughout (no bareprint()).
5. Deficit Discovery¶
- No new TODOs or FIXMEs in new code.
- 4 known limitations documented (blast_radius_to_fill_c calibration, morale constants not changed, assessment config per-scenario not per-commander, Weibull shape global). All are Phase 46/47 scope.
- No missing error handling at system boundaries.
6. Documentation Freshness¶
- CLAUDE.md: Phase 45 summary added, test count updated to 7,837.
- README.md: Test count updated to 7,837.
- docs/index.md: Test count updated to 7,837.
- devlog/index.md: Phase 45 entry added.
- development-phases-block5.md: Phase 45 status updated.
- mkdocs.yml: Phase 45 nav entry added.
- MEMORY.md: Status, lessons learned, and phase table updated.
7. Performance Sanity¶
- Phase 45 test run: 7,565 passed in 137.12s.
- Phase 44 baseline: 7,544 passed in 139.53s.
- Delta: +21 tests, −2.4s (within noise). No performance regression.
8. Summary¶
- Scope: On target — all 5 sub-phases delivered as planned
- Quality: High — physics-based blast model, literature citations, pydantic config, all backward-compatible
- Integration: Fully wired — all new features exercised in source and tests
- Deficits: 0 new (4 known limitations are Phase 46/47 scope, already documented)
- Action items: None — ready for commit