diff --git a/.github/workflows/test_ci_scripts.yml b/.github/workflows/test_ci_scripts.yml index b0ec7ab39..37f8dcf70 100644 --- a/.github/workflows/test_ci_scripts.yml +++ b/.github/workflows/test_ci_scripts.yml @@ -10,8 +10,8 @@ on: # yamllint disable-line rule:truthy required: true jobs: - test-scripts: - name: "Test Deployment Scripts" + test-python: + name: "Run CI Injection Tests" runs-on: "ubuntu-latest" timeout-minutes: 5 steps: @@ -32,6 +32,14 @@ jobs: run: | pytest tests/ci/ -v -s + verify-godot: + name: "Test Godot Asset Infrastructure and Signature Verification" + runs-on: "ubuntu-latest" + timeout-minutes: 10 # Generous timeout to download the 1.2 GB template file + steps: + - name: "Checkout code" + uses: "actions/checkout@v7" + - name: "Test Godot Asset Infrastructure and Signature Verification" run: | chmod +x .github/scripts/verify_godot.sh diff --git a/Dockerfile b/Dockerfile index e969fe4d0..809a4aed0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,23 +39,23 @@ RUN pip install pytest-html pytest-timeout # Install markdownlint-cli2 via npm (Node.js tool) RUN npm install -g markdownlint-cli2@0.12.1 -# Download and verify Godot v4.6.3 binary using the official SHA256SUMS file -RUN wget -q https://github.com/godotengine/godot/releases/download/4.6.3-stable/SHA256SUMS.txt \ +# Download and verify Godot v4.6.3 binary using the official GitHub SHA512-SUMS file +RUN wget -q https://github.com/godotengine/godot/releases/download/4.6.3-stable/SHA512-SUMS.txt \ && wget -q https://github.com/godotengine/godot/releases/download/4.6.3-stable/Godot_v4.6.3-stable_linux.x86_64.zip \ - && grep " Godot_v4.6.3-stable_linux.x86_64.zip$" SHA256SUMS.txt | sha256sum --check --status \ + && grep " Godot_v4.6.3-stable_linux.x86_64.zip$" SHA512-SUMS.txt | sha512sum --check --status \ && unzip Godot_v4.6.3-stable_linux.x86_64.zip \ && mv Godot_v4.6.3-stable_linux.x86_64 /usr/local/bin/godot \ && chmod +x /usr/local/bin/godot \ - && rm Godot_v4.6.3-stable_linux.x86_64.zip SHA256SUMS.txt + && rm Godot_v4.6.3-stable_linux.x86_64.zip SHA512-SUMS.txt -# Download, verify, and extract export templates using the official SHA256SUMS file -RUN wget -q https://github.com/godotengine/godot/releases/download/4.6.3-stable/SHA256SUMS.txt \ +# Download, verify, and extract export templates using the official GitHub SHA512-SUMS file +RUN wget -q https://github.com/godotengine/godot/releases/download/4.6.3-stable/SHA512-SUMS.txt \ && wget -q https://github.com/godotengine/godot/releases/download/4.6.3-stable/Godot_v4.6.3-stable_export_templates.tpz \ - && grep " Godot_v4.6.3-stable_export_templates.tpz$" SHA256SUMS.txt | sha256sum --check --status \ + && grep " Godot_v4.6.3-stable_export_templates.tpz$" SHA512-SUMS.txt | sha512sum --check --status \ && mkdir -p "${XDG_DATA_HOME}/godot/export_templates/${GODOT_VERSION}" \ && unzip Godot_v4.6.3-stable_export_templates.tpz -d /tmp/templates \ && mv /tmp/templates/templates/* "${XDG_DATA_HOME}/godot/export_templates/${GODOT_VERSION}/" \ - && rm -rf /tmp/templates Godot_v4.6.3-stable_export_templates.tpz SHA256SUMS.txt \ + && rm -rf /tmp/templates Godot_v4.6.3-stable_export_templates.tpz SHA512-SUMS.txt \ && chown -R godotuser:godotuser "${XDG_DATA_HOME}" # Install GDUnit4 v6.1.3 diff --git a/README.md b/README.md index bd89d00fe..2464e4483 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Project Start](https://img.shields.io/badge/Project_Start-Jul_28%2C_2025-blue?style=flat-square)](https://github.com/ikostan/SkyLockAssault/commit/c412eb3cea0bbc73f716a14afa678d21c7d4d0d0) [![Made with Godot](https://img.shields.io/badge/Made%20with-Godot-478CBF?style=flat-square&logo=godot%20engine&logoColor=white)](https://godotengine.org) -[![Godot](https://img.shields.io/badge/Godot-4.5-blue?style=flat-square&logo=godot-engine)](https://godotengine.org/) +[![Godot](https://img.shields.io/badge/Godot-4.6.3-blue?style=flat-square&logo=godot-engine)](https://godotengine.org/) [![GDScript](https://img.shields.io/badge/Language-GDScript-brightgreen?style=flat-square)](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/index.html) [![Itch.io](https://img.shields.io/badge/Deployment-Itch.io-purple?style=flat-square&logo=itch-dot-io)](https://itch.io/) ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/ikostan/SkyLockAssault?utm_source=oss&utm_medium=github&utm_campaign=ikostan%2FSkyLockAssault&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews) @@ -330,26 +330,26 @@ planned carryover work to future milestones. #### 🌟 New Features & Polish -* **UI Audio Polish:** Implemented a dedicated `slider.wav` sound effect +- **UI Audio Polish:** Implemented a dedicated `slider.wav` sound effect for audio menus that strictly triggers only upon manual player adjustments, cleanly decoupling it from programmatic `value_changed` signals. -* **Encrypted Local Storage:** Transitioned local save configurations +- **Encrypted Local Storage:** Transitioned local save configurations (`settings.cfg`) to use Godot's native encrypted `ConfigFile` methods. -* **Hardware-Bound Encryption:** Implemented an automatic encryption key +- **Hardware-Bound Encryption:** Implemented an automatic encryption key generation system (`Globals._get_encryption_key()`) that combines the OS name with a hardware-unique ID to securely lock local save data. #### πŸ› οΈ Architecture & Refactors -* **Centralized Configuration I/O:** Refactored core singletons (`Settings`, +- **Centralized Configuration I/O:** Refactored core singletons (`Settings`, `AudioManager`) to route all file operations through a unified `Globals.safe_load_config()` helper. -* **In-Memory Testing (Separation of Concerns):** Decoupled the config +- **In-Memory Testing (Separation of Concerns):** Decoupled the config parsing logic from the physical file I/O. Our GUT and GdUnit4 error-handling test suites now construct `ConfigFile` objects entirely in-memory, bypassing the disk and permanently eliminating fatal C++ crypto layer crashes in the CI/CD pipeline. -* **Codebase Audit & Technical Debt:** Conducted a comprehensive audit of the +- **Codebase Audit & Technical Debt:** Conducted a comprehensive audit of the core singletons. Verified a warning-free baseline by confirming no syntax errors exist in legacy `settings.gd` fallback calls. diff --git a/files/docs/milestones/18/PART_1_Encryption_key_generation_failure_in_web_export.md b/files/docs/milestones/18/PART_1_Encryption_key_generation_failure_in_web_export.md index e8edad0b4..9fe2413e8 100644 --- a/files/docs/milestones/18/PART_1_Encryption_key_generation_failure_in_web_export.md +++ b/files/docs/milestones/18/PART_1_Encryption_key_generation_failure_in_web_export.md @@ -129,6 +129,7 @@ Drove entire PR #607 (10+ commits) fixing web export encryption key crash (linked to #600). **Core changes:** + - **globals.gd**: Replaced ProjectSettings salt with CI-injected placeholder `"CI_INJECT_SALT_HERE"`, switched to `OS.has_feature("web")` check, implemented SHA-256 key generation, added diff --git a/files/docs/milestones/18/PART_2_CI_CD_Infrastructure_Updates_&_Caching_Hardening.md b/files/docs/milestones/18/PART_2_CI_CD_Infrastructure_Updates_&_Caching_Hardening.md index 2163f0031..5320069b6 100644 --- a/files/docs/milestones/18/PART_2_CI_CD_Infrastructure_Updates_&_Caching_Hardening.md +++ b/files/docs/milestones/18/PART_2_CI_CD_Infrastructure_Updates_&_Caching_Hardening.md @@ -62,12 +62,12 @@ issues. ## Contributions -### @ikostan’s Key Contributions to PR #654: +### @ikostan’s Key Contributions to PR #654 * Created the PR, self-assigned it, linked to issue #614, and provided full documentation. * **.github/workflows/deploy_to_itch.yml**: Upgraded Butler - to **15.27.0**, updated download URL, added centralized + to **15.27.0**, updated download URL, added centralized `BUTLER_VERSION` + `BUTLER_SHA256` env vars, implemented version-aware caching, conditional download on cache miss, SHA-256 integrity verification, and proper setup (`chmod`, @@ -80,7 +80,7 @@ issues. cache key) and addressed bot feedback while preserving project constraints. -All commits authored by @ikostan. This PR improves deployment +All commits authored by @ikostan. This PR improves deployment reliability, caching stability, and supply-chain security. --- @@ -140,5 +140,5 @@ since the pipeline requires linux-amd64). --- -**Full Contributors List (for GitHub recognition)**: +**Full Contributors List (for GitHub recognition)**: @ikostan, @sourcery-ai, @coderabbitai, @deepsource-io (or @deepsource-bot). diff --git a/files/docs/milestones/19/PART_1_test_plan_implementation_for_ui_bus_persistence.md b/files/docs/milestones/19/PART_1_test_plan_implementation_for_ui_bus_persistence.md index b577255a9..ec302156e 100644 --- a/files/docs/milestones/19/PART_1_test_plan_implementation_for_ui_bus_persistence.md +++ b/files/docs/milestones/19/PART_1_test_plan_implementation_for_ui_bus_persistence.md @@ -7,6 +7,7 @@ session for the **SkyLockAssault** project. --- ## πŸš€ Key Accomplishments + * **Epic Validation**: Completed 100% automated test suite coverage for the newly introduced core UI audio channels. * **Architecture Integrity**: All test files are fully type-hinted, @@ -40,7 +41,7 @@ and fallback handling under isolated testing conditions. ### 2. Interface Interlock Suite (`res://test/gut/test_ui_mute_logic.gd`) -Tracks component hierarchy instantiation, tree interactions, and +Tracks component hierarchy instantiation, tree interactions, and signal propagation paths. * **Signal Interlocks**: Monitors UI node checkbox inputs to prove that @@ -83,27 +84,27 @@ handling of config files in headless/CI environments. | Issue | Objective | Addressed | Explanation | |------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------| -| https://github.com/ikostan/SkyLockAssault/issues/499 | Add GUT unit tests to verify UI/Menu bus volume and mute persistence across save/load cycles, including AudioServer state restoration and safe defaults when configuration is missing or incomplete. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/499 | Add GUT unit tests to verify UI mute toggle signal propagation so that muting/unmuting the UI/Menu bus updates the corresponding AudioServer bus state and the volume slider’s editability. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/499 | Ensure all new tests use isolated temporary configuration/state and clean up any created files or AudioServer buses after execution. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/707 | Implement a unit test that verifies UI/Menu bus volume persistence using the save β†’ mutate state β†’ load β†’ verify restoration pattern, confirming AudioManager.get_volume(AudioConstants.BUS_SFX_MENU) returns the saved value and that the corresponding AudioServer bus volume reflects this value. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/707 | Use a dedicated test settings file for the audio configuration, ensuring any prior test file is removed before the test, initializing AudioManager to use this path, and cleaning up the test file during teardown. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/708 | Implement a unit test (in the UI audio persistence test suite) that verifies the Menu/UI bus mute state is saved to a dedicated config file, can be overwritten in memory, and is correctly restored from disk such that AudioManager.get_muted(AudioConstants.BUS_SFX_MENU) returns true after reload, with proper temporary file cleanup. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/709 | Implement a unit test in the UI audio persistence suite that: (1) saves a known Menu/UI volume value, (2) modifies the in-memory value, (3) reloads settings, (4) inspects the corresponding AudioServer bus, and (5) asserts that AudioManager’s volume is restored to the saved value and the AudioServer bus volume reflects that value. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/709 | Ensure the test explicitly verifies that `load_volumes()` restores configuration data and that the restored value is applied to the runtime audio system (AudioServer) via the AudioManager volume-application logic (e.g., `apply_all_volumes()`), keeping AudioManager and AudioServer synchronized. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/710 | Implement an automated test in `test_ui_mute_logic.gd` that verifies that reloading settings from disk accurately re-applies the restored mute state down to the AudioServer bus level, ensuring complete configuration-to-runtime synchronization. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/711 | Implement an automated test in `test_ui_mute_logic.gd` that instantiates the audio settings menu, simulates toggling the Menu/UI mute control, lets signal handlers execute, and verifies that the corresponding AudioServer bus mute state follows the toggle. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/711 | Within the same test, verify that the associated Menu/UI volume slider becomes non-editable when muted and becomes editable again when unmuted, staying in sync with the AudioServer mute state. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/712 | Add a unit test in the UI audio persistence test suite that loads audio settings from an empty or incomplete settings file and verifies that no errors occur and the Menu/UI bus volume and mute state fall back to their default configuration values. | βœ… | | +| | Add GUT unit tests to verify UI/Menu bus volume and mute persistence across save/load cycles, including AudioServer state restoration and safe defaults when configuration is missing or incomplete. | βœ… | | +| | Add GUT unit tests to verify UI mute toggle signal propagation so that muting/unmuting the UI/Menu bus updates the corresponding AudioServer bus state and the volume slider’s editability. | βœ… | | +| | Ensure all new tests use isolated temporary configuration/state and clean up any created files or AudioServer buses after execution. | βœ… | | +| | Implement a unit test that verifies UI/Menu bus volume persistence using the save β†’ mutate state β†’ load β†’ verify restoration pattern, confirming AudioManager.get_volume(AudioConstants.BUS_SFX_MENU) returns the saved value and that the corresponding AudioServer bus volume reflects this value. | βœ… | | +| | Use a dedicated test settings file for the audio configuration, ensuring any prior test file is removed before the test, initializing AudioManager to use this path, and cleaning up the test file during teardown. | βœ… | | +| | Implement a unit test (in the UI audio persistence test suite) that verifies the Menu/UI bus mute state is saved to a dedicated config file, can be overwritten in memory, and is correctly restored from disk such that AudioManager.get_muted(AudioConstants.BUS_SFX_MENU) returns true after reload, with proper temporary file cleanup. | βœ… | | +| | Implement a unit test in the UI audio persistence suite that: (1) saves a known Menu/UI volume value, (2) modifies the in-memory value, (3) reloads settings, (4) inspects the corresponding AudioServer bus, and (5) asserts that AudioManager’s volume is restored to the saved value and the AudioServer bus volume reflects that value. | βœ… | | +| | Ensure the test explicitly verifies that `load_volumes()` restores configuration data and that the restored value is applied to the runtime audio system (AudioServer) via the AudioManager volume-application logic (e.g., `apply_all_volumes()`), keeping AudioManager and AudioServer synchronized. | βœ… | | +| | Implement an automated test in `test_ui_mute_logic.gd` that verifies that reloading settings from disk accurately re-applies the restored mute state down to the AudioServer bus level, ensuring complete configuration-to-runtime synchronization. | βœ… | | +| | Implement an automated test in `test_ui_mute_logic.gd` that instantiates the audio settings menu, simulates toggling the Menu/UI mute control, lets signal handlers execute, and verifies that the corresponding AudioServer bus mute state follows the toggle. | βœ… | | +| | Within the same test, verify that the associated Menu/UI volume slider becomes non-editable when muted and becomes editable again when unmuted, staying in sync with the AudioServer mute state. | βœ… | | +| | Add a unit test in the UI audio persistence test suite that loads audio settings from an empty or incomplete settings file and verifies that no errors occur and the Menu/UI bus volume and mute state fall back to their default configuration values. | βœ… | | ### Possibly linked issues -- **#499**: The PR implements all specified GUT tests for UI/Menu audio +* **#499**: The PR implements all specified GUT tests for UI/Menu audio persistence, mute behavior, AudioServer sync, and defaults from the issue. -- **#N/A**: The PR’s `test_ui_menu_mute_persistence` implements the +* **#N/A**: The PR’s `test_ui_menu_mute_persistence` implements the described UI/Menu mute persistence test in the specified file. -- **#unknown**: The PR implements the specified UI/Menu volume persistence +* **#unknown**: The PR implements the specified UI/Menu volume persistence test, following the saveβ†’mutateβ†’loadβ†’verify pattern and file path. --- @@ -118,26 +119,26 @@ audio bus, including AudioServer synchronization and headless/CI safety. #### AI/Bot Contributors -- **@sourcery-ai** β€” Provided detailed PR summaries, Reviewer's Guide, +* **@sourcery-ai** β€” Provided detailed PR summaries, Reviewer's Guide, file-level analysis, assessment against issue #499 epic, pre-merge checks, and code quality feedback. -- **@coderabbitai** β€” Delivered structured walkthrough, release notes, test +* **@coderabbitai** β€” Delivered structured walkthrough, release notes, test coverage highlights, and review effort estimation. -- **@deepsource-io** β€” No visible review or comments on this PR. +* **@deepsource-io** β€” No visible review or comments on this PR. --- #### @ikostan’s Contributions -- Created the PR and authored all changes. -- Added two new GUT test suites: - - `test/gut/test_ui_audio_persistence.gd` β€” Tests volume/mute save/load +* Created the PR and authored all changes. +* Added two new GUT test suites: + * `test/gut/test_ui_audio_persistence.gd` β€” Tests volume/mute save/load cycles, AudioServer sync, config fallbacks, and safe teardown. - - `test/gut/test_ui_mute_logic.gd` β€” Tests UI mute signal propagation and + * `test/gut/test_ui_mute_logic.gd` β€” Tests UI mute signal propagation and slider behavior. -- Created detailed milestone documentation: `files/docs/milestones/19/PART_1_test_plan_implementation_for_ui_bus_persistence.md`. -- Ensured tests are headless-safe, use temporary configs, and follow +* Created detailed milestone documentation: `files/docs/milestones/19/PART_1_test_plan_implementation_for_ui_bus_persistence.md`. +* Ensured tests are headless-safe, use temporary configs, and follow project testing standards. -- Addressed bot feedback while preserving architectural intent. +* Addressed bot feedback while preserving architectural intent. ---- \ No newline at end of file +--- diff --git a/files/docs/milestones/19/PART_2_Add_UI_auto_mute_with_click_SFX_and_expand_audio_integrity_tests.md b/files/docs/milestones/19/PART_2_Add_UI_auto_mute_with_click_SFX_and_expand_audio_integrity_tests.md index 42697c10e..dc0c3dde8 100644 --- a/files/docs/milestones/19/PART_2_Add_UI_auto_mute_with_click_SFX_and_expand_audio_integrity_tests.md +++ b/files/docs/milestones/19/PART_2_Add_UI_auto_mute_with_click_SFX_and_expand_audio_integrity_tests.md @@ -62,10 +62,10 @@ the UI layer implements a strict input focus-gating pattern. Audio feedback streams (such as the `"check"` SFX asset) are structurally isolated within execution blocks that validate runtime UI focus states: -* **Slider Proxy Inputs:** Volume sliders check `active_slider.has_focus()` +- **Slider Proxy Inputs:** Volume sliders check `active_slider.has_focus()` during boundary threshold evaluations before permitting audio playback tracking. -* **Centralized Mute Toggles:** The underlying utility function +- **Centralized Mute Toggles:** The underlying utility function `_execute_bus_mute_toggle()` computes focus dynamically prior to state execution: @@ -91,8 +91,8 @@ tests and CI workflows. | Change | Details | Files | |------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Add symmetric auto-mute/unmute coupling to volume sliders with focus-gated click SFX for all audio buses. |
  • Extend _on_global_volume_changed to update sliders and apply near-zero AUTO_MUTE_VOLUME_THRESHOLD logic for auto-muting/unmuting per bus.
  • Gate click SFX playback on slider focus and introduce a master-specific token + hardware delay to let confirmation sounds play before forcing volume to 0 dB.
  • Use AudioManager.get_muted/set_muted and AudioServer bus indices to keep UI, manager state, and hardware volumes in sync when crossing the threshold.
