saumwacht-godot/tools/luneraria/sim_match.gd
Martin Stickelbroeck b565546c37 M1: autoritativer Drei-Lane-Kern (Karte, Kampf, Partie, Wellen, Bots)
LuneMap spiegelsymmetrisch mit 16 Türmen und 2 Ancients, LuneCombat mit
reihenfolgeunabhängigem Zweiphasenschritt und Gebäudesperren für jede
Schadensquelle, LuneMatch mit Planungsentwürfen, atomarer Übernahme,
Einmal-Belohnung und Kernsieg/Unentschieden, endlose LuneWaves, Bots über
dieselbe Befehlsschnittstelle. 87 Kernprüfungen bestanden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:57:37 +02:00

48 lines
2.7 KiB
GDScript

extends Node
## Headless six-bot matches for balance and acceptance. A QA round limit is reported as
## "Testlimit erreicht / Partie nicht abgeschlossen", never as a result.
## Run: godot --headless --path . res://tools/luneraria/sim_match.tscn -- --seeds=1,2,3 --limit=60
func _ready() -> void:
call_deferred("run")
func run() -> void:
var seeds: Array = [1]
var limit: int = 60
var out: String = ""
for arg: String in OS.get_cmdline_user_args():
if arg.begins_with("--seeds="):
seeds = Array(arg.substr(8).split(",")).map(func(s: String) -> int: return int(s))
elif arg.begins_with("--limit="):
limit = int(arg.substr(8))
elif arg.begins_with("--out="):
out = arg.substr(6)
var reports: Array = []
for seed: int in seeds:
var started: int = Time.get_ticks_msec()
var match_state := LuneMatch.new()
match_state.setup({"seed": seed, "human_slot": -1})
while match_state.phase != LuneMatch.Phase.OVER and match_state.round_number <= limit:
for slot: int in range(6):
LuneBot.plan(match_state, slot)
match_state.begin_combat()
var combat: LuneCombat = match_state.combat
match_state.finish_combat()
if combat.end_reason == "timeout" and "--diagnose" in OS.get_cmdline_user_args():
for unit: LuneUnit in combat.units:
if unit.alive and unit.is_mobile():
print(" STUCK t%d %s %s %s pos=%s prog=%.1f base=%s target=%s hp=%.0f/%.0f" % [unit.team, unit.lane, unit.kind, unit.def.get("id", ""), str(unit.position.snapped(Vector2(.1, .1))), unit.progress, unit.reached_base, (unit.target.def.get("id", "") if unit.target != null else "-"), unit.hp, unit.max_hp])
var h: Dictionary = match_state.history[-1]
print("R%02d %s %.1fs buildings %s lost %s gold %s" % [h.round, h.end_reason, h.combat_time, str(h.buildings), str(h.lost), str(h.gold)])
var result: String = "Testlimit erreicht / Partie nicht abgeschlossen"
if match_state.phase == LuneMatch.Phase.OVER:
result = "Unentschieden" if match_state.winner == LuneMatch.DRAW else ("Sieg Blau" if match_state.winner == 0 else "Sieg Rot")
var report := {"seed": seed, "result": result, "rounds": match_state.history.size(), "note": match_state.end_note,
"profiles": range(6).map(func(s: int) -> String: return LuneBot.profile_for(match_state, s)),
"bunds": match_state.players.map(func(p: Dictionary) -> String: return p.bund),
"wall_ms": Time.get_ticks_msec() - started, "history": match_state.history}
reports.append(report)
print("SIM_MATCH seed=%d %s after %d rounds (%s) in %d ms" % [seed, result, report.rounds, report.note, report.wall_ms])
if not out.is_empty():
var file := FileAccess.open(out, FileAccess.WRITE)
file.store_string(JSON.stringify(reports, "\t"))
file.close()
get_tree().quit()