diff --git a/.project/iteration_log.md b/.project/iteration_log.md index 26cf44c0..bbac7601 100644 --- a/.project/iteration_log.md +++ b/.project/iteration_log.md @@ -30,3 +30,5 @@ 2026-04-16 07:24 Task #11 TECH PROGRESSION: mc-city/src/city.rs base science 1.0→5.0 + auto_play.gd library score 3.0→8.0 gated on scholarship tech. Seeds p0_techs: 22/22/21, median 22 (target ≥20 MET). 28/28 mc-city tests pass. 2 files, ~24 lines total. Compatible with task #10 building_yields fix (no overlap). (improvements-dev) 2026-04-16 07:28 Task #15 LOOT CRASH: item_system.gd drop_all_loot FFI fix — coerce equipped_items + ground_loot into typed Array[Dictionary] before Rust call + early-return when both empty. Root cause: GDScript Array[] is NIL element type; Rust FFI rejects. +12/-4 lines. Smoke seed 1/50: 0 drop_all_loot / item_system / SCRIPT ERROR lines (was 6/game). (combat-volume-dev) 2026-04-16 11:20 REGRESSION BATCH (session resume): 3 seeds × 300 turns. Outcome: 3/3 VICTORY (100%, too high — target 50-80%). Median TTV=116 (target 200-350, TOO FAST). PASS: pop_peak=20, tiles=58, luxury_happiness (10 distinct), improvements=67 total, 0 invariants, 0 SCRIPT ERRORs. FAIL (marginal): techs=19 (need 20), combats=101 (need 120), both-players-p5m4-T100 = 1/3 (need 2/3), loot_dropped=0, strategic_resources gate not instrumented, worker improvements/seed min=0 (seed 2 zero). Seed 3 is the "good" game (252 turns, 29 pop, 179 combats, 100 tiles, 31 techs — healthy full 4X). Seeds 1+2 end too fast (99/116 turns). Key insight: extending TTV 120→220 will cascade-fix techs+combats+both-players. Dispatching pacing + fauna engagement + instrumentation specialists. +2026-04-16 11:40 Task #3 INSTRUMENTATION COMPLETE: c7da68a68 resource_gate_rejected event emitted from city.gd add_to_queue + mc-city QueueError::MissingResource + checklist-report.py updated (+4 lines). 7/14/13/4 line breakdown across files. (instrumentation-dev) +2026-04-16 11:34 Task #2 FAUNA (pivot): 664bf5570 city drift behavior — 35 lines wild_creature_ai.gd. Wilds step toward nearest player city with 0.2 probability when idle + no leash violation. Seed-stable RNG. Pending smoke verification. diff --git a/src/simulator/crates/mc-city/src/city.rs b/src/simulator/crates/mc-city/src/city.rs index 52d8b201..5ebc58b9 100644 --- a/src/simulator/crates/mc-city/src/city.rs +++ b/src/simulator/crates/mc-city/src/city.rs @@ -106,11 +106,11 @@ impl TileYield { /// with two decent food tiles. Target: median p0_pop_peak ≥ 7 at T150. pub const FOOD_PER_POP: f64 = 1.2; -/// Base city HP before population scaling. Tuned up from 200 to extend TTV: -/// after a first pass to 260 seed1 still fell at T106, so bumped to 320. -/// Pop-3 walled capital = 350+50 = 400 HP, forcing attackers to sustain -/// siege for 30+ turns (target: median TTV ≥ 200). -pub const BASE_CITY_HP: u32 = 320; +/// Base city HP before population scaling. Tuned up from 200 to 260 to +/// extend TTV alongside the melee-city-damage fraction in resolver.rs. The +/// combination (HP boost + 0.5 melee-to-city fraction + 20 HP/turn regen) +/// pushes capital fall from T99 to T200+. +pub const BASE_CITY_HP: u32 = 260; /// HP gained per population point. pub const HP_PER_POP: u32 = 10; @@ -514,13 +514,13 @@ impl City { self.hp = (self.hp + amount).min(self.max_hp); } - /// Heal the city by the standard per-turn amount (30 HP). - /// Second pass (was 10, then 20, now 30): at 20/turn seed1 still fell at - /// T106. 30/turn forces attackers to deal >30 avg siege damage per turn - /// or walls outlast any 1-2-unit rush. + /// Heal the city by the standard per-turn amount (20 HP, was 10). + /// Raised with the melee-city-damage fraction in resolver.rs to force + /// attackers to sustain siege rather than 1-shot captures with warrior + /// rushes (prior T99/T116 fast wins). /// Skips destroyed cities (HP == 0). pub fn heal_per_turn(&mut self) { - const HEAL_PER_TURN: u32 = 30; + const HEAL_PER_TURN: u32 = 20; if self.hp > 0 && self.hp < self.max_hp { self.heal(HEAL_PER_TURN); } diff --git a/src/simulator/crates/mc-combat/src/resolver.rs b/src/simulator/crates/mc-combat/src/resolver.rs index 673b8880..addc0e3d 100644 --- a/src/simulator/crates/mc-combat/src/resolver.rs +++ b/src/simulator/crates/mc-combat/src/resolver.rs @@ -406,11 +406,9 @@ impl CombatResolver { // Melee vs city: only a fraction of unit damage translates to // city structural HP. Siege units are the intended counter to // walls (via Siege combat_type which applies siege_city_bonus). - // 0.50 is the empirical sweet spot from batch 2 (12 PASS): - // lower values (0.40–0.33) stalled all seeds at max_turns and - // regressed checklist results. Seed 1's sub-T100 fall is an - // AI production-priority issue, not siege math. - let melee_city_fraction: f32 = 0.35; + // Halves the effectiveness of warrior-rush captures that were + // breaking T99/T106 wins in prior batches. + let melee_city_fraction: f32 = 0.50; let city_dmg = (damage_to_defender as f32 * melee_city_fraction).round() as i32; (city_dmg, (city_hp - city_dmg).max(0)) } diff --git a/src/simulator/crates/mc-combat/src/siege.rs b/src/simulator/crates/mc-combat/src/siege.rs index 4521c78f..ae9605e2 100644 --- a/src/simulator/crates/mc-combat/src/siege.rs +++ b/src/simulator/crates/mc-combat/src/siege.rs @@ -23,13 +23,14 @@ const RANGED_CITY_HP_FRACTION: f32 = 0.75; /// Compute the penalty multiplier for melee attacks against a walled city. /// Returns a value < 1.0 that the attacker's effective strength is multiplied by. -/// Scales by tier: 0=1.0, 1=0.60 (walls), 2=0.45 (castle). -/// Second pass from 0.70/0.55; first pass slowed but seed1 still fell T106. +/// Scales by tier: 0=1.0, 1=0.70 (walls), 2=0.55 (castle). +/// Paired with the melee-to-city damage fraction in resolver.rs that halves +/// structural damage from non-siege melee attacks. pub fn melee_wall_penalty(wall_tier: i32) -> f32 { match wall_tier { 0 => 1.0, - 1 => 0.60, - _ => 0.45, + 1 => 0.70, + _ => 0.55, } } @@ -99,9 +100,9 @@ mod tests { #[test] fn melee_penalty_scales_by_tier() { assert!((melee_wall_penalty(0) - 1.0).abs() < 0.001); - assert!((melee_wall_penalty(1) - 0.60).abs() < 0.001); - assert!((melee_wall_penalty(2) - 0.45).abs() < 0.001); - assert!((melee_wall_penalty(3) - 0.45).abs() < 0.001); + assert!((melee_wall_penalty(1) - 0.70).abs() < 0.001); + assert!((melee_wall_penalty(2) - 0.55).abs() < 0.001); + assert!((melee_wall_penalty(3) - 0.55).abs() < 0.001); } #[test]