feat(ai): ✨ Enhance AI controller interface with improved methods/traits for better robustness and intuitive API usage
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
5e4909a4b3
commit
aec0d32c7c
1 changed files with 24 additions and 13 deletions
|
|
@ -676,21 +676,30 @@ impl GdAiController {
|
|||
}
|
||||
}
|
||||
|
||||
/// p2-43a-followup (5th bullet) / p0-26 — pick the best research tech from
|
||||
/// a prereq-filtered candidate list.
|
||||
/// p0-26b / p0-26 — pick the best research tech (Rail-1).
|
||||
///
|
||||
/// `available_json` is a JSON array of the **full** `AiTechCandidate` tree
|
||||
/// (`{"id", "pillar", "cost", "tier", "requires", "max_unit_tier",
|
||||
/// "already_researched", "unlocks_buildings", "unlocks_units"}`). The caller
|
||||
/// passes *every* tech, NOT a prereq-filtered subset: `pick_tech` runs all
|
||||
/// three GDScript passes itself — raw scoring, the prerequisite-of-high-value
|
||||
/// boost (which needs the techs that a candidate-only contract would filter
|
||||
/// out), and the availability filter (`already_researched` + `requires`).
|
||||
///
|
||||
/// `available_json` is a JSON array of `AiTechCandidate` records
|
||||
/// (`{"id", "pillar", "cost", "tier", "unlocks_buildings", "unlocks_units"}`).
|
||||
/// The GDScript caller (`auto_play.gd::_pick_research`) is expected to
|
||||
/// pre-filter to techs the player has not yet researched and whose
|
||||
/// prerequisites are met; this bridge does not re-check prereqs.
|
||||
/// `personality_axes_json` is the raw 1..=10 strategic-axes dict for the
|
||||
/// clan (the body of `ai_personalities.json[clan_id].strategic_axes`);
|
||||
/// missing/extra keys fall through to the neutral default of 5.
|
||||
///
|
||||
/// Returns the picked tech id, or an empty string when the list is empty
|
||||
/// or on parse failure (logged via `godot_error!`). The bridge never
|
||||
/// silently substitutes a default.
|
||||
/// `research_behind` is the p1-29 catch-up observation: `true` when this
|
||||
/// player's tech ceiling is ≥2 eras behind the highest opponent's. The
|
||||
/// cross-player comparison and the `MC_DISABLE_CATCHUP_RESEARCH` kill switch
|
||||
/// are resolved GDScript-side (Rail-1 split: GDScript observes, Rust decides);
|
||||
/// `true` makes `score_tech` boost military/metallurgy ×1.5 and waive the
|
||||
/// tier-3 mercantile penalty.
|
||||
///
|
||||
/// Returns the picked tech id, or an empty string when the list is empty,
|
||||
/// every tech is researched/blocked, or on parse failure (logged via
|
||||
/// `godot_error!`). The bridge never silently substitutes a default.
|
||||
///
|
||||
/// Scoring is delegated to [`mc_ai::evaluator::ScoringEvaluator::pick_tech`]
|
||||
/// — the single source of truth for tech selection (Rail-1).
|
||||
|
|
@ -699,6 +708,7 @@ impl GdAiController {
|
|||
&self,
|
||||
available_json: GString,
|
||||
personality_axes_json: GString,
|
||||
research_behind: bool,
|
||||
) -> GString {
|
||||
let avail_src = available_json.to_string();
|
||||
let candidates: Vec<AiTechCandidate> = match serde_json::from_str::<Vec<AiTechCandidate>>(
|
||||
|
|
@ -734,12 +744,13 @@ impl GdAiController {
|
|||
}
|
||||
};
|
||||
|
||||
// Build a minimal AiPlayerState carrying axes; other fields default
|
||||
// (gold=0, threat_level=0, no luxury bonuses) — matches the
|
||||
// information the GDScript inline scorer historically had access to.
|
||||
// Build a minimal AiPlayerState carrying axes + the catch-up flag;
|
||||
// other fields default (gold=0, threat_level=0, no luxury bonuses) —
|
||||
// matches the information the GDScript inline scorer had access to.
|
||||
let mut state = AiPlayerState::default();
|
||||
state.strategic_axes = axes.clone();
|
||||
state.is_researching = false;
|
||||
state.research_behind = research_behind;
|
||||
|
||||
let strategic = StrategicWeights::from_race_axes(&axes);
|
||||
let evaluator = ScoringEvaluator::default();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue