fix(@projects/@magic-civilization): 🐛 add ai promotion debug logging

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-06 10:54:24 -07:00
parent 690039cdf3
commit 587a10bca0
3 changed files with 55 additions and 6 deletions

View file

@ -469,9 +469,17 @@ case "${MODE}" in
# 2026-04-18. Opt-in via env override; gpu-walltime flips
# per-iteration as its explicit comparison.
GPU_ENV="AI_GPU_ROLLOUT=${AI_GPU_ROLLOUT:-false}"
echo "[$(date +%H:%M:%S)] smoke batch: ${SEEDS} seeds T${TURNS} PARALLEL=${PARALLEL} ${GPU_ENV}"
# p2-44b: forward MC_AI_* diagnostic envs so instrumentation prints surface in game.log
MC_AI_ENV=""
for var in MC_AI_PROMOTION_DEBUG; do
val="${!var:-}"
if [[ -n "${val}" ]]; then
MC_AI_ENV="${MC_AI_ENV} ${var}=${val}"
fi
done
echo "[$(date +%H:%M:%S)] smoke batch: ${SEEDS} seeds T${TURNS} PARALLEL=${PARALLEL} ${GPU_ENV}${MC_AI_ENV}"
ssh "${APRICOT}" "set -euo pipefail; cd '${SCRATCH_ABS}' && \
AI_USE_MCTS=true ${GPU_ENV} PARALLEL=${PARALLEL} \
AI_USE_MCTS=true ${GPU_ENV}${MC_AI_ENV} PARALLEL=${PARALLEL} \
bash tools/autoplay-batch.sh ${SEEDS} ${TURNS} ${RESULTS_ABS}/smoke 2>&1 | tail -30"
;;
clan)

View file

@ -47,14 +47,24 @@ static func dispatch_action(
## the audio chime, throne-room counter, and chronicle log all fire on the
## same path the human picker uses.
static func dispatch_promotion_picked(fields: Dictionary, index_maps: Dictionary) -> bool:
var unit: RefCounted = resolve_unit(int(fields.get("unit_id", -1)), index_maps)
if unit == null or not unit.is_alive():
return false
var debug: bool = OS.get_environment("MC_AI_PROMOTION_DEBUG") != ""
var uid: int = int(fields.get("unit_id", -1))
var promo_id: String = String(fields.get("promotion_id", ""))
if debug:
print("[promo-debug] dispatch_promotion_picked entry uid=", uid, " promo=", promo_id)
var unit: RefCounted = resolve_unit(uid, index_maps)
if unit == null or not unit.is_alive():
if debug:
print("[promo-debug] dispatch_promotion_picked ABORT unit_null_or_dead uid=", uid)
return false
if promo_id.is_empty():
if debug:
print("[promo-debug] dispatch_promotion_picked ABORT empty_promo_id uid=", uid)
return false
unit.promote(promo_id)
EventBus.unit_promoted.emit(unit, promo_id)
if debug:
print("[promo-debug] dispatch_promotion_picked SUCCESS uid=", uid, " promo=", promo_id, " new_vlvl=", unit.veteran_level)
return true

View file

@ -91,6 +91,15 @@ static func build_tactical_state(focal: RefCounted) -> Dictionary:
if p == null:
continue
players.append(player_to_dict(p))
if OS.get_environment("MC_AI_PROMOTION_DEBUG") != "":
var total_with_choices: int = 0
var total_units: int = 0
for pdict: Dictionary in players:
for udict: Dictionary in pdict.get("units", []):
total_units += 1
if not (udict.get("pending_promotion_choices", []) as Array).is_empty():
total_with_choices += 1
print("[promo-debug] build_tactical_state turn=", int(GameState.turn_number), " focal=", int(focal.index), " total_units=", total_units, " units_with_choices=", total_with_choices)
return {
"current_player": int(focal.index),
"turn": int(GameState.turn_number),
@ -412,27 +421,45 @@ static func _load_clan_entry(clan_id: String) -> Dictionary:
## 4. The promotion's prerequisite (if any) must already be owned.
## 5. The promotion's level entry must equal the unit's next veteran level.
static func _eligible_promotion_ids(unit: RefCounted) -> Array:
var debug: bool = OS.get_environment("MC_AI_PROMOTION_DEBUG") != ""
var out: Array = []
if unit == null or not unit.has_method("can_promote") or not unit.can_promote():
if unit == null or not unit.has_method("can_promote"):
if debug:
print("[promo-debug] eligible: unit=null or no can_promote")
return out
var can: bool = unit.can_promote()
var uid: int = int(unit.get("id")) if "id" in unit else -1
if not can:
if debug:
print("[promo-debug] eligible unit=", uid, " kind=", unit.unit_id, " xp=", unit.xp, " vlvl=", unit.veteran_level, " can_promote=false")
return out
var trees: Dictionary = DataLoader.get_promotion_trees()
if trees.is_empty():
if debug:
print("[promo-debug] eligible unit=", uid, " ABORT trees_empty")
return out
var owned: Array = Array(unit.promo_ids)
var next_level: int = int(unit.veteran_level) + 1
var unit_data: Dictionary = unit.get_data()
var unit_flags: Array = unit_data.get("flags", [])
var unit_combat_type: String = String(unit_data.get("unit_type", "melee"))
var rejected_trees: Array = []
var matched_trees: Array = []
var no_level_match: Array = []
for tree_key: String in trees:
var tree: Dictionary = trees[tree_key] as Dictionary
if tree == null:
continue
if not _tree_applies_to(tree, unit_combat_type, unit_flags):
rejected_trees.append(tree_key)
continue
matched_trees.append(tree_key)
var levels: Array = tree.get("levels", [])
var any_level_match: bool = false
for level_entry: Dictionary in levels:
if int(level_entry.get("level", 0)) != next_level:
continue
any_level_match = true
for choice: Dictionary in level_entry.get("choices", []):
var pid: String = String(choice.get("id", ""))
if pid.is_empty() or owned.has(pid):
@ -441,6 +468,10 @@ static func _eligible_promotion_ids(unit: RefCounted) -> Array:
if not prereq.is_empty() and prereq != "<null>" and not owned.has(prereq):
continue
out.append(pid)
if not any_level_match:
no_level_match.append(tree_key)
if debug:
print("[promo-debug] eligible unit=", uid, " kind=", unit.unit_id, " xp=", unit.xp, " vlvl=", unit.veteran_level, " ctype=", unit_combat_type, " flags=", unit_flags, " next_lvl=", next_level, " matched=", matched_trees, " rejected=", rejected_trees, " no_lvl=", no_level_match, " out=", out)
return out