266 Commits

Author SHA1 Message Date
Tom di Mino
40a28926e9 feat: Knesset Ptah Phase 0-2 — telemetry 15/17, espionage_rating, cleanup, dead code removal
Phase 0 — Telemetry Coverage:
- Fix SYS_STORY tagging: story events (0x210-0x39A) tagged as "story" not "events"
- Fix SYS_REPAIR: RepairCheckPerformed event when fleet at shipyard with damage_control > 0
- Delete dead resolve_death_star() stub (no callers, DS fires via DeathStarSystem::fire())
- Replace weapon fire variance TODO with documentation (known ±20% approximation)
- Fixture injections: uprising (tanked loyalty), death_star (construction), betrayal (low loyalty)
- Coverage: 10/17 → 15/17 required, 2 optional (uprising/betrayal RNG-dependent)

Phase 1 — Correctness:
- Fix victory_state.death_star_location in both main.rs and simulation.rs paths
- Add cleanup_destroyed_system(): removes fleets, troops, facilities, cancels in-transit orders
- Add MovementState::cancel_orders_to() for fleet rerouting

Phase 2 — espionage_rating:
- Add espionage_rating: f32 to System struct with #[serde(default)]
- Wire into InciteUprising mission probability (counter_intel subtraction)
- Bump SAVE_VERSION to 6, reject v5 saves
- New test: incite_uprising_reduced_by_espionage_rating

