extends RefCounted ## Audio feedback: a looping crowd bed that follows the crowd mood, and radio ## crackle on open/close. All procedural, no audio assets, headless-safe. const AudioFeedback := preload("res://src/game/audio_feedback.gd") const CrowdSystem := preload("res://src/core/crowd_system.gd") func test_bed_volume_follows_mood() -> bool: var quiet: float = AudioFeedback._level_to_db(0.0) var loud: float = AudioFeedback._level_to_db(1.0) var mid: float = AudioFeedback._level_to_db(0.5) var a := TestUtils.check(quiet < -30.0, "a calm street keeps the bed quiet") var b := TestUtils.check(loud > quiet, "panic raises the bed") var c := TestUtils.check(mid > quiet and mid < loud, "the rise is gradual") return a and b and c func test_crowd_level_is_clamped() -> bool: var audio: Node = AudioFeedback.new() audio.set_crowd_level(-1.0) var a := TestUtils.check_eq(audio.crowd_level, 0.0, "negative mood clamps to 0") audio.set_crowd_level(3.0) var b := TestUtils.check_eq(audio.crowd_level, 1.0, "over-unity mood clamps to 1") audio.free() return a and b func test_crowd_stream_loops() -> bool: var audio: Node = AudioFeedback.new() var stream: AudioStreamWAV = audio._build_crowd_stream() var a := TestUtils.check(stream != null, "a crowd stream is generated") var b := TestUtils.check_eq(stream.loop_mode, AudioStreamWAV.LOOP_FORWARD, "the bed loops") var c := TestUtils.check(stream.data.size() > 1000, "the bed has audio data") var d := TestUtils.check(stream.data.size() == stream.loop_end * 2, "loop covers the whole buffer") audio.free() return a and b and c and d func test_crowd_bed_is_headless_safe() -> bool: # No tree, no audio server: none of these may crash. var audio: Node = AudioFeedback.new() audio.start_crowd_bed() var a := TestUtils.check(audio.bed_playing, "start marks the bed as playing") audio.set_crowd_level(0.78) var b := TestUtils.check(is_equal_approx(audio.crowd_level, 0.78), "level is stored") audio.stop_crowd_bed() var c := TestUtils.check(not audio.bed_playing, "stop clears the playing flag") audio.free() return a and b and c func test_radio_crackle_methods() -> bool: var audio: Node = AudioFeedback.new() audio.play_radio_open() audio.play_radio_close() var a := TestUtils.check_eq(audio.radio_crackles, 2, "open and close each count a crackle") var b := TestUtils.check(audio._free_player() == null, "no players exist before _ready, and that is safe") audio.free() return a and b func test_game_manager_wires_audio() -> bool: var src := FileAccess.get_file_as_string("res://src/game/game_manager.gd") var a := TestUtils.check(src.contains("audio.start_crowd_bed()"), "the bed starts with the shift") var b := TestUtils.check(src.contains("audio.set_crowd_level(float(payload.get("), "crowd events drive the bed") var c := TestUtils.check(src.contains("audio.stop_crowd_bed()"), "the bed stops at shift end") var d := TestUtils.check(src.contains("audio.play_radio_open()"), "opening the radio crackles") var e := TestUtils.check(src.contains("audio.play_radio_close()"), "closing the radio crackles") return a and b and c and d and e func test_mood_events_reach_the_bed() -> bool: ## The bed level is fed from the same payload the crowd reactions use. var crowd := CrowdSystem.new() var seen: Array = [] crowd.changed.connect(func(_p): pass) crowd.apply_event("aggressive_behavior") seen.append(crowd.mood) crowd.apply_event("emergency_responder") seen.append(crowd.mood) var a := TestUtils.check(seen[1] > seen[0], "mood rises with a responder arrival") crowd = null return a