saumwacht-godot/tools/verify_phase6_runtime.gd
Martin Stickelbroeck c2b447610c Baseline: Saumwacht 0.15.0 (Phase 20A) vor dem Umbau zu Champions of Luneraria
Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern
unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore
unversioniert; letztere sind unter ../backups gesichert.

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

140 lines
5.6 KiB
GDScript

extends Node
const AI = preload("res://scripts/ai/simple_ai.gd")
var main: Node3D
var checks: int = 0
var failures: Array[String] = []
var expedition: bool = "--expedition" in OS.get_cmdline_user_args()
func check(value: bool, message: String) -> void:
checks += 1
if not value:
failures.append(message)
printerr("RUNTIME_FAIL: ", message)
func key(code: Key) -> void:
var event := InputEventKey.new()
event.keycode = code
event.pressed = true
get_viewport().push_input(event,true)
event = event.duplicate()
event.pressed = false
get_viewport().push_input(event,true)
func _ready() -> void:
call_deferred("run")
func run() -> void:
GameState.reset_match()
GameState.factions = ["glutpakt", "glutpakt"]
GameState.timed_build = true
GameState.select_mode(GameState.MODE_SHORT)
main = load("res://scenes/main.tscn").instantiate()
add_child(main)
WaveManager.set_physics_process(false)
await get_tree().process_frame
main.hud._overlay_action.pressed.emit()
main.hud._starter_button.pressed.emit()
await get_tree().process_frame
var cursor := Vector2(530,360)
var press := InputEventMouseButton.new()
press.position = cursor
press.button_index = MOUSE_BUTTON_MIDDLE
press.pressed = true
get_viewport().push_input(press,true)
var motion := InputEventMouseMotion.new()
motion.position = cursor + Vector2(90,40)
motion.relative = Vector2(90,40)
motion.button_mask = MOUSE_BUTTON_MASK_MIDDLE
var initial_focus: Vector3 = main._focus
get_viewport().push_input(motion,true)
press = press.duplicate()
press.pressed = false
get_viewport().push_input(press,true)
check(main._manual_camera and main._focus != initial_focus, "Middle drag pans and disables auto-follow")
key(KEY_R)
check(not main._manual_camera and main._focus.is_equal_approx(initial_focus), "Reset resumes following")
main.hud._ready_button.grab_focus()
key(KEY_F1)
key(KEY_SPACE)
check(GameState.phase == GameState.Phase.BUILD and main.hud.settings_panel.visible, "Ready button cannot activate behind options")
key(KEY_ESCAPE)
check(main.hud._ready_button.has_focus(), "Closing options restores keyboard focus")
# Exercise volume, mute and lane filtering without altering saved preferences.
var old_mute: bool = Preferences.muted
var old_volume: float = Preferences.volume
Preferences.muted = false
Preferences.volume = .65
Preferences.apply()
Soundscape.enabled = true
Soundscape.viewed_team = 0
await get_tree().create_timer(.15).timeout
check(Soundscape.play("ability"), "An audible effect starts")
check(not Soundscape.play("ability"), "Immediate duplicate effect is rate-limited")
check(not Soundscape.play("stone",1,true), "Hidden lane is silent")
Preferences.muted = true
Preferences.apply()
check(not Soundscape.play("error"), "Mute prevents effects")
Preferences.muted = false
Preferences.apply()
# Advance seven regular waves, then render a timed 2x boss fight.
GameState.ai_level = GameState.AI_LEARNING
if expedition:
GameState.reset_match()
if "--wurzelbund" in OS.get_cmdline_user_args():
GameState.factions = ["wurzelbund", "wurzelbund"]
GameState.select_mode(GameState.MODE_EXPEDITION)
GameState.ai_level = GameState.AI_STANDARD
WaveManager.start_match()
var ai := AI.new()
Soundscape.enabled = false
var last_wave: int = DataCatalog.wave_count()
main.battle.render_enabled = false
for number: int in range(1,last_wave+1):
ai.plan(0,number,WaveManager.rival_snapshot(),GameState.last_reports[0],GameState.AI_STANDARD)
if number == last_wave:
main.battle.render_enabled = true
WaveManager.ready_for_battle()
if number == last_wave:
break
for step: int in range(5401):
if GameState.phase != GameState.Phase.COMBAT:
break
WaveManager.advance(1.0/60.0)
WaveManager.ready_for_battle()
GameState.speed = 2
Soundscape.enabled = true
WaveManager.set_physics_process(true)
await get_tree().create_timer(2.0).timeout
var frames: Array[float] = []
var calls: Array[int] = []
var start: int = Time.get_ticks_usec()
var previous: int = start
var sound_start: int = Soundscape.played
var units_start: int = main.battle.units.size()
while Time.get_ticks_usec() - start < 12000000:
await get_tree().process_frame
var now: int = Time.get_ticks_usec()
frames.append((now - previous)/1000.0)
calls.append(int(Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)))
previous = now
frames.sort()
calls.sort()
var mean: float = 0
for value: float in frames:
mean += value
mean /= frames.size()
var sounds: int = Soundscape.played - sound_start
check(sounds > 0 and sounds <= 190, "2x audio stays bounded and produces effects")
var result: Dictionary = {"engine": Engine.get_version_info().string, "device": RenderingServer.get_video_adapter_name(),
"viewport": str(get_viewport().get_visible_rect().size), "wave": last_wave, "speed": 2, "sample_seconds": 12,
"units_at_start": units_start, "frames":frames.size(),"mean_frame_ms":mean,"p95_frame_ms":frames[floori(frames.size()*.95)],
"mean_fps":1000.0/mean,"max_draw_calls":calls.back(),"audio_events":sounds,"checks":checks,"failures":failures}
var phase: String = "phase10" if "--phase10" in OS.get_cmdline_user_args() else "phase9" if "--wurzelbund" in OS.get_cmdline_user_args() else "phase8" if expedition else ("phase7" if "--phase7" in OS.get_cmdline_user_args() else "phase6")
result["renderer"] = RenderingServer.get_current_rendering_method()
var file := FileAccess.open("res://docs/" + phase + "-runtime.json", FileAccess.WRITE)
file.store_string(JSON.stringify(result,"\t")); file.close()
print("PHASE6_RUNTIME: ",JSON.stringify(result))
Preferences.volume = old_volume
Preferences.muted = old_mute
Preferences.apply()
await Soundscape.quit_game(0 if failures.is_empty() else 1)