extends SceneTree func _initialize() -> void: call_deferred("run_test") func run_test() -> void: var world := Node3D.new() root.add_child(world) var room := Node3D.new() room.set_script(load("res://scripts/room.gd")) world.add_child(room) var light := OmniLight3D.new() light.position = Vector3(0, 4, 0) light.omni_range = 24 light.light_energy = 4 world.add_child(light) var player := CharacterBody3D.new() player.set_script(load("res://scripts/player.gd")) player.position.y = 0.1 world.add_child(player) Input.mouse_mode = Input.MOUSE_MODE_VISIBLE await create_timer(0.2).timeout player.camera.rotation.x = 0.12 var aim: Vector3 = player.camera.rotation player.take_damage(12) assert(player.hp == 88) assert(player.damage_feedback.blood_left > 0) assert(player.damage_feedback.splats.size() == 7) await create_timer(0.06).timeout assert(player.damage_pivot.position.length() > 0.0001) assert(player.camera.rotation.is_equal_approx(aim)) if DisplayServer.get_name() != "headless": await RenderingServer.frame_post_draw root.get_texture().get_image().save_png("/tmp/zombie-player-hit.png") # Multiple hits reuse a bounded pool; no camera offset accumulates. player.take_damage(12) player.take_damage(12) assert(player.damage_feedback.intensity <= 1) assert(player.damage_feedback.splats.size() == 7) await create_timer(1.35).timeout assert(player.damage_pivot.transform.is_equal_approx(Transform3D.IDENTITY)) assert(player.camera.rotation.is_equal_approx(aim)) assert(player.damage_feedback.blood_left == 0) player.take_damage(12) player.take_damage(1000) assert(not player.damage_feedback.active) assert(player.damage_pivot.transform.is_equal_approx(Transform3D.IDENTITY)) await create_timer(3).timeout assert(player.camera.position.y < 0.25) assert(player.death_overlay.color.a > 0.99) print("PASS: damage shake, screen blood, aim preserved, repeated hits bounded, camera reset, death fade intact") world.queue_free() await process_frame quit()