diff --git a/.project/objectives/README.md b/.project/objectives/README.md
index 3ae5ad12..f464bba0 100644
--- a/.project/objectives/README.md
+++ b/.project/objectives/README.md
@@ -17,8 +17,8 @@
| **P0** | 44 | 0 | 0 | 0 | 0 | 0 | 44 |
| **P1** | 88 | 0 | 0 | 0 | 0 | 1 | 89 |
| **P2** | 130 | 0 | 0 | 0 | 0 | 1 | 131 |
-| **P3 (oos)** | 31 | 0 | 3 | 0 | 1 | 29 | 64 |
-| **total** | **293** | **0** | **3** | **0** | **1** | **31** | **328** |
+| **P3 (oos)** | 31 | 0 | 4 | 0 | 0 | 29 | 64 |
+| **total** | **293** | **0** | **4** | **0** | **0** | **31** | **328** |
diff --git a/.project/objectives/p3-20-weather-affects-scouting.md b/.project/objectives/p3-20-weather-affects-scouting.md
index b4a9aab1..313bd398 100644
--- a/.project/objectives/p3-20-weather-affects-scouting.md
+++ b/.project/objectives/p3-20-weather-affects-scouting.md
@@ -2,7 +2,7 @@
id: p3-20
title: Weather affects scouting — vision/LoS penalty under storms, blizzards, dust
priority: p3
-status: missing
+status: partial
scope: game1
owner: warcouncil
updated_at: 2026-06-25
@@ -18,6 +18,10 @@ effect** — weather slows movement and hurts units, but does not reduce scoutin
range. So storms/blizzards/dust have no effect on exploration, fog reveal, or
surprise — a missed gameplay lever for the weather system.
+## Progress (2026-06-25)
+
+Weather-producer side done: `WeatherEvent.vision_penalty: i32` added (`mc-climate`); `derive_events` sets it per kind (storm 1, blizzard 2, dust_storm 2; heat_wave/drought/flood 0). Tests assert the per-kind values (mc-climate 45/0). **Remaining:** `compute_vision` (mc-vision) consumes a per-tile vision-penalty to shrink a unit's effective sight under weather + the bridge/GDScript wiring (build the penalty map from live weather) + dylib/GUT. *(Per-kind values inline for now; data-drive to WeatherThresholds/climate_spec.json is a follow-up.)*
+
## Acceptance
- [ ] `mc-climate::weather::WeatherEvent` gains a `vision_penalty` (per type;
diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json
index 4d9b13f9..61a367d7 100644
--- a/public/games/age-of-dwarves/data/objectives.json
+++ b/public/games/age-of-dwarves/data/objectives.json
@@ -1,12 +1,12 @@
{
- "generated_at": "2026-06-25T19:16:18Z",
+ "generated_at": "2026-06-25T19:45:10Z",
"totals": {
- "stub": 0,
- "oos": 31,
- "partial": 3,
- "done": 293,
- "missing": 1,
"in_progress": 0,
+ "oos": 31,
+ "stub": 0,
+ "missing": 0,
+ "partial": 4,
+ "done": 293,
"total": 328
},
"objectives": [
@@ -3244,7 +3244,7 @@
"id": "p3-20",
"title": "Weather affects scouting — vision/LoS penalty under storms, blizzards, dust",
"priority": "p3",
- "status": "missing",
+ "status": "partial",
"scope": "game1",
"owner": "warcouncil",
"updated_at": "2026-06-25",
diff --git a/src/simulator/crates/mc-climate/src/climate_effects.rs b/src/simulator/crates/mc-climate/src/climate_effects.rs
index 463b7dbb..861e0f00 100644
--- a/src/simulator/crates/mc-climate/src/climate_effects.rs
+++ b/src/simulator/crates/mc-climate/src/climate_effects.rs
@@ -143,6 +143,7 @@ mod tests {
temperature_delta: temp_d,
movement_penalty: 0.5,
unit_damage: dmg,
+ vision_penalty: 0,
}
}
diff --git a/src/simulator/crates/mc-climate/src/weather.rs b/src/simulator/crates/mc-climate/src/weather.rs
index 526d01a3..eaa74128 100644
--- a/src/simulator/crates/mc-climate/src/weather.rs
+++ b/src/simulator/crates/mc-climate/src/weather.rs
@@ -29,6 +29,12 @@ pub struct WeatherEvent {
pub movement_penalty: f32,
#[serde(deserialize_with = "crate::gd_compat::de_i32_flexible")]
pub unit_damage: i32,
+ /// p3-20 — sight-radius reduction for units under this event. Storms,
+ /// blizzards and dust storms cut scouting/vision; clear-weather events leave
+ /// it 0. Consumed by the vision computation (mc-vision) to shrink a unit's
+ /// effective sight while it stands in the event's footprint.
+ #[serde(default, deserialize_with = "crate::gd_compat::de_i32_flexible")]
+ pub vision_penalty: i32,
}
/// Thresholds pulled from climate_spec.json → `weather.thresholds`.
@@ -251,6 +257,7 @@ pub fn derive_events(
temperature_delta: 0.0,
movement_penalty: thresholds.storm_movement_penalty,
unit_damage: 0,
+ vision_penalty: 1,
});
continue;
}
@@ -270,6 +277,7 @@ pub fn derive_events(
temperature_delta: thresholds.heat_wave_temperature_delta,
movement_penalty: 0.0,
unit_damage: thresholds.heat_wave_unit_damage,
+ vision_penalty: 0,
});
continue;
}
@@ -289,6 +297,7 @@ pub fn derive_events(
temperature_delta: thresholds.blizzard_temperature_delta,
movement_penalty: thresholds.blizzard_movement_penalty,
unit_damage: thresholds.blizzard_unit_damage,
+ vision_penalty: 2,
});
continue;
}
@@ -312,6 +321,7 @@ pub fn derive_events(
temperature_delta: 0.0,
movement_penalty: 0.0,
unit_damage: 0,
+ vision_penalty: 0,
});
continue;
}
@@ -332,6 +342,7 @@ pub fn derive_events(
temperature_delta: 0.0,
movement_penalty: thresholds.flood_movement_penalty,
unit_damage: thresholds.flood_unit_damage,
+ vision_penalty: 0,
});
continue;
}
@@ -353,6 +364,7 @@ pub fn derive_events(
temperature_delta: 0.0,
movement_penalty: thresholds.dust_storm_movement_penalty,
unit_damage: thresholds.dust_storm_unit_damage,
+ vision_penalty: 2,
});
}
}
@@ -390,6 +402,8 @@ mod tests {
let events = derive_events(&g, &t, 1, 42);
assert_eq!(events.len(), 16);
assert!(events.iter().all(|e| e.kind == "storm"));
+ // p3-20: storms cut scouting (vision_penalty > 0).
+ assert!(events.iter().all(|e| e.vision_penalty == 1), "storms reduce vision");
}
#[test]
@@ -401,6 +415,8 @@ mod tests {
assert_eq!(events.len(), 16);
assert!(events.iter().all(|e| e.kind == "heat_wave"));
assert!(events[0].unit_damage > 0);
+ // p3-20: heat waves don't impair scouting.
+ assert_eq!(events[0].vision_penalty, 0, "heat waves do not reduce vision");
}
#[test]
@@ -411,6 +427,8 @@ mod tests {
let events = derive_events(&g, &t, 1, 42);
assert_eq!(events.len(), 16);
assert!(events.iter().all(|e| e.kind == "blizzard"));
+ // p3-20: blizzards cut scouting hardest.
+ assert!(events.iter().all(|e| e.vision_penalty == 2), "blizzards reduce vision most");
}
#[test]
|