Top windet sich über das Hochland, Mid quert die Kluft über die Mittelplattform, Bottom führt durch das Tiefland über die Deltabrücke; Top und Bottom sind exakt gleich lang, alles bleibt spiegelsymmetrisch. LuneTerrain liefert ein exakt gespiegeltes Höhenfeld, auf dem Figuren stehen. Bossgrotte als Terrasse auf der Achse zwischen Top und Mid, Aufmarschbereiche je Lane, perspektivische Kamera mit Sprungtasten (F2–F4, B, G, H, O, R, Q/E). Bestandsassets wiederverwendet, neue Formen als Blockout für G2/G4. Geprüft: verify_g1 443/443, Regeln 91/91, M2 73/73, verify_m1 38/38, verify_m2 14/14, Netz 43/43, Loopback 12/12, Balance 40 Partien (Seitentausch 20/20), Legacy 209/463. Vorher/Nachher-Aufnahmen und Messungen in G1-KARTE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
114 lines
5.3 KiB
GDScript
114 lines
5.3 KiB
GDScript
extends Node
|
|
## Frame and simulation cost in a real match scene at representative maximum occupation:
|
|
## six armies at the supply cap and a round-13 wave on all three lanes (synthetic fixture,
|
|
## gold granted to the fixture only). Two camera setups are measured one after the other:
|
|
## a lane view (Mid, Blau side, grotto in the background) and the whole-map overview,
|
|
## where every lane, tower, base and the grotto are drawn at once. VSync off; the
|
|
## figures apply to this machine only.
|
|
## Run: godot --path . --resolution 1920x1080 res://tools/luneraria/lune_benchmark.tscn -- --out=res://docs/luneraria/logs/g1/perf.json
|
|
const Game = preload("res://scripts/luneraria/lune_game.gd")
|
|
const PHASE_SECONDS: float = 6.0
|
|
|
|
var out: String = ""
|
|
## Start of the current frame's main-thread work (set by a node that processes first).
|
|
var frame_start: int = 0
|
|
|
|
class FrameClock:
|
|
extends Node
|
|
var owner_bench: Node
|
|
func _process(_delta: float) -> void:
|
|
owner_bench.frame_start = Time.get_ticks_usec()
|
|
|
|
func _ready() -> void:
|
|
for arg: String in OS.get_cmdline_user_args():
|
|
if arg.begins_with("--out="):
|
|
out = arg.substr(6)
|
|
var clock := FrameClock.new()
|
|
clock.owner_bench = self
|
|
clock.process_priority = -1000
|
|
add_child(clock)
|
|
call_deferred("run")
|
|
|
|
func mean(values: Array) -> float:
|
|
var total: float = 0.0
|
|
for value: Variant in values:
|
|
total += float(value)
|
|
return total / maxf(1.0, values.size())
|
|
|
|
func measure(game: Node3D, state: LuneMatch) -> Dictionary:
|
|
for i: int in 30:
|
|
await get_tree().process_frame
|
|
var frames: Array[float] = []
|
|
var cpu: Array[float] = []
|
|
var gpu: Array[float] = []
|
|
var draws: Array[float] = []
|
|
var primitives: Array[float] = []
|
|
var objects: Array[float] = []
|
|
var mobile: Array[int] = []
|
|
var last: int = Time.get_ticks_usec()
|
|
var end: int = Time.get_ticks_msec() + int(PHASE_SECONDS * 1000.0)
|
|
while Time.get_ticks_msec() < end and state.phase == LuneMatch.Phase.COMBAT:
|
|
await RenderingServer.frame_post_draw
|
|
var now: int = Time.get_ticks_usec()
|
|
frames.append((now - last) / 1000.0)
|
|
last = now
|
|
# Main-thread time from the first _process of the frame until the frame was drawn.
|
|
cpu.append((now - frame_start) / 1000.0)
|
|
gpu.append(RenderingServer.viewport_get_measured_render_time_gpu(get_viewport().get_viewport_rid()))
|
|
draws.append(Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME))
|
|
primitives.append(Performance.get_monitor(Performance.RENDER_TOTAL_PRIMITIVES_IN_FRAME))
|
|
objects.append(Performance.get_monitor(Performance.RENDER_TOTAL_OBJECTS_IN_FRAME))
|
|
mobile.append(state.combat.mobile_count())
|
|
frames.sort()
|
|
return {"frames": frames.size(), "frame_ms_mean": mean(frames), "frame_ms_p95": frames[int(frames.size() * .95)] if not frames.is_empty() else -1.0,
|
|
"cpu_frame_ms_mean": mean(cpu), "gpu_ms_mean": mean(gpu), "draw_calls_mean": mean(draws), "primitives_mean": mean(primitives),
|
|
"objects_mean": mean(objects), "mobile_units_mean": mean(mobile), "camera_zoom": game.camera.zoom}
|
|
|
|
func run() -> void:
|
|
Soundscape.enabled = false
|
|
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
|
var t0: int = Time.get_ticks_usec()
|
|
var game: Node3D = Game.new()
|
|
add_child(game)
|
|
game.start({"seed": 5, "human_slot": 0})
|
|
var build_ms: float = (Time.get_ticks_usec() - t0) / 1000.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()
|
|
RenderingServer.viewport_set_measure_render_time(get_viewport().get_viewport_rid(), true)
|
|
game.speed_index = 1
|
|
game.camera.look_at_point(Vector3(-10, 0, 10), 34.0, true)
|
|
var lane_view: Dictionary = await measure(game, state)
|
|
game.camera.overview(true)
|
|
var overview: Dictionary = await measure(game, state)
|
|
# Pure simulation cost of one fixed step at this occupation, measured separately.
|
|
var probe := LuneMatch.new()
|
|
probe.setup({"seed": 5, "human_slot": -1})
|
|
probe.round_number = 13
|
|
for slot: int in range(6):
|
|
probe.players[slot].draft.gold = 100000
|
|
LuneBot.plan(probe, slot, "ausgewogen")
|
|
probe.begin_combat()
|
|
var start_units: int = probe.combat.mobile_count()
|
|
var s1: int = Time.get_ticks_usec()
|
|
for i: int in 200:
|
|
probe.step_combat()
|
|
var step_ms: float = (Time.get_ticks_usec() - s1) / 1000.0 / 200.0
|
|
var report := {"window": get_window().size, "renderer": RenderingServer.get_current_rendering_method(), "gpu": RenderingServer.get_video_adapter_name(),
|
|
"cpu": OS.get_processor_name(), "engine": Engine.get_version_info().string, "scene_build_ms": build_ms,
|
|
"mobile_units_at_start": start_units, "buildings": 18, "sim_step_ms": step_ms, "sim_speed": "2x (40 steps/s)",
|
|
"lane_view": lane_view, "overview": overview,
|
|
"video_mem_mb": Performance.get_monitor(Performance.RENDER_VIDEO_MEM_USED) / 1048576.0,
|
|
"texture_mem_mb": Performance.get_monitor(Performance.RENDER_TEXTURE_MEM_USED) / 1048576.0,
|
|
"static_mem_mb": Performance.get_monitor(Performance.MEMORY_STATIC) / 1048576.0}
|
|
var path: String = out if not out.is_empty() else "res://docs/luneraria/logs/perf-%dx%d.json" % [get_window().size.x, get_window().size.y]
|
|
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(path.get_base_dir()))
|
|
var file := FileAccess.open(path, FileAccess.WRITE)
|
|
file.store_string(JSON.stringify(report, "\t"))
|
|
file.close()
|
|
print("LUNE_BENCH ", JSON.stringify(report))
|
|
get_tree().quit()
|