saumwacht-godot/tools/luneraria/g2_cost_split.gd
Martin Stickelbroeck 9f62e30377 G2: Ancients, Türme, Bossgrotte und Superboss; macOS- und Windows-ARM64-Pakete
Mondherz-Ancients (Saumkern-Kristall als Herz, Sichelklingen, Ringe, Krone,
Teamwappen, Energieadern) mit den Stufen stabil/instabil/kritisch, kurzem
Zusammenbruch und bleibender Ruine; der Sieg wartet nie auf die Grafik.
Turmfamilie aus einem Blender-Generator: Grenzwacht (Tower 1), Bastion
(Tower 2), Kernwacht (Basisturm in der Formensprache des Ancients), mit
Schadensstufen, Einsturz, Ruine und sichtbarem Angriff (Zielring, Aufladen,
Bolzen, Einschlag) - der Schaden bleibt vollständig in der Simulation.
Bossgrotte Mondschlund mit Tor, Treppe in die Tiefe, Altar, Säulen, Felsen
und fünf Zuständen; der Generator hält Abstand zur Top-Lane, die Höhle
braucht eine kleine Geländesenke (nur Darstellung). Superboss Schlundkrone
mit 24 Knochen und 11 Clips (inkl. Brüllen und Sieg), Adern in Teamfarbe,
dasselbe Modell für Grotte und Lane. Gemeinsame LN-Materialfamilie (Stein,
Metall, Kristall, Rune, Stoff, Mondwasser, Kreaturenhaut), Occlusion-Fade
ohne dithernde Schatten. Keine Regeländerung.

Geprüft: verify_g2 245/245, verify_g1 443/443, Regeln 91/91 und 73/73,
Stress 17/17, Balance 40 Partien identisch mit G1, verify_m1 38/38,
verify_m2 14/14, Netz 43/43 und Loopback 12/12, Legacy 209 und 463.
21 Ansichten vorher/nachher (1920x1080), Videos Turmkampf und Bossbegegnung,
Leistung G1/G2 auf demselben Mac (+0,5 ms in vollen Kampfansichten).
Pakete 0.17.0-g2 für macOS (Universal, ad-hoc signiert), Windows x64/ARM64
und Linux; macOS nativ und unter Rosetta, beide Windows-Pakete in Windows 11
ARM (Parallels, OpenGL-Fallback) mit --lune-smoke bestanden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:12:49 +02:00

86 lines
3.5 KiB
GDScript

extends Node
## Where does a frame go? (G2 performance analysis.) The benchmark fight of round 13 in the
## lane view, paused so every variant renders the same scene; each group is hidden or
## simplified for four seconds and the frame time is taken again. On a GPU-bound machine the
## differences are the render cost of the group. VSync may cap the result (120 Hz = 8.33 ms).
## Run: godot --path . --resolution 1920x1080 res://tools/luneraria/g2_cost_split.tscn -- --out=res://docs/luneraria/logs/g2/cost-split.json
const Game = preload("res://scripts/luneraria/lune_game.gd")
var out: String = "res://docs/luneraria/logs/g2/cost-split.json"
var results: Array = []
func _ready() -> void:
for arg: String in OS.get_cmdline_user_args():
if arg.begins_with("--out="):
out = arg.get_slice("=", 1)
call_deferred("run")
func measure(label: String) -> void:
for i: int in 20:
await get_tree().process_frame
var t0: int = Time.get_ticks_usec()
var count: int = 0
var end: int = Time.get_ticks_msec() + 4000
while Time.get_ticks_msec() < end:
await RenderingServer.frame_post_draw
count += 1
var entry := {"variant": label, "frame_ms": (Time.get_ticks_usec() - t0) / 1000.0 / count,
"draw_calls": Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)}
results.append(entry)
print("COST %-28s %.2f ms/frame, %d draw calls" % [label, entry.frame_ms, entry.draw_calls])
func run() -> void:
Soundscape.enabled = false
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
var game: Node3D = Game.new()
add_child(game)
game.start({"seed": 5, "human_slot": 0})
var state: LuneMatch = game.match_state
state.round_number = 13
for slot: int in range(6):
state.players[slot].draft.gold = 100000
state.players[slot].draft.roster.clear()
LuneBot.plan(state, slot, "ausgewogen")
game._on_ready()
game.camera.look_at_point(Vector3(-10, 0, 10), 34.0, true)
for i: int in 60:
await get_tree().process_frame
game.paused = true
var world: Node3D = game.world
await measure("everything")
for view: Node3D in world.tower_views.values():
view.visible = false
await measure("towers hidden")
for view: Node3D in world.tower_views.values():
view.visible = true
var plain := StandardMaterial3D.new()
plain.albedo_color = Color(.7, .68, .62)
var saved: Array = []
for view: Node3D in world.tower_views.values():
for instance: GeometryInstance3D in view._instances:
saved.append([instance, instance.material_override])
instance.material_override = plain
await measure("towers with a plain material")
for entry: Array in saved:
(entry[0] as GeometryInstance3D).material_override = entry[1]
for view: Node3D in world.ancient_views.values():
view.visible = false
await measure("Ancients hidden")
for view: Node3D in world.ancient_views.values():
view.visible = true
world.grotto_view.visible = false
await measure("grotto hidden")
world.grotto_view.visible = true
game._units_root.visible = false
await measure("figures hidden")
game._units_root.visible = true
world.sun.shadow_enabled = false
await measure("sun shadows off")
world.sun.shadow_enabled = true
var report := {"window": get_window().size, "gpu": RenderingServer.get_video_adapter_name(), "engine": Engine.get_version_info().string,
"scene": "round 13, six full armies, lane view (-10, 10) zoom 34, combat paused", "results": results}
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(out.get_base_dir()))
var file := FileAccess.open(out, FileAccess.WRITE)
file.store_string(JSON.stringify(report, "\t"))
file.close()
get_tree().quit()