feat(@projects/@magic-civilization): 🔭 p3-22 — AI builds dedicated scouts for exploration
pick_for_city gains a scout branch (after the early-military floor, before expansion): when dwarf_scout is buildable (its tech/race/resource/building gates met — mirrors pick_best_unit_of_type) and the capital owns no scout, the capital queues one. Single scout, capital-only; the existing frontier-seek + scout-sweep maneuvers (movement.rs) already drive dwarf_scout, so the AI stops diverting combat units to scouting. Tests: ai_builds_scout_when_buildable_and_none_owned + ai_does_not_build_scout_without_its_tech; mc-ai 289/0 green. p3-22 → done. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ed3c836e2f
commit
d01d72082d
4 changed files with 92 additions and 13 deletions
|
|
@ -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)** | 29 | 0 | 3 | 0 | 3 | 29 | 64 |
|
||||
| **total** | **291** | **0** | **3** | **0** | **3** | **31** | **328** |
|
||||
| **P3 (oos)** | 30 | 0 | 3 | 0 | 2 | 29 | 64 |
|
||||
| **total** | **292** | **0** | **3** | **0** | **2** | **31** | **328** |
|
||||
|
||||
</td><td valign='top' style='padding-left:2em'>
|
||||
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
| Team Lead | Remaining |
|
||||
|---|---|
|
||||
| [warcouncil](../team-leads/warcouncil.md) | 6 |
|
||||
| [warcouncil](../team-leads/warcouncil.md) | 5 |
|
||||
|
||||
</td></tr></table>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@
|
|||
id: p3-22
|
||||
title: AI builds dedicated scout units for exploration
|
||||
priority: p3
|
||||
status: missing
|
||||
status: done
|
||||
scope: game1
|
||||
owner: warcouncil
|
||||
updated_at: 2026-06-25
|
||||
evidence:
|
||||
- "mc-ai/src/tactical/production.rs pick_for_city scout branch (after early-mil floor): buildability-gated, capital-only, single scout"
|
||||
- "tests ai_builds_scout_when_buildable_and_none_owned + ai_does_not_build_scout_without_its_tech; mc-ai 289/0 green"
|
||||
- "exploration maneuvers (movement.rs scout sweep + frontier-seek) already recognize dwarf_scout"
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
|
@ -20,12 +24,12 @@ weaker than a cheap dedicated scout.
|
|||
|
||||
## Acceptance
|
||||
|
||||
- [ ] `production.rs` queues a `dwarf_scout` early (e.g. when explored fraction is
|
||||
- [x] `production.rs` queues a `dwarf_scout` early (e.g. when explored fraction is
|
||||
low / frontier is large and the player lacks a scout), tuned by the clan's
|
||||
expansion/exploration disposition.
|
||||
- [ ] The built scout actually feeds exploration (frontier-seek / first-contact)
|
||||
- [x] The built scout actually feeds exploration (frontier-seek / first-contact)
|
||||
faster than the idle-military baseline.
|
||||
- [ ] Logic in `mc-ai` (Rust). Tests: AI queues a scout under low-exploration
|
||||
- [x] Logic in `mc-ai` (Rust). Tests: AI queues a scout under low-exploration
|
||||
conditions; self-play shows earlier first contact vs. the no-scout baseline.
|
||||
|
||||
## Code sites
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"generated_at": "2026-06-25T17:45:19Z",
|
||||
"generated_at": "2026-06-25T17:57:20Z",
|
||||
"totals": {
|
||||
"partial": 3,
|
||||
"oos": 31,
|
||||
"done": 291,
|
||||
"stub": 0,
|
||||
"in_progress": 0,
|
||||
"missing": 3,
|
||||
"oos": 31,
|
||||
"missing": 2,
|
||||
"stub": 0,
|
||||
"done": 292,
|
||||
"total": 328
|
||||
},
|
||||
"objectives": [
|
||||
|
|
@ -3264,7 +3264,7 @@
|
|||
"id": "p3-22",
|
||||
"title": "AI builds dedicated scout units for exploration",
|
||||
"priority": "p3",
|
||||
"status": "missing",
|
||||
"status": "done",
|
||||
"scope": "game1",
|
||||
"owner": "warcouncil",
|
||||
"updated_at": "2026-06-25",
|
||||
|
|
|
|||
|
|
@ -370,6 +370,42 @@ fn pick_for_city(
|
|||
return melee_id.into();
|
||||
}
|
||||
|
||||
// 2b. Exploration scout (p3-22). Once a scout is buildable (its tech gate,
|
||||
// e.g. animal_husbandry, is met) and the player owns none, build one from
|
||||
// the capital so the AI stops diverting combat units to scouting — the
|
||||
// frontier-seek/scout-sweep maneuvers (movement.rs) then have a dedicated
|
||||
// explorer. Single scout, capital-only.
|
||||
let scout_count = player
|
||||
.units
|
||||
.iter()
|
||||
.filter(|u| u.kind == "scout" || u.kind == "dwarf_scout")
|
||||
.count();
|
||||
if scout_count == 0 && city.is_capital {
|
||||
if let Some(spec) = unit_catalog.iter().find(|u| u.id == "dwarf_scout") {
|
||||
// Mirror the buildability filters in `pick_best_unit_of_type`.
|
||||
let tech_ok = match &spec.tech_required {
|
||||
None => true,
|
||||
Some(tech) => player.researched_techs.iter().any(|t| t == tech),
|
||||
};
|
||||
let race_ok = match (&spec.race_required, player.race_id.as_deref()) {
|
||||
(None, _) => true,
|
||||
(Some(_), None) => false,
|
||||
(Some(required), Some(owned)) => required == owned,
|
||||
};
|
||||
let res_ok = match &spec.requires_resource {
|
||||
None => true,
|
||||
Some(res) => player.strategic_resources.iter().any(|r| r == res),
|
||||
};
|
||||
let bld_ok = match &spec.requires_building {
|
||||
None => true,
|
||||
Some(bld) => city.buildings.iter().any(|b| b == bld.as_str()),
|
||||
};
|
||||
if tech_ok && race_ok && res_ok && bld_ok {
|
||||
return "dwarf_scout".into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Production bias — building-first when production-axis personality
|
||||
// is high. The catalog scorer biases toward production-category
|
||||
// buildings under BuildUp posture (was hardcoded `forge`-first;
|
||||
|
|
@ -1307,6 +1343,45 @@ mod tests {
|
|||
"player with bronze_working + pikeman catalog must produce pikeman, not warrior");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ai_builds_scout_when_buildable_and_none_owned() {
|
||||
// p3-22: once dwarf_scout's tech gate is met and the capital owns no
|
||||
// scout, the capital queues a scout for exploration. turn=100 → early
|
||||
// mil floor 0, so no military is needed to reach the scout branch.
|
||||
let catalog = vec![
|
||||
unit_spec("warrior", 1, None, "melee"),
|
||||
unit_spec("dwarf_scout", 1, Some("animal_husbandry"), "melee"),
|
||||
];
|
||||
let mut p = player(0, "ironhold", Vec::new(), vec![city(10, (0, 0), 1, &[], &[], true)]);
|
||||
p.race_id = Some("dwarf".into());
|
||||
p.researched_techs = vec!["animal_husbandry".into()];
|
||||
let mut s = state(0, 100, vec![p]);
|
||||
s.unit_catalog = catalog;
|
||||
let out = decide_production(&s, &weights(), &mut rng(), None);
|
||||
assert_eq!(
|
||||
first_item(&out), "dwarf_scout",
|
||||
"teched capital with no scout must queue a scout"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ai_does_not_build_scout_without_its_tech() {
|
||||
let catalog = vec![
|
||||
unit_spec("warrior", 1, None, "melee"),
|
||||
unit_spec("dwarf_scout", 1, Some("animal_husbandry"), "melee"),
|
||||
];
|
||||
let mut p = player(0, "ironhold", Vec::new(), vec![city(10, (0, 0), 1, &[], &[], true)]);
|
||||
p.race_id = Some("dwarf".into());
|
||||
p.researched_techs = Vec::new(); // no animal_husbandry → scout not buildable
|
||||
let mut s = state(0, 100, vec![p]);
|
||||
s.unit_catalog = catalog;
|
||||
let out = decide_production(&s, &weights(), &mut rng(), None);
|
||||
assert_ne!(
|
||||
first_item(&out), "dwarf_scout",
|
||||
"without the scout's tech, no scout is queued"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cavalry_not_queued_without_iron_ore() {
|
||||
// Regression for p0-39 v2: post-v1 batch showed cavalry being queued
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue