fix(@projects/@magic-civilization): 🐛 update ai player and unit initialization

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-04-17 08:45:37 -07:00
parent 084661c4ba
commit e104ed91c9

View file

@ -109,7 +109,6 @@ func _initialize() -> void:
add_child(_mock_tm)
_setup_game_state()
(_tech_web as TechWebScript).initialize()
var game_map: RefCounted = GameState.get_game_map()
var map_w: int = (game_map as GameMapScript).width if game_map is GameMapScript else 0
@ -202,13 +201,10 @@ func _collect_start_positions(
func _create_ai_player(
p_name: String, race_id: String, clan_id: String, personality: Dictionary
) -> RefCounted:
var player: RefCounted = PlayerScript.new()
player.player_name = p_name
player.race_id = race_id
var player: RefCounted = PlayerScript.new(-1, p_name, race_id)
if "clan_id" in player:
player.clan_id = clan_id
player.is_player_controlled = false
player.science_per_turn = 3
player.is_human = false
player.gold = 50
player.gold_per_turn = 5
player.happiness = 5
@ -220,19 +216,9 @@ func _create_ai_player(
func _create_founder(owner_idx: int, pos: Vector2i) -> RefCounted:
var unit: UnitScript = UnitScript.new()
var unit: UnitScript = UnitScript.new("dwarf_founder", owner_idx, pos)
unit.id = "founder_%d" % owner_idx
unit.type_id = "dwarf_founder"
unit.owner = owner_idx
unit.position = pos
unit.unit_type = "civilian"
unit.can_found_city = true
var data: Dictionary = DataLoader.get_unit("dwarf_founder")
if not data.is_empty():
unit.apply_data(data)
unit.owner = owner_idx
unit.position = pos
unit.id = "founder_%d" % owner_idx
return unit
@ -357,9 +343,9 @@ func _draw() -> void:
)
var clan_id: String = str(p.get("clan_id"))
var clan_color: Color = clan_colors.get(clan_id, Color.WHITE) as Color
var win_rate_any: float = float(stats.get("win_rate", -1.0))
var has_win: bool = stats.has("win_rate") and stats["win_rate"] != null
var win_str: String = (
"--" if win_rate_any < 0.0 else "%.1f%%" % (win_rate_any * 100.0)
"%.1f%%" % (float(stats["win_rate"]) * 100.0) if has_win else "--"
)
var values: Array[String] = [
p.player_name,
@ -379,49 +365,11 @@ func _draw() -> void:
HORIZONTAL_ALIGNMENT_LEFT, -1, 12, col)
y += lh + 2
y += 16
draw_rect(Rect2(24, y, 1872, 1), Color(0.35, 0.30, 0.22))
y += 8
draw_string(font, Vector2(24, y),
"CITIES BY CLAN (founded during %d-turn simulation)" % TOTAL_TURNS,
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, Color(0.8, 0.6, 0.2))
y += lh + 4
var game_map: RefCounted = GameState.get_game_map()
for p: RefCounted in _players:
var clan_id: String = str(p.get("clan_id"))
var clan_color: Color = clan_colors.get(clan_id, Color.WHITE) as Color
var line_parts: PackedStringArray = PackedStringArray()
for city: CityScript in p.cities:
if city == null:
continue
var suffix: String = " pop=%d" % city.population
if game_map != null:
var ylds: Dictionary = city.get_yields(game_map)
suffix += " F:%d P:%d" % [
int(ylds.get("food", 0)), int(ylds.get("production", 0)),
]
line_parts.append("%s@%s%s" % [city.city_name, str(city.position), suffix])
var joined: String = (
", ".join(line_parts) if not line_parts.is_empty()
else "(no cities founded)"
)
var line: String = "%s: %s" % [p.player_name, joined]
draw_string(font, Vector2(24, y), line,
HORIZONTAL_ALIGNMENT_LEFT, -1, 11, clan_color)
y += lh
if not _error_log.is_empty():
y += lh
draw_string(font, Vector2(24, y),
"ERRORS (%d)" % _error_log.size(),
"ERRORS (%d) — see text summary for full detail" % _error_log.size(),
HORIZONTAL_ALIGNMENT_LEFT, -1, 12, Color(0.9, 0.4, 0.4))
y += lh
for err: String in _error_log.slice(0, 8):
draw_string(font, Vector2(24, y), err,
HORIZONTAL_ALIGNMENT_LEFT, -1, 10, Color(0.9, 0.5, 0.5))
y += lh
func _capture_and_quit() -> void:
@ -430,24 +378,102 @@ func _capture_and_quit() -> void:
_captured = true
var exit_code: int = 0 if _error_log.is_empty() else 1
if DisplayServer.get_name() == "headless":
get_tree().quit(exit_code)
return
var image: Image = get_viewport().get_texture().get_image()
if image == null:
get_tree().quit(exit_code)
return
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(OUTPUT_DIR))
var timestamp: String = Time.get_datetime_string_from_system().replace(":", "-").replace("T", "_")
var rel_path: String = "%s/%s_%s.png" % [OUTPUT_DIR, _screenshot_name, timestamp]
var abs_path: String = ProjectSettings.globalize_path(rel_path)
var err: Error = image.save_png(abs_path)
if err == OK:
print("SCREENSHOT_PATH:%s" % abs_path)
print("Screenshot: %dx%d saved to %s" % [image.get_width(), image.get_height(), abs_path])
else:
push_error("AISanityProof: Save failed: %s" % error_string(err))
var abs_path: String = _save_viewport_or_summary()
if abs_path.is_empty():
exit_code = maxi(1, exit_code)
get_tree().quit(exit_code)
## Save an image of the overlay if a real display backs the viewport, OR a
## deterministic text summary when the process is fully headless (dummy
## rendering driver returns null textures). Returns the path written, or
## "" on failure. Both outputs land under OUTPUT_DIR with the same stem so
## Phase Gate review pipelines can pick up whichever exists.
func _save_viewport_or_summary() -> String:
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(OUTPUT_DIR))
var timestamp: String = (
Time.get_datetime_string_from_system().replace(":", "-").replace("T", "_")
)
var img_rel: String = "%s/%s_%s.png" % [OUTPUT_DIR, _screenshot_name, timestamp]
var img_abs: String = ProjectSettings.globalize_path(img_rel)
var texture: ViewportTexture = get_viewport().get_texture()
var image: Image = texture.get_image() if texture != null else null
if image != null and image.get_width() > 0:
var err: Error = image.save_png(img_abs)
if err == OK:
print("SCREENSHOT_PATH:%s" % img_abs)
print("Screenshot: %dx%d saved to %s" % [
image.get_width(), image.get_height(), img_abs,
])
return img_abs
push_error("AISanityProof: PNG save failed: %s" % error_string(err))
# Headless-fallback text summary — same filename stem, .txt suffix. The
# simulation results are what matters for the Phase Gate review; the
# dummy renderer only prevents the overlay bitmap, not the data.
var txt_rel: String = "%s/%s_%s.txt" % [OUTPUT_DIR, _screenshot_name, timestamp]
var txt_abs: String = ProjectSettings.globalize_path(txt_rel)
var summary: String = _build_text_summary()
var f: FileAccess = FileAccess.open(txt_abs, FileAccess.WRITE)
if f == null:
push_error("AISanityProof: text summary open failed")
return ""
f.store_string(summary)
f.close()
print("SCREENSHOT_PATH:%s" % txt_abs)
print("Text summary: %s (headless fallback)" % txt_abs)
return txt_abs
func _build_text_summary() -> String:
var lines: PackedStringArray = PackedStringArray()
lines.append("AI Sanity Proof -- 5 clans, %d turns, seed %d" % [TOTAL_TURNS, MAP_SEED])
lines.append("Status: %s" % ("PASS" if _error_log.is_empty() else "FAIL"))
lines.append("")
var header: String = (
"| Clan | Cities | Units | Pop | Gold | Techs "
+ "| MCTS Path | Rollouts | Win% | Action |"
)
lines.append(header)
lines.append(
"|---------------------|--------|-------|-----|------|-------"
+ "|------------|----------|--------|-------------|"
)
for p: RefCounted in _players:
var stats: Dictionary = AiTurnBridgeScript.get_last_mcts_stats(
_final_turn - 1, p.index
)
var has_win: bool = stats.has("win_rate") and stats["win_rate"] != null
var win_str: String = (
"%.1f%%" % (float(stats["win_rate"]) * 100.0) if has_win else "--"
)
lines.append(
"| %-19s | %6d | %5d | %3d | %4d | %5d | %-10s | %8d | %-6s | %-11s |" % [
p.player_name, p.cities.size(), p.units.size(),
_get_total_pop(p), p.gold, p.researched_techs.size(),
String(stats.get("path", "--")),
int(stats.get("rollouts", 0)),
win_str,
String(stats.get("action", "--")),
]
)
lines.append("")
lines.append("Cities by clan:")
for p: RefCounted in _players:
var parts: PackedStringArray = PackedStringArray()
for city: CityScript in p.cities:
if city == null:
continue
parts.append("%s@%s pop=%d" % [city.city_name, str(city.position), city.population])
var line: String = " %s: %s" % [
p.player_name,
", ".join(parts) if not parts.is_empty() else "(no cities founded)",
]
lines.append(line)
if not _error_log.is_empty():
lines.append("")
lines.append("Errors:")
for err: String in _error_log:
lines.append(" - %s" % err)
return "\n".join(lines) + "\n"