- AudioFeedback.start_crowd_bed() builds a 4 s looping AudioStreamWAV at runtime (one-pole filtered noise plus a 68/103 Hz drone, seeded) and set_crowd_level() maps crowd mood 0..1 to -34..-12 dB, so the street gets louder as it gets agitated or panics. The bed starts with the shift and stops at shift end. - play_radio_open()/play_radio_close() push short filtered-noise bursts; wired to opening the radio panel, submitting a report, cancelling, and closing the panel. - Still asset-free and headless-safe: with a dummy audio server the calls no-op. _free_player() returns null before _ready and callers handle it. Tests/docs - Headless suite 127 -> 134 (test_audio_feedback.gd). - New smoke_audio.gd: bed playing, level follows mood events, radio crackles once per open/close, bed stops at shift end. Verified windowed (real audio server). - README, TODO section moved to DONE, MEMORY. Version 0.2.8.
86 lines
2.4 KiB
GDScript
86 lines
2.4 KiB
GDScript
extends SceneTree
|
|
## Audio smoke: starts a shift, verifies the crowd bed is playing and follows mood
|
|
## events, and that opening/closing the radio triggers crackle cues.
|
|
##
|
|
## Usage:
|
|
## godot --headless --path . --script res://src/test/smoke_audio.gd
|
|
## (Headless uses a dummy audio server; the calls must still be safe.)
|
|
|
|
const MAX_FRAMES := 900
|
|
|
|
var frame := 0
|
|
var gm: Node = null
|
|
var started := false
|
|
var step := 0
|
|
var failures: Array[String] = []
|
|
|
|
func _initialize() -> void:
|
|
root.size = Vector2i(1280, 720)
|
|
root.add_child(load("res://scenes/main.tscn").instantiate())
|
|
|
|
func _process(_delta: float) -> bool:
|
|
frame += 1
|
|
if gm == null:
|
|
gm = root.find_child("GameManager", true, false)
|
|
return false
|
|
if not started:
|
|
gm.call("_on_venue_selected", "hollywood_building")
|
|
started = true
|
|
return false
|
|
if frame > MAX_FRAMES:
|
|
_fail("timeout")
|
|
return true
|
|
if frame < 120:
|
|
return false
|
|
match step:
|
|
0:
|
|
_check(gm.audio.bed_playing, "the crowd bed is playing during the shift")
|
|
var before: float = gm.audio.crowd_level
|
|
gm.coordinator.crowd.apply_event("aggressive_behavior")
|
|
_check(gm.audio.crowd_level > before, "an aggressive event raises the bed level")
|
|
gm.coordinator.crowd.apply_event("emergency_responder")
|
|
_check(gm.audio.crowd_level > before, "the responder keeps it up")
|
|
step = 1
|
|
1:
|
|
if gm.npcs.is_empty():
|
|
return false
|
|
gm.call("_on_guard_target", gm.npcs[0])
|
|
var crackles: int = gm.audio.radio_crackles
|
|
gm.call("_open_radio", false)
|
|
_check(gm.audio.radio_crackles == crackles + 1, "opening the radio crackles once")
|
|
gm.call("_on_radio_submitted", {
|
|
"npc_id": gm.current_incident_npc_id,
|
|
"location": "entrance",
|
|
"description": "tourist",
|
|
"behavior": "standing",
|
|
"weapons_seen": false,
|
|
"danger_level": 1,
|
|
}, false)
|
|
_check(gm.audio.radio_crackles == crackles + 2, "closing the radio crackles once")
|
|
gm.coordinator.call("_finish_shift")
|
|
_check(not gm.audio.bed_playing, "the bed stops when the shift ends")
|
|
step = 2
|
|
2:
|
|
_finish()
|
|
return true
|
|
return false
|
|
|
|
func _check(condition: bool, message: String) -> void:
|
|
if condition:
|
|
print("ok: " + message)
|
|
else:
|
|
_fail(message)
|
|
|
|
func _fail(message: String) -> void:
|
|
failures.append(message)
|
|
_finish()
|
|
|
|
func _finish() -> void:
|
|
if failures.is_empty():
|
|
print("SMOKE OK: crowd bed and radio crackle")
|
|
quit(0)
|
|
else:
|
|
for message in failures:
|
|
printerr("SMOKE FAIL: " + message)
|
|
quit(1)
|