22 files changed, +214 -25 lines. 401 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 00:21:38 -04:00
Tom di Mino
c21770a9eb fix(victory): set death_star_location when DS fires (#9)
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>
2026-03-29 00:13:13 -04:00
Tom di Mino
ee0f8cbd71 feat(telemetry): fix SYS_STORY tagging, SYS_REPAIR emission, reach 15/17 coverage
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>
2026-03-29 00:12:02 -04:00
Tom di Mino
0b7463c136 feat(telemetry): Knesset Ptah Phase 0 — 15/17 telemetry coverage + dead code cleanup
- Fix SYS_STORY tagging: story events (0x380-0x39A) now tagged as "story"
  instead of "events" in integrator apply_fired_events()
- Fix SYS_REPAIR empty advance: RepairCheckPerformed event emits when fleet
  at shipyard has ships with damage_control > 0
- Delete dead resolve_death_star() stub — DS fires via DeathStarSystem::fire()
- Replace weapon fire variance TODO with documentation (known ±20% approximation)
- Fixture injections in telemetry test: uprising (tanked loyalty), death_star
  (construction), betrayal (low loyalty characters)
- Coverage: 10/17 → 15/17 required, 2 optional (uprising/betrayal RNG-dependent)
- New systems firing: story (6), repair (1669), death_star (1), jedi (2), victory (1)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 00:11:11 -04:00
Tom di Mino
a3d8e81666 refactor(net_protocol): replace 186-line all_variants() with strum::EnumIter
Review findings from code-simplicity-reviewer and pattern-recognition-specialist:
- Remove #[non_exhaustive] (no external consumers, contradicts closed-enum philosophy)
- Add strum 0.26 as dev-dependency, derive EnumIter + EnumCount behind cfg_attr(test)
- Replace hand-maintained all_variants() helper (186 LOC) with NetMessage::iter()
- Remove count_variants() wrapper
- Exact count assertion (== 183) instead of floor (>= 178)
- Net: 965 → 769 LOC (-196 lines, -20%)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 12:42:50 -04:00
Tom di Mino
ae87b6e25f docs: update CLAUDE.md Ereshkigal reference (all phases complete)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 12:34:31 -04:00
Tom di Mino
64857cf227 docs: mark Knesset Ereshkigal Phase 5 complete, update test count to 401
- Plan status: active → completed (all 6 phases done)
- Phase 5 row: 179 NetMessage variants + telemetry coverage test
- CLAUDE.md: 393 → 401 tests, Phase 0-5 complete, net_protocol.rs in workspace table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 12:34:23 -04:00
Tom di Mino
f159090a39 docs: mark Knesset Ereshkigal plan as completed (all 6 phases done)
Phase 5 (bc0302e): 179 NetMessage variants + telemetry coverage test.
Phase 4 (5c9e85d): PerceptionIntegrator extraction (73% LOC reduction).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 12:33:57 -04:00
Tom di Mino
bc0302e761 feat(protocol): add NetMessage enum (179 variants) and telemetry coverage test
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>
2026-03-28 12:32:54 -04:00
Tom di Mino
5c9e85d167 Knesset Ereshkigal Phase 4: PerceptionIntegrator extraction
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>
2026-03-28 11:58:24 -04:00
Tom di Mino
229ec156cb feat(repair): wire RepairSystem into simulation tick
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>
2026-03-28 11:51:26 -04:00
Tom di Mino
cc1da6f49a fix: clean up unused imports + stale doc from review findings
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>
2026-03-28 11:48:15 -04:00
Tom di Mino
bbff00a1cd docs: update simulation + architecture for PerceptionIntegrator
- simulation.md: integration order now shows integrator method calls,
  updated "Adding a New System" guide (step 7 = integrator method),
  updated mission/event guides to reference integrator.rs
- architecture.md: data modules section includes integrator.rs,
  simulation loop diagram shows integrator pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:42:21 -04:00
Tom di Mino
6a14d4bf55 docs: Phase 4 complete — update plan status and CLAUDE.md
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>
2026-03-28 11:39:31 -04:00
Tom di Mino
81dff2a6f3 refactor(integrator): WP-6 through WP-10 — extract all remaining sections
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>
2026-03-28 11:38:21 -04:00
Tom di Mino
3ac8ea7911 refactor: hybrid upscale pipeline — Vertex proven, Gemini portraits iterating
Vertex AI Imagen 4.0 is the primary upscaler for all non-portrait packs
(ships, damage, reactors, facilities, events, UI, buttons, tactical).
Pure super-resolution, zero hallucination, $11 for 2,231 BMPs.

Gemini Pro + references for portraits parked — reference images contaminate
non-portrait categories. Needs more prompt engineering iteration.

- vertex-upscale-batch.py: dedicated Vertex batch script with 5 RPM rate limiting
- gemini-upscale.py: hybrid router (gemini/vertex per pack), corrupt file handling
- reference-packs.json: upscale_method field (gemini for 3 portrait packs, vertex for 9)
- select-references.py: force-flag bugfix (was overwriting selections file)
- build-comparison-html.py: side-by-side original vs HD comparison page
- Updated CLAUDE.md + assets.md with proven/iterating strategy split

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:27:35 -04:00
Tom di Mino
acaf7b687c refactor(integrator): WP-5 — extract missions + escapes to integrator
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>
2026-03-28 11:01:14 -04:00
Tom di Mino
475223211a refactor(integrator): WP-4 — extract combat apply to integrator
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>
2026-03-28 10:59:03 -04:00
Tom di Mino
a54d9d7114 refactor(integrator): WP-3 — extract manufacturing + movement to integrator
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>
2026-03-28 10:56:18 -04:00
Tom di Mino
0df3127762 refactor(integrator): WP-2 — extract economy apply to integrator
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>
2026-03-28 10:03:09 -04:00
Tom di Mino
afa2898043 refactor(integrator): WP-1 — move payload helpers to integrator.rs
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>
2026-03-28 09:54:16 -04:00
Tom di Mino
821c1557e6 fix(economy): add strong_support bit guard to Empire troop doubling
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>
2026-03-28 09:49:33 -04:00
Tom di Mino
d5ae8a0863 fix(main): wire economy, build completions, and faction-aware AI dispatch
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>
2026-03-28 09:37:44 -04:00
Tom di Mino
c61aa3ef8f fix(economy): collection rate formula + f32 rounding + integrator Step 1
Phase 3c review fixes:
- Collection rate: was `base/support_pct`, now `(100*base)/support_int`
  matching FUN_0053c8d0_calculate_percentage traced through RE
- f32→round() at drift bracket boundaries (0.20/0.30/0.40 thresholds)
- Garrison requirement uses .round() for integer conversion
- Migrated fog/victory/snapshot from simulation.rs to integrator.rs (Step 1)
- Updated architecture docs (GameEffect 36→39 variants)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 09:13:13 -04:00
Tom di Mino
3dc142fb0f feat: HD upscale pipeline + 1,320 audited reference images
Two-step Gemini upscale pipeline:
- select-references.py: vision LLM picks best 3-5 refs per BMP via contact sheets
- gemini-upscale.py: Gemini Pro upscales with source-faithful prompts + selected refs
- reference-packs.json: 12 packs, 24 routing rules, category-specific prompts
- build-resource-entity-map.py: 162 GOKRES resource IDs → character/ship names

Reference collections audited (non-OT portraits removed, text pages quarantined):
- 4 new: SWCCG cards (76), SWG TCG art (67), McQuarrie ROTJ+ESB (79), Wookieepedia OT (10)
- Existing audited: Wookieepedia 60→11, CCG 56→28, book pages 170 text removed
- 1,320 usable references across 21 collections

Also: economy.rs review fixes, README badge update (97/97/99%), WASM rebuild.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:03:45 -04:00
Tom di Mino
ebc869130b docs: Phase 3b completion — source tree report + docs update
New: docs/reports/2026-03-27-source-tree-cross-reference.md
- 14,884-file dataset catalog (216 classes, 93 EventRecord types)
- Economy pipeline: 16/17 functions (94% parity, was 17%)
- 17 GNPRTB indices traced with values from game data
- 179 net_notify functions confirmed
- 2 critical bugs documented (swapped IDs, wrong notification model)

CLAUDE.md: 389 tests, Phase 0-3b complete, economy 17%→94%
architecture.md: economy.rs expanded description (~1200 LOC)
simulation.md: Economy system row updated with full pipeline description

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:18:49 -04:00
Tom di Mino
d10a6f3d3c feat(economy): incident state + uprising visibility (FUN_0050a970/0050ac70)
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>
2026-03-27 22:15:28 -04:00
Tom di Mino
4759b41db6 feat(economy): troop/fleet summary propagation (7 functions, FUN_0050a670-0050aa50)
New structs: SystemSummary, FleetPosture, FighterPosture.
Computed per-system per-tick by compute_system_summary().

Implements:
- FUN_0050a670: troop surplus (troops - garrison_requirement)
- FUN_0050ac00: total controlling troops
- FUN_0050ace0: shipyard presence flag
- FUN_0050add0/af70/b4c0: fleet posture (capship counts, contested flag)
- FUN_0050aa50: fighter posture (contested, per-faction counts)

2 new tests: troop_surplus (-1 deficit), fleet_posture (capship/fighter counts).
387 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:12:54 -04:00
Tom di Mino
2a3fdd58ee feat(economy): troop-based side resolution (FUN_0050a780_system_join_side)
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>
2026-03-27 22:09:38 -04:00
Tom di Mino
af7eb9de27 feat(economy): KDY production modifier (FUN_0050a480_adjust_for_kdy)
Formula: production_modifier = clamp(100 - capships*GNPRTB[7684] - fighters*GNPRTB[7685], 0, 100)
where GNPRTB[7684]=5 (per capship) and GNPRTB[7685]=2 (per fighter).

Added alliance_capships/empire_capships fields to MilitaryPresence,
counting actual hull entries (ShipEntry.count) not fleet objects.

2 new tests: kdy_production_modifier_reduces_with_ships (77 = 100-3*5-4*2),
kdy_production_modifier_floors_at_zero (0 = 100-10*5-30*2 clamped).
382 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:07:44 -04:00
Tom di Mino
44fffe96c7 feat(economy): resource capacity enforcement (FUN_00509ed0/ef0/0050a220)
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>
2026-03-27 17:33:26 -04:00
Tom di Mino
07541a96ca feat(economy): add all Phase 3b GNPRTB indices with values from game data
11 GNPRTB indices added to stock_gnprtb test fixture, all confirmed
from GNPRTB.DAT:
- 7680=2 (Empire troop mult), 7682=2 (uprising garrison mult)
- 7684=5 (KDY capship penalty), 7685=2 (KDY fighter penalty)
- 7691=0 (Empire transition delta), 7693=1 (support favors controller)
- 7694=30 (maintenance rate controlled), 7695=-1 (support opposes)
- 7696=30 (maintenance rate neutral), 7697=-1 (controlled delta)
- 7760=60 (energy control threshold)

Named constants added: GNPRTB_EMPIRE_TROOP_MULT, GNPRTB_UPRISING_GARRISON_MULT,
GNPRTB_KDY_CAPSHIP_PENALTY, GNPRTB_KDY_FIGHTER_PENALTY,
GNPRTB_ENERGY_CONTROL_THRESHOLD, GNPRTB_MAINTENANCE_RATE_CONTROLLED,
GNPRTB_MAINTENANCE_RATE_NEUTRAL.

Raw 7680/7682 references replaced with named constants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:24:50 -04:00
Tom di Mino
df7786397f docs: fold Knesset Shamash into Ereshkigal as Phase 3b
- Phase 3b added to status table + full detail (13 sub-sections)
- Economy parity revised: 17% (3/17 functions), not 97%
- 14,884-file source tree dataset cataloged (216 classes, 93 EventRecord types)
- 18-function economy pipeline mapped with GNPRTB indices
- 11 missing GNPRTB indices identified (7680, 7682, 7684, 7685, 7691, 7693-7697, 7760)
- CLAUDE.md: test count 379→377, Phase 3b status noted

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:09:55 -04:00
Tom di Mino
ce7d959657 fix(economy): Phase 3b critical bugs + formula corrections from source tree cross-ref
Bug 1: Swapped event IDs — EVT_NATURAL_DISASTER is 0x154 (was 0x155),
EVT_RESOURCE_DISCOVERY is 0x155 (was 0x154). Confirmed from
FUN_00511810_net_notify_system_disaster_incident dispatching 0x154.

Bug 2: Removed 8 random notification events from define_story_events().
Original fires incidents via state-transition logic in the economy tick
(FUN_0050a970), not random per-tick probability. Bits 16-19 of system
field_0x88 compared against neutral galaxy state.

Support drift fix (FUN_005583c0):
- Integer 0-100 arithmetic (was f32 0.0-1.0 throughout)
- Empire troop doubling via GNPRTB[7680]=2 (FUN_005582e0)
- Removed arbitrary *0.01 per-tick scaling
- Added GNPRTB 7680+7682 to stock_gnprtb test fixture

Garrison requirement fix (FUN_00558760):
- Integer ceil division: (divisor-1+dividend)/divisor
- Empire halving via /GNPRTB[7680] (was hardcoded 0.5)
- Uprising doubling via *GNPRTB[7682] on ControlKind::Uprising

377 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:09:15 -04:00
Tom di Mino
616a08b5e5 fix(events): rebalance Jabba timing + notification event frequency
Jabba Palace timing rebalance (P3 review finding):
- Leia captured: tick 120→108, prob 0.12→0.15 (preempts Luke capture)
- Chewie captured: tick 125→110, prob 0.10→0.12 (preempts Luke capture)
- Luke captured: add Random(0.20) gate (was deterministic, dominated all outcomes)
- Han self-escape: tick 140→135, prob 0.08→0.10

Now all 5 Jabba outcomes are reachable. Rescue path is still the most
common (no Random gate), but capture outcomes can preempt each other.

Notification event frequency halved (P2 review finding):
- Support change: 0.03→0.015 (~1/67 ticks)
- Informant intel: 0.05→0.02 (~1/50 ticks)
- Resource discovery: 0.02→0.01 (~1/100 ticks)
- Natural disaster: 0.01→0.005 (~1/200 ticks)
- Maintenance shortfall: 0.04→0.015 (~1/67 ticks)
- Saboteur detected: 0.03→0.01 (~1/100 ticks)
- Traitor revealed: 0.02→0.01 (~1/100 ticks)
- Jabba prisoners: 0.06→0.02 (~1/50 ticks)

Added high-roll negative test for notification events.
Exact original probabilities in GNPRTB not yet traced — marked for
future Ghidra lookup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:27:26 -04:00
Tom di Mino
1e9f0c5ab0 docs: update plan and CLAUDE.md with Phase 3 review outcomes (379 tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:04:22 -04:00
Tom di Mino
56b8ffe52a fix(events): resolve 10 Phase 3 review findings from 3 review agents
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>
2026-03-27 12:03:48 -04:00
Tom di Mino
308840e05a docs: update plan, CLAUDE.md, architecture for Phase 3 completion
- Plan: Phase 3 marked complete (376 tests)
- CLAUDE.md: test count 360→376, Phase 0-3 complete
- architecture.md: game_events 39→54 constants, story_events updated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:18:07 -04:00
Tom di Mino
7f1e1d2c0a feat(events): Phase 3 — story completeness + co-location + missing event IDs
Jabba's Palace 5-case outcome switch:
- Han self-escape (0x384): low probability after extended carbonite
- Jabba captures Leia (0x385): random during rescue attempt
- Jabba captures Chewie (0x386): random during rescue attempt
- Existing rescue path (0x383→0x39A) and Luke capture (0x399) preserved

Final Battle with Emperor:
- Emperor Palpatine lookup (by name) added to story_events
- CharactersCoLocated condition: checks all characters at same system
- EVT_FINAL_BATTLE (0x220) now requires Luke+Vader+Emperor co-location

Leia Force Discovery (0x362):
- Fires after Dagobah completion, tick ≥ 80
- Sets Leia to ForceTier::Aware

Emperor Arrival (0x230):
- Fires at tick ≥ 90 with 100+ Empire systems

8 system notification events:
- Support change (0x100), informant intel (0x153), resource discovery (0x154)
- Natural disaster (0x155), maintenance shortfall (0x304), saboteur (0x305)
- Traitor revealed (0x361), Jabba prisoners (0x231)

17 new RE event ID constants in events.rs + 15 telemetry constants in game_events.rs

Phase 2 pending tests (6):
- 4 DS shield hull-damage tests (blocks/unblocks/normal-ships/comparison)
- 2 officer combat rating tests (bonus increases damage, no officer = 1x)

Phase 3 tests (10):
- Han self-escape, Leia Force, notification events, Emperor arrival
- CharactersCoLocated (co-located, not co-located, vacuous)

376 tests passing (was 360), zero warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:16:30 -04:00
Tom di Mino
4e952ddf74 feat(assets): reference image library + vision-LLM batch rename pipeline
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>
2026-03-27 11:11:40 -04:00
Tom di Mino
167e58f0a7 docs: update plan with Phase 2 review outcomes (21 findings tracked)
- Phase 2 marked DONE with 360 tests, 13 commits
- Follow-ons section expanded: 21 findings across 6 review agents
- All implemented fixes documented with commit references
- 6 pending items tracked for Phase 3+

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:29:39 -04:00
Tom di Mino
a2669d63c9 fix: implement all Phase 2 review findings (8 fixes)
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>
2026-03-27 09:28:25 -04:00
Tom di Mino
8f1b484e18 fix: resolve Phase 2 review findings (repair spam + officer formula)
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>
2026-03-27 08:33:29 -04:00
Tom di Mino
db9d172143 docs: update CLAUDE.md, architecture, simulation for Phase 0-2
CLAUDE.md:
- Header: 360 tests, Phase 0-2 complete
- Workspace: rebellion-core now lists effects.rs, economy.rs, repair.rs
- Conventions: 4 new functional programming manifesto principles
- Agent docs: references for Knesset Ereshkigal plan + cross-reference report

architecture.md:
- Simulation modules: +effects.rs, economy.rs, repair.rs
- Simulation loop: economy runs before manufacturing (position 0)
- game_events.rs: 39 event type constants (was 26)

simulation.md:
- Updated to 17 systems (was 15)
- Added Economy (#16) and Repair (#17) to inventory table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:29:38 -04:00
Tom di Mino
55d3891a50 feat(repair): add ship repair system framework (CombatUnitUnderRepair)
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>
2026-03-27 08:22:46 -04:00
Tom di Mino
eff8bd6341 feat(missions): add decoy mission subsystem (TDECOYTB/FDECOYTB)
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>
2026-03-27 08:16:15 -04:00
Tom di Mino
aaef8b12ec feat(combat): Death Star shield generator + officer combat rating
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>
2026-03-27 08:11:48 -04:00
Tom di Mino
bcb4c33f77 fix(missions): replace fabricated InciteUprising counter-intel with stub
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>
2026-03-26 23:11:09 -04:00
Tom di Mino
6926635dac fix(economy): resolve 4 review findings from economy code review
- 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>
2026-03-26 23:07:51 -04:00
Tom di Mino
e099c149d4 fix: resolve 5 review findings from code review agents
effects.rs:
- Remove dead SkillModified variant (missing skill discriminant)
- Add ControlChanged inversion (from/to swap)
- Add stability comment on combine_effects sort

economy.rs:
- Remove redundant double-assignment of collection_rate (line 179)

simulation.rs:
- Update doc comment to include economy in canonical tick order

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:06:21 -04:00