extends SceneTree const PairedBGMType = preload("res://scripts/ui/paired_bgm.gd") const FeedbackType = preload("res://scripts/ui/ui_audio_feedback.gd") const ShutdownType = preload("res://scripts/ui/ui_audio_owner_shutdown.gd") var _checks := 0 var _failures := 0 var _main: Control var _runtime: Variant var _audio: Variant var _cues: Array[StringName] = [] func _initialize() -> void: call_deferred("_run") func _run() -> void: print("P22: Paired BGM and actual Main controls") await _test_paired_bgm_candidate() await _test_actual_main_bgm() if _failures == 0: await _test_bgm_window_close() return push_error("FAIL: %d of %d checks failed" % [_failures, _checks]) quit(_failures) func _expect(condition: bool, label: String) -> void: _checks += 1 if condition: print(" ✓ %s" % label) return _failures += 1 push_error(" ✗ %s" % label) func _test_paired_bgm_candidate() -> void: var bgm := PairedBGMType.new() bgm.configure(0) root.add_child(bgm) bgm.set_process(false) _expect(bgm.startup_error.is_empty(), "paired BGM loads both compatible PCM arrangements") _expect(bgm._synchronized != null and bgm._synchronized.stream_count == 2, "one synchronized stream contains both arrangements") _expect(bgm._player.playing and bgm.playback_starts == 1, "both arrangements start through one player once") _expect(bgm.process_mode == Node.PROCESS_MODE_ALWAYS and bgm._player.process_mode == Node.PROCESS_MODE_ALWAYS, "music continues independently of SceneTree pause") for index: int in range(2): var stream := bgm._synchronized.get_sync_stream(index) as AudioStreamWAV _expect(stream.loop_mode == AudioStreamWAV.LOOP_FORWARD and stream.loop_begin == 0 and stream.loop_end == 1881600, "arrangement %d loops all exact PCM frames" % index) var runtime: Node = root.get_node("SimulationRuntime") var before := var_to_bytes(runtime.call("create_snapshot")) _write_full_state("user://crossfade-runtime-before.bin", before) var playback_id := bgm._player.get_stream_playback().get_instance_id() bgm.set_layer(1) _expect(bgm.mix == 0.0 and bgm.target_mix == 1.0, "layer change begins continuously from current mix") bgm._process(0.625) _expect(is_equal_approx(bgm.mix, 0.5), "crossfade reaches midpoint") var gains := Vector2(db_to_linear(bgm._synchronized.get_sync_stream_volume(0)), db_to_linear(bgm._synchronized.get_sync_stream_volume(1))) _expect(is_equal_approx(gains.length_squared(), 1.0), "midpoint follows equal power gain") bgm.set_layer(0) _expect(is_equal_approx(bgm.mix, 0.5), "mid-fade reversal retains current mix") bgm._process(0.625) _expect(is_equal_approx(bgm.mix, 0.25), "reversal advances from retained mix") bgm._process(0.625) _expect(bgm.mix == 0.0, "reversal arrives exactly at front arrangement") bgm.set_layer(1) bgm._process(1.25) _expect(bgm.mix == 1.0, "complete transition arrives exactly at back arrangement") bgm.set_layer(1) bgm._process(-1.0) bgm._process(NAN) _expect(bgm.mix == 1.0 and bgm.playback_starts == 1 and bgm._player.get_stream_playback().get_instance_id() == playback_id, "repeat and invalid delta do not restart playback") _expect(before == var_to_bytes(runtime.call("create_snapshot")), "crossfade operations preserve full typed Runtime snapshot") _write_full_state("user://crossfade-runtime-after.bin", var_to_bytes(runtime.call("create_snapshot"))) var feedback := FeedbackType.new() feedback.configure(runtime, "", false) root.add_child(feedback) var old_settings: Dictionary = feedback.settings.duplicate(true) _expect(feedback.update_settings({&"master_volume": 0.3, &"ui_volume": 0.5, &"muted": false}), "existing master volume setting accepts change") _expect(bgm._player.bus == &"Master" and is_equal_approx(db_to_linear(AudioServer.get_bus_volume_db(0)), 0.3), "BGM routes through existing master volume") _expect(feedback.update_settings({&"master_volume": 0.3, &"ui_volume": 0.5, &"muted": true}) and AudioServer.is_bus_mute(0), "existing mute setting controls music master bus") _expect(bgm._player.playing and bgm.playback_starts == 1, "muting retains shared music playback clock") _expect(feedback.update_settings(old_settings), "test restores prior master settings without writing config") feedback.queue_free() bgm._player.seek(42.55) var loop_started := Time.get_ticks_usec() var observed_end := false var loop_observations: Array = [] while Time.get_ticks_usec() - loop_started < 2000000: var position := bgm._player.get_playback_position() loop_observations.append({&"elapsed_us": Time.get_ticks_usec() - loop_started, &"position": position}) observed_end = observed_end or position > 40.0 if observed_end and position < 1.0: break await process_frame var after_loop_position := bgm._player.get_playback_position() print("BGM_LOOP_OBSERVATION " + JSON.stringify({&"playing": bgm._player.playing, &"position": after_loop_position, &"starts": bgm.playback_starts, &"driver": AudioServer.get_driver_name()})) _expect(bgm._player.playing and observed_end and after_loop_position < 1.0, "actual player wraps end of loop without restart") var decoder_reports := _test_native_paired_decoder(bgm) var targets := bgm.capture_owned_targets() _expect(targets.size() == 6, "captures owner player synchronized resource both WAVs and actual playback") bgm.queue_free() var started := Time.get_ticks_usec() await process_frame while Time.get_ticks_usec() - started < 500000 and _bgm_any_alive(targets): await process_frame _expect(not _bgm_any_alive(targets), "ordinary owner deletion releases all actual music resources within bounded wall time") var final_targets: Array = [] for row: Dictionary in targets: final_targets.append({&"kind": row[&"kind"], &"role": row[&"role"], &"instance_id": row[&"instance_id"], &"alive": row[&"weak"].get_ref() != null}) DirAccess.make_dir_recursive_absolute(OS.get_user_data_dir()) var file := FileAccess.open("user://paired-bgm-proof.json", FileAccess.WRITE) file.store_string(JSON.stringify({&"owned_engine_pid": OS.get_process_id(), &"checks": _checks, &"failures": _failures, &"native_decoder_reports": decoder_reports, &"loop_observations": loop_observations, &"audio_driver": AudioServer.get_driver_name(), &"post_loop_position_seconds": after_loop_position, &"waited_us": Time.get_ticks_usec() - started, &"maximum_wait_us": 500000, &"final_targets": final_targets, &"actual_human_hearing": false, &"normal_Main_accepted": false, &"WindowQuit_accepted": false, &"native_Windows_accepted": false}, "\t")) file.close() func _bgm_any_alive(targets: Array[Dictionary]) -> bool: for row: Dictionary in targets: if row[&"weak"].get_ref() != null: return true return false func _test_native_paired_decoder(bgm: Node) -> Array: var reports: Array = [] DirAccess.make_dir_recursive_absolute(OS.get_user_data_dir()) for position: float in [0.0, 0.5, 1.0]: bgm.mix = position bgm._apply_mix() var synchronized: AudioStreamPlayback = bgm._synchronized.instantiate_playback() var front: AudioStreamPlayback = bgm._synchronized.get_sync_stream(0).instantiate_playback() var back: AudioStreamPlayback = bgm._synchronized.get_sync_stream(1).instantiate_playback() for playback: AudioStreamPlayback in [synchronized, front, back]: playback.start(42.55) var mixed: PackedVector2Array = synchronized.mix_audio(1.0, 12288) var front_pcm: PackedVector2Array = front.mix_audio(1.0, 12288) var back_pcm: PackedVector2Array = back.mix_audio(1.0, 12288) var front_gain := db_to_linear(bgm._synchronized.get_sync_stream_volume(0)) var back_gain := db_to_linear(bgm._synchronized.get_sync_stream_volume(1)) var maximum_error := 0.0 var energy := 0.0 for index: int in range(mixed.size()): maximum_error = maxf(maximum_error, (mixed[index] - front_pcm[index] * front_gain - back_pcm[index] * back_gain).length()) energy += mixed[index].length_squared() _expect(mixed.size() == 12288 and front_pcm.size() == 12288 and back_pcm.size() == 12288, "native mixer returns full frames across loop at mix %.1f" % position) _expect(maximum_error < 0.000002 and energy > 0.0001, "actual paired mix matches both independently decoded PCM streams at mix %.1f" % position) _expect(synchronized.is_playing() and synchronized.get_playback_position() < 1.0, "native synchronized decoder loops both arrangements at mix %.1f" % position) _expect(absf(front.get_playback_position() - back.get_playback_position()) < 0.000001, "both independent arrangement decoders retain same sample position") var names := ["mixed", "front", "back"] var buffers := [mixed, front_pcm, back_pcm] var files: Array = [] for index: int in range(3): var path := "user://paired-%d-%s.f32" % [int(position * 10), names[index]] var file := FileAccess.open(path, FileAccess.WRITE) file.store_buffer(buffers[index].to_byte_array()) file.close() files.append(path) reports.append({&"mix": position, &"frames": mixed.size(), &"maximum_error": maximum_error, &"energy": energy, &"front_gain": front_gain, &"back_gain": back_gain, &"loop_count": synchronized.get_loop_count(), &"position": synchronized.get_playback_position(), &"files": files}) for playback: AudioStreamPlayback in [synchronized, front, back]: playback.stop() return reports func _test_actual_main_bgm() -> void: _runtime = root.get_node("SimulationRuntime") var clock: Variant = root.get_node("GameClock") var state: Variant = root.get_node("GameState") clock.reset(1, 6, 0) state.reset() _runtime.initialize(clock, state, 822252) clock.set_paused(true) root.size = Vector2i(1440, 810) _main = load("res://scenes/main/main.tscn").instantiate() as Control _main.set("audio_settings_path", "user://tests/main_audio_%d/settings.cfg" % OS.get_process_id()) _main.set("manual_save_path", "user://tests/main_audio_%d/manual" % OS.get_process_id()) root.add_child(_main) _runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) await process_frame await process_frame var music: Variant = _main.get("_paired_bgm") _expect(music != null and music.startup_error.is_empty() and music._player.playing, "actual Main owns playing paired BGM") music.set_process(false) var music_playback_id: int = music._player.get_stream_playback().get_instance_id() (_main.get("back_button") as Button).pressed.emit() _expect(music.target_mix == 1.0 and state.active_layer == 1, "actual back-layer button targets back music") music._process(1.25) (_main.find_child("ManualSaveButton", true, false) as Button).pressed.emit() _expect(FileAccess.file_exists(String(_main.get("manual_save_path")) + "/world.bin"), "normal Main manual-save writes actual formal world") (_main.get("front_button") as Button).pressed.emit() music._process(1.25) _expect(music.mix == 0.0 and state.active_layer == 0, "actual front-layer button targets front music") (_main.find_child("SaveLoadMenu", true, false) as MenuButton).get_popup().id_pressed.emit(0) _expect(state.active_layer == 1 and music.target_mix == 1.0, "actual Main load restores saved layer into music crossfade target") music._process(1.25) _expect(music.mix == 1.0 and music.playback_starts == 1 and music._player.get_stream_playback().get_instance_id() == music_playback_id, "save/load and layer controls preserve one uninterrupted music clock") _audio = _main.get("_audio_feedback") _expect(_audio != null, "normal Main owns one presentation audio service") _audio.playback_enabled = false _audio.cue_requested.connect(_record_cue) var button := _main.call("_button", "audio binding test", _noop) as Button _main.call("_apply_button_style", button) _main.call("_apply_button_style", button) button.pressed.emit() _expect(_cues == [&"click"], "normal styled button plays one click after repeated style registration") _cues.clear() button.disabled = true button.pressed.emit() _expect(_cues.is_empty(), "disabled button never requests audio even through emitted signal") button.set_pressed_no_signal(true) _expect(_cues.is_empty(), "programmatic selection is silent") button.free() _main.call("_set_status", "queued text", true) _main.call("_set_status", "rejected text", false) _main.call("_set_status", "quiet text", true, true) _main.call("_refresh_dashboard") _expect(_cues.is_empty(), "status and repeated dashboard refresh are not sound dispatchers") var key := InputEventKey.new() key.keycode = KEY_I key.pressed = true _main.call("_unhandled_key_input", key) _expect(_cues == [&"click"], "normal keyboard shortcut requests one click") _cues.clear() key.echo = true _main.call("_unhandled_key_input", key) _expect(_cues.is_empty(), "keyboard repeat echo is silent") var hire := (_main.get("staff_candidate_buttons") as Dictionary).get(&"staff_akane") as Button _expect(hire != null and not hire.disabled, "normal hiring button is actionable") if hire != null and not hire.disabled: hire.pressed.emit() _expect(_cues.count(&"success") == 1 and _cues.count(&"click") == 1, "real paused hire button has one completion and one click") _expect(_runtime.staff_service.get_staff(&"staff_akane") != null, "normal hire command reaches actual Runtime state") _cues.clear() (_main.find_child("AudioSettingsButton", true, false) as Button).pressed.emit() var dialog: Variant = _main.get("_audio_settings_dialog") _expect(dialog.visible and _cues == [&"click"], "normal audio settings button opens actual controls") _cues.clear() dialog.master_slider.value = 40 _expect(is_equal_approx(db_to_linear(AudioServer.get_bus_volume_db(0)), 0.4) and music._player.bus == &"Master", "actual Main master slider controls music bus") dialog.ui_slider.value = 25 _expect(is_equal_approx(float(_audio.settings[&"ui_volume"]), 0.25), "normal slider changes AudioServer-linked UI volume") dialog.mute_button.button_pressed = true _expect(_audio.settings[&"muted"] and AudioServer.is_bus_mute(AudioServer.get_bus_index(&"Master")), "normal mute control reaches Master bus") _expect(music._player.playing and music.playback_starts == 1, "actual Main mute retains music sample clock") dialog.call("_save_settings") _expect(FileAccess.file_exists(_main.get("audio_settings_path")), "normal settings save writes isolated presentation preference") _audio.update_settings(UIAudioFeedback.DEFAULT_SETTINGS) var before: Dictionary = _runtime.create_snapshot() _main.call("_on_save_operation_completed", {&"ok": false, &"operation": &"load"}) _expect(var_to_bytes(before) == var_to_bytes(_runtime.create_snapshot()), "failed load audio hook leaves Runtime exact bytes") _expect(_cues.is_empty(), "settings save and failed load do not fabricate command sounds") var unchanged_before := var_to_bytes(_runtime.create_snapshot()) _write_full_state("user://main-delete-runtime-before.bin", unchanged_before) var targets: Array[Dictionary] = music.capture_owned_targets() targets.append_array(ShutdownType._capture_targets(_audio)) targets.append({&"kind": &"main_owner", &"role": &"main", &"weak": weakref(_main), &"instance_id": _main.get_instance_id()}) _main.queue_free() var started := Time.get_ticks_usec() await process_frame while Time.get_ticks_usec() - started < 500000 and _bgm_any_alive(targets): await process_frame _expect(not _bgm_any_alive(targets), "actual Main ordinary deletion releases music and cue resources within bounded wall time") _expect(unchanged_before == var_to_bytes(_runtime.create_snapshot()), "actual Main deletion preserves full paused Runtime snapshot") _write_full_state("user://main-delete-runtime-after.bin", var_to_bytes(_runtime.create_snapshot())) var rows: Array = [] for row: Dictionary in targets: rows.append({&"kind": row[&"kind"], &"role": row[&"role"], &"instance_id": row[&"instance_id"], &"alive": row[&"weak"].get_ref() != null}) var file := FileAccess.open("user://paired-bgm-main-proof.json", FileAccess.WRITE) file.store_string(JSON.stringify({&"owned_engine_pid": OS.get_process_id(), &"checks": _checks, &"failures": _failures, &"audio_driver": AudioServer.get_driver_name(), &"final_targets": rows, &"maximum_wait_us": 500000, &"waited_us": Time.get_ticks_usec() - started, &"full_Runtime_unchanged_during_delete": unchanged_before == var_to_bytes(_runtime.create_snapshot()), &"normal_OS_input_accepted": false, &"actual_human_hearing": false, &"WindowQuit_accepted": false, &"native_Windows_accepted": false}, "\t")) file.close() func _noop() -> void: pass func _record_cue(role: StringName, _correlation: Dictionary) -> void: _cues.append(role) var _close_main: Control var _window_before := PackedByteArray() func _write_full_state(path: String, bytes: PackedByteArray) -> void: DirAccess.make_dir_recursive_absolute(OS.get_user_data_dir()) var file := FileAccess.open(path, FileAccess.WRITE) if file == null: push_error("Cannot write owned typed BGM evidence: " + path) return file.store_buffer(bytes) file.close() func _test_bgm_window_close() -> void: _close_main = load("res://scenes/main/main.tscn").instantiate() as Control _close_main.set("audio_settings_path", "user://tests/window_bgm/settings.cfg") _close_main.set("manual_save_path", "user://tests/window_bgm/manual") root.add_child(_close_main) await process_frame await process_frame var music: Variant = _close_main.get("_paired_bgm") _expect(music.mix == 1.0 and music._player.playing and music.playback_starts == 1, "new Main initializes BGM directly in restored back layer") _close_main.get("_audio_feedback").request_click() _window_before = var_to_bytes(_runtime.create_snapshot()) _write_full_state("user://window-runtime-before.bin", _window_before) _close_main.ui_audio_window_close_drained.connect(_on_bgm_close_drained) root.close_requested.emit() func _on_bgm_close_drained(report: Dictionary) -> void: var rows: Array = report[&"initial_targets"] var counts := {} for row: Dictionary in rows: var kind: String = String(row[&"kind"]) counts[kind] = int(counts.get(kind, 0)) + 1 _expect(bool(report[&"passed"]) and bool(report[&"all_owned_targets_released"]), "real close coordinator drains music and cue ownership before quit") _expect(counts.get("bgm_owner", 0) == 1 and counts.get("bgm_player", 0) == 1 and counts.get("bgm_synchronized", 0) == 1 and counts.get("bgm_stream", 0) == 2 and counts.get("bgm_playback", 0) == 1, "window close captures all six actual music owners and resources") _expect(counts.get("stream", 0) == 4 and counts.get("packet_sequence", 0) == 4 and counts.get("playback", 0) >= 1, "window close retains all existing cue resources and live click playback") _expect(bool(report[&"clock_process_held_stopped"]) and bool(report[&"window_input_held_disabled"]) and not bool(report[&"settings_mutated"]) and not bool(report[&"shared_bus_deleted"]), "close retains existing input clock settings and shared bus contract") var after := var_to_bytes(_runtime.create_snapshot()) _write_full_state("user://window-runtime-after.bin", after) _expect(_window_before == after, "actual close keeps full typed Runtime bytes unchanged") _write_full_state("user://paired-window-proof.json", JSON.stringify({&"owned_engine_pid": OS.get_process_id(), &"checks": _checks, &"failures": _failures, &"report": report, &"normal_OS_input_accepted": false, &"actual_human_hearing": false, &"native_Windows_accepted": false}, "\t").to_utf8_buffer()) if _failures == 0: print("PASS: %d checks" % _checks) else: push_error("FAIL: %d of %d checks failed" % [_failures, _checks])