feat(@projects/@magic-civilization): ✨ add per-slot personality pinning via env vars
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
412dbb3ebf
commit
a9b8e23ae7
5 changed files with 63 additions and 9 deletions
|
|
@ -49,6 +49,15 @@ fi
|
|||
if [ -n "${AI_PIN_PERSONALITY:-}" ]; then
|
||||
FLATPAK_ENVS+=("--env=AI_PIN_PERSONALITY=$AI_PIN_PERSONALITY")
|
||||
fi
|
||||
# Per-slot pin overrides — matchup-grid.sh sets these to populate clan_id on
|
||||
# both player slots (incl. the human slot) so meta.json player_clans has every
|
||||
# clan and matchup_balance verdict can attribute all wins.
|
||||
for i in 0 1 2 3 4 5 6 7; do
|
||||
var="AI_PIN_PERSONALITY_P${i}"
|
||||
if [ -n "${!var:-}" ]; then
|
||||
FLATPAK_ENVS+=("--env=${var}=${!var}")
|
||||
fi
|
||||
done
|
||||
|
||||
GODOT_ARGS=("--path" "." "--rendering-method" "gl_compatibility")
|
||||
WESTON_PID=""
|
||||
|
|
|
|||
|
|
@ -2495,6 +2495,14 @@ func _append_turn_stats(outcome: String) -> void:
|
|||
for p: Variant in GameState.players:
|
||||
if int(p.index) == _victory_winner:
|
||||
winner_personality = str(p.get("clan_id") if p.get("clan_id") != null else "")
|
||||
# Defensive fallback: if clan_id was never assigned (e.g. human
|
||||
# slot in a legacy matchup-grid run without per-slot pinning),
|
||||
# fall back to the AI_PIN_PERSONALITY_P{index} env var so the
|
||||
# downstream verdict can still attribute the win.
|
||||
if winner_personality.is_empty():
|
||||
winner_personality = OS.get_environment(
|
||||
"AI_PIN_PERSONALITY_P%d" % _victory_winner
|
||||
)
|
||||
break
|
||||
# p0-34: prefer prologue.display_turn() during the -1 → 0 → 1 cold-open so
|
||||
# the first lines of turn_stats.jsonl carry the prologue turn sequence
|
||||
|
|
|
|||
|
|
@ -8,11 +8,25 @@ extends RefCounted
|
|||
|
||||
|
||||
static func assign(player: RefCounted, rng: RandomNumberGenerator) -> void:
|
||||
if player == null or player.is_human:
|
||||
if player == null:
|
||||
return
|
||||
var clans: Dictionary = DataLoader.get_data("ai_personalities")
|
||||
if clans.is_empty():
|
||||
return
|
||||
|
||||
# Per-slot pin: AI_PIN_PERSONALITY_P{index} forces a specific clan into a
|
||||
# specific player slot. Overrides the is_human guard so matchup-grid and
|
||||
# arena harnesses can deterministically populate every slot's clan_id.
|
||||
# Without this, the meta.json player_clans map under-counts non-pinned
|
||||
# slots and matchup_balance verdict mis-attributes wins.
|
||||
var slot_pin: String = OS.get_environment("AI_PIN_PERSONALITY_P%d" % int(player.index))
|
||||
if not slot_pin.is_empty() and clans.has(slot_pin):
|
||||
_apply_clan(player, clans[slot_pin], slot_pin)
|
||||
return
|
||||
|
||||
if player.is_human:
|
||||
return
|
||||
|
||||
var ids: Array = clans.keys()
|
||||
ids.sort()
|
||||
# AI_PIN_PERSONALITY=<clan_id> forces all AI players to a specific clan for
|
||||
|
|
@ -23,7 +37,10 @@ static func assign(player: RefCounted, rng: RandomNumberGenerator) -> void:
|
|||
chosen_id = pin
|
||||
else:
|
||||
chosen_id = ids[rng.randi() % ids.size()]
|
||||
var pick: Dictionary = clans.get(chosen_id, {})
|
||||
_apply_clan(player, clans[chosen_id], chosen_id)
|
||||
|
||||
|
||||
static func _apply_clan(player: RefCounted, pick: Dictionary, chosen_id: String) -> void:
|
||||
var axes: Dictionary = pick.get("strategic_axes", {})
|
||||
if axes.is_empty():
|
||||
return
|
||||
|
|
|
|||
|
|
@ -69,6 +69,22 @@ def _collect(gd: Path) -> dict:
|
|||
player_clans = {str(k): str(v) for k, v in raw.items() if v}
|
||||
except (OSError, json.JSONDecodeError):
|
||||
pass
|
||||
# Defensive fallback for legacy matchup-grid runs (pre per-slot pinning):
|
||||
# if any player slot has empty clan_id, derive it from the parent dir name
|
||||
# `<root>/<clan_a>_vs_<clan_b>/as_<clan_X>/game_*`. The pinned clan was
|
||||
# historically placed on slot 1, the other on slot 0.
|
||||
parent = gd.parent
|
||||
pair_root = parent.parent
|
||||
if parent.name.startswith("as_") and "_vs_" in pair_root.name:
|
||||
pinned_clan = parent.name[len("as_"):]
|
||||
pair_clans = pair_root.name.split("_vs_")
|
||||
if len(pair_clans) == 2 and pinned_clan in pair_clans:
|
||||
other_clan = pair_clans[0] if pair_clans[1] == pinned_clan else pair_clans[1]
|
||||
# Legacy: pinned on slot 1, "other" on slot 0
|
||||
if "0" not in player_clans:
|
||||
player_clans["0"] = other_clan
|
||||
if "1" not in player_clans:
|
||||
player_clans["1"] = pinned_clan
|
||||
return {
|
||||
"turns": final.get("turn", 0), "outcome": final.get("outcome", "?"),
|
||||
"winner_personality": final.get("winner_personality", ""),
|
||||
|
|
|
|||
|
|
@ -92,24 +92,28 @@ for pair in "${PAIRS[@]}"; do
|
|||
# pairs, which keeps determinism-compare usable later.
|
||||
offset=$((SEED_BASE + pair_idx * 100))
|
||||
|
||||
# Half the games: clan_a on slot 1 (AI opponent). Other half: clan_b.
|
||||
# This keeps positional fairness — the "who's AI vs who's heuristic"
|
||||
# question doesn't bias the grid.
|
||||
# Per-slot pinning: clan_a in slot 0, clan_b in slot 1 for one half;
|
||||
# swap positions for the other half to remove positional bias.
|
||||
# AI_PIN_PERSONALITY_P{N} (added with personality_assigner.gd per-slot
|
||||
# support) overrides the is_human guard so BOTH players' clan_id is set
|
||||
# in meta.json — matchup_balance verdict can attribute every win.
|
||||
half=$((COUNT / 2))
|
||||
second_half=$((COUNT - half))
|
||||
|
||||
echo -e "${YELLOW}[${pair_idx}/${#PAIRS[@]}]${NC} $pair (seeds $((offset + 1))..$((offset + COUNT)))"
|
||||
|
||||
# Batch with clan_a as AI
|
||||
AI_PIN_PERSONALITY="$clan_a" \
|
||||
# Batch with clan_a in slot 0, clan_b in slot 1
|
||||
AI_PIN_PERSONALITY_P0="$clan_a" \
|
||||
AI_PIN_PERSONALITY_P1="$clan_b" \
|
||||
SEED_OFFSET=$offset \
|
||||
PARALLEL=$PARALLEL \
|
||||
bash "$REPO_ROOT/tools/autoplay-batch.sh" "$half" "$TURN_LIMIT" \
|
||||
"$pair_dir/as_${clan_a}" > "$pair_dir/as_${clan_a}.log" 2>&1
|
||||
a_rc=$?
|
||||
|
||||
# Batch with clan_b as AI
|
||||
AI_PIN_PERSONALITY="$clan_b" \
|
||||
# Batch with clan_b in slot 0, clan_a in slot 1 (positional swap)
|
||||
AI_PIN_PERSONALITY_P0="$clan_b" \
|
||||
AI_PIN_PERSONALITY_P1="$clan_a" \
|
||||
SEED_OFFSET=$((offset + half)) \
|
||||
PARALLEL=$PARALLEL \
|
||||
bash "$REPO_ROOT/tools/autoplay-batch.sh" "$second_half" "$TURN_LIMIT" \
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue