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)

	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)
