extends Node var failures := 0 func check(ok: bool, message: String) -> void: if not ok: failures += 1 push_error(message) func count_numbers() -> int: var count := 0 for child: Node in get_children(): if child.get_script() == load("res://scripts/combat/damage_number.gd"): count += 1 return count func _ready() -> void: GameState.save_locked = true call_deferred("run") func run() -> void: var hero = load("res://scenes/player/player.tscn").instantiate() add_child(hero) hero.set_physics_process(false) var enemy = load("res://scenes/enemies/enemy.tscn").instantiate() add_child(enemy) enemy.set_physics_process(false) enemy.active = true # The squirrel's zero-damage lunge must not immunize the hero against its # real body contact, including when both land during the same frame. enemy.setup("crazy_squirrel") enemy.target = hero hero.health = 100.0 hero.invulnerability = 0.0 var health_events: Array = [] hero.health_changed.connect(func(current: float, _maximum: float): health_events.append(current)) hero.take_damage(0.0) check(is_zero_approx(hero.invulnerability) and health_events.is_empty(), "zero damage grants no immunity or hurt event") enemy._perform_direct_hit(Vector2.RIGHT) check(hero.health < 100.0, "squirrel body contact still hurts after its zero-damage lunge") var wounded: float = hero.health var contacts := int(GameState.get_entity_counter("enemies", "crazy_squirrel", "times_damaged_player")) enemy._perform_direct_hit(Vector2.RIGHT) check(hero.health == wounded, "real hurt immunity still blocks subsequent contact") check(GameState.get_entity_counter("enemies", "crazy_squirrel", "times_damaged_player") == contacts, "a refused contact does not count as damage") hero.invulnerability = 0.0 for actor in [hero, enemy]: actor.health = 100.0 actor.attack_invulnerable = false var before := count_numbers() actor.take_damage(0.0, Vector2.RIGHT, 100.0) check(count_numbers() == before, "intentional zero hit emits no number") check(actor.health == 100.0 and actor.knockback_velocity.x > 0.0, "zero hit preserves health and knockback") if actor == hero: hero.invulnerability = 0.0 actor.attack_invulnerable = true before = count_numbers() actor.take_damage(0.0) check(count_numbers() == before, "invulnerability does not display an intentional zero") actor.take_damage(5.0) check(count_numbers() == before + 1, "blocked positive damage retains its feedback") actor.attack_invulnerable = false before = count_numbers() actor.take_damage(2.0) check(count_numbers() == before + 1, "positive damage still displays") check(hero._roll_damage(0.0) == 0.0, "zero authored hero damage stays zero") check(hero._roll_damage(0.1) >= 1.0, "positive damage retains existing minimum") AudioDirector.stop_all() await get_tree().process_frame print("ZERO HIT NUMBERS: ", "PASSED" if failures == 0 else "FAILED") get_tree().quit(0 if failures == 0 else 1)