Files
aetherbound-guild/runtime/tests/fg2/audio_router_test.gd
T

115 lines
5.4 KiB
GDScript

extends SceneTree
const Router = preload("res://campaign/fg2_audio_router.gd")
var failures: Array[String] = []
var host: Node
var router
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
host = Node.new()
root.add_child(host)
router = Router.new()
router.attach(host)
router.reset(false, false, false)
await settle(3)
verify_bed_and_routing()
verify_pool_priority_and_resources()
verify_mute_and_cleanup()
host.queue_free()
await settle(60)
if failures.is_empty():
print("ABG_FG2_AUDIO_ROUTER_OK assets=9 buses=Music+SFX voices=6 low_power=3 dedupe=true per_source_cooldown=0.09 priority_replacement=true duck=-6db mute_safe=true cleanup=true")
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func verify_bed_and_routing() -> void:
var state: Dictionary = router.snapshot()
expect(bool(state.valid), "manifest and streams are valid")
expect(int(state.voice_count) == 6 and int(state.active_voice_limit) == 6, "normal mode owns six voices")
expect(bool(state.music_playing) and bool(state.ambience_playing), "unmuted music and ambience loops play")
expect(String(state.music_bus) == "Music" and String(state.ambience_bus) == "Music", "music and ambience use Music bus")
expect(router.music_player.stream.loop_mode == AudioStreamWAV.LOOP_FORWARD, "music stream loops forward")
expect(router.ambience_player.stream.loop_mode == AudioStreamWAV.LOOP_FORWARD, "ambience stream loops forward")
expect(router.music_player.stream.loop_end > router.music_player.stream.loop_begin, "music loop owns a nonempty sample range")
expect(router.ambience_player.stream.loop_end > router.ambience_player.stream.loop_begin, "ambience loop owns a nonempty sample range")
for bus_name in state.sfx_buses:
expect(String(bus_name) == "SFX", "every pooled effect voice uses SFX bus")
var route_event := event("receipt-route-1", "route_committed", "route-low", {})
var decisions: Array[Dictionary] = router.consume([route_event])
expect(String(decisions[0].result) == "played", "successful route receipt plays commit cue")
state = router.snapshot()
expect(int(state.asset_play_counts.get("FG2-SFX-COMMIT", 0)) == 1, "commit cue plays exactly once")
expect(float(state.duck_remaining) > 0.0 and is_equal_approx(float(state.music_volume_db), -18.0), "commit ducks market music by six decibels")
router.consume([route_event])
expect(int(router.snapshot().duplicate_suppressed) == 1, "duplicate event ID cannot replay")
var impact := event("damage-1", "damage", "scribe-1", {"raw": 8, "effective": 8, "guard_before": 0})
var repeated := event("damage-2", "damage", "scribe-1", {"raw": 8, "effective": 8, "guard_before": 0})
router.consume([impact, repeated])
state = router.snapshot()
expect(int(state.asset_play_counts.get("FG2-SFX-IMPACT", 0)) == 1, "first source impact plays")
expect(int(state.cooldown_suppressed) == 1, "same source impact is limited inside 90 ms")
router.advance(0.10)
var guarded := event("damage-3", "damage", "scribe-1", {"raw": 10, "effective": 4, "guard_before": 9})
router.consume([guarded])
expect(int(router.snapshot().asset_play_counts.get("FG2-SFX-GUARD", 0)) == 1, "raw/effective damage selects guard cue after cooldown")
func verify_pool_priority_and_resources() -> void:
router.reset(false, false, true)
for index in range(6):
router.consume([event("pool-%d" % index, "damage", "source-%d" % index, {"raw": 5, "effective": 5, "guard_before": 0})])
var state: Dictionary = router.snapshot()
expect(int(state.active_voices) == 6, "six routine impacts fill the normal pool")
router.consume([event("fallen-priority", "fallen", "enemy", {})])
state = router.snapshot()
expect(int(state.priority_replacements) == 1, "casualty replaces a lower-priority routine impact")
expect(int(state.asset_play_counts.get("FG2-SFX-CASUALTY", 0)) == 1, "priority replacement plays casualty")
router.configure(false, true)
state = router.snapshot()
expect(int(state.active_voice_limit) == 3 and int(state.active_voices) <= 3, "low-power mode caps active voices at three")
expect(is_equal_approx(float(router.ambience_player.volume_db), -20.0), "low-power ambience uses bounded lower gain")
func verify_mute_and_cleanup() -> void:
var recovery_before := int(router.snapshot().asset_play_counts.get("FG2-SFX-RECOVERY", 0))
router.configure(true, true)
router.consume([event("recovery-muted", "recovery", "system", {})])
var state: Dictionary = router.snapshot()
expect(not bool(state.music_playing) and not bool(state.ambience_playing), "mute stops both continuous beds")
expect(int(state.active_voices) == 0, "mute stops every active effect voice immediately")
expect(int(state.muted_suppressed) == 1, "muted event remains consumed without playback")
expect(int(state.asset_play_counts.get("FG2-SFX-RECOVERY", 0)) == recovery_before, "mute never starts a recovery voice")
router.cleanup()
state = router.snapshot()
expect(bool(state.cleaned_up) and not bool(state.music_playing) and int(state.active_voices) == 0, "cleanup releases active playback")
func event(event_id: String, kind: String, source_id: String, detail: Dictionary) -> Dictionary:
return {
"event_id": event_id,
"kind": kind,
"source_id": source_id,
"target_id": "target",
"detail": detail,
}
func settle(frames: int) -> void:
for _index in range(frames):
await process_frame
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("FG-2 audio router: %s" % message)