Phase 20: WW2 Era¶
Summary¶
Phase 20 introduces the era framework and the first historical era expansion: World War II. The era framework allows the simulation engine to load era-specific YAML data (units, weapons, ammunition, sensors, signatures, doctrines, commanders) from data/eras/{era}/ directories, and optionally instantiate era-specific engine extensions. Three new engine modules cover WW2-specific combat domains: naval gunnery (bracket firing and fire control), convoy/anti-submarine warfare (wolf packs and depth charges), and strategic bombing (CEP area damage, flak, fighter escort).
Key metric: 137 new tests, 5,244 total (up from 5,107). 4 new source files + 3 modified + ~60 YAML data files. No new dependencies.
What Was Built¶
20a: Era Framework + WW2 Unit Data (56 tests)¶
core/era.py—Eraenum (MODERN, WW2),EraConfigpydantic model (pre-defined configs for MODERN/WW2),get_era_config()factory,register_era_config()for custom erassimulation/scenario.py(modified) —erafield onCampaignScenarioConfig,era_config/naval_gunnery_engine/convoy_engine/strategic_bombing_enginefields onSimulationContext, era-aware_create_loaders()loading fromdata/eras/{era}/,era_configinget_state()simulation/engine.py(no changes needed) — existing None-check pattern handles engine gating for era-specific engines- 15 WW2 unit YAMLs: 5 armor (Sherman M4A3, T-34/85, Tiger I, Panther, Panzer IV H), 3 infantry (US/Wehrmacht/Soviet rifle squads), 4 air (Bf 109G, P-51D, Spitfire IX, B-17G), 3 naval (Type VIIC U-boat, Fletcher DD, Iowa BB)
- 8 weapon YAMLs: 75mm M3, 88mm KwK 36, 85mm ZIS-S-53, M2 .50 cal, MG 42, Mk 14 torpedo, 5"/38 naval, 16"/50 naval
- 13 ammo YAMLs: AP/HE variants per caliber + torpedo + naval rounds
- 4 sensor YAMLs: Mk I Eyeball WW2, SCR-584 radar, Type 271 naval radar, WW2 hydrophone
- 15 signature YAMLs: one per unit, zeroed thermal signatures (no IR detection in WW2)
20b: Engine Extensions (53 tests)¶
combat/naval_gunnery.py—NavalGunneryConfigpydantic model,BracketStatetracker,NavalGunneryEnginewith bracket firing, fire control quality, 2D Gaussian dispersion, straddle mechanicsmovement/convoy.py—ConvoyConfigpydantic model,ConvoyState,ConvoyEnginewith convoy formation, speed limiting to slowest ship, straggler probability, wolf pack attack modeling, depth charge patternscombat/strategic_bombing.py—StrategicBombingConfigpydantic model,BomberStreamState,TargetDamageState,StrategicBombingEnginewith CEP-based area damage, Norden bombsight altitude scaling, flak Poisson Pk, fighter escort modeling, bomber defensive fire, target regeneration
20c: Doctrine & Commanders (YAML only)¶
- 4 doctrine YAMLs: blitzkrieg, soviet_deep_ops, british_deliberate, us_combined_arms_ww2
- 3 commander profiles: aggressive_patton, methodical_montgomery, operational_zhukov
20d: Validation Scenarios (28 tests)¶
- Kursk/Prokhorovka: Eastern Front armored engagement using WW2 unit/weapon data
- Midway: Naval carrier battle using WW2 naval units and naval gunnery engine
- Normandy Bocage: Combined arms infantry/armor scenario in close terrain
- Each scenario uses
era: ww2field and WW2-specific unit/weapon/ammo references - Backward compatibility with all existing modern scenarios verified
Design Decisions¶
-
Era framework uses existing None-check pattern: Disabled modules simply stay
NoneonSimulationContext. No runtime branching in the hot path. Era-specific engines (naval_gunnery, convoy, strategic_bombing) are instantiated only whenera_configenables them. -
Era-specific YAML loaded from
data/eras/{era}/: Distinct IDs prevent conflicts with basedata/directory. WW2 Sherman issherman_m4a3, notm1a2. No name collisions, no fallback resolution complexity. -
Engine core is era-agnostic: The strategy pattern means eras are data packages (YAML) plus targeted engine extensions (Python modules). The simulation loop, detection pipeline, AI decision-making, and logistics all work unchanged across eras.
-
Zeroed thermal signatures for WW2: WW2 predates practical IR detection. Setting thermal signatures to zero in YAML effectively disables thermal sensors without code changes.
-
Naval gunnery bracket firing: WW2 naval gunnery is fundamentally different from modern guided missile combat (Phase 4
naval_surface.py). Bracket firing with straddle mechanics, fire control quality degradation, and 2D Gaussian dispersion captures the statistical nature of WW2 naval combat. -
Convoy system as movement extension: Wolf pack attacks and convoy defense are movement-phase phenomena (formation, speed constraints, straggler detection) with embedded combat resolution (depth charges, torpedo attacks). Placing in
movement/convoy.pyreflects this dual nature. -
Strategic bombing CEP model: Area bombing effectiveness modeled via CEP (circular error probable) with Norden bombsight altitude scaling. This is a different abstraction than the precision strike model used for modern munitions.
Deviations from Plan¶
simulation/engine.pyrequired no modifications — the existing None-check pattern for optional engines (established in Phases 16-18) handled era-specific engine gating without changes.- No new dependencies required — all WW2 physics models (Gaussian dispersion, Poisson flak, bracket firing) use existing numpy/scipy capabilities.
Issues & Fixes¶
- Era-aware loader path resolution:
_create_loaders()needed to searchdata/eras/{era}/directories first, then fall back to basedata/for shared assets (e.g., terrain data). Resolved by having era-specific loaders only load from era directory with distinct IDs. - Thermal signature zeroing: Initial approach of omitting thermal fields caused validation errors in pydantic models. Setting to 0.0 explicitly is cleaner and works with existing detection pipeline (SNR=-inf for zero signature effectively disables detection).
Known Limitations¶
- No era-specific detection model changes (WW2 radar performance approximated via sensor YAML parameters, not physics model changes)
- No era-specific C2 model (WW2 communications limitations approximated via comm equipment YAML, not structural changes)
- Convoy engine does not model individual escort positions (abstract effectiveness parameter)
- Strategic bombing target regeneration is linear (no industrial interdependency graph)
- No era-specific morale model (WW2 morale dynamics use same Markov chain as modern)
- Fighter escort in strategic bombing is a probability modifier, not a full air combat sub-simulation
- Only 3 validation scenarios — no Pacific theater island hopping or Eastern Front winter warfare
- No era-specific logistics (WW2 supply constraints approximated via YAML consumption rates)
Lessons Learned¶
- None-check gating pattern pays off across phases: The pattern established in Phase 16 (EW), refined in Phases 17-18 (Space, CBRN), and continued here means era-specific engines require zero engine.py modifications. New optional subsystems just need a field on SimulationContext.
- Era-specific data directories avoid naming conflicts: The
data/eras/{era}/convention is simpler and more maintainable than prefixing IDs or using namespace resolution. Each era is a self-contained data package. - YAML-driven eras scale well: Adding a new era is primarily a YAML authoring task. Only domains that fundamentally differ (e.g., WW2 naval gunnery vs modern missile combat) need new Python engine modules.
- Zeroed signatures are effective feature flags: Setting WW2 thermal signatures to 0.0 disables IR detection without conditional code. The SNR math naturally produces no-detection results.
- Historical validation scenarios catch data errors quickly: Kursk scenario immediately revealed a missing ammo YAML reference and an incorrect weapon range value.
Postmortem¶
1. Delivered vs Planned¶
- Scope: On target. All planned items shipped (4 source files, ~60 YAML, 3 modified files, 3 scenarios).
- Test count: 137 tests delivered. Total suite: 5,244 tests.
- No items dropped or deferred. No unplanned features added.
2. Integration Audit¶
- Era framework wiring solid:
erafield onCampaignScenarioConfigflows through toSimulationContext.era_config. Era-aware loaders correctly load fromdata/eras/ww2/. - Checkpoint/restore:
era_configin SimulationContextget_state()/set_state(). - Engine gating works:
naval_gunnery_engine,convoy_engine,strategic_bombing_engineareNonefor modern scenarios. No runtime cost. - All 5,107 existing tests pass unchanged: Full backward compatibility confirmed.
- No new event types (era-specific engines use existing event infrastructure).
3. Test Quality Review¶
- Overall rating: 7/10
- Strengths: Comprehensive YAML loading tests for all ~60 data files, historical plausibility checks in validation scenarios, backward compatibility verification, era framework unit tests cover registration and factory patterns.
- Gaps: Convoy wolf pack attack tested at unit level but not through full campaign loop; strategic bombing target regeneration tested in isolation but not multi-tick campaign integration; no cross-era comparison tests (modern vs WW2 same terrain).
4. API Surface Check¶
- Quality: Good. All public functions have type hints.
get_logger(__name__)throughout. DI pattern followed. Pydantic for all config models. Consistent with Phase 16-19 patterns. - Era enum is extensible:
register_era_config()allows future eras (WW1, Napoleonic, Ancient) without modifyingcore/era.py.
5. New Deficits (added to devlog/index.md)¶
- Convoy engine does not model individual escort positions (abstract effectiveness parameter)
- Strategic bombing target regeneration is linear (no industrial interdependency graph)
- Fighter escort in strategic bombing is probability modifier, not full air combat sub-simulation
- ScenarioLoader doesn't auto-wire era-specific engines from YAML (extends existing EW/Space/CBRN/Schools gap)
6. Documentation Freshness¶
- CLAUDE.md: To be updated with Phase 20 status + completed phase section.
- development-phases-post-mvp.md: Phase 20 to be marked COMPLETE with all sub-phases.
- devlog/index.md: Phase 20 status updated to Complete with link, new deficits added.
- project-structure.md: era.py and era-specific modules to be added to source tree.
- README.md: Test count (5,244), phase badge (20), status table to be updated.
- MEMORY.md: Current status and lessons learned to be updated.
7. Performance¶
- Full suite: 5,244 passed. Phase 20 tests alone add minimal overhead (~0.6s). No performance regression from era framework or new engine modules.
8. Summary¶
- Scope: On target
- Quality: Good (7/10 tests, good API surface)
- Integration: Fully wired — era framework, era-aware loaders, and era-specific engines all functional
- Deficits: 4 new items (convoy abstraction, linear regeneration, escort simplification, auto-wiring gap)
- Backward compatibility: All 5,107 existing tests pass unchanged