| `scripts/ui/menus/audio_settings.gd`
`scenes/volume_controls/master_volume_control.tscn`
`scenes/volume_controls/music_volume_control.tscn`
`scenes/volume_controls/sfx_menu_volume_control.tscn`
`scenes/volume_controls/sfx_rotors_volume_control.tscn`
`scenes/volume_controls/sfx_volume_control.tscn`
`scenes/volume_controls/sfx_weapon_volume_control.tscn` | -| Centralize mute toggle handling and bus-to-UI lookups with focus-aware click SFX and deferred hardware mute. |
  • Introduce _get_slider_for_bus and _get_mute_button_for_bus helpers to map AudioConstants bus names to scene controls with assertion-backed default branches.
  • Replace per-bus _on_*_mute_toggled implementations with a unified async _execute_bus_mute_toggle that updates AudioManager, UI interactivity, applies volume to the bus, defers hardware mute by MUTE_HARDWARE_DELAY, and saves volumes.
  • Play the shared "check" SFX only when the interaction originates from focused buttons/sliders, keeping background sync operations silent.
| `scripts/ui/menus/audio_settings.gd`
`scripts/managers/audio_manager.gd`
`files/sounds/sfx/check.wav.import` | -| Harden centralized SFX playback to be safe against missing/corrupt resources and enforce pooling/LRU guarantees. |
  • Guard AudioManager.play_sfx with ResourceLoader.exists before load, log warnings, and cache missing SFX in _missing_sfx_cache to avoid repeated disk hits.
  • Differentiate between missing and unparseable streams in log messages while preserving the LRU cache semantics and fixed pool size.
  • Add GUT tests that validate pool overlap behavior, flooding/hijack behavior, LRU eviction, missing-asset suppression, and constant AudioManager child count.
| `scripts/managers/audio_manager.gd`
`test/gut/test_audio_sfx_centralization.gd`
`test/gut/test_audio_sfx_centralization.gd.uid` | +| Centralize mute toggle handling and bus-to-UI lookups with focus-aware click SFX and deferred hardware mute. |
  • Introduce _get_slider_for_bus and _get_mute_button_for_bus helpers to map AudioConstants bus names to scene controls with assertion-backed default branches.
  • Replace per-bus _on_*_mute_toggled implementations with a unified async_execute_bus_mute_toggle that updates AudioManager, UI interactivity, applies volume to the bus, defers hardware mute by MUTE_HARDWARE_DELAY, and saves volumes.
  • Play the shared "check" SFX only when the interaction originates from focused buttons/sliders, keeping background sync operations silent.
| `scripts/ui/menus/audio_settings.gd`
`scripts/managers/audio_manager.gd`
`files/sounds/sfx/check.wav.import` | +| Harden centralized SFX playback to be safe against missing/corrupt resources and enforce pooling/LRU guarantees. |
  • Guard AudioManager.play_sfx with ResourceLoader.exists before load, log warnings, and cache missing SFX in_missing_sfx_cache to avoid repeated disk hits.
  • Differentiate between missing and unparseable streams in log messages while preserving the LRU cache semantics and fixed pool size.
  • Add GUT tests that validate pool overlap behavior, flooding/hijack behavior, LRU eviction, missing-asset suppression, and constant AudioManager child count.
| `scripts/managers/audio_manager.gd`
`test/gut/test_audio_sfx_centralization.gd`
`test/gut/test_audio_sfx_centralization.gd.uid` | | Align audio UI tests with slider step behavior, encryption, and new mute-delay semantics. |
  • Update many GUT tests (audio reset, music/SFX/weapon/rotor controls, reset scenarios, preserve_other_sections, audio_sync_decoupling) to use step-aligned values (e.g., 0.495/0.693/0.99) or assert_almost_eq tolerances instead of exact 0.5/0.7/1.0 expectations.
  • Introduce waits (~0.2s) in tests that depend on the new MUTE_HARDWARE_DELAY to ensure hardware mute/save completes before assertions.
  • Ensure tests use Globals.set_test_encryption_key and encrypted ConfigFile save/load where appropriate to avoid engine errors with encrypted configs.
| `test/gut/test_audio_reset_button.gd`
`test/gut/test_master_volume_control_and_music.gd`
`test/gut/test_sfx_volume_control.gd`
`test/gut/test_sfx_weapon_volume_control.gd`
`test/gut/test_sfx_rotor_volume_control.gd`
`test/gut/test_audio_sync_decoupling.gd`
`test/gut/test_preserve_other_sections.gd`
`test/gdunit4/test_audio_settings.gd`
`test/gut/test_reset_scenarios.gd` | | Add comprehensive auto-mute, signal-decoupling, and integrity tests for audio and sprite resources. |
  • Add test_audio_settings_comprehensive.gd to cover focus-driven auto-mute threshold behavior per bus, automation-vs-manual SFX triggering, idempotent near-zero updates, and upward unmute transitions with safety timing derived from production constants.
  • Add test_audio_signal_decoupling.gd to assert that programmatic AudioManager changes (e.g., WebBridge/Playwright) do not emit UI click SFX via pooled players.
  • Introduce asset integrity suites that recursively scan configured sprite and audio directories, loading each resource, validating dimensions/durations, and checking codec-specific data/image payloads.
  • Add corresponding .uid files for new GUT tests.
| `test/gut/test_audio_settings_comprehensive.gd`
`test/gut/test_audio_settings_comprehensive.gd.uid`
`test/gut/test_audio_signal_decoupling.gd`
`test/gut/test_audio_signal_decoupling.gd.uid`
`test/gut/test_sprite_integrity.gd`
`test/gut/test_sprite_integrity.gd.uid`
`test/gut/test_audio_resource_integrity.gd`
`test/gut/test_audio_resource_integrity.gd.uid` | | Document the UI audio persistence/mute test plan and reviewer guidance. |
  • Extend the milestone 19 test-plan markdown with a detailed reviewer guide, file-level changes table, issue mapping table, and a summary of bot vs human contributions for the earlier UI bus persistence work.
  • Clarify which new GUT suites were added for UI audio persistence and mute logic, and how they satisfy specific GitHub issues (e.g., #499, #707–#712).
| `files/docs/milestones/19/PART_1_test_plan_implementation_for_ui_bus_persistence.md` | @@ -102,30 +102,30 @@ tests and CI workflows. | Issue | Objective | Addressed | Explanation | |------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------| -| https://github.com/ikostan/SkyLockAssault/issues/496 | Ensure the SFX_Menu audio bus is fully configured and treated as a first-class channel in code: BUS_SFX_MENU constant present and used, BUS_CONFIG registers menu_volume/menu_muted, and AudioManager exposes menu_volume/menu_muted properties wired through its volume/mute APIs. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/496 | Integrate the SFXMenu UI row into the AudioSettings scene using the sfx_menu_volume_control.tscn subscene (HSlider + mute CheckButton), structurally sequenced after the SFXRotors row. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/496 | Unify menu SFX UI behavior in audio_settings.gd with the other buses, including clean slider event processing, auto-mute/auto-unmute around a near-zero threshold, and centralized mute toggle handling for BUS_SFX_MENU. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/568 | Provide a dedicated `check.wav` audio feedback for all mute-related user interactions in the Audio Settings UI, wired through the centralized AudioManager SFX API and only triggered on explicit manual interactions (focused buttons/sliders), not on programmatic state changes. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/568 | Ensure the backend AudioManager exposes a robust, non-blocking SFX playback pipeline (pooled AudioStreamPlayers + LRU cache + missing-asset handling) that can safely serve `check.wav` and other UI sounds without stutter or engine errors in both normal and headless/CI environments. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/568 | Audit and prove mute signal decoupling via tests and documentation so that data-driven UI refreshes, initialization, WebBridge/Playwright sync loops, and other automated routines can mutate audio state in complete silence while manual hardware inputs still emit `check.wav` feedback. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/569 | Add the `check.wav` sound asset to the SFX asset library under `res://files/sounds/sfx/` and ensure it is imported and tracked by Godot/source control. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/569 | Ensure `check.wav` is correctly loadable and referenceable via the standard SFX pipeline (e.g., `AudioManager.play_sfx`), suitable for playback on the `AudioConstants.BUS_SFX_MENU` bus, and accessible to future AudioManager integrations. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/569 | Integrate the new mute-toggle confirmation SFX without modifying or replacing existing audio assets, and validate asset integrity/import behavior in Godot (editor/exports). | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/570 | Introduce a public centralized SFX playback API in AudioManager that UI components call via an identifier string (with safe default bus and optional overrides). | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/570 | Implement high-performance centralized SFX playback using a pre-allocated AudioStreamPlayer pool with hijacking under load, constant node count, and an LRU AudioStream cache plus failure cache to avoid I/O stutters and repeated disk lookups. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/570 | Wire UI/menu interactions to this centralized SFX system (e.g., click and auto-mute feedback using identifier-based SFX like "check"), ensuring correct bus routing, pitch/volume overrides support, and that programmatic updates do not trigger SFX. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/571 | Play the dedicated confirmation sound via AudioManager.play_sfx("check") whenever a user manually toggles any mute control (Master, Music, SFX, Weapon, Rotors, Menu) or triggers an intentional proxy unmute via slider interaction, with exactly one playback per physical interaction. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/571 | Ensure that audio feedback for mute/volume changes is strictly limited to direct, focused user interactions and remains completely silent for programmatic updates (initialization, config load/restore, WebBridge/Playwright sync, and other backend AudioManager changes), with no duplicate sounds per single human action. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/571 | Update audio_settings.gd and related tests/documentation to encode and verify the new behavior (focus-gated click SFX, auto-mute/auto-unmute coupling, and signal decoupling) across all relevant buses and UI elements. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/572 | Implement focus-gated mute/volume audio feedback so that manual UI interactions (mute buttons, slider proxy toggles, auto-mute at threshold) play the click SFX exactly once, while programmatic state changes (AudioManager sync, WebBridge, Playwright, config load/reset, UI init) cannot reach those SFX paths. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/572 | Ensure that all automated operations (menu initialization, configuration load/reset, inbound WebBridge synchronization, headless/Playwright-driven changes) update UI and AudioManager state silently, with no unintended audio playback or coupling between automated data pipelines and hardware interaction listeners, and verify this via automated tests across all six audio buses. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/572 | Formally document the mute signal isolation and decoupling patterns, including how focus-gated SFX, centralized mute toggle handling, and test coverage prevent regressions in future UI audio development. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/723 | Implement symmetric auto-mute and auto-unmute coupling for all six audio channels so that when a volume slider reaches a near-zero threshold the corresponding bus is muted, the associated CheckButton reflects the muted/unmuted state, and the behavior mirrors existing auto-unmute logic. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/723 | Ensure confirmation audio feedback (check.wav) is played exactly once only for manual user adjustments (when the relevant HSlider or mute control has focus), remains completely silent during programmatic updates (WebBridge/config load/automated tests), and avoid feedback loops between slider changes and mute checkbox synchronization. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/723 | Wire the auto-mute/auto-unmute behavior into the existing volume_changed monitoring for all six channels and add tests/documentation that verify manual interactive cases (audible) and automated cases (silent) per the described scenarios. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/724 | Create a dedicated GUT unit test file that isolates and tests the auto-mute threshold rules for audio settings. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/724 | Verify correct auto-mute/auto-unmute state transitions, including manual (focused) vs programmatic/unfocused interactions, idempotent zero-volume updates, and coverage of all six primary audio buses with signal isolation under focus and non-focus contexts. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/724 | Ensure the new tests are robust and compliant: they reset backend state in before_each/after_each for isolation, avoid unintended sound during background sync, and adhere to project linting (gdlint) and execution requirements (runnable via run_gut.sh selector). | βœ… | | +| | Ensure the SFX_Menu audio bus is fully configured and treated as a first-class channel in code: BUS_SFX_MENU constant present and used, BUS_CONFIG registers menu_volume/menu_muted, and AudioManager exposes menu_volume/menu_muted properties wired through its volume/mute APIs. | βœ… | | +| | Integrate the SFXMenu UI row into the AudioSettings scene using the sfx_menu_volume_control.tscn subscene (HSlider + mute CheckButton), structurally sequenced after the SFXRotors row. | βœ… | | +| | Unify menu SFX UI behavior in audio_settings.gd with the other buses, including clean slider event processing, auto-mute/auto-unmute around a near-zero threshold, and centralized mute toggle handling for BUS_SFX_MENU. | βœ… | | +| | Provide a dedicated `check.wav` audio feedback for all mute-related user interactions in the Audio Settings UI, wired through the centralized AudioManager SFX API and only triggered on explicit manual interactions (focused buttons/sliders), not on programmatic state changes. | βœ… | | +| | Ensure the backend AudioManager exposes a robust, non-blocking SFX playback pipeline (pooled AudioStreamPlayers + LRU cache + missing-asset handling) that can safely serve `check.wav` and other UI sounds without stutter or engine errors in both normal and headless/CI environments. | βœ… | | +| | Audit and prove mute signal decoupling via tests and documentation so that data-driven UI refreshes, initialization, WebBridge/Playwright sync loops, and other automated routines can mutate audio state in complete silence while manual hardware inputs still emit `check.wav` feedback. | βœ… | | +| | Add the `check.wav` sound asset to the SFX asset library under `res://files/sounds/sfx/` and ensure it is imported and tracked by Godot/source control. | βœ… | | +| | Ensure `check.wav` is correctly loadable and referenceable via the standard SFX pipeline (e.g., `AudioManager.play_sfx`), suitable for playback on the `AudioConstants.BUS_SFX_MENU` bus, and accessible to future AudioManager integrations. | βœ… | | +| | Integrate the new mute-toggle confirmation SFX without modifying or replacing existing audio assets, and validate asset integrity/import behavior in Godot (editor/exports). | βœ… | | +| | Introduce a public centralized SFX playback API in AudioManager that UI components call via an identifier string (with safe default bus and optional overrides). | βœ… | | +| | Implement high-performance centralized SFX playback using a pre-allocated AudioStreamPlayer pool with hijacking under load, constant node count, and an LRU AudioStream cache plus failure cache to avoid I/O stutters and repeated disk lookups. | βœ… | | +| | Wire UI/menu interactions to this centralized SFX system (e.g., click and auto-mute feedback using identifier-based SFX like "check"), ensuring correct bus routing, pitch/volume overrides support, and that programmatic updates do not trigger SFX. | βœ… | | +| | Play the dedicated confirmation sound via AudioManager.play_sfx("check") whenever a user manually toggles any mute control (Master, Music, SFX, Weapon, Rotors, Menu) or triggers an intentional proxy unmute via slider interaction, with exactly one playback per physical interaction. | βœ… | | +| | Ensure that audio feedback for mute/volume changes is strictly limited to direct, focused user interactions and remains completely silent for programmatic updates (initialization, config load/restore, WebBridge/Playwright sync, and other backend AudioManager changes), with no duplicate sounds per single human action. | βœ… | | +| | Update audio_settings.gd and related tests/documentation to encode and verify the new behavior (focus-gated click SFX, auto-mute/auto-unmute coupling, and signal decoupling) across all relevant buses and UI elements. | βœ… | | +| | Implement focus-gated mute/volume audio feedback so that manual UI interactions (mute buttons, slider proxy toggles, auto-mute at threshold) play the click SFX exactly once, while programmatic state changes (AudioManager sync, WebBridge, Playwright, config load/reset, UI init) cannot reach those SFX paths. | βœ… | | +| | Ensure that all automated operations (menu initialization, configuration load/reset, inbound WebBridge synchronization, headless/Playwright-driven changes) update UI and AudioManager state silently, with no unintended audio playback or coupling between automated data pipelines and hardware interaction listeners, and verify this via automated tests across all six audio buses. | βœ… | | +| | Formally document the mute signal isolation and decoupling patterns, including how focus-gated SFX, centralized mute toggle handling, and test coverage prevent regressions in future UI audio development. | βœ… | | +| | Implement symmetric auto-mute and auto-unmute coupling for all six audio channels so that when a volume slider reaches a near-zero threshold the corresponding bus is muted, the associated CheckButton reflects the muted/unmuted state, and the behavior mirrors existing auto-unmute logic. | βœ… | | +| | Ensure confirmation audio feedback (check.wav) is played exactly once only for manual user adjustments (when the relevant HSlider or mute control has focus), remains completely silent during programmatic updates (WebBridge/config load/automated tests), and avoid feedback loops between slider changes and mute checkbox synchronization. | βœ… | | +| | Wire the auto-mute/auto-unmute behavior into the existing volume_changed monitoring for all six channels and add tests/documentation that verify manual interactive cases (audible) and automated cases (silent) per the described scenarios. | βœ… | | +| | Create a dedicated GUT unit test file that isolates and tests the auto-mute threshold rules for audio settings. | βœ… | | +| | Verify correct auto-mute/auto-unmute state transitions, including manual (focused) vs programmatic/unfocused interactions, idempotent zero-volume updates, and coverage of all six primary audio buses with signal isolation under focus and non-focus contexts. | βœ… | | +| | Ensure the new tests are robust and compliant: they reset backend state in before_each/after_each for isolation, avoid unintended sound during background sync, and adhere to project linting (gdlint) and execution requirements (runnable via run_gut.sh selector). | βœ… | | --- @@ -152,7 +152,7 @@ documentation quality, and overall code health. categories, providing an overall grade, inline comments, and a comprehensive review report. -These automated contributions strengthened testing robustness, asset integrity +These automated contributions strengthened testing robustness, asset integrity checks, workflow pinning, and reviewer documentation. --- @@ -162,7 +162,7 @@ checks, workflow pinning, and reviewer documentation. - **@ikostan**: Primary author and main contributor. Implemented core features including audio feedback for mute buttons, centralized mute handling with focus-aware SFX, auto-mute/unmute logic, AudioManager hardening (SFX pooling - + LRU cache), extensive test suite updates (GUT/GDUnit4), CI refinements, + - LRU cache), extensive test suite updates (GUT/GDUnit4), CI refinements, and detailed milestone documentation with reviewer guides and sequence diagrams. - **@espanakosta-jpg**: Contributed custom audio assets, specifically diff --git a/files/docs/milestones/19/PART_3_Update_Godot_export_action_pin_and_configure_Codecov_token.md b/files/docs/milestones/19/PART_3_Update_Godot_export_action_pin_and_configure_Codecov_token.md index 09eb1a948..cfcf8f617 100644 --- a/files/docs/milestones/19/PART_3_Update_Godot_export_action_pin_and_configure_Codecov_token.md +++ b/files/docs/milestones/19/PART_3_Update_Godot_export_action_pin_and_configure_Codecov_token.md @@ -14,9 +14,9 @@ ensure test reports upload correctly to Codecov. ### Chores - - Updated build and export automation dependencies to maintain +- Updated build and export automation dependencies to maintain compatibility and reliability. - - Enhanced CI/CD pipeline security by properly configuring token access +- Enhanced CI/CD pipeline security by properly configuring token access for automated code coverage reporting. --- @@ -27,7 +27,6 @@ Updates CI workflows to use a newer pinned revision of the firebelley/godot-export GitHub Action and ensures the Codecov upload step has the required token set via environment variables. - ### File-Level Changes @@ -41,7 +40,7 @@ has the required token set via environment variables. ## Bots/AI Contributions Summary for PR #736 -This PR focuses on CI/CD maintenance: updating the pinned SHA for the +This PR focuses on CI/CD maintenance: updating the pinned SHA for the `firebelley/godot-export` GitHub Action across multiple workflows and configuring the `CODECOV_TOKEN` for authenticated coverage uploads. It received valuable support from automated bots and AI tools for dependency diff --git a/files/docs/milestones/19/PART_4_gameplay_settings_audio_interaction_and_asset_tracking.md b/files/docs/milestones/19/PART_4_gameplay_settings_audio_interaction_and_asset_tracking.md index 5374b1d25..af87743a1 100644 --- a/files/docs/milestones/19/PART_4_gameplay_settings_audio_interaction_and_asset_tracking.md +++ b/files/docs/milestones/19/PART_4_gameplay_settings_audio_interaction_and_asset_tracking.md @@ -17,7 +17,7 @@ design patterns established within the Audio Settings menu, the implementation operates under a strict **Mute Signal Isolation and Decoupling Pattern**. -### Core Architectural Axioms: +### Core Architectural Axioms * **Focus-Gated Control:** Audio playback is strictly decoupled from the low-level data engine layer. Sound effects are never permitted to fire @@ -104,7 +104,7 @@ registered: ``` -### Resource Metadata Definitions: +### Resource Metadata Definitions * **Asset Path:** `res://files/sounds/sfx/slider.wav` * **Import Profile Configuration:** Controlled via tracking metadata @@ -120,7 +120,7 @@ registered: The sound asset `slider.wav` is flagged as an **actively referenced runtime gameplay UI dependency**. -### Maintenance Directives for Future Contributors: +### Maintenance Directives for Future Contributors * **Exclusion from Optimization Suites:** This file **is unsafe to remove** or exclude during asset compression passes, engine pruning commands, or @@ -190,7 +190,7 @@ interaction architecture and related CI maintenance work. | Change | Details | Files | |----------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Gate difficulty slider SFX behind focus/interaction checks and ensure reset and JS paths are treated as interactive. |
  • Extend _on_difficulty_value_changed to accept an is_interactive flag defaulting to false.
  • Determine slider focus state and compute a should_play_audio flag based on focus or the interactive override.
  • Invoke a new _play_slider_sfx helper when audio should play, and route reset button and JS callbacks through the handler with the interactive flag set to true.
  • Add an AudioManager presence/method check in _play_slider_sfx to avoid crashes in headless/test environments and log when unavailable.
| `scripts/ui/menus/gameplay_settings.gd` | +| Gate difficulty slider SFX behind focus/interaction checks and ensure reset and JS paths are treated as interactive. |
  • Extend _on_difficulty_value_changed to accept an is_interactive flag defaulting to false.
  • Determine slider focus state and compute a should_play_audio flag based on focus or the interactive override.
  • Invoke a new_play_slider_sfx helper when audio should play, and route reset button and JS callbacks through the handler with the interactive flag set to true.
  • Add an AudioManager presence/method check in _play_slider_sfx to avoid crashes in headless/test environments and log when unavailable.
| `scripts/ui/menus/gameplay_settings.gd` | | Expose AudioManager pool inspection and control helpers used by tests. |
  • Add is_any_sfx_playing to report whether any pooled AudioStreamPlayer is currently playing.
  • Add get_active_sfx_playback_count to count active SFX channels.
  • Add stop_all_sfx to stop playback and clear streams on all pooled AudioStreamPlayers.
| `scripts/managers/audio_manager.gd` | | Add GUT tests covering gameplay settings audio behavior across interactive, programmatic, reset, and JS paths with safe audio mocking. |
  • Instantiate the gameplay settings scene with a deterministic Globals.settings and a real or dummy AudioManager.
  • Provide helper methods to clear SFX, query if sound is playing, and use AudioManager’s new APIs.
  • Test that initialization and programmatic changes are silent, interactive/focused and JS override paths play audio, reset emits exactly one SFX, and malformed JS inputs do not change difficulty or play sound.
  • Introduce a DummyAudioManager class to satisfy test calls when the real autoload is missing.
| `test/gut/test_gameplay_settings_audio.gd`
`test/gut/test_gameplay_settings_audio.gd.uid` | | Document the gameplay settings audio interaction architecture and CI workflow maintenance for Godot export and Codecov. |
  • Describe focus-gated vs silent pathways for the difficulty slider, including JS overrides and reset behavior.
  • Record the runtime dependency mapping from gameplay_settings.gd to the slider.wav asset and outline asset-pruning safeguards and regression-prevention notes.
  • Document CI changes updating the pinned firebelley/godot-export action SHA across workflows and configuring the CODECOV_TOKEN for Codecov uploads, along with a reviewer’s guide and bots/AI contribution notes.
| `files/docs/milestones/19/PART_4_gameplay_settings_audio_interaction_and_asset_tracking.md`
`files/docs/milestones/19/PART_3_Update_Godot_export_action_pin_and_configure_Codecov_token.md` | @@ -199,25 +199,25 @@ interaction architecture and related CI maintenance work. | Issue | Objective | Addressed | Explanation | |------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------| -| https://github.com/ikostan/SkyLockAssault/issues/728 | Implement focus-gated audio feedback for the difficulty slider (using slider.wav via AudioManager) for all intentional user interactions, including focused native UI (mouse/keyboard/controller), reset button, and JavaScript web bridge, while routing JS interactions through the same native handler. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/728 | Ensure all programmatic and synchronization pathways for the difficulty setting (initialization, config restoration, lifecycle sync, scripted mutations, reopening the menu) remain silent by bypassing the interaction layer and using set_value_no_signal() for UI updates where applicable, with explicit typing on new code paths. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/728 | Add automated tests and documentation describing the gameplay settings audio interaction architecture (focus gating, JS routing, silent vs interactive pathways) and explicitly track the dependency on slider.wav. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/729 | Refactor the difficulty slider pipeline so that audio feedback (slider.wav) is only played for verified interactive operations (focused native UI, JS overlay with explicit override, and gameplay reset) while all initialization, restoration, and programmatic synchronizations remain completely silent. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/729 | Ensure the JavaScript overlay difficulty change path reuses the same internal interaction handler as native UI input, forwarding an explicit interaction override while preserving existing JS validation, bounds checking, and behavior. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/729 | Preserve or improve defensive programming practices (instance checks, logging, type and bounds validation) with explicit datatypes for new code, and document and test the new gameplay settings audio interaction behavior. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/730 | Implement a deterministic GUT automated test suite for the difficulty slider audio behavior (initialization silence, focus-gated interaction, silent programmatic updates, reset behavior, JS override path, and invalid JS input) as specified in TC-GUT-DIFF-01 through TC-GUT-DIFF-06. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/730 | Align gameplay_settings.gd difficulty slider audio behavior with the specified architecture: audio plays only for verified interactive/focus-gated events (local UI, reset button, JS override) and remains silent for programmatic/synchronization paths. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/730 | Ensure the test infrastructure is isolation-safe and headless-friendly by providing audio manager helpers and cleanup to prevent audio leakage, race conditions, and dependence on real audio hardware. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/731 | Document the Gameplay Settings audio interaction architecture and behavior, clearly distinguishing interactive (audible) pathways from silent synchronization pathways, including focus-gated behavior and JS overlay routing, in project documentation. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/731 | Record an explicit runtime dependency mapping between res://scripts/ui/menus/gameplay_settings.gd and res://files/sounds/sfx/slider.wav, including notes that slider.wav is an active runtime dependency and unsafe to remove during asset cleanup or export optimization, so contributors can identify this without code tracing. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/731 | Establish reusable guidance and regression-prevention notes for future interactive UI audio systems, explaining why audio is not attached directly to generic value_changed signals, why synchronization remains silent, why JS overlays require explicit routing, and how this relates to asset tracking (especially for web/CI environments). | βœ… | | +| | Implement focus-gated audio feedback for the difficulty slider (using slider.wav via AudioManager) for all intentional user interactions, including focused native UI (mouse/keyboard/controller), reset button, and JavaScript web bridge, while routing JS interactions through the same native handler. | βœ… | | +| | Ensure all programmatic and synchronization pathways for the difficulty setting (initialization, config restoration, lifecycle sync, scripted mutations, reopening the menu) remain silent by bypassing the interaction layer and using set_value_no_signal() for UI updates where applicable, with explicit typing on new code paths. | βœ… | | +| | Add automated tests and documentation describing the gameplay settings audio interaction architecture (focus gating, JS routing, silent vs interactive pathways) and explicitly track the dependency on slider.wav. | βœ… | | +| | Refactor the difficulty slider pipeline so that audio feedback (slider.wav) is only played for verified interactive operations (focused native UI, JS overlay with explicit override, and gameplay reset) while all initialization, restoration, and programmatic synchronizations remain completely silent. | βœ… | | +| | Ensure the JavaScript overlay difficulty change path reuses the same internal interaction handler as native UI input, forwarding an explicit interaction override while preserving existing JS validation, bounds checking, and behavior. | βœ… | | +| | Preserve or improve defensive programming practices (instance checks, logging, type and bounds validation) with explicit datatypes for new code, and document and test the new gameplay settings audio interaction behavior. | βœ… | | +| | Implement a deterministic GUT automated test suite for the difficulty slider audio behavior (initialization silence, focus-gated interaction, silent programmatic updates, reset behavior, JS override path, and invalid JS input) as specified in TC-GUT-DIFF-01 through TC-GUT-DIFF-06. | βœ… | | +| | Align gameplay_settings.gd difficulty slider audio behavior with the specified architecture: audio plays only for verified interactive/focus-gated events (local UI, reset button, JS override) and remains silent for programmatic/synchronization paths. | βœ… | | +| | Ensure the test infrastructure is isolation-safe and headless-friendly by providing audio manager helpers and cleanup to prevent audio leakage, race conditions, and dependence on real audio hardware. | βœ… | | +| | Document the Gameplay Settings audio interaction architecture and behavior, clearly distinguishing interactive (audible) pathways from silent synchronization pathways, including focus-gated behavior and JS overlay routing, in project documentation. | βœ… | | +| | Record an explicit runtime dependency mapping between res://scripts/ui/menus/gameplay_settings.gd and res://files/sounds/sfx/slider.wav, including notes that slider.wav is an active runtime dependency and unsafe to remove during asset cleanup or export optimization, so contributors can identify this without code tracing. | βœ… | | +| | Establish reusable guidance and regression-prevention notes for future interactive UI audio systems, explaining why audio is not attached directly to generic value_changed signals, why synchronization remains silent, why JS overlays require explicit routing, and how this relates to asset tracking (especially for web/CI environments). | βœ… | | ### Possibly linked issues -- **#728**: The PR implements the requested focus-gated difficulty slider +* **#728**: The PR implements the requested focus-gated difficulty slider audio behavior, JS bridge routing, reset handling, and validation/tests. -- **#728**: PR directly implements the epic’s difficulty slider audio +* **#728**: PR directly implements the epic’s difficulty slider audio behavior, including focus-gating, JS pathway reuse, and silent sync. --- @@ -233,17 +233,17 @@ summarization, documentation refinement, and quality review. ### Automated Bots & AI Tools -- **@sourcery-ai**: Actively contributed to multiple documentation updates +* **@sourcery-ai**: Actively contributed to multiple documentation updates (co-author on several commits) and provided the primary PR summary. Highlighted new features (focus-gated slider audio), enhancements (AudioManager utilities for SFX pool control), tests (GUT suite for interactive vs. silent paths), and documentation (architecture, asset tracking, and CI milestone notes). -- **@coderabbitai**: Delivered a concise summary focusing on new features +* **@coderabbitai**: Delivered a concise summary focusing on new features (conditional audio playback for user interactions), expanded test coverage (interactive, programmatic, reset, and JS paths), documentation improvements, and related chores. -- **@deepsource-io**: Performed automated static code analysis and code +* **@deepsource-io**: Performed automated static code analysis and code review across the changes in `gameplay_settings.gd`, `audio_manager.gd`, tests, and documentation. Provided an overall grade across Security, Reliability, Complexity, and Hygiene categories, along with inline comments @@ -255,7 +255,7 @@ prior audio infrastructure work. ### Human Maintainers -- **@ikostan**: Primary contributor and PR author. Led the full implementation, +* **@ikostan**: Primary contributor and PR author. Led the full implementation, including focus/interaction-gated audio logic in the difficulty slider pipeline, AudioManager extensions (`is_any_sfx_playing`, `stop_all_sfx`, etc.), safe headless/test helpers, comprehensive GUT test suite (covering diff --git a/files/docs/milestones/20/Part_1_UI_audio_logic_unit_tests.md b/files/docs/milestones/20/Part_1_UI_audio_logic_unit_tests.md index a544233b4..18adde626 100644 --- a/files/docs/milestones/20/Part_1_UI_audio_logic_unit_tests.md +++ b/files/docs/milestones/20/Part_1_UI_audio_logic_unit_tests.md @@ -8,23 +8,27 @@ This PR introduces an automated testing suite for `AudioSettings` and implements ## πŸš€ Key Changes ### 1. Architectural Testing Suite (`test_audio_settings_interaction.gd`) + * **GUT Integration:** Established a comprehensive test suite using the GUT framework. * **Focus-Gate Validation:** Added stress tests to verify that audio triggers only fire when the UI component has focus, validating the logic in `audio_settings.gd`. * **Resilience Testing:** Introduced boundary and stress tests to ensure: - * Rapid UI interaction (spamming) does not overwhelm the audio pool. - * Out-of-range volume values and invalid SFX keys are handled gracefully without crashing. + * Rapid UI interaction (spamming) does not overwhelm the audio pool. + * Out-of-range volume values and invalid SFX keys are handled gracefully without crashing. * **State Management:** Mocked `Globals.previous_scene` during test execution to prevent unintended scene changes (restarts) during UI interaction tests. ### 2. AudioManager Refactoring (`audio_manager.gd`) + * **Diagnostic APIs:** Added `is_sfx_playing()` and `get_active_sfx_stream_path()` to expose internal state to tests. - * *Note:* These are explicitly commented as `## [DIAGNOSTIC]` to signal their appropriate use. + * *Note:* These are explicitly commented as `## [DIAGNOSTIC]` to signal their appropriate use. * **Determinism:** Updated `get_active_sfx_stream_path()` to return the most recently played SFX, ensuring tests have a deterministic target rather than an arbitrary pool index. ### 3. Component Resilience (`volume_slider.gd`) + * **Signal Integrity:** Updated the volume logic to allow programmatic updates via `_on_value_changed()` (explicitly called by tests), bypassing issues where `slider.value` property setters do not emit signals. * **Input Clamping:** Implemented `clamp()` logic in `_on_value_changed` to ensure slider values always stay within valid bounds, improving resilience against invalid user or test inputs. ### 4. Repository Health + * **Metadata Cleanup:** Updated `.gitignore` to ignore engine-generated `*.uid` files, eliminating "noisy" diffs and potential merge conflicts. --- @@ -53,6 +57,7 @@ This PR introduces an automated testing suite for `AudioSettings` and implements ## Reviewer's Guide Adds diagnostic AudioManager APIs to inspect active SFX, tightens VolumeSlider value handling, and introduces a comprehensive GUT-based UI interaction test suite for audio settings, plus a minor CI workflow bump and docs for the milestone. + ### File-Level Changes | Change | Details | Files | @@ -66,12 +71,12 @@ Adds diagnostic AudioManager APIs to inspect active SFX, tightens VolumeSlider v | Issue | Objective | Addressed | Explanation | |------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------| -| https://github.com/ikostan/SkyLockAssault/issues/494 | Add a comprehensive GUT test suite for the audio settings UI (audio_settings.gd) that validates interaction-to-audio mapping with AudioManager (mute toggles, sliders, reset), focus-gate behavior, spam/regression scenarios, invalid inputs, and pool/leak resilience using public inspection APIs and compatible with headless CI. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/494 | Expose or extend AudioManager’s public inspection APIs to allow tests to query SFX playback state (whether SFX are playing and which stream is active) without accessing internal pool details. | βœ… | | +| | Add a comprehensive GUT test suite for the audio settings UI (audio_settings.gd) that validates interaction-to-audio mapping with AudioManager (mute toggles, sliders, reset), focus-gate behavior, spam/regression scenarios, invalid inputs, and pool/leak resilience using public inspection APIs and compatible with headless CI. | βœ… | | +| | Expose or extend AudioManager’s public inspection APIs to allow tests to query SFX playback state (whether SFX are playing and which stream is active) without accessing internal pool details. | βœ… | | ### Possibly linked issues -- **#ISSUE_NUMBER**: The PR implements the specified audio settings UI GUT tests and public AudioManager inspection APIs matching the issue scope. +* **#ISSUE_NUMBER**: The PR implements the specified audio settings UI GUT tests and public AudioManager inspection APIs matching the issue scope. --- @@ -81,19 +86,19 @@ This PR adds public diagnostic helpers (`is_sfx_playing()`, `get_active_sfx_stre ### Automated Bots & AI Tools -- **@dependabot[bot]**: Handled the dependency update by bumping `codecov/codecov-action` from 6.0.1 to 7.0.0 (major version) in the browser test workflow, including the automated commit and related merge. This improves coverage reporting reliability and security. +* **@dependabot[bot]**: Handled the dependency update by bumping `codecov/codecov-action` from 6.0.1 to 7.0.0 (major version) in the browser test workflow, including the automated commit and related merge. This improves coverage reporting reliability and security. -- **@sourcery-ai**: Provided the primary structured PR summary and reviewer's guide. Highlighted new AudioManager APIs, the extensive GUT test suite (UI interactions, resilience, stress scenarios), CI updates, and linked the work to issue #494. Also contributed to title and description refinement. +* **@sourcery-ai**: Provided the primary structured PR summary and reviewer's guide. Highlighted new AudioManager APIs, the extensive GUT test suite (UI interactions, resilience, stress scenarios), CI updates, and linked the work to issue #494. Also contributed to title and description refinement. -- **@coderabbitai**: Delivered a focused summary covering bug fixes (volume slider clamping), new comprehensive test suite for audio settings interactions, and maintenance chores (workflow dependency update). +* **@coderabbitai**: Delivered a focused summary covering bug fixes (volume slider clamping), new comprehensive test suite for audio settings interactions, and maintenance chores (workflow dependency update). -- **@deepsource-io**: Performed automated static code analysis and code review on the changes (AudioManager extensions, test suite, CI workflow). Provided an overall grade across Security, Reliability, Complexity, and Hygiene categories, along with inline comments and a full review report. +* **@deepsource-io**: Performed automated static code analysis and code review on the changes (AudioManager extensions, test suite, CI workflow). Provided an overall grade across Security, Reliability, Complexity, and Hygiene categories, along with inline comments and a full review report. These tools enhanced test coverage documentation, reviewer guidance, dependency security, and overall code health validation. ### Human Maintainers -- **@ikostan**: Primary contributor and PR author. Led the full implementation, including new diagnostic APIs on `AudioManager`, volume slider input clamping fix, comprehensive GUT test suite (`test_audio_settings_interaction.gd`) covering interaction scenarios, focus-gating, resilience, and stress tests, test harness helpers, sequence diagrams, milestone integration, and CI workflow updates. +* **@ikostan**: Primary contributor and PR author. Led the full implementation, including new diagnostic APIs on `AudioManager`, volume slider input clamping fix, comprehensive GUT test suite (`test_audio_settings_interaction.gd`) covering interaction scenarios, focus-gating, resilience, and stress tests, test harness helpers, sequence diagrams, milestone integration, and CI workflow updates. --- diff --git a/files/docs/milestones/20/Part_2_Upgrade_project_environment_to_Godot_4_6_3.md b/files/docs/milestones/20/Part_2_Upgrade_project_environment_to_Godot_4_6_3.md index 19c276a94..bbd34857a 100644 --- a/files/docs/milestones/20/Part_2_Upgrade_project_environment_to_Godot_4_6_3.md +++ b/files/docs/milestones/20/Part_2_Upgrade_project_environment_to_Godot_4_6_3.md @@ -82,21 +82,21 @@ Centralizes Godot binary download and checksum verification into a reusable scri | Issue | Objective | Addressed | Explanation | |------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------| -| https://github.com/ikostan/SkyLockAssault/issues/747 | Update the project configuration and documentation to target Godot 4.6.3 instead of 4.5. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/747 | Update the Docker-based development environment and related tooling to install and use Godot 4.6.3, while keeping any needed legacy 4.5 support isolated. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/747 | Update CI/CD workflows (exports and test suites) and test code so that automated tests and exports run successfully under Godot 4.6.3, including compatibility with GUT and GDUnit4. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/749 | Update `godot_executable_download_url` in `.github/workflows/browser_test.yml` to use the Godot 4.6.3-stable Linux binary URL. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/749 | Update `godot_export_templates_download_url` in `.github/workflows/browser_test.yml` to use the Godot 4.6.3-stable export templates URL. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/751 | Update the `gdunit4_tests.yml` workflow to download and use the Godot 4.6.3-stable Linux binary instead of 4.5-stable. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/751 | Ensure the GDUnit4 version used in the `gdunit4_tests.yml` workflow is compatible and stable with Godot 4.6.3 (including adjusting the installed GDUnit4 version if needed). | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/751 | Keep GDUnit4 automated test execution and report generation in the `gdunit4_tests.yml` workflow functional after upgrading to Godot 4.6.3. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/752 | Update the gut_tests.yml GitHub Actions workflow to download and use the Godot 4.6.3-stable Linux binary for running GUT tests. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/752 | Ensure GUT v9.5.0 works correctly with Godot 4.6.3 for project initialization and test execution (including any necessary compatibility fixes or setup). | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/752 | Maintain correct generation and handling of GUT test artifacts (e.g., JUnit XML) and preserve existing CI reporting integrations such as Codecov when moving to Godot 4.6.3. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/753 | Update the Dockerfile ENV GODOT_VERSION value to use Godot 4.6.3-stable. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/753 | Update the Dockerfile Godot engine binary and export template download URLs to use the 4.6.3-stable artifacts. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/755 | Fix the CI/CD `Deploy to itch.io` workflow so that the Godot checksum manifest is fetched from a valid URL (no 404) and the downloaded binaries are cryptographically verified before export. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/755 | Make the Godot version used in CI/CD workflows configurable while ensuring the deploy workflow and related CI tests correctly pass and use this version for downloading and verifying Godot assets. | βœ… | | +| | Update the project configuration and documentation to target Godot 4.6.3 instead of 4.5. | βœ… | | +| | Update the Docker-based development environment and related tooling to install and use Godot 4.6.3, while keeping any needed legacy 4.5 support isolated. | βœ… | | +| | Update CI/CD workflows (exports and test suites) and test code so that automated tests and exports run successfully under Godot 4.6.3, including compatibility with GUT and GDUnit4. | βœ… | | +| | Update `godot_executable_download_url` in `.github/workflows/browser_test.yml` to use the Godot 4.6.3-stable Linux binary URL. | βœ… | | +| | Update `godot_export_templates_download_url` in `.github/workflows/browser_test.yml` to use the Godot 4.6.3-stable export templates URL. | βœ… | | +| | Update the `gdunit4_tests.yml` workflow to download and use the Godot 4.6.3-stable Linux binary instead of 4.5-stable. | βœ… | | +| | Ensure the GDUnit4 version used in the `gdunit4_tests.yml` workflow is compatible and stable with Godot 4.6.3 (including adjusting the installed GDUnit4 version if needed). | βœ… | | +| | Keep GDUnit4 automated test execution and report generation in the `gdunit4_tests.yml` workflow functional after upgrading to Godot 4.6.3. | βœ… | | +| | Update the gut_tests.yml GitHub Actions workflow to download and use the Godot 4.6.3-stable Linux binary for running GUT tests. | βœ… | | +| | Ensure GUT v9.5.0 works correctly with Godot 4.6.3 for project initialization and test execution (including any necessary compatibility fixes or setup). | βœ… | | +| | Maintain correct generation and handling of GUT test artifacts (e.g., JUnit XML) and preserve existing CI reporting integrations such as Codecov when moving to Godot 4.6.3. | βœ… | | +| | Update the Dockerfile ENV GODOT_VERSION value to use Godot 4.6.3-stable. | βœ… | | +| | Update the Dockerfile Godot engine binary and export template download URLs to use the 4.6.3-stable artifacts. | βœ… | | +| | Fix the CI/CD `Deploy to itch.io` workflow so that the Godot checksum manifest is fetched from a valid URL (no 404) and the downloaded binaries are cryptographically verified before export. | βœ… | | +| | Make the Godot version used in CI/CD workflows configurable while ensuring the deploy workflow and related CI tests correctly pass and use this version for downloading and verifying Godot assets. | βœ… | | ### Possibly linked issues @@ -120,8 +120,9 @@ Centralizes Godot binary download and checksum verification into a reusable scri These tools enhanced code quality, documentation, and maintainability without replacing core human-driven changes. ### Human Contributions (@ikostan) + **@ikostan** (primary maintainer and author): Drove the entire PR as the main contributor. Key efforts include: - + - Planning and executing the Godot 4.6.3 upgrade across core files, workflows (browser_test.yml, gdunit4_tests.yml, gut_tests.yml, deploy_to_itch.yml), Dockerfile (with legacy 4.5 archival), project.godot, scenes, and test scripts. - Implementing bug fixes and robustness improvements (e.g., AudioManager guards/cleanup, empty ConfigFile handling to prevent crashes, GUT/GDUnit4 compatibility). - Adding security enhancements (SHA-256 checksum verification for downloads). diff --git a/files/docs/milestones/21/Part_1_UI_Accept_&_Cancel_SFX.md b/files/docs/milestones/21/Part_1_UI_Accept_&_Cancel_SFX.md index 7b4b7cad7..8013887cb 100644 --- a/files/docs/milestones/21/Part_1_UI_Accept_&_Cancel_SFX.md +++ b/files/docs/milestones/21/Part_1_UI_Accept_&_Cancel_SFX.md @@ -32,10 +32,10 @@ This PR hardens our centralized UI audio routing pipelines, eliminates double-au ### Verification Checklist -- [x] **Issue #491 Contract:** Centralized directional menu navigation audio registers on keyboard and gamepad pads while blocking echo inputs. Horizontal slider actions cleanly bypass the tick sfx. -- [x] **Issue #763 Contract:** Only native base buttons bind to the global accept chime; specialized checkboxes and options are excluded via string class name matching, and all connections deploy thread-safely via `CONNECT_DEFERRED`. -- [x] **Lifecycle Sequence Verification:** Metadata exclusion tags execute before `node_added` notifications, silencing the global hook on main menu entries without build latency. -- [x] **Static Analysis Alignment:** Dismissed false positive recommendations from generic static analysis tools regarding echo duplication, editor environment leaks, and python-centric object probing constraints. +* [x] **Issue #491 Contract:** Centralized directional menu navigation audio registers on keyboard and gamepad pads while blocking echo inputs. Horizontal slider actions cleanly bypass the tick sfx. +* [x] **Issue #763 Contract:** Only native base buttons bind to the global accept chime; specialized checkboxes and options are excluded via string class name matching, and all connections deploy thread-safely via `CONNECT_DEFERRED`. +* [x] **Lifecycle Sequence Verification:** Metadata exclusion tags execute before `node_added` notifications, silencing the global hook on main menu entries without build latency. +* [x] **Static Analysis Alignment:** Dismissed false positive recommendations from generic static analysis tools regarding echo duplication, editor environment leaks, and python-centric object probing constraints. --- @@ -49,7 +49,7 @@ Centralizes UI button sound effects for accept/cancel actions, refines main menu |------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Refactor main menu initialization to run a configurable, guarded fade-in sequence asynchronously and adjust quit flow timing for audio flushing. |
  • Introduce exported intro_delay, fade_duration, and audio_flush_delay properties with clamped ranges for the main menu.
  • Move fade-in logic into a new _run_fade_in_sequence coroutine that safely bails out if the scene is torn down and assigns initial focus after animation.
  • Reorder _ready to connect core button signals, configure dialogs, and then kick off the fade-in sequence.
  • Change native quit handling to await audio_flush_delay before quitting, and Web quit handling to use JavaScript setTimeout with the same delay, hiding the quit dialog immediately on confirm.
  • Refine quit dialog cancel handling to focus-restore more defensively without changing behavior.
| `scripts/ui/menus/main_menu.gd` | | Sanitize quit dialog buttons and add explicit cancel SFX handling to avoid duplicate or missing sounds. |
  • During quit dialog setup, iterate existing pressed signal connections on OK/Cancel buttons and disconnect the global _on_global_button_pressed handler when present.
  • Connect the quit dialog Cancel button to a new _on_cancel_button_clicked handler that plays the ui_cancel sound effect.
  • Log that quit dialog signals have been connected and internal buttons sanitized for clarity.
| `scripts/ui/menus/main_menu.gd` | -| Add a global, metadata-aware button listener that wires UI buttons to a centralized accept SFX handler while excluding special cases. |
  • Connect SceneTree.node_added to Globals._on_node_added in Globals._ready to monitor runtime node instantiation.
  • Implement _on_node_added to detect Button nodes by class name, skip flat buttons or those tagged with no_global_sound metadata, and skip buttons inside AcceptDialogs.
  • Connect eligible buttons’ pressed signal (deferred) to a new Globals._on_global_button_pressed handler, guarding against duplicate connections.
  • Implement _on_global_button_pressed to route all such presses to AudioManager.play_sfx("ui_accept").
  • Tag main menu Start/Options/Quit buttons with no_global_sound metadata in _enter_tree to keep them from triggering the global handler.
| `scripts/core/globals.gd`
`scripts/ui/menus/main_menu.gd` | +| Add a global, metadata-aware button listener that wires UI buttons to a centralized accept SFX handler while excluding special cases. |
  • Connect SceneTree.node_added to Globals._on_node_added in Globals._ready to monitor runtime node instantiation.
  • Implement_on_node_added to detect Button nodes by class name, skip flat buttons or those tagged with no_global_sound metadata, and skip buttons inside AcceptDialogs.
  • Connect eligible buttons’ pressed signal (deferred) to a new Globals._on_global_button_pressed handler, guarding against duplicate connections.
  • Implement _on_global_button_pressed to route all such presses to AudioManager.play_sfx("ui_accept").
  • Tag main menu Start/Options/Quit buttons with no_global_sound metadata in _enter_tree to keep them from triggering the global handler.
| `scripts/core/globals.gd`
`scripts/ui/menus/main_menu.gd` | | Improve ESC/ui_cancel handling so cancel sounds play only in appropriate menu contexts and navigation SFX uses the current event rather than global input state. |
  • Rename Globals._input parameter to event and gate out non-menu contexts as before.
  • When ui_cancel is pressed in a menu, play the ui_cancel SFX only if focus is not on LineEdit/TextEdit/Range/CheckButton/OptionButton controls.
  • Add an extra guard to avoid cancel SFX when the focused control looks like an input remap widget (has action/action_name properties or cancel_remap method).
  • Change navigation SFX gating from Input.is_action_just_pressed to event.is_action_pressed(action, false) to satisfy tests and still avoid double audio when a Slider has focus.
| `scripts/core/globals.gd` | | Enable patch delta configuration for Web exports and remove deprecated advanced options flags. |
  • Remove advanced_options flags from Web export presets.
  • Add patch_delta_encoding, compression level, min reduction, include, and exclude filters for both Web and Web_thread_off presets.
| `export_presets.cfg` | | Update CI workflows to newer action versions for checkout and release drafting. |
  • Bump actions/checkout uses from v6.x to v7 across multiple workflows.
  • Update release-drafter/release-drafter to a newer pinned commit SHA in release_drafter and release_drafter_pr workflows.
| `.github/workflows/browser_test.yml`
`.github/workflows/codeql.yml`
`.github/workflows/deploy_to_itch.yml`
`.github/workflows/gdlint.yml`
`.github/workflows/gdunit4_tests.yml`
`.github/workflows/gut_tests.yml`
`.github/workflows/lint_readme.yml`
`.github/workflows/release_drafter.yml`
`.github/workflows/release_drafter_pr.yml`
`.github/workflows/snyk.yml`
`.github/workflows/test_ci_scripts.yml`
`.github/workflows/trivy.yml`
`.github/workflows/yamllint.yml` | @@ -59,19 +59,19 @@ Centralizes UI button sound effects for accept/cancel actions, refines main menu | Issue | Objective | Addressed | Explanation | |------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------| -| https://github.com/ikostan/SkyLockAssault/issues/491 | Centralize UI navigation detection in the Globals autoload `_input(event)` for actions `ui_up`, `ui_down`, `ui_left`, `ui_right`, `ui_focus_next`, and `ui_focus_prev`, and use it to trigger the navigation sound (`ui_navigation.wav`). | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/491 | Prevent key-repeat spam for navigation sounds by using `event.is_action_pressed(action, false)` (or equivalent) so holding a key/D-pad does not cause repeated SFX triggers. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/491 | Add a slider protection gate so that when a `Slider` has focus, horizontal navigation inputs (`ui_left`/`ui_right`) adjust the slider without playing the global navigation sound. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/492 | Implement global ui_cancel interception in Globals._input with context gating, echo suppression, and focus-owner guards for editing/toggle/selection/remap controls, playing the ui_cancel sound via AudioManager. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/492 | Handle quit dialog confirmation and cancellation in main_menu.gd using localized signals, including an exported configurable audio_flush_delay and platform-specific quit delay (Godot timer on native, JavaScript setTimeout via JavaScriptBridge on Web) so ui_accept plays fully before exit/redirect. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/492 | Route all accept/cancel UI audio through AudioManager.play_sfx("ui_accept"/"ui_cancel"), including confirmation sounds for buttons and dialogs, while preventing double-triggering and protecting flat buttons and internal dialog buttons from unintended global sounds. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/763 | In Globals._ready(), connect a global scene tree listener using get_tree().node_added.connect(_on_node_added) to monitor UI element instantiation. | βœ… | | -| https://github.com/ikostan/SkyLockAssault/issues/763 | Implement _on_node_added(node) in Globals to: (a) strictly filter native base buttons via node.get_class() == "Button" (excluding CheckButton, CheckBox, OptionButton, etc.), and (b) connect their pressed signal with CONNECT_DEFERRED to play AudioManager.play_sfx("ui_accept") when activated. | βœ… | | +| | Centralize UI navigation detection in the Globals autoload `_input(event)` for actions `ui_up`, `ui_down`, `ui_left`, `ui_right`, `ui_focus_next`, and `ui_focus_prev`, and use it to trigger the navigation sound (`ui_navigation.wav`). | βœ… | | +| | Prevent key-repeat spam for navigation sounds by using `event.is_action_pressed(action, false)` (or equivalent) so holding a key/D-pad does not cause repeated SFX triggers. | βœ… | | +| | Add a slider protection gate so that when a `Slider` has focus, horizontal navigation inputs (`ui_left`/`ui_right`) adjust the slider without playing the global navigation sound. | βœ… | | +| | Implement global ui_cancel interception in Globals._input with context gating, echo suppression, and focus-owner guards for editing/toggle/selection/remap controls, playing the ui_cancel sound via AudioManager. | βœ… | | +| | Handle quit dialog confirmation and cancellation in main_menu.gd using localized signals, including an exported configurable audio_flush_delay and platform-specific quit delay (Godot timer on native, JavaScript setTimeout via JavaScriptBridge on Web) so ui_accept plays fully before exit/redirect. | βœ… | | +| | Route all accept/cancel UI audio through AudioManager.play_sfx("ui_accept"/"ui_cancel"), including confirmation sounds for buttons and dialogs, while preventing double-triggering and protecting flat buttons and internal dialog buttons from unintended global sounds. | βœ… | | +| | In Globals._ready(), connect a global scene tree listener using get_tree().node_added.connect(_on_node_added) to monitor UI element instantiation. | βœ… | | +| | Implement _on_node_added(node) in Globals to: (a) strictly filter native base buttons via node.get_class() == "Button" (excluding CheckButton, CheckBox, OptionButton, etc.), and (b) connect their pressed signal with CONNECT_DEFERRED to play AudioManager.play_sfx("ui_accept") when activated. | βœ… | | ### Possibly linked issues -- **#**: PR adds global cancel SFX, button auto-wiring, dialog-specific accept/cancel sounds, and platform-specific audio flush as requested. -- **#FEATURE_UI_NAV_FOCUS_SFX_GLOBAL_AUDIO**: PR delivers global UI accept/cancel SFX and centralized button handling, matching the issue’s global UI audio goal. +* **#**: PR adds global cancel SFX, button auto-wiring, dialog-specific accept/cancel sounds, and platform-specific audio flush as requested. +* **#FEATURE_UI_NAV_FOCUS_SFX_GLOBAL_AUDIO**: PR delivers global UI accept/cancel SFX and centralized button handling, matching the issue’s global UI audio goal. --- @@ -81,17 +81,17 @@ Centralizes UI button sound effects for accept/cancel actions, refines main menu This PR benefited significantly from automated tools and AI-powered code review/suggestion systems, which helped refine code quality, catch issues, suggest improvements, update dependencies, and ensure compliance with best practices. Key contributors (in GitHub-mentionable format): -- **@sourcery-ai**: Provided a comprehensive PR summary, reviewer's guide, sequence diagrams, and detailed feedback on architecture, UX enhancements, and potential issues (e.g., magic numbers for delays, duplicate SFX triggers, and Web-specific user-gesture concerns). It highlighted new features like global button listeners and configurable fade/quit timings. -- **@coderabbitai** (CodeRabbit): Delivered a structured summary focusing on new features (UI audio feedback, quit flow enhancements), bug fixes (preventing unwanted cancel sounds, duplicate actions), and chores (scene unique_ids, export presets, CI updates). -- **@dependabot[bot]**: Handled dependency updates, including bumping `actions/checkout` from v6 to v7 across multiple workflows and updating `release-drafter/release-drafter`. These were merged into the PR. -- **@deepsource-io** (DeepSource / DeepsourceReview): Contributed automated static analysis, security reviews, and code quality checks. It helped align changes with integration test contracts, dismiss false positives, and maintain overall codebase health (consistent with its role across the repo). +* **@sourcery-ai**: Provided a comprehensive PR summary, reviewer's guide, sequence diagrams, and detailed feedback on architecture, UX enhancements, and potential issues (e.g., magic numbers for delays, duplicate SFX triggers, and Web-specific user-gesture concerns). It highlighted new features like global button listeners and configurable fade/quit timings. +* **@coderabbitai** (CodeRabbit): Delivered a structured summary focusing on new features (UI audio feedback, quit flow enhancements), bug fixes (preventing unwanted cancel sounds, duplicate actions), and chores (scene unique_ids, export presets, CI updates). +* **@dependabot[bot]**: Handled dependency updates, including bumping `actions/checkout` from v6 to v7 across multiple workflows and updating `release-drafter/release-drafter`. These were merged into the PR. +* **@deepsource-io** (DeepSource / DeepsourceReview): Contributed automated static analysis, security reviews, and code quality checks. It helped align changes with integration test contracts, dismiss false positives, and maintain overall codebase health (consistent with its role across the repo). These tools accelerated iteration by providing early feedback on race conditions, signal handling, platform-specific behaviors (Web vs. native), input guarding, and CI/maintenance tasks. ### Human Contributors -- **@ikostan**: Primary author and implementer. Drove the core changes, including centralized UI audio routing in `Globals.gd`, main menu lifecycle fixes, quit dialog signal sanitization, asset integration (ui_accept/ui_cancel SFX), input event handling improvements, metadata tagging, export preset updates, and multiple refinement commits addressing reviews. Also managed merges from main and dependency PRs. -- **@espanakosta-jpg**: Contributed new sound effect assets (ui_accept.wav and ui_cancel.wav with Godot import metadata). +* **@ikostan**: Primary author and implementer. Drove the core changes, including centralized UI audio routing in `Globals.gd`, main menu lifecycle fixes, quit dialog signal sanitization, asset integration (ui_accept/ui_cancel SFX), input event handling improvements, metadata tagging, export preset updates, and multiple refinement commits addressing reviews. Also managed merges from main and dependency PRs. +* **@espanakosta-jpg**: Contributed new sound effect assets (ui_accept.wav and ui_cancel.wav with Godot import metadata). **Overall PR Impact**: The changes centralize accept/cancel SFX, fix audio double-triggering and lifecycle races, improve ESC/menu navigation handling, add audio-flush-aware quits, and update related scenes/workflows. AI/bot input was instrumental in polishing the implementation for robustness and maintainability. diff --git a/files/docs/milestones/21/Part_2_Route_menu_UI_SFX_through_AudioManager_&_add_GUT_tests.md b/files/docs/milestones/21/Part_2_Route_menu_UI_SFX_through_AudioManager_&_add_GUT_tests.md new file mode 100644 index 000000000..6da3dab78 --- /dev/null +++ b/files/docs/milestones/21/Part_2_Route_menu_UI_SFX_through_AudioManager_&_add_GUT_tests.md @@ -0,0 +1,148 @@ +# Route menu UI SFX through AudioManager and add GUT tests + + +## Technical Documentation: PR #782 Summary + +### Overview + +**Title:** Route menu UI SFX through AudioManager and add GUT tests +**Author:** @ikostan +**PR:** [#782](https://github.com/ikostan/SkyLockAssault/pull/782) +**Project:** SkyLockAssault (Godot 4.x) +**Status:** Merged / In Review (as of June 26, 2026) +**Related Issue:** [#495](https://github.com/ikostan/SkyLockAssault/issues/495) β€” [FEATURE] UI Audio Logic Unit Tests (GUT): Confirmation & Cancellation Triggering + +### Purpose + +This PR centralizes menu UI sound effects (navigation and cancel) through the existing `AudioManager` singleton, eliminates duplicate sound triggers, removes dead or split audio responsibilities, and introduces a robust, cross-platform unit testing framework using GUT. It ensures zero environment leakage across test suites and guarantees stability on both local viewports and headless CI/CD runners. + +Use AudioManager.play_sfx for menu navigation/cancel sounds and add test helpers to support GUT. globals.gd: add a test-fallback menu detection via current_scene.name and route _play_ui_navigation_sfx through AudioManager. main_menu.gd: remove redundant cancel-button connection and trigger ui_cancel via AudioManager in _on_quit_dialog_canceled. Add comprehensive GUT tests (test_nav_escape_sfx.gd, test_quit_game_confirm_dialog_sfx.gd) with mock AudioManager scripts and UID files to validate navigation, cancel, and confirmation audio pathways. + +This PR focused on audio centralization, test coverage for UI interactions, and infrastructure hardening, with AI bots enhancing review depth and maintainability. The final output benefits from iterative human + bot collaboration. + +--- + +### Key Changes + +#### 1. Audio Centralization & Fallback Isolation (`scripts/core/globals.gd`) + +- **Legacy Player Removal**: Removed the local `_nav_sfx_player` variable, its preloaded asset definition, and its initialization sequence inside `_ready()`. +- **Asset Alignment**: Corrected the central audio routing key inside `_play_ui_navigation_sfx()` to use `"ui_navigation"`. This explicitly matches the underlying asset name (`ui_navigation.wav`) managed by the `AudioManager` pool. +- **Production-Safe Fallback Gate**: Wrapped the test-helper scene context detection within explicit feature guards: + + ```gdscript + if ( + (OS.has_feature("debug") or OS.has_feature("ci")) + and not is_menu_context + and get_tree().current_scene + and "Menu" in get_tree().current_scene.name + ): + is_menu_context = true + ``` + +This implementation completely isolates test-only fallback mechanics from exported release production templates. + +#### 2. Dialogue Dismissal Single-Dispatch Safety (`scripts/ui/menus/main_menu.gd`) + +- **Duplicate SFX Mitigation**: Disconnected the redundant `close_requested` window wire from `_setup_quit_dialog()`. This completely prevents dual execution loops during title-bar or Escape-key window dismissals. +- **Centralized Cancellation Audio**: Placed the cancellation trigger directly inside `_on_quit_dialog_canceled()` via `AudioManager.play_sfx("ui_cancel")`. +- **Dead Code Eradication**: Permanently deleted the orphaned manual click handler `_on_cancel_button_clicked()` to ensure warning-free compilation runs. +- **Process Termination Shield**: Introduced a `bypass_quit_for_testing` variable gated by feature flags inside `_on_quit_dialog_confirmed()`: + + ```gdscript + if (OS.has_feature("debug") or OS.has_feature("ci")) and bypass_quit_for_testing: + Globals.log_message("Bypassing game quit execution for unit testing.", Globals.LogLevel.DEBUG) + return + ``` + +This blocks `get_tree().quit()` execution streams from tearing down the running editor process or silently corrupting automated CI pipelines. + +#### 3. Advanced GUT Test Suites + +##### Suite A: `test/gut/test_nav_escape_sfx.gd` + +- **Headless Environment Recovery**: Programmed `before_each()` to dynamically construct and mount a temporary `dummy_scene_node` straight to `get_tree().current_scene` if the runner executes in a headless server context. +- **Cross-Suite Contamination Guard**: Configured explicit teardown blocks inside `after_each()` to unmount the dummy nodes and restore altered shared scene attributes. This guarantees zero string or naming state pollution leaks down the rest of the testing tree. +- **Optimization**: Eliminated structural dead weight by wiping a legacy `possible_fields` dictionary mapping routine that previously generated no-op execution traces. +- **Asset Synchronicity**: Updated directional validation assertions to track the synchronized `"ui_navigation"` lookup key string. + +##### Suite B: `test/gut/test_quit_game_confirm_dialog_sfx.gd` + +- **Hierarchy Tree Realization**: Replaced naked script instantiation calls (`MainMenuScript.new()`) with an explicit `MainMenuScene.instantiate()` pipeline structure. This properly builds out and evaluates `@onready` structural children to avoid base null instance crashes. +- **Active Protection Coverage**: Refactored `test_flat_button_anti_trigger_protection` to feed simulated flat button layouts directly into `Globals._on_node_added()` and validated behaviors using native `.pressed.emit()` signals. This transforms a false-positive test path into an authentic functional gate validator. +- **Automation Stability**: Added initialization rules to dynamically flip `bypass_quit_for_testing = true` on the scene instance prior to executing test frames. + +#### 4. Infrastructure Isolation Teardowns + +- **Pool Restoration**: Connected `restore_audio_mock()` logic to invoke `AudioManager.cleanup_for_test()` in the suite lifecycle `after_all()` teardown phase. This flushes out spy tracking structures and re-allocates a pristine, populated production channel pool for downstream testing scripts. +- **Checksum Verification**: Retained baseline asset checks matching version metrics within our workflow scripts. + +--- + +### Technical Benefits + +- **Pure Centralization**: Devolves voice allocation and LRU tracking exclusively onto `AudioManager`, removing rogue node footprint overhead. +- **Architectural Security**: Protects compilation and execution workflows via explicit feature-flag gating, isolating development code hooks from shipped deployment copies. +- **Testing Reliability**: Achieves authentic single-dispatch safety verification and removes cross-suite environmental leakage. + +### Testing & Validation + +- Fully validated locally inside the Godot editor workspace. +- Headless verification passes cleanly via automated terminal triggers. +- Complete CI/CD testing guarantees **9/9 passing gates** on remote Ubuntu integration pipelines. + +### Risks & Considerations + +- **Scene Name Dependency**: The debug/CI fallback relies on `"Menu"` substring in `current_scene.name`. Future scene renames could break this (mitigated by feature flags). +- **Test Brittleness**: Heavy use of mocks, signal emission, and scene tree manipulation β€” requires maintenance if Godot internals or GUT change. +- **Performance**: Negligible, but repeated `get_tree().current_scene` checks in `_input` (already guarded). +- **Platform Quirks**: Quit bypass only active in `debug`/`ci` builds; Web export behavior remains unchanged. + +--- + +## Reviewer's Guide + +Routes menu navigation and cancel SFX through AudioManager instead of a dedicated AudioStreamPlayer, introduces test-oriented menu-context detection and quit-bypass hooks, and adds GUT test suites plus CI/Docker hardening for Godot asset verification. + +### File-Level Changes + +| Change | Details | Files | +|-----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Route global menu navigation/cancel SFX through AudioManager and add a test-only menu-context fallback. |
  • Remove the dedicated _nav_sfx_player AudioStreamPlayer and its initialization, relying on AudioManager for SFX playback instead.
  • In_input, add a debug/CI-only fallback that treats scenes with "Menu" in their name as menu context when standard detection reports false.
  • Update _play_ui_navigation_sfx to invoke AudioManager.play_sfx("ui_navigation") and fix the key name to match the real asset.
| `scripts/core/globals.gd` | +| Centralize quit-dialog cancel SFX via AudioManager and add a test guard to prevent real quits. |
  • Introduce a bypass_quit_for_testing flag on the main menu script to prevent get_tree().quit() during automated tests.
  • Update quit dialog setup to rely solely on the canceled signal (which already covers close_requested) and remove the explicit close_requested connection and cancel-button pressed handler.
  • Play the cancel SFX inside _on_quit_dialog_canceled using AudioManager.play_sfx("ui_cancel"), replacing the removed _on_cancel_button_clicked path.
  • Add an early-return guard in _on_quit_dialog_confirmed when bypass_quit_for_testing is enabled, logging that quit was bypassed instead of quitting.
| `scripts/ui/menus/main_menu.gd` | +| Add GUT tests for global navigation/escape SFX routing and quit dialog confirmation/cancel audio behavior, using a mocked AudioManager. |
  • Create test_nav_escape_sfx.gd to cover menu-context gating, non-menu suppression, echo/slider/LineEdit gates, unrelated actions, and single-dispatch behavior for ui_navigation/ui_cancel.
  • . In test_nav_escape_sfx.gd, snapshot/restore Globals menu-context-related fields and current_scene.name, and drive inputs through Globals._input/_unhandled_input while stubbing AudioManager.play_sfx via a temporary script with an sfx_calls array.
  • Create test_quit_game_confirm_dialog_sfx.gd to instantiate the real main menu scene, enable bypass_quit_for_testing, and assert that _on_quit_dialog_confirmed/_on_quit_dialog_canceled trigger ui_accept/ui_cancel once, while flat buttons do not spuriously fire ui_accept via global hooks.
  • Add .uid files for the new GUT test scripts to register them with Godot’s resource system.
| `test/gut/test_nav_escape_sfx.gd`
`test/gut/test_quit_game_confirm_dialog_sfx.gd`
`test/gut/test_nav_escape_sfx.gd.uid`
`test/gut/test_quit_game_confirm_dialog_sfx.gd.uid` | +| Harden Godot binary/template verification in Docker and split CI jobs for Python tests and Godot asset checks. |
  • In Dockerfile, switch from SHA256SUMS.txt/sha256sum to SHA512-SUMS.txt/sha512sum for both engine and export-template downloads, updating comments and cleanup accordingly.
  • In test_ci_scripts.yml, rename the Python test job to test-python with a clearer name, and introduce a separate verify-godot job that checks out the repo and runs the existing verify_godot.sh script under a more descriptive job name and timeout.
| `Dockerfile`
`.github/workflows/test_ci_scripts.yml` | + +### Assessment against linked issues + +| Issue | Objective | Addressed | Explanation | +|--------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------| +| | Centralize UI navigation and cancel/quit dialog sound effects through AudioManager, removing direct AudioStreamPlayer usage and redundant button/signal wiring that could double-trigger SFX. | βœ… | | +| | Add a GUT test suite in res://test/gut/test_nav_escape_sfx.gd targeting Globals (globals.gd) that verifies global navigation and ui_cancel behavior, including menu-context gating, echo mitigation, focus gates for LineEdit/Slider, single-dispatch guarantee, and suppression for unrelated actions. | βœ… | | +| | Add a GUT test suite in res://test/gut/test_quit_game_confirm_dialog_sfx.gd targeting MainMenu (main_menu.gd) that verifies quit dialog confirmation and cancellation audio routing via AudioManager and ensures flat menu buttons do not trigger global ui_accept confirmation SFX. | βœ… | | + +### Possibly linked issues + +- **#N/A**: They match: PR implements the specified GUT suites (files, behaviors, AudioManager mocking) for UI audio logic. + +--- + +**Bots/AI Contributions to PR #782** + +### AI/Code Review Bots + +These automated tools provided summaries, reviews, suggestions, and feedback that contributed to the PR's quality, structure, and testing improvements: + +- **@sourcery-ai**: Generated a detailed PR summary, reviewer's guide, sequence diagrams, and code review comments. Highlighted issues like potential double SFX playback in `globals.gd`, test fallback concerns, and dead code in `main_menu.gd`. Provided high-level feedback and actionable prompts. +- **@coderabbitai**: Delivered a walkthrough of changes, bug fix/test/CI summaries, nitpick comments (e.g., dead `_on_cancel_button_clicked` handler, test snapshot logic improvements), and maintainability suggestions. Included poem and pre-merge checks. +- **@deepsourcebot** (DeepSource): Performed automated code review on changes (e.g., commits cd67305...0d3edef), providing a PR report card (Security, Reliability, Complexity, Hygiene) and inline comments via the DeepSource platform. No specific "DeepsourceReview" bot username observed; standard integration uses `@deepsourcebot`. + +No evidence of **@dependabot** or other dependency bots in this PR (all commits by human author; changes were manual code/test/CI updates). + +### Human Contributors + +- **@ikostan**: Primary author and sole code committer. Implemented core changes: routing menu UI SFX (`ui_navigation`/`ui_cancel`) through `AudioManager` in `globals.gd` and `main_menu.gd`; added comprehensive GUT tests (`test_nav_escape_sfx.gd`, `test_quit_game_confirm_dialog_sfx.gd` with mocks/UIDs); introduced test helpers (e.g., `bypass_quit_for_testing`, scene-name fallback for CI/debug); updated Dockerfile (SHA512 verification) and CI workflow (split jobs). Addressed review feedback iteratively across multiple commits. +- **@espanakosta-jpg**: No contributions or mentions found in this PR (commits, reviews, or conversation). + +--- + diff --git a/scripts/core/globals.gd b/scripts/core/globals.gd index 8c3cef891..a06b3ea21 100644 --- a/scripts/core/globals.gd +++ b/scripts/core/globals.gd @@ -38,9 +38,6 @@ var _is_loading_settings: bool = false # Guard flag ## Preloaded stream to prevent disk I/O lag during fast menu navigation. var _ui_nav_stream: AudioStream = preload(UI_NAV_SOUND_PATH) -# NEW: The persistent audio player to prevent node churn -var _nav_sfx_player: AudioStreamPlayer - # List of actions that should trigger the navigation sound var _nav_actions: Array[String] = [ "ui_up", "ui_down", "ui_left", "ui_right", "ui_focus_next", "ui_focus_prev" @@ -51,12 +48,6 @@ func _ready() -> void: # Keep processing inputs even when the game is paused! process_mode = Node.PROCESS_MODE_ALWAYS - # --- NEW: Initialize the permanent SFX player --- - _nav_sfx_player = AudioStreamPlayer.new() - _nav_sfx_player.stream = _ui_nav_stream - _nav_sfx_player.bus = AudioConstants.BUS_SFX_MENU - add_child(_nav_sfx_player) - # Load the resource here instead of preloading at the top settings = load("res://config_resources/default_settings.tres") as GameSettingsResource if settings == null: @@ -401,6 +392,16 @@ func _input(event: InputEvent) -> void: get_tree().paused or options_open or not hidden_menus.is_empty() or ui_has_focus ) + # Test helper fallback: support menu context detection via current_scene name for GUT tests + # Gated strictly behind debug/ci features to prevent leakage into production shipped builds. + if ( + (OS.has_feature("debug") or OS.has_feature("ci")) + and not is_menu_context + and get_tree().current_scene + and "Menu" in get_tree().current_scene.name + ): + is_menu_context = true + if not is_menu_context: return @@ -441,12 +442,8 @@ func _input(event: InputEvent) -> void: ## Internal helper to play the navigation sound through the dedicated Menu SFX bus. func _play_ui_navigation_sfx() -> void: - if not is_instance_valid(_nav_sfx_player): - return - - # If the sound is already playing (e.g., from rapid button presses), - # restart it from the beginning to feel responsive. - _nav_sfx_player.play() + # FIX: Correct the key string to match the true filename asset (ui_navigation.wav) + AudioManager.play_sfx("ui_navigation") ## Ensures the encryption key is initialized and returns it. diff --git a/scripts/ui/menus/main_menu.gd b/scripts/ui/menus/main_menu.gd index 83d364f61..ad38ac560 100644 --- a/scripts/ui/menus/main_menu.gd +++ b/scripts/ui/menus/main_menu.gd @@ -37,6 +37,8 @@ var quit_dialog: ConfirmationDialog var unbound_dialog: ConfirmationDialog var options_menu: PackedScene = preload("res://scenes/options_menu.tscn") var last_focused_button: Button = null # Tracks which button opened the dialog +# FIX: Safety flag to shield test runners from process termination loops +var bypass_quit_for_testing: bool = false var _start_pressed_cb: JavaScriptObject var _options_pressed_cb: JavaScriptObject var _quit_pressed_cb: JavaScriptObject @@ -203,12 +205,12 @@ func _setup_quit_dialog() -> void: # Confirmed = user wants to quit if not quit_dialog.confirmed.is_connected(_on_quit_dialog_confirmed): quit_dialog.confirmed.connect(_on_quit_dialog_confirmed) - # Canceled = Cancel button or Esc + + # Centralized Dismissal: In Godot, 'canceled' covers the explicit Cancel button, + # the Escape key, and title-bar Close (X) actions natively. Connecting close_requested + # here is redundant and would cause double audio triggers. if not quit_dialog.canceled.is_connected(_on_quit_dialog_canceled): quit_dialog.canceled.connect(_on_quit_dialog_canceled) - # Close button (Γ—) in title bar or other "just hide" cases - if not quit_dialog.close_requested.is_connected(_on_quit_dialog_canceled): - quit_dialog.close_requested.connect(_on_quit_dialog_canceled) # Clear generic audio connections on the internal Cancel button var cancel_button := quit_dialog.get_cancel_button() @@ -221,10 +223,6 @@ func _setup_quit_dialog() -> void: ): cancel_button.pressed.disconnect(connection.callable) - # Cleanly route explicit button clicks to play the cancellation audio - if not cancel_button.pressed.is_connected(_on_cancel_button_clicked): - cancel_button.pressed.connect(_on_cancel_button_clicked) - # Do the same for the OK button to prevent double-triggering the accept sound var ok_button := quit_dialog.get_ok_button() if is_instance_valid(ok_button): @@ -309,6 +307,13 @@ func _on_quit_dialog_confirmed() -> void: if is_instance_valid(quit_dialog): quit_dialog.hide() + # FIX: Guard against terminating the engine/editor during automated test execution + if bypass_quit_for_testing: + Globals.log_message( + "Bypassing game quit execution for unit testing.", Globals.LogLevel.DEBUG + ) + return + # 3. Execute platform-specific quit execution path if OS.get_name() == "Web": # Offload the delay to JavaScript instead of utilizing a Godot await @@ -328,14 +333,10 @@ func _on_quit_dialog_confirmed() -> void: Globals.log_message("Native quit executed!", Globals.LogLevel.DEBUG) -func _on_cancel_button_clicked() -> void: - ## Triggers the cancel audio stream exclusively on manual mouse clicks. - AudioManager.play_sfx("ui_cancel") - - func _on_quit_dialog_canceled() -> void: ## Handles quit dialog cancellation visual resets and focus recovery. ## :rtype: void + AudioManager.play_sfx("ui_cancel") quit_dialog.hide() Globals.log_message("Quit canceled.", Globals.LogLevel.DEBUG) diff --git a/test/gut/test_nav_escape_sfx.gd b/test/gut/test_nav_escape_sfx.gd new file mode 100644 index 000000000..02f934ebc --- /dev/null +++ b/test/gut/test_nav_escape_sfx.gd @@ -0,0 +1,263 @@ +## Copyright (C) 2026 Egor Kostan +## SPDX-License-Identifier: GPL-3.0-or-later +## test_nav_escape_sfx.gd +## GUT unit tests for global navigation and escape audio routing gates. + +extends "res://addons/gut/test.gd" + +var globals_instance: Node +var original_audio_script: Script +var original_scene_name: String = "" +var dummy_scene_node: Node = null + +## Suite setup: Double the AudioManager using a decoupled script to bypass lifecycle destruction guards. +## :rtype: void +func before_all() -> void: + if is_instance_valid(AudioManager): + original_audio_script = AudioManager.get_script() + var mock_script := GDScript.new() + mock_script.source_code = """ +extends Node +var sfx_calls: Array = [] +func play_sfx(key: String, extra: Variant = null) -> void: + sfx_calls.append([key, extra]) +""" + mock_script.reload() + AudioManager.set_script(mock_script) + + +## Suite cleanup: Safely restore original production script after all tests execute. +## :rtype: void +func after_all() -> void: + if original_audio_script and is_instance_valid(AudioManager): + AudioManager.set_script(original_audio_script) + # Re-populate and rebuild the internal variable states wiped by set_script() + if AudioManager.has_method("cleanup_for_test"): + AudioManager.cleanup_for_test() + + +## Per-test setup: Snapshot the shared SceneTree state and reset mock logs. +## :rtype: void +func before_each() -> void: + globals_instance = Globals + + # Securely snapshot the active root scene name before any mutations occur + if get_tree().current_scene: + original_scene_name = get_tree().current_scene.name + dummy_scene_node = null + else: + # FIX: If running headlessly/CI, create a temporary dummy node to safely act as current_scene + dummy_scene_node = Node.new() + get_tree().root.add_child(dummy_scene_node) + get_tree().current_scene = dummy_scene_node + original_scene_name = "" + + if AudioManager.get("sfx_calls") != null: + AudioManager.set("sfx_calls", []) + + await get_tree().process_frame + + +## Per-test cleanup: Restore the shared engine scene tree name wrapper cleanly. +## :rtype: void +func after_each() -> void: + # FIX: Cleanly teardown and unmount the dummy scene tracker if initialized + if dummy_scene_node and is_instance_valid(dummy_scene_node): + if get_tree().current_scene == dummy_scene_node: + get_tree().current_scene = null + dummy_scene_node.queue_free() + dummy_scene_node = null + elif original_scene_name != "" and get_tree().current_scene: + get_tree().current_scene.name = original_scene_name + + original_scene_name = "" + await get_tree().process_frame + + +## Helper assertions for tracking explicit mock array triggers +func _assert_sfx_called(key: String) -> void: + var found := false + var calls: Array = AudioManager.get("sfx_calls") + for c: Array in calls: + if c[0] == key: + found = true + break + assert_true(found, "Expected play_sfx to be called with: " + key) + + +func _assert_sfx_call_count(count: int) -> void: + var actual_count: int = AudioManager.get("sfx_calls").size() + assert_eq(actual_count, count, "Expected play_sfx to be called %d times. Got %d." % [count, actual_count]) + + +func _assert_sfx_not_called() -> void: + var actual_count: int = AudioManager.get("sfx_calls").size() + assert_eq(actual_count, 0, "Expected zero play_sfx calls. Got %d." % actual_count) + + +## Helper to safely mutate menu context layout titles to engage the debug feature gates. +## :rtype: void +func _set_menu_context(value: bool) -> void: + if get_tree().current_scene: + get_tree().current_scene.name = "MainMenu" if value else "GameLevel" + + +## Helper to feed simulated inputs safely and directly through the pipeline contexts. +## :rtype: void +func _simulate_input(event: InputEvent) -> void: + # FIX: Invoke only the explicit _input path to ensure we strictly test the audio routing contract + if is_instance_valid(globals_instance) and globals_instance.has_method("_input"): + globals_instance._input(event) + + +## Assert that simulating a ui_cancel input event when inside a menu context triggers cancel audio. +## :rtype: void +func test_global_cancellation_in_menu_context() -> void: + _set_menu_context(true) + + var event: InputEventAction = InputEventAction.new() + event.action = "ui_cancel" + event.pressed = true + + _simulate_input(event) + _assert_sfx_called("ui_cancel") + _assert_sfx_call_count(1) + + +## Assert that no interface audio streams are triggered if input events occur outside menu contexts. +## :rtype: void +func test_global_out_of_context_suppression() -> void: + _set_menu_context(false) + + var event_cancel: InputEventAction = InputEventAction.new() + event_cancel.action = "ui_cancel" + event_cancel.pressed = true + + var event_nav: InputEventAction = InputEventAction.new() + event_nav.action = "ui_up" + event_nav.pressed = true + + _simulate_input(event_cancel) + _simulate_input(event_nav) + _assert_sfx_not_called() + + +## Assert that immediately after leaving a menu context, subsequent cancel inputs do not trigger audio. +## :rtype: void +func test_menu_context_exit_cleanup() -> void: + _set_menu_context(true) + _set_menu_context(false) + + var event: InputEventAction = InputEventAction.new() + event.action = "ui_cancel" + event.pressed = true + + _simulate_input(event) + _assert_sfx_not_called() + + +## Assert that repeated input instances flagged as echo completely bypass the audio frame. +## :rtype: void +func test_input_repeat_gate_echo_mitigation() -> void: + _set_menu_context(true) + + var event: InputEventKey = InputEventKey.new() + event.echo = true + event.pressed = true + event.physical_keycode = KEY_UP + + _simulate_input(event) + _assert_sfx_not_called() + + +## Assert that directional actions successfully trigger the navigation tick when components are focused. +## :rtype: void +func test_navigation_positive_case() -> void: + _set_menu_context(true) + + var dummy_btn: Button = Button.new() + add_child_autofree(dummy_btn) + dummy_btn.grab_focus() + await get_tree().process_frame + + var event: InputEventAction = InputEventAction.new() + event.action = "ui_up" + event.pressed = true + + _simulate_input(event) + _assert_sfx_called("ui_navigation") + + +## Assert that a single discrete navigation input event produces exactly one audio trigger request. +## :rtype: void +func test_single_dispatch_guarantee() -> void: + _set_menu_context(true) + + var dummy_btn: Button = Button.new() + add_child_autofree(dummy_btn) + dummy_btn.grab_focus() + await get_tree().process_frame + + var event: InputEventAction = InputEventAction.new() + event.action = "ui_down" + event.pressed = true + + _simulate_input(event) + _assert_sfx_call_count(1) + + +## Assert that when a LineEdit control node holds active focus, global interface sound is suppressed. +## :rtype: void +func test_value_editing_text_gate() -> void: + _set_menu_context(true) + + var line_edit: LineEdit = LineEdit.new() + add_child_autofree(line_edit) + line_edit.grab_focus() + await get_tree().process_frame + + var event: InputEventAction = InputEventAction.new() + # FIX: Swapped from ui_accept to ui_cancel to properly challenge the text control bypass branch + event.action = "ui_cancel" + event.pressed = true + + _simulate_input(event) + _assert_sfx_not_called() + + +## Assert that when a Slider control holds active focus, horizontal directional inputs are bypassed. +## :rtype: void +func test_slider_double_audio_gate() -> void: + _set_menu_context(true) + + var slider: HSlider = HSlider.new() + add_child_autofree(slider) + slider.grab_focus() + await get_tree().process_frame + + var event_left: InputEventAction = InputEventAction.new() + event_left.action = "ui_left" + event_left.pressed = true + + var event_right: InputEventAction = InputEventAction.new() + event_right.action = "ui_right" + event_right.pressed = true + + _simulate_input(event_left) + _simulate_input(event_right) + _assert_sfx_not_called() + + +## Assert that unrelated input actions never trigger interface selection, navigation, or cancellation audio. +## :rtype: void +func test_unrelated_action_integrity() -> void: + _set_menu_context(true) + + var actions: Array[String] = ["weapon_fire", "pause_toggle", "move_left"] + for act: String in actions: + var event: InputEventAction = InputEventAction.new() + event.action = act + event.pressed = true + _simulate_input(event) + + _assert_sfx_not_called() diff --git a/test/gut/test_nav_escape_sfx.gd.uid b/test/gut/test_nav_escape_sfx.gd.uid new file mode 100644 index 000000000..f4d825944 --- /dev/null +++ b/test/gut/test_nav_escape_sfx.gd.uid @@ -0,0 +1 @@ +uid://xw6wj4banogu diff --git a/test/gut/test_quit_game_confirm_dialog_sfx.gd b/test/gut/test_quit_game_confirm_dialog_sfx.gd new file mode 100644 index 000000000..a624d298f --- /dev/null +++ b/test/gut/test_quit_game_confirm_dialog_sfx.gd @@ -0,0 +1,129 @@ +## Copyright (C) 2026 Egor Kostan +## SPDX-License-Identifier: GPL-3.0-or-later +## test_quit_game_confirm_dialog_sfx.gd +## GUT unit tests for main menu quit dialog confirmation audio pathways. + +extends "res://addons/gut/test.gd" + +# FIX: Load the PackedScene layout to build the required @onready subnode tree hierarchy +var MainMenuScene: PackedScene = load("res://scenes/main_menu.tscn") +var main_menu_instance: Control +var original_audio_script: Script +var original_fields := {} + +## Suite setup: Double the AudioManager using a decoupled script to bypass lifecycle destruction guards. +## :rtype: void +func before_all() -> void: + if is_instance_valid(AudioManager): + original_audio_script = AudioManager.get_script() + var mock_script := GDScript.new() + mock_script.source_code = """ +extends Node +var sfx_calls: Array = [] +func play_sfx(key: String, extra: Variant = null) -> void: + sfx_calls.append([key, extra]) +""" + mock_script.reload() + AudioManager.set_script(mock_script) + + +## Suite cleanup: Safely restore original production script after all tests execute. +## :rtype: void +func after_all() -> void: + if original_audio_script and is_instance_valid(AudioManager): + AudioManager.set_script(original_audio_script) + # FIX: Re-populate and rebuild the internal variable states wiped by set_script() + if AudioManager.has_method("cleanup_for_test"): + AudioManager.cleanup_for_test() + + +## Per-test setup: Instantiate target menu scene layout tree and clear call logs. +## :rtype: void +func before_each() -> void: + # Instantiate the scene instead of using script.new() to initialize @onready child nodes + main_menu_instance = MainMenuScene.instantiate() as Control + + # FIX: Turn on the safety guard to block get_tree().quit() from crashing the runner + if "bypass_quit_for_testing" in main_menu_instance: + main_menu_instance.set("bypass_quit_for_testing", true) + + add_child_autofree(main_menu_instance) + + if AudioManager.get("sfx_calls") != null: + AudioManager.set("sfx_calls", []) + + await get_tree().process_frame + + +## Per-test cleanup: Release active control focuses cleanly. +## :rtype: void +func after_each() -> void: + var focus_owner: Control = get_viewport().gui_get_focus_owner() + if is_instance_valid(focus_owner): + focus_owner.release_focus() + await get_tree().process_frame + + +## Helper assertions for tracking explicit mock array triggers +func _assert_sfx_called(key: String) -> void: + var found := false + var calls: Array = AudioManager.get("sfx_calls") + for c: Array in calls: + if c[0] == key: + found = true + break + assert_true(found, "Expected play_sfx to be called with: " + key) + + +func _assert_sfx_call_count(count: int) -> void: + var actual_count: int = AudioManager.get("sfx_calls").size() + assert_eq(actual_count, count, "Expected play_sfx to be called %d times. Got %d." % [count, actual_count]) + + +func _assert_sfx_not_called(key: String) -> void: + var found := false + var calls: Array = AudioManager.get("sfx_calls") + for c: Array in calls: + if c[0] == key: + found = true + break + assert_false(found, "Expected play_sfx NOT to be called with: " + key) + + +## Assert that calling the localized _on_quit_dialog_confirmed sequence triggers an accept chime. +## :rtype: void +func test_dialog_confirmation_audio() -> void: + if main_menu_instance.has_method("_on_quit_dialog_confirmed"): + main_menu_instance._on_quit_dialog_confirmed() + + _assert_sfx_called("ui_accept") + _assert_sfx_call_count(1) + + +## Assert that invoking the localized _on_quit_dialog_canceled sequence triggers a cancellation sound. +## :rtype: void +func test_dialog_cancellation_audio() -> void: + if main_menu_instance.has_method("_on_quit_dialog_canceled"): + main_menu_instance._on_quit_dialog_canceled() + + _assert_sfx_called("ui_cancel") + _assert_sfx_call_count(1) + + +## Assert that standard menu buttons do not trigger global confirmation requests on ui_accept. +## :rtype: void +func test_flat_button_anti_trigger_protection() -> void: + var start_button: Button = Button.new() + start_button.flat = true + main_menu_instance.add_child(start_button) + + # FIX: Explicitly drive the button through the global connection hook to mimic tree entry + Globals._on_node_added(start_button) + await get_tree().process_frame + + # FIX: Directly emit the pressed signal to verify the global hook was successfully blocked + start_button.pressed.emit() + await get_tree().process_frame + + # Global accept confirmation must remain untouched to respect native inspector themes + _assert_sfx_not_called("ui_accept") diff --git a/test/gut/test_quit_game_confirm_dialog_sfx.gd.uid b/test/gut/test_quit_game_confirm_dialog_sfx.gd.uid new file mode 100644 index 000000000..bd55c76d1 --- /dev/null +++ b/test/gut/test_quit_game_confirm_dialog_sfx.gd.uid @@ -0,0 +1 @@ +uid://c4pkp12c0867y