CRITICAL BUG: VictorySystem::check_death_star() short-circuits to None
when death_star_location is unset. The Empire could never win by Death
Star in either interactive or headless mode.
Fix both code paths:
- main.rs: set victory_state.death_star_location = Some(system) in
FireDeathStar handler. Changed victory_state from &VictoryState to
&mut VictoryState.
- simulation.rs: set states.victory.death_star_location after
apply_death_star_events for PlanetDestroyed events.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Knesset Ptah Phase 0 — telemetry coverage fixes:
- integrator.rs: Expand is_story_event() to include 0x210/0x212/0x220/0x221
(Dagobah, Bounty, Final Battle story chains) in addition to 0x380-0x39A
- repair.rs: Add RepairCheckPerformed variant that fires when fleet at shipyard
has ships with damage_control > 0 (interim solution until ShipInstance promotion)
- integrator.rs: Handle RepairCheckPerformed in apply_repair_events()
- telemetry_coverage.rs: Fix uprising fixture (tank both popularities to prevent
economy-driven control flip), add victory to optional systems
Result: 15/17 required systems verified. 2 optional (uprising, betrayal — RNG-dependent).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 5 of Knesset Ereshkigal: multiplayer protocol foundation.
- net_protocol.rs: 179 NetMessage variants covering all original REBEXE.EXE
notification types from Ghidra RE (tactical, entity state, character, role,
mission lifecycle, story chains, system state, fleet, faction, combat phases)
- Each variant maps to original notification string and event ID where known
- event_id() returns Option<u16> for the 27 registered event IDs
- category() returns domain grouping for telemetry
- 5 unit tests (variant count, unique IDs, categories, serde round-trip, known IDs)
- Integration test: 1000-tick dual-AI playtest verifies 10 required SYS_*
constants emit events; 7 condition-dependent systems are optional
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
simulation.rs 1,658→451 LOC (73% reduction). All 17 simulation sections
route through PerceptionIntegrator for world mutation + telemetry.
Review fixes: economy wired into interactive game, build completions
applied, faction-aware AI dispatch, strong_support bit guard, RepairSystem
wired into tick. 0 P0 findings from simplicity reviewer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add RepairSystem::advance() call at position 12b (after research, before
jedi). Add apply_repair_events() to PerceptionIntegrator for telemetry.
RepairState added to SimulationStates in both simulation.rs and playtest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P1-2/P1-3: Remove 15 unused imports from simulation.rs (types that
moved to integrator.rs during extraction).
P2-1: Update stale module doc in integrator.rs (no longer a scaffold).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
simulation.rs: 1,658→449 LOC (73% reduction). All 17 simulation
sections now route through PerceptionIntegrator for mutation + telemetry.
Phase 4 marked DONE in Ereshkigal plan.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move events, AI, blockade, uprising, betrayal, death star, research,
and jedi sections from simulation.rs to PerceptionIntegrator methods.
Delete events Vec — integrator.finish() is now the sole return path.
Remove apply_event_actions_to_world and apply_ai_actions_to_world
helpers (now in integrator.rs as private functions).
simulation.rs: 963→449 LOC (73% total reduction from 1,658 baseline).
integrator.rs: 805→1,185 LOC. All 17 system sections now route through
the integrator for both world mutation and telemetry emission.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move apply_mission_effects (176 LOC, 14 match arms) and escape handling
from simulation.rs to integrator.apply_mission_result() and
apply_escape_effects(). simulation.rs: 1178→963 (-215).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move apply_space_combat_result, apply_ground_combat_result, and
bombardment telemetry from simulation.rs to integrator methods.
Combat trigger detection and system calls stay in simulation.rs.
simulation.rs: 1316→1178 (-138).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move apply_build_completions() (143 LOC build completion body + telemetry)
and apply_arrivals() (fleet location + system fleet list updates + telemetry)
from simulation.rs to integrator.rs. simulation.rs: 1500→1316 (-184).
main.rs updated to use integrator::apply_build_completion_inner.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move 7-variant EconomyEvent match (support drift, collection rate,
garrison, incidents, control resolution, overcaps) from simulation.rs
to integrator.apply_economy_events(). simulation.rs: 1578→1500 (-78).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move sys_json, ai_action_json from simulation.rs to integrator.rs.
Delete duplicate sys_name/char_name from simulation.rs (now imported
from integrator). simulation.rs: 1658→1578 LOC (-80).
integrator.rs: 144→206 LOC (+62).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FUN_005582e0_adjust_value_for_strong_support only doubles Empire troop
suppression when field_0x88 bit 11 ("strong support") is set. Previously
we always doubled. Now the strong_support flag is computed per-system
per-tick in SystemSummary (support > drift threshold) and passed to
calculate_support_drift as the guard condition.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P0: EconomySystem::advance() now runs before manufacturing in the
interactive game loop — support drift, collection rate, garrison,
incidents, and control resolution all apply every tick.
P1: Build completions (ships, facilities, troops) are applied to
GameWorld via apply_build_completion() — manufactured items no longer
vanish after construction messages appear.
P2: AI mission dispatch uses ai_state.faction instead of hardcoded
MissionFaction::Empire — Alliance AI now correctly dispatches Alliance
missions when playing as Empire.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
State-transition-driven incident system replacing random per-tick model.
IncidentFlags struct tracks bits 16-19 of original field_0x88:
- uprising: ControlKind::Uprising or troop deficit > 2
- informant: garrison shortfall (troop_surplus < 0)
- disaster: controlling support below 20%
- resource: zero energy or raw materials
Events fire only on transitions (flag false→true). Same flag on next
tick produces no event — matching original's bitwise_compare_and_update.
uprising_visible flag set from ControlKind::Uprising.
2 new tests: disaster incident on state transition (+ no re-fire),
informant on garrison shortfall. 389 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per-tick control evaluation from troop presence:
- Alliance troops only → Controlled(Alliance)
- Empire troops only → Controlled(Empire)
- Both → Contested
- Neither → preserve existing control
New EconomyEvent::ControlResolved emitted when control changes.
Wired into simulation.rs to apply sys.control mutations and emit
EVT_CONTROL_CHANGED telemetry.
resolve_system_control() function matching original logic. GNPRTB[7760]
energy threshold noted but troop presence is the primary signal.
3 new tests: alliance_only, both_contested, no_troops_preserves.
385 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Step 0a in the economy tick pipeline: sum facility/mine outputs, cap at
system limits (total_energy, raw_materials).
New SystemEconomy fields: energy_allocated, raw_material_allocated,
energy_overcapped, raw_material_overcapped.
New EconomyEvent variants: EnergyOvercapped, RawMaterialOvercapped.
Wired into simulation.rs telemetry.
calculate_resource_allocation() counts production_facilities for energy
and manufacturing_facilities for raw materials. Overcap detection emits
events; actual facility pruning deferred (original randomly removes
excess facilities — destructive and rare).
3 new tests: energy_overcap, no_overcap_within_limits, raw_material_overcap.
380 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P0 fix: EVT_LEIA_FORCE assigned distinct ID 0x363 (was colliding with
EVT_FORCE_DISCOVERED at 0x362)
P1 fix: Chewie capture uses new EVT_JABBA_CAPTURES_CHEWIE (0x387)
instead of reusing EVT_SIDE_CHANGE (0x386)
P1 fix: Jabba mutual exclusion completed — rescue (0x383) now guards
against all 3 capture events (0x399, 0x385, 0x387). Luke capture
(0x399) now also guards against 0x383.
P2 fix: CharactersCoLocated evaluator now checks character.current_system
as fallback when character is not in any fleet roster.
Test fixes:
- Officer combat test uses strict > (was >=)
- DS shield test verifies attacker took damage (confirms combat occurred)
- Emperor test checks specific event IDs (was weak threshold)
- Added: non-existent character co-location (returns false)
- Added: cross-fleet co-location at same system
- Added: current_system fallback co-location
- Added: single-character vacuous truth
379 tests passing (was 376).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Acquired 518 reference files across 21 collections for Gemini multi-image
upscaling of 2,231 DLL BMPs. 60 Wookieepedia character portraits, 56 CCG
card art, 25 Galactic Battlegrounds concept art, Incredible Cross-Sections
PDF, plus existing EData collections.
Built batch-rename-references.py: sends images to Gemini Flash Lite via
OpenRouter ($0.32 for 1,097 images), gets 5-8 word descriptions, renames
files to descriptive slugs. 1,095/1,097 renamed successfully.
5 progressive-disclosure catalog files in agent_docs/references/ (portraits,
ships, weapons, planets, scenes) listing all 1,097 named images with DLL
asset category mappings. Updated CLAUDE.md and assets.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Combat (death_star shield + officer rating):
1. DS shield now gates hull damage in resolve_space(), not just fire()
- is_death_star: bool on ShipSnap, set from fleet.has_death_star + family 0x34
- phase_hull_damage() zeroes pending damage for DS ships when shield active
- resolve_space() accepts death_star_shield_active parameter
- All call sites (simulation.rs, main.rs, 14 tests) updated
2. Officer formula uses ch.combat.base only (not variance/2)
3. Attacker-only asymmetry documented (defender officers not factored)
Missions (decoy):
4. Decoy now uses FDECOYTB table lookup when available
- Falls back to 0.65 only when table not loaded
- GNPRTB[3588] applied as penalty multiplier (1 - penalty)
- Skill-dependent: high-espionage agents get higher decoy success
Repair:
5. Suppressed events for undamaged ships (avoids log spam)
6. Added TODO for shipyard class filtering
360 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
repair.rs:
- Suppress repair events for undamaged ships (avoids log spam)
- Events will emit only when ShipInstance hull tracking is promoted
- Add TODO for shipyard class filtering (currently any mfg facility)
- Update test to expect empty events until ShipInstance exists
combat.rs:
- Officer bonus uses ch.combat.base only (not variance/2 midpoint)
- Document attacker-only asymmetry as intentional per RE pattern
- The original reads rolled-concrete stat at +0x86 which is base
Tracked follow-ons from reviews:
- DS shield should gate hull damage in resolve_space(), not just fire()
- Decoy should use TDECOYTB/FDECOYTB table lookup, not flat 65%
- Add integration test for decoy through advance() path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New repair.rs module from community disassembly cross-reference:
- RepairSystem::advance() scans fleets at shipyard systems
- Ships with damage_control > 0 at manufacturing facilities get repair
- RepairEvent::ShipRepaired with fleet/ship_index/hull tracking
- Initial framework — actual per-hull restoration awaits ShipInstance
promotion to fleet-level storage
- 4 tests: repair_at_shipyard, no_repair_without_shipyard,
no_repair_without_ticks, no_repair_at_destroyed_system
Phase 2 COMPLETE (4/4). 360 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
From community disassembly FUN_005871d0 + FUN_0055cbe0:
- is_decoy: bool field on ActiveMission
- Decoy missions draw enemy counter-intelligence (foil checks)
but produce no world effects on success
- 65% success rate (GNPRTB[3588] = 35% penalty)
- Foiled decoys return MissionOutcome::Foiled
- 3 new tests: no_effects, can_be_foiled, success_at_low_roll
356 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Death Star shield (entity 0x25, community disassembly FUN_0051b2c0-0051b460):
- shield_generator_active: bool on DeathStarState (default true)
- fire() gated on shield — DS cannot fire while shield active
- destroy_shield() method for combat resolution
- 2 new tests: fire_blocked_by_shield, fire_after_shield_destroyed
Officer combat rating in ground combat (offset +0x86):
- Sum combat skills of all characters on attacker's fleets at system
- Apply as multiplier: damage *= 1.0 + (officer_sum / 200.0)
- Officers with higher combat skill give troops a meaningful advantage
353 tests passing, zero failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review finding: enemy_pop * 50.0 counter-intelligence penalty had no
RE basis and introduced phantom difficulty. Replaced with counter_intel = 0
(matches pre-fix behavior) until System::espionage_rating field is added.
Updated test to assert equality with diplomacy formula while stubbed.
Also noted: target_character is never set by AI/UI dispatchers, so
Assassination/Abduction/Recruitment defense subtraction is structurally
correct but functionally inert. Tracked as follow-on for dispatch wiring.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove spurious * 2.0 on troop suppression (deviates from original formula)
- Tag Empire garrison halving as AUGMENTED (no GNPRTB source identified)
- Fix doc comment: advance() mutates state, not pure
- Add clamp(1.0, 100.0) on collection_rate to bound the range
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>