Files
Bullosseum/tests/performance_test.gd
T
richard 34dc5b026e Lobby barrel-smash reward + roll/slam barrel destruction
Bull's boulder roll now homes on and ploughs through lobby barrels, and
the slam shockwave bursts them within its radius. HUD tallies barrels on
load and fires a confetti "WOooOoW!" + two gold bonus HP pips when the
last one breaks; gates open from run state as fights are cleared.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 01:38:57 +03:00

146 lines
4.6 KiB
GDScript

extends SceneTree
## Performance regression test — loads the full scene under a heavy matador load,
## samples per-frame times during normal play and during a mass-ragdoll stress
## spike, and asserts the frame budget holds.
##
## Run (headless):
## godot --headless --script tests/performance_test.gd
##
## Exit code 0 = within budget, 1 = a budget was exceeded (or a frame was non-finite).
##
## Budgets are deliberately generous regression guards, not a target frame rate:
## they only trip on a pathological slowdown, so the test stays stable across the
## range of machines it runs on. Every run also prints the measured numbers so a
## gradual creep is visible even when it doesn't fail.
const STRESS_MATADORS := 20
const WARMUP_SEC := 1.0
const SAMPLE_FRAMES := 120
# Generous ceilings — a healthy build sits far below these.
const AVG_BUDGET_MS := 16.0 # sustained cost must leave headroom for 60 fps
const MAX_BUDGET_MS := 100.0 # a single hitch this large is a real regression
var _passed: int = 0
var _failed: int = 0
func _init() -> void:
_run.call_deferred()
func _run() -> void:
print("=".repeat(60))
print("PERFORMANCE TEST")
print("=".repeat(60))
var dp: Node = root.get_node_or_null("/root/DP")
if dp:
dp.set_value("mat_spawn_count", STRESS_MATADORS)
# The run now opens in the fightless lobby; force the matador arena so the stress scene
# loads with matadors.
var run: Node = root.get_node_or_null("/root/Run")
if run != null:
run.ensure_run() # build the run first — the shell's own ensure_run would wipe the override
run.debug_level_override = run.get_level(&"arena")
var scene_res := load("res://scene.tscn")
if not scene_res:
push_error("performance_test: failed to load scene.tscn")
quit(1)
return
var scene_inst: Node = scene_res.instantiate()
root.add_child(scene_inst)
current_scene = scene_inst # match runtime: game code (sword throw, overlays) uses current_scene
# The matadors now stab the (idle) player; drop the HUD so a resulting player
# death doesn't raise the game-over screen and pause the tree mid-measurement.
var hud := _find_hud(scene_inst)
if hud != null:
hud.queue_free()
await create_timer(WARMUP_SEC).timeout
var matadors := get_nodes_in_group(&"matador")
print(" matadors alive: %d" % matadors.size())
# The mass-ragdoll below kills every matador; hold the spawner's alive count high
# so that doesn't trip the win screen (which pauses the tree) mid-measurement.
var spawners := get_nodes_in_group(&"matador_spawn")
if not spawners.is_empty():
spawners[0]._alive = 100000
var normal := await _sample_frames(SAMPLE_FRAMES)
_report_phase("normal play", normal)
# Stress spike: ragdoll every matador on the same frame.
for mat: Node in get_nodes_in_group(&"matador"):
if mat.has_method("_enter_ragdoll"):
mat._enter_ragdoll(Vector3(randf() - 0.5, 0.0, randf() - 0.5).normalized(), 12.0)
var stress := await _sample_frames(SAMPLE_FRAMES)
_report_phase("mass ragdoll", stress)
_finish()
# Awaits SAMPLE_FRAMES idle frames, returning [avg_ms, max_ms, all_finite].
func _sample_frames(count: int) -> Array:
var total_us := 0
var max_us := 0
var all_finite := true
var last := Time.get_ticks_usec()
for _i in count:
await process_frame
var now := Time.get_ticks_usec()
var frame_us := now - last
last = now
total_us += frame_us
max_us = maxi(max_us, frame_us)
if not is_finite(float(frame_us)):
all_finite = false
var avg_ms := (total_us / float(count)) / 1000.0
return [avg_ms, max_us / 1000.0, all_finite]
func _report_phase(label: String, sample: Array) -> void:
var avg_ms: float = sample[0]
var max_ms: float = sample[1]
var all_finite: bool = sample[2]
print("\n-- %s --" % label)
print(" avg %.2f ms/frame (~%.0f fps) peak %.2f ms" % [
avg_ms, 1000.0 / maxf(avg_ms, 0.001), max_ms])
_assert_true(all_finite, "%s: all frame times finite" % label)
_assert_true(avg_ms < AVG_BUDGET_MS,
"%s: avg %.2f ms under %.0f ms budget" % [label, avg_ms, AVG_BUDGET_MS])
_assert_true(max_ms < MAX_BUDGET_MS,
"%s: peak %.2f ms under %.0f ms budget" % [label, max_ms, MAX_BUDGET_MS])
func _find_hud(n: Node) -> Node:
if n is CanvasLayer and n.has_method(&"_show_game_over"):
return n
for c: Node in n.get_children():
var r := _find_hud(c)
if r != null:
return r
return null
func _assert_true(condition: bool, desc: String) -> void:
if condition:
_passed += 1
print(" PASS: %s" % desc)
else:
_failed += 1
print(" FAIL: %s" % desc)
func _finish() -> void:
print("")
print("=".repeat(60))
print("PERFORMANCE TEST: %d passed, %d failed" % [_passed, _failed])
print("=".repeat(60))
quit(1 if _failed > 0 else 0)