Skip to content

Route menu UI SFX through AudioManager and add GUT tests#782

Merged
ikostan merged 23 commits into
mainfrom
ui-audio-logic-unit-tests-gut-confirmation-cancellation-triggering
Jun 27, 2026
Merged

Route menu UI SFX through AudioManager and add GUT tests#782
ikostan merged 23 commits into
mainfrom
ui-audio-logic-unit-tests-gut-confirmation-cancellation-triggering

Conversation

@ikostan

@ikostan ikostan commented Jun 26, 2026

Copy link
Copy Markdown
Owner

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.


name: Default Pull Request Template
about: Suggesting changes to SkyLockAssault
title: ''
labels: ''
assignees: ''

Description

What does this PR do? (e.g., "Fixes player jump physics in level 2" or "Adds
new enemy AI script")

Related Issue

Closes #ISSUE_NUMBER (if applicable)

Changes

  • List key changes here (e.g., "Updated Jump.gd to use Godot 4.4's new Tween
    system")
  • Any breaking changes? (e.g., "Deprecated old signal; migrate to new one")

Testing

  • Ran the game in Godot v4.5 editor—describe what you tested (e.g., "Jump
    works on Win10 with 60 FPS")
  • Any new unit tests added? (Link to test scene if yes)
  • Screenshots/GIFs if UI-related: (Attach below)

Checklist

  • Code follows Godot style guide (e.g., snake_case for variables)
  • No console errors in editor/output
  • Ready for review!

Additional Notes

Anything else? (e.g., "Tested on Win10 64-bit; needs Linux validation")

Summary by Sourcery

Route menu UI navigation and cancel sounds through the central AudioManager and add automated coverage for menu audio behavior and Godot asset verification.

New Features:

  • Add GUT test suites to validate global menu navigation/cancel audio routing and main menu quit dialog confirmation/cancellation sounds.

Bug Fixes:

  • Ensure UI navigation SFX uses the correct AudioManager key matching the ui_navigation.wav asset.
  • Prevent duplicate quit dialog cancel audio by removing redundant close_requested connections and routing cancellation through a single handler.
  • Guard quit dialog confirmation logic in tests via a bypass flag to avoid terminating the engine during automated runs.
  • Add debug/CI-only fallback menu detection via current scene name to stabilize menu audio tests.

Enhancements:

  • Refine global menu input handling to use AudioManager for UI navigation SFX instead of a dedicated AudioStreamPlayer instance.

Build:

  • Update Dockerfile to verify Godot editor and export templates using official SHA512 checksums instead of SHA256.

CI:

  • Split CI workflow into separate Python CI-injection tests and a dedicated Godot asset verification job.

Tests:

  • Introduce GUT tests for global navigation/escape audio routing and main menu quit dialog SFX behavior, using AudioManager mocks and UID metadata files.

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes

    • Improved menu navigation and cancellation audio routing for more consistent ui_navigation / ui_cancel behavior.
    • Quit dialog confirm/cancel sounds now follow the dialog interaction reliably.
    • Audio is suppressed outside menu context and while specific UI controls are focused.
  • Tests

    • Added/expanded GUT coverage for navigation/escape and quit dialog sound effects, including anti-trigger and audio routing edge cases.
  • Documentation

    • Added milestone documentation describing the menu sound routing and testing updates.
  • CI / Build

    • Updated test CI job naming and added Godot asset verification.
    • Switched Docker integrity checks to SHA-512.

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).

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.

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.
@sourcery-ai

sourcery-ai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Route menu navigation and cancel UI sound effects through the centralized AudioManager, add GUT test suites to validate global menu/quit-dialog audio behavior, and tighten CI/Godot integrity checks and milestone docs formatting.

Sequence diagram for routed menu UI SFX through AudioManager

sequenceDiagram
    actor User
    participant Globals as Globals
    participant MainMenu as MainMenu
    participant QuitDialog as ConfirmationDialog
    participant AudioManager as AudioManager

    %% Navigation SFX via Globals
    User->>Globals: _input(event: InputEvent)
    alt [event is ui_nav action]
        alt [debug/ci menu detection via current_scene.name]
            Globals-->>Globals: is_menu_context = true
        end
        Globals->>Globals: _play_ui_navigation_sfx()
        Globals->>AudioManager: play_sfx(ui_navigation)
    end

    %% Cancel SFX via quit dialog
    User->>QuitDialog: cancel (button/Esc/close)
    QuitDialog->>MainMenu: canceled
    MainMenu->>MainMenu: _on_quit_dialog_canceled()
    MainMenu->>AudioManager: play_sfx(ui_cancel)

    %% Confirm SFX and guarded quit
    User->>QuitDialog: confirm
    QuitDialog->>MainMenu: confirmed
    MainMenu->>MainMenu: _on_quit_dialog_confirmed()
    MainMenu->>QuitDialog: hide()
    alt [bypass_quit_for_testing]
        MainMenu-->>MainMenu: log_message("Bypassing game quit execution for unit testing.")
    else [normal runtime]
        alt [OS.get_name() == Web]
            MainMenu->>JavaScriptBridge: eval(setTimeout(...))
        else [native]
            MainMenu->>MainMenu: await get_tree().create_timer(audio_flush_delay).timeout
            MainMenu->>MainMenu: get_tree().quit()
        end
    end
Loading

File-Level Changes

Change Details Files
Route global menu navigation and cancel SFX through AudioManager instead of a dedicated AudioStreamPlayer and add a menu-context fallback for tests.
  • Remove the persistent _nav_sfx_player AudioStreamPlayer and its initialization in the globals singleton.
  • Update _play_ui_navigation_sfx to call AudioManager.play_sfx("ui_navigation") with a key matching the ui_navigation.wav asset.
  • Introduce a debug/CI-only fallback in _input that treats scenes with "Menu" in their name as menu context when standard detection fails.
scripts/core/globals.gd
Simplify quit dialog cancel handling, centralize cancel SFX, and add a test guard to avoid terminating the engine during automated runs.
  • Add a bypass_quit_for_testing flag on the main menu script and short‑circuit _on_quit_dialog_confirmed when it is enabled in debug/CI builds.
  • Rely solely on the ConfirmationDialog.canceled signal for cancel/close behavior and remove the redundant close_requested connection.
  • Play ui_cancel via AudioManager.play_sfx in _on_quit_dialog_canceled and remove the unused _on_cancel_button_clicked handler.
scripts/ui/menus/main_menu.gd
Add GUT test suites that mock AudioManager to verify global navigation/cancel SFX routing and quit dialog audio behavior.
  • Introduce test_nav_escape_sfx.gd to drive Globals._input/_unhandled_input with synthetic InputEvents, asserting navigation/cancel SFX behavior under menu-context, echo, focus, and unrelated-action scenarios.
  • Introduce test_quit_game_confirm_dialog_sfx.gd to instantiate the real main menu scene, enable bypass_quit_for_testing, and assert ui_accept/ui_cancel playback for confirm/cancel while flat buttons do not spuriously trigger ui_accept.
  • Register the new tests with Godot via .uid metadata files and ensure AudioManager’s original script is restored and reinitialized after tests via cleanup_for_test when available.
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 and split CI responsibilities between Python tests and Godot asset checks.
  • Switch Dockerfile Godot engine and export template verification from SHA256SUMS/sha256sum to SHA512-SUMS/sha512sum, updating comments and cleanup accordingly.
  • Rename the CI job that runs Python tests to test-python and add a separate verify-godot job that runs the verify_godot.sh asset/infrastructure check.
Dockerfile
.github/workflows/test_ci_scripts.yml
Tidy milestone and README documentation formatting around audio, testing, and CI milestones.
  • Normalize list markers, headings, and URL formatting in multiple milestone markdown files to satisfy markdownlint and improve readability.
  • Clarify some descriptions (e.g., contributors sections, issue tables) without changing behavior.
  • Fix minor list-style and spacing issues in README audio/architecture sections.
files/docs/milestones/19/PART_2_Add_UI_auto_mute_with_click_SFX_and_expand_audio_integrity_tests.md
files/docs/milestones/19/PART_1_test_plan_implementation_for_ui_bus_persistence.md
files/docs/milestones/19/PART_4_gameplay_settings_audio_interaction_and_asset_tracking.md
files/docs/milestones/21/Part_1_UI_Accept_&_Cancel_SFX.md
files/docs/milestones/20/Part_2_Upgrade_project_environment_to_Godot_4_6_3.md
files/docs/milestones/20/Part_1_UI_audio_logic_unit_tests.md
files/docs/milestones/18/PART_2_CI_CD_Infrastructure_Updates_&_Caching_Hardening.md
files/docs/milestones/19/PART_3_Update_Godot_export_action_pin_and_configure_Codecov_token.md
files/docs/milestones/18/PART_1_Encryption_key_generation_failure_in_web_export.md
README.md
files/docs/milestones/21/Part_2_Route_menu_UI_SFX_through_AudioManager_&_add_GUT_tests.md

Assessment against linked issues

Issue Objective Addressed Explanation
#495 Add a GUT test suite res://test/gut/test_nav_escape_sfx.gd targeting Globals (globals.gd) that validates global navigation and ui_cancel behavior, including menu-context gating, out-of-context suppression, context-exit cleanup, echo mitigation, navigation positive/single-dispatch behavior, LineEdit and Slider focus gates, and suppression for unrelated actions, using an AudioManager mock to assert play_sfx calls.
#495 Add a GUT test suite res://test/gut/test_quit_game_confirm_dialog_sfx.gd targeting MainMenu (main_menu.gd) that validates quit dialog confirmation and cancellation audio routing via AudioManager.play_sfx("ui_accept"/"ui_cancel") and ensures flat/standard menu buttons do not trigger the global confirmation sound, using appropriate scene instantiation and AudioManager mocking.
#495 Align the UI audio implementation with the testing design: route menu navigation and cancel/quit dialog SFX through AudioManager instead of local AudioStreamPlayer instances, avoid redundant/cancel button-specific manual SFX calls, keep flat buttons and local theme audio decoupled from the global action-map confirmation system, and add any necessary test-only hooks (e.g., menu context fallback, quit bypass) without affecting production behavior.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 852b1b8f-1cfb-4521-8d52-31f5654f9828

📥 Commits

Reviewing files that changed from the base of the PR and between f93d0e6 and 2cf5ea7.

📒 Files selected for processing (12)
  • README.md
  • files/docs/milestones/18/PART_1_Encryption_key_generation_failure_in_web_export.md
  • files/docs/milestones/18/PART_2_CI_CD_Infrastructure_Updates_&_Caching_Hardening.md
  • files/docs/milestones/19/PART_1_test_plan_implementation_for_ui_bus_persistence.md
  • files/docs/milestones/19/PART_2_Add_UI_auto_mute_with_click_SFX_and_expand_audio_integrity_tests.md
  • files/docs/milestones/19/PART_3_Update_Godot_export_action_pin_and_configure_Codecov_token.md
  • files/docs/milestones/19/PART_4_gameplay_settings_audio_interaction_and_asset_tracking.md
  • files/docs/milestones/20/Part_1_UI_audio_logic_unit_tests.md
  • files/docs/milestones/20/Part_2_Upgrade_project_environment_to_Godot_4_6_3.md
  • files/docs/milestones/21/Part_1_UI_Accept_&_Cancel_SFX.md
  • files/docs/milestones/21/Part_2_Route_menu_UI_SFX_through_AudioManager_&_add_GUT_tests.md
  • test/gut/test_nav_escape_sfx.gd
✅ Files skipped from review due to trivial changes (8)
  • files/docs/milestones/18/PART_1_Encryption_key_generation_failure_in_web_export.md
  • files/docs/milestones/19/PART_1_test_plan_implementation_for_ui_bus_persistence.md
  • README.md
  • files/docs/milestones/19/PART_2_Add_UI_auto_mute_with_click_SFX_and_expand_audio_integrity_tests.md
  • files/docs/milestones/21/Part_1_UI_Accept_&_Cancel_SFX.md
  • files/docs/milestones/19/PART_3_Update_Godot_export_action_pin_and_configure_Codecov_token.md
  • files/docs/milestones/20/Part_1_UI_audio_logic_unit_tests.md
  • files/docs/milestones/19/PART_4_gameplay_settings_audio_interaction_and_asset_tracking.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/gut/test_nav_escape_sfx.gd
📜 Recent review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: CI/CD Infrastructure Tests / Test Godot Asset Infrastructure and Signature Verification
  • GitHub Check: GUT Unit Tests / unit-test
🧰 Additional context used
🪛 LanguageTool
files/docs/milestones/18/PART_2_CI_CD_Infrastructure_Updates_&_Caching_Hardening.md

[uncategorized] ~69-~69: The official name of this software platform is spelled with a capital “H”.
Context: ...and provided full documentation. * .github/workflows/deploy_to_itch.yml: Upgrade...

(GITHUB)

files/docs/milestones/20/Part_2_Upgrade_project_environment_to_Godot_4_6_3.md

[uncategorized] ~88-~88: The official name of this software platform is spelled with a capital “H”.
Context: ...date godot_executable_download_url in .github/workflows/browser_test.yml to use the ...

(GITHUB)


[uncategorized] ~89-~89: The official name of this software platform is spelled with a capital “H”.
Context: ...godot_export_templates_download_urlin.github/workflows/browser_test.yml` to use the ...

(GITHUB)

files/docs/milestones/21/Part_2_Route_menu_UI_SFX_through_AudioManager_&_add_GUT_tests.md

[style] ~73-~73: ‘prior to’ might be wordy. Consider a shorter alternative.
Context: ...r_testing = true` on the scene instance prior to executing test frames. #### 4. Infrast...

(EN_WORDINESS_PREMIUM_PRIOR_TO)


[uncategorized] ~114-~114: The official name of this software platform is spelled with a capital “H”.
Context: ... | Dockerfile
.github/workflows/test_ci_scripts.yml ...

(GITHUB)


📝 Walkthrough

Walkthrough

The PR routes menu UI sound effects through AudioManager, changes quit-dialog cancel handling, adds GUT coverage for navigation and quit flows, and updates CI, Docker, README, and milestone documentation.

Changes

UI audio routing and verification

Layer / File(s) Summary
Global UI audio routing
scripts/core/globals.gd
Globals removes the persistent navigation SFX player, adds a scene-name fallback for menu context detection, and plays UI navigation sounds through AudioManager.play_sfx("ui_navigation").
Quit-dialog audio and bypass
scripts/ui/menus/main_menu.gd, test/gut/test_quit_game_confirm_dialog_sfx.gd, test/gut/test_quit_game_confirm_dialog_sfx.gd.uid
main_menu.gd adds a quit-confirmation bypass flag, disconnects redundant cancel wiring, and makes _on_quit_dialog_canceled() play ui_cancel; the quit-dialog GUT test mocks AudioManager and checks confirm, cancel, and unrelated button paths.
Navigation and escape audio tests
test/gut/test_nav_escape_sfx.gd, test/gut/test_nav_escape_sfx.gd.uid
The navigation and escape GUT suite replaces AudioManager with a mock, sets temporary scene context, and verifies cancel, navigation, focus, echo, slider, and unrelated input cases.
CI, Docker, and docs
.github/workflows/test_ci_scripts.yml, Dockerfile, README.md, files/docs/milestones/*
test_ci_scripts.yml renames the Python injection job and adds verify-godot; the Dockerfile switches Godot checksum verification to SHA512; README and milestone docs are reformatted and updated to describe the same UI audio, testing, and verification changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

menu, GUI, controls

Poem

🐇 I hopped through menus, bright and keen,
ui_navigation rang across the scene.
ui_cancel chimed when dialogs said no,
And tests kept every little bunny flow.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes routing menu UI SFX through AudioManager and adding GUT tests.
Description check ✅ Passed The description covers the main changes, testing, checklist, and notes, though the related issue field is left generic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ui-audio-logic-unit-tests-gut-confirmation-cancellation-triggering

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@deepsource-io

deepsource-io Bot commented Jun 26, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in cd67305...2cf5ea7 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Python Jun 27, 2026 3:38a.m. Review ↗
JavaScript Jun 27, 2026 3:38a.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In globals.gd, _play_ui_navigation_sfx() now calls AudioManager.play_sfx() but still retains the _nav_sfx_player validity check and related logic that is no longer used; consider either delegating everything to AudioManager and removing the dead _nav_sfx_player code, or keeping _nav_sfx_player as the single source of playback to avoid split responsibility.
  • The test helper fallback that infers is_menu_context from get_tree().current_scene.name containing "Menu" affects runtime behavior globally; consider narrowing this (e.g., behind a test flag, checking Engine.is_editor_hint(), or using a dedicated test hook) so production scenes are not misclassified as menus based solely on naming.
  • After removing the cancel button connection in main_menu.gd, _on_cancel_button_clicked() appears to be unused; consider removing it or documenting why it must remain to avoid confusion about which path is responsible for playing the cancel SFX.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `globals.gd`, `_play_ui_navigation_sfx()` now calls `AudioManager.play_sfx()` but still retains the `_nav_sfx_player` validity check and related logic that is no longer used; consider either delegating everything to `AudioManager` and removing the dead `_nav_sfx_player` code, or keeping `_nav_sfx_player` as the single source of playback to avoid split responsibility.
- The test helper fallback that infers `is_menu_context` from `get_tree().current_scene.name` containing "Menu" affects runtime behavior globally; consider narrowing this (e.g., behind a test flag, checking `Engine.is_editor_hint()`, or using a dedicated test hook) so production scenes are not misclassified as menus based solely on naming.
- After removing the cancel button connection in `main_menu.gd`, `_on_cancel_button_clicked()` appears to be unused; consider removing it or documenting why it must remain to avoid confusion about which path is responsible for playing the cancel SFX.

## Individual Comments

### Comment 1
<location path="scripts/core/globals.gd" line_range="449-450" />
<code_context>
+## Internal helper to play the navigation sound through the dedicated Menu SFX bus.
 ## Internal helper to play the navigation sound through the dedicated Menu SFX bus.
 func _play_ui_navigation_sfx() -> void:
+	# Route through AudioManager for centralization and test suite assertion tracking
+	AudioManager.play_sfx("ui_nav_tick")
+
 	if not is_instance_valid(_nav_sfx_player):
</code_context>
<issue_to_address>
**issue (bug_risk):** Navigation SFX is likely being played twice (AudioManager and `_nav_sfx_player`).

The added `AudioManager.play_sfx("ui_nav_tick")` call now runs in addition to the existing `_nav_sfx_player` logic, so the sound may play twice when `_nav_sfx_player` is valid, which also conflicts with the stated centralization goal. If you want to centralize via `AudioManager`, consider removing or conditioning the `_nav_sfx_player` path, or choose a single playback path (e.g., via a feature flag).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread scripts/core/globals.gd Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/core/globals.gd (1)

448-457: 🩺 Stability & Availability | 🟡 Minor

Align the navigation SFX name with the existing asset

AudioManager.play_sfx("ui_nav_tick") points at a missing file; only ui_navigation.wav exists. That makes the new call a no-op while _nav_sfx_player.play() still drives the sound. Rename the AudioManager call to the existing asset name or add ui_nav_tick.wav before removing the legacy player.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/core/globals.gd` around lines 448 - 457, The navigation SFX
identifier in _play_ui_navigation_sfx() points to a missing asset, so the
AudioManager call becomes a no-op while the legacy _nav_sfx_player still plays
the sound. Update the AudioManager.play_sfx(...) argument to match the existing
ui_navigation.wav asset, or add the missing ui_nav_tick.wav asset before
removing the fallback player path. Use the _play_ui_navigation_sfx and
_nav_sfx_player symbols to locate the affected logic.
🧹 Nitpick comments (2)
scripts/ui/menus/main_menu.gd (1)

327-330: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

_on_cancel_button_clicked is now dead code.

Its only wiring (the cancel-button pressed connection in _setup_quit_dialog) was removed in this PR, and the cancel SFX now lives in _on_quit_dialog_canceled. This handler is no longer referenced and can be deleted.

🧹 Proposed removal
-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:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ui/menus/main_menu.gd` around lines 327 - 330, Delete the now-unused
_on_cancel_button_clicked handler in main_menu.gd because its only connection
was removed and the cancel sound logic has moved to _on_quit_dialog_canceled;
make sure no remaining calls or signal bindings reference
_on_cancel_button_clicked after removing it.
test/gut/test_nav_escape_sfx.gd (1)

40-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Snapshot fields never match Globals, so _set_menu_context only flips the scene name.

is_menu_context is a local variable inside Globals._input, not a member field — none of the possible_fields exist on the singleton, so original_fields stays empty and the for field ... set(field, value) loop in _set_menu_context is a no-op. The tests therefore exercise only the new scene-name fallback (current_scene.name = "MainMenu"/"GameLevel"). That is fine for what's under test, but the field-snapshot machinery is dead weight and is misleading about what's being asserted. Worth simplifying to just the scene-name toggle.

Also applies to: 88-92

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/gut/test_nav_escape_sfx.gd` around lines 40 - 47, The field snapshot
logic in the menu-context test is dead code because Globals does not expose any
of the listed members, so _set_menu_context only affects the scene-name
fallback. Simplify the test helper by removing the
possible_fields/original_fields probing from test_nav_escape_sfx.gd and rely on
the current_scene.name toggle used by _set_menu_context. Keep the test focused
on the behavior actually under test and apply the same cleanup to the duplicate
block referenced by the related comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/core/globals.gd`:
- Around line 404-406: The scene-name fallback in the menu context check is
leaking test-only behavior into runtime. Update the `_input`/menu detection
logic in `globals.gd` so the `current_scene.name` contains-"Menu" fallback only
runs under a test or debug feature gate, while normal shipped builds rely on the
existing menu detection path. Keep the change localized to the menu-context
helper near the `is_menu_context` assignment so real gameplay scenes like
`MenuLevel` do not trigger UI SFX.

In `@scripts/ui/menus/main_menu.gd`:
- Line 335: The quit dialog dismissal path in _setup_quit_dialog() is wired so
both canceled and close_requested can reach _on_quit_dialog_canceled(), which
causes AudioManager.play_sfx("ui_cancel") to fire twice. Update the signal
connections on the quit dialog so only one of those signals uses
_on_quit_dialog_canceled(), or add a guard in _on_quit_dialog_canceled() to
prevent duplicate handling when the title-bar close path is used.

In `@test/gut/test_nav_escape_sfx.gd`:
- Around line 91-92: The _set_menu_context helper is mutating
get_tree().current_scene.name on the shared test-runner scene, and that state is
not being restored between tests. Update the test setup/teardown around
_set_menu_context and after_each so the original current_scene name is saved
before renaming and restored afterward, preventing cross-test contamination.

In `@test/gut/test_quit_game_confirm_dialog_sfx.gd`:
- Around line 107-124: The test is exercising the wrong path:
main_menu_instance._input does not trigger ui_accept SFX, so the assertion is
vacuously passing. Update test_flat_button_anti_trigger_protection to drive the
real global accept flow by going through Globals._on_node_added and the button
pressed path (or the relevant global button handler), then verify the
flat-button gating against the accept sound there.

---

Outside diff comments:
In `@scripts/core/globals.gd`:
- Around line 448-457: The navigation SFX identifier in
_play_ui_navigation_sfx() points to a missing asset, so the AudioManager call
becomes a no-op while the legacy _nav_sfx_player still plays the sound. Update
the AudioManager.play_sfx(...) argument to match the existing ui_navigation.wav
asset, or add the missing ui_nav_tick.wav asset before removing the fallback
player path. Use the _play_ui_navigation_sfx and _nav_sfx_player symbols to
locate the affected logic.

---

Nitpick comments:
In `@scripts/ui/menus/main_menu.gd`:
- Around line 327-330: Delete the now-unused _on_cancel_button_clicked handler
in main_menu.gd because its only connection was removed and the cancel sound
logic has moved to _on_quit_dialog_canceled; make sure no remaining calls or
signal bindings reference _on_cancel_button_clicked after removing it.

In `@test/gut/test_nav_escape_sfx.gd`:
- Around line 40-47: The field snapshot logic in the menu-context test is dead
code because Globals does not expose any of the listed members, so
_set_menu_context only affects the scene-name fallback. Simplify the test helper
by removing the possible_fields/original_fields probing from
test_nav_escape_sfx.gd and rely on the current_scene.name toggle used by
_set_menu_context. Keep the test focused on the behavior actually under test and
apply the same cleanup to the duplicate block referenced by the related comment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: db3eae54-4b29-4f78-b90b-1a2a29a84429

📥 Commits

Reviewing files that changed from the base of the PR and between cd67305 and af0c222.

📒 Files selected for processing (6)
  • scripts/core/globals.gd
  • scripts/ui/menus/main_menu.gd
  • test/gut/test_nav_escape_sfx.gd
  • test/gut/test_nav_escape_sfx.gd.uid
  • test/gut/test_quit_game_confirm_dialog_sfx.gd
  • test/gut/test_quit_game_confirm_dialog_sfx.gd.uid
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: CI/CD Infrastructure Tests / Test Deployment Scripts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-05-27T04:07:45.235Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 719
File: scripts/ui/menus/audio_settings.gd:300-313
Timestamp: 2026-05-27T04:07:45.235Z
Learning: In Godot 4 GDScript UI code (e.g., slider/drag signal handlers), treat high-frequency callbacks as potentially re-entrant: if you have a conditional state-guard (the “circuit breaker”) inside the handler, put the guard at the very top of the conditional block and perform it before any bus-specific calls, async/yield operations, or other side effects (like `play_sfx("check")` or token increments). This prevents rapid drag frames from re-entering before the first call finishes, which can cause duplicate SFX triggers and redundant state updates.

Applied to files:

  • scripts/ui/menus/main_menu.gd
📚 Learning: 2026-06-22T05:17:36.437Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 762
File: scripts/core/globals.gd:408-409
Timestamp: 2026-06-22T05:17:36.437Z
Learning: For Godot 4 GDScript, prefer the event-driven form inside `_input(event)` handlers: use `event.is_action_pressed(action: StringName, allow_echo: bool = false, exact_match: bool = false)` with the default `allow_echo` (i.e., pass `false`) to suppress echo/key-repeat at the engine level so the action behaves like “just pressed”. Prefer `event.is_action_pressed("action", false)` over polling `Input.is_action_just_pressed()` for architectural/performance reasons, and avoid review suggestions that recommend replacing one with the other.

Applied to files:

  • scripts/ui/menus/main_menu.gd
  • test/gut/test_quit_game_confirm_dialog_sfx.gd
  • scripts/core/globals.gd
  • test/gut/test_nav_escape_sfx.gd
📚 Learning: 2026-03-30T04:02:23.747Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 500
File: test/gut/test_audio_web_bridge.gd:131-145
Timestamp: 2026-03-30T04:02:23.747Z
Learning: In GUT (Godot Unit Test) for Godot 4, when using `assert_called` / `assert_called_count` with parameter matching, include *every* argument the mocked method accepts, including parameters with default values. GUT does not auto-fill default arguments during call matching. For example, if `JavaScriptBridgeWrapper.eval(script: String, global_exec: bool = false)` is invoked as `eval(js_string)`, the actual call recorded by GUT includes the default (`eval(js_string, false)`), so your assertion must match both arguments (e.g., `.bind(js_string, false)`, not `.bind(js_string)`). Apply this rule to GUT assertions in `test/gut` tests.

Applied to files:

  • test/gut/test_quit_game_confirm_dialog_sfx.gd
  • test/gut/test_nav_escape_sfx.gd
🔇 Additional comments (3)
test/gut/test_nav_escape_sfx.gd.uid (1)

1-1: LGTM!

test/gut/test_quit_game_confirm_dialog_sfx.gd.uid (1)

1-1: LGTM!

test/gut/test_quit_game_confirm_dialog_sfx.gd (1)

87-92: 🩺 Stability & Availability

No runner-quit risk here. main_menu_instance is auto-freed at teardown, so this coroutine stops before the 0.2s timer can resume and reach get_tree().quit().

			> Likely an incorrect or invalid review comment.

Comment thread scripts/core/globals.gd
Comment thread scripts/ui/menus/main_menu.gd
Comment thread test/gut/test_nav_escape_sfx.gd
Comment thread test/gut/test_quit_game_confirm_dialog_sfx.gd
ikostan added 9 commits June 25, 2026 17:37
Update Dockerfile to download Godot v4.6.3 binary and export templates from the TuxFamily mirror instead of GitHub releases. The existing SHA256 verification, extraction, and installation steps are preserved (grep/sha256sum checks, unzip, move to /usr/local/bin, and clean up). This change improves download sourcing while keeping integrity checks and installation behavior unchanged.
Switch Godot v4.6.3 download and verification from the TuxFamily mirror/SHA256 to the official GitHub release SHA512-SUMS. Update wget URLs for the Linux binary and export templates, use sha512sum to verify SHA512-SUMS.txt entries, and adjust cleanup to remove SHA512-SUMS.txt filenames. Installation and extraction steps remain unchanged.
Ensure AudioManager restores internal state after set_script() in test teardowns. Adds a has_method check and calls cleanup_for_test() in test/gut/test_nav_escape_sfx.gd and test/gut/test_quit_game_confirm_dialog_sfx.gd to repopulate/rebuild variables wiped by set_script(), preventing cross-test state leakage.
The issue causing test_global_cancellation_in_menu_context to fail specifically on the GitHub Actions CI/CD environment comes down to a difference in how the Godot engine initializes the SceneTree in headless test runner configurations compared to your local editor viewports.

We can fix this seamlessly by adding "options_open" to the property snapshot tracking list. Because options_open is an actual variable on the Globals autoload singleton, the context helper will directly toggle this boolean flag. This forces the production check to register as a valid menu context on any CI server or container, completely independent of scene or window focus initialization.
Remove the previously added persistent AudioStreamPlayer for UI navigation sounds from globals.gd. Deleted the _nav_sfx_player variable, its initialization and child addition in _ready(), and the instance validity/play restart logic in _play_ui_navigation_sfx(). Playback is now centralized through AudioManager.play_sfx("ui_nav_tick"). This simplifies node management and removes the extra node churn introduced by the persistent player.
Rename the existing 'test-scripts' job to 'test-python' (name changed to "Run CI Injection Tests") and add a new 'verify-godot' job to run Godot asset infrastructure and signature verification. The new job runs on ubuntu-latest with a 10 minute timeout (to allow downloading a ~1.2 GB template), checks out the repository, and prepares the verify_godot.sh script (chmod +x) before running verification.
main_menu.gd's _input only handles Web background-music unlock; it doesn't route accept SFX. The global accept path lives in Globals._on_node_added/_on_global_button_pressed, which isn't exercised by manually calling main_menu_instance._input(event). So the assertion passes trivially regardless of flat-button gating. If the intent is to verify flat-button protection, drive Globals._on_node_added / the button pressed signal instead.
_set_menu_context overwrites get_tree().current_scene.name. Since current_scene is the shared test-runner scene, this rename persists across tests and could affect any later suite that inspects the scene name. Consider restoring the original name in after_each.
This branch treats any scene whose name contains "Menu" as menu context. Since _input is an autoload handler invoked at runtime, a real gameplay scene named e.g. MenuLevel/SubMenuArea would start emitting UI navigation/cancel SFX. Consider gating it behind a test feature (e.g. OS.has_feature("ci")/"debug") so the helper does not leak into shipped builds.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/gut/test_quit_game_confirm_dialog_sfx.gd (1)

90-95: 🩺 Stability & Availability | 🟠 Major

Don't call the full quit handler in this test. test_dialog_confirmation_audio() invokes scripts/ui/menus/main_menu.gd::_on_quit_dialog_confirmed(), which plays ui_accept and then, on native platforms, awaits audio_flush_delay before calling get_tree().quit(). Isolate the SFX assertion from the quit path so the test can't shut down the rest of the suite.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/gut/test_quit_game_confirm_dialog_sfx.gd` around lines 90 - 95, The test
is invoking the full quit-confirmation handler, which can reach the quit path
and disrupt the suite. Update test_dialog_confirmation_audio() to isolate only
the SFX behavior by avoiding the direct call to
main_menu_instance._on_quit_dialog_confirmed() and instead trigger or mock the
audio side effect through a narrower path in main_menu.gd. Keep the assertions
focused on _assert_sfx_called("ui_accept") and _assert_sfx_call_count(1), and
ensure no code path can call get_tree().quit() during this test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@test/gut/test_quit_game_confirm_dialog_sfx.gd`:
- Around line 90-95: The test is invoking the full quit-confirmation handler,
which can reach the quit path and disrupt the suite. Update
test_dialog_confirmation_audio() to isolate only the SFX behavior by avoiding
the direct call to main_menu_instance._on_quit_dialog_confirmed() and instead
trigger or mock the audio side effect through a narrower path in main_menu.gd.
Keep the assertions focused on _assert_sfx_called("ui_accept") and
_assert_sfx_call_count(1), and ensure no code path can call get_tree().quit()
during this test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 746f137e-a0f8-40c4-97e4-22ead793e970

📥 Commits

Reviewing files that changed from the base of the PR and between 42505d5 and 25f742a.

📒 Files selected for processing (3)
  • scripts/core/globals.gd
  • test/gut/test_nav_escape_sfx.gd
  • test/gut/test_quit_game_confirm_dialog_sfx.gd
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/core/globals.gd
  • test/gut/test_nav_escape_sfx.gd
📜 Review details
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-03-30T04:02:23.747Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 500
File: test/gut/test_audio_web_bridge.gd:131-145
Timestamp: 2026-03-30T04:02:23.747Z
Learning: In GUT (Godot Unit Test) for Godot 4, when using `assert_called` / `assert_called_count` with parameter matching, include *every* argument the mocked method accepts, including parameters with default values. GUT does not auto-fill default arguments during call matching. For example, if `JavaScriptBridgeWrapper.eval(script: String, global_exec: bool = false)` is invoked as `eval(js_string)`, the actual call recorded by GUT includes the default (`eval(js_string, false)`), so your assertion must match both arguments (e.g., `.bind(js_string, false)`, not `.bind(js_string)`). Apply this rule to GUT assertions in `test/gut` tests.

Applied to files:

  • test/gut/test_quit_game_confirm_dialog_sfx.gd
📚 Learning: 2026-06-22T05:17:36.437Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 762
File: scripts/core/globals.gd:408-409
Timestamp: 2026-06-22T05:17:36.437Z
Learning: For Godot 4 GDScript, prefer the event-driven form inside `_input(event)` handlers: use `event.is_action_pressed(action: StringName, allow_echo: bool = false, exact_match: bool = false)` with the default `allow_echo` (i.e., pass `false`) to suppress echo/key-repeat at the engine level so the action behaves like “just pressed”. Prefer `event.is_action_pressed("action", false)` over polling `Input.is_action_just_pressed()` for architectural/performance reasons, and avoid review suggestions that recommend replacing one with the other.

Applied to files:

  • test/gut/test_quit_game_confirm_dialog_sfx.gd
📚 Learning: 2026-06-26T01:41:48.842Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 782
File: test/gut/test_quit_game_confirm_dialog_sfx.gd:110-127
Timestamp: 2026-06-26T01:41:48.842Z
Learning: For Godot 4 GDScript GUT tests in `test/gut` that cover UI accept/click SFX behavior (files matching `*_sfx.gd`), verify the sound-triggering path through the real global listener pipeline: exercise the `ui_accept` SFX by routing via `Globals._on_node_added` → the global button-pressed listener and the control’s native `pressed` signal. Do not validate/trigger this behavior by directly calling `scripts/ui/menus/main_menu.gd` `_input()` or `_unhandled_input()`; for cases like flat-button gating (e.g., `test/gut/test_quit_game_confirm_dialog_sfx.gd`), confirm gating works through the same global listener flow.

Applied to files:

  • test/gut/test_quit_game_confirm_dialog_sfx.gd

ikostan added 2 commits June 25, 2026 20:14
Avoid wiring both dismiss signals to the same handler — _on_quit_dialog_canceled() can run twice on the title-bar close path, so ui_cancel may play twice. Connect only one of canceled / close_requested, or guard against duplicate handling in _setup_quit_dialog().
After removing the cancel button connection in main_menu.gd, _on_cancel_button_clicked() appears to be unused; consider removing it or documenting why it must remain to avoid confusion about which path is responsible for playing the cancel SFX.
@ikostan

ikostan commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author
  • In globals.gd, _play_ui_navigation_sfx() now calls AudioManager.play_sfx() but still retains the _nav_sfx_player validity check and related logic that is no longer used; consider either delegating everything to AudioManager and removing the dead _nav_sfx_player code, or keeping _nav_sfx_player as the single source of playback to avoid split responsibility.
  • The test helper fallback that infers is_menu_context from get_tree().current_scene.name containing "Menu" affects runtime behavior globally; consider narrowing this (e.g., behind a test flag, checking Engine.is_editor_hint(), or using a dedicated test hook) so production scenes are not misclassified as menus based solely on naming.
  • After removing the cancel button connection in main_menu.gd, _on_cancel_button_clicked() appears to be unused; consider removing it or documenting why it must remain to avoid confusion about which path is responsible for playing the cancel SFX.

🚀 PR Review Status Update: All Issues Resolved

Every piece of feedback from the code reviews has been refactored, tested, and verified against the headless CI/CD pipeline. Below is the final status of each item:

1. Audio Centralization & Dead Code Removal (globals.gd)

  • Issue: The _play_ui_navigation_sfx() helper had split responsibilities, invoking AudioManager while retaining legacy, un-spied _nav_sfx_player tracking logic.
  • Resolution: Fully centralized. The standalone _nav_sfx_player variable, its setup in _ready(), and its redundant validity checks have been completely stripped out. The navigation audio pathways are now routed cleanly via AudioManager.play_sfx("ui_nav_tick").

2. Production Environment Safety & Leak Isolation (globals.gd)

  • Issue: The fallback helper checking if "Menu" in get_tree().current_scene.name was running unfiltered in production builds, posing a risk of misclassifying active gameplay scenes with similar names.
  • Resolution: Strictly gated. The fallback matching logic is now securely wrapped using Godot's environment feature flags (OS.has_feature("debug") or OS.has_feature("ci")). The helper automatically drops out of production builds, ensuring complete runtime safety for shipped distributions.

3. Redundant Window Dismissal Signals (main_menu.gd)

  • Issue: _setup_quit_dialog() was subscribing both canceled and close_requested signals to _on_quit_dialog_canceled(). In Godot 4, a dialog window closure via the title bar or Escape key natively triggers both signals, causing the cancellation audio chime to double-trigger.

  • Resolution: Single-dispatch safety implemented. The duplicate close_requested signal mapping was removed. The single canceled hook safely, natively handles all window dismissal pathways (Cancel button clicks, Escape key actions, and title-bar Close clicks) without duplicate voice triggers.

4. Dead Code Cleanup (main_menu.gd)

  • Issue: The explicit mouse-click button handler _on_cancel_button_clicked() became entirely unused and orphan code after the audio triggers were centralized into the signal-handling pipeline.
  • Resolution: Cleaned. The dead _on_cancel_button_clicked() block has been completely deleted from the script to ensure zero linting or cleanliness warnings.

5. False Positive Test Refactoring (test_quit_game_confirm_dialog_sfx.gd)

  • Issue: test_flat_button_anti_trigger_protection was passing trivially because it manually called _input loops on an inactive branch rather than checking actual engine tree entry.
  • Resolution: Modernized. The test case has been rewritten to route the simulated button control directly into the project's native tree entry hook (Globals._on_node_added) and validates anti-trigger protection directly by firing the button's native .pressed.emit() signal.

6. Cross-Suite Scene Tree Contamination (test_nav_escape_sfx.gd)

  • Issue: Toggling menu context names via get_tree().current_scene.name = "MainMenu" altered properties on a shared runner root node, bleeding state into downstream unit testing suites.
  • Resolution: Isolated. The test suite setup has been enhanced to preserve a snapshot of the original scene identifier name during before_each(), cleanly restoring the shared tree state back to its production baseline during after_each().

@ikostan

ikostan commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author

test/gut/test_quit_game_confirm_dialog_sfx.gd (1)

90-95: 🩺 Stability & Availability | 🟠 Major
Don't call the full quit handler in this test. test_dialog_confirmation_audio() invokes scripts/ui/menus/main_menu.gd::_on_quit_dialog_confirmed(), which plays ui_accept and then, on native platforms, awaits audio_flush_delay before calling get_tree().quit(). Isolate the SFX assertion from the quit path so the test can't shut down the rest of the suite.


Brilliant catch! I completely overlooked that running _on_quit_dialog_confirmed() on native platforms fires get_tree().quit() directly. Locally, this kills the editor, and in the CI/CD workflow, it tricks the process into exiting early with a 0 status, masking downstream test skipping.

To resolve this cleanly, I have introduced a bypass_quit_for_testing safety flag to main_menu.gd which early-returns right after the ui_accept sound effect is queued. The unit test suite toggles this guard on automatically inside before_each(). This completely isolates our audio verification step and ensures the test runner finishes safely. The clean changes are pushed!

Add a safety flag bypass_quit_for_testing to MainMenu to prevent actual engine quitting when the quit confirmation is confirmed during automated tests. The quit handler now logs and returns early when the flag is set. Update the GUT test to instantiate the scene (so @onready children initialize) and enable the bypass flag on the instance to avoid get_tree().quit() terminating the test runner.
Fix incorrect SFX key used for UI navigation: change AudioManager.play_sfx call from "ui_nav_tick" to "ui_navigation" to match the actual asset filename (ui_navigation.wav). Update the unit test assertion to expect "ui_navigation" so tests align with production behavior and verify the correct audio request.
@ikostan

ikostan commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author

scripts/core/globals.gd (1)

448-457: 🩺 Stability & Availability | 🟡 Minor
Align the navigation SFX name with the existing asset
AudioManager.play_sfx("ui_nav_tick") points at a missing file; only ui_navigation.wav exists. That makes the new call a no-op while _nav_sfx_player.play() still drives the sound. Rename the AudioManager call to the existing asset name or add ui_nav_tick.wav before removing the legacy player.


Amazing catch! I completely crossed my wires during the centralization refactor by passing "ui_nav_tick" instead of referencing the actual file asset ui_navigation.wav. This would have left menu navigation completely silent in production. I have renamed the string mapping inside _play_ui_navigation_sfx() to "ui_navigation" so it resolves seamlessly. I also updated our custom GUT unit test suites to track the correct asset string name accordingly. The clean changes are pushed!

@ikostan

ikostan commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The _input fallback that infers is_menu_context from get_tree().current_scene.name when debug/ci features are present couples behavior to scene naming; consider making this an explicit test hook (e.g., a dedicated test-only flag/method) to avoid brittle reliance on scene names and feature flags.
  • The bypass_quit_for_testing flag in main_menu.gd adds test-specific logic into production code; you might want to route this behavior through an injectable dependency or OS.has_feature check so tests can control quitting without adding a general-purpose toggle to the runtime script.
  • Both new GUT suites duplicate the inline mock AudioManager script definition; consider extracting a small shared test helper or base class to centralize this setup and reduce maintenance overhead if the mocking strategy changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `_input` fallback that infers `is_menu_context` from `get_tree().current_scene.name` when `debug/ci` features are present couples behavior to scene naming; consider making this an explicit test hook (e.g., a dedicated test-only flag/method) to avoid brittle reliance on scene names and feature flags.
- The `bypass_quit_for_testing` flag in `main_menu.gd` adds test-specific logic into production code; you might want to route this behavior through an injectable dependency or `OS.has_feature` check so tests can control quitting without adding a general-purpose toggle to the runtime script.
- Both new GUT suites duplicate the inline mock `AudioManager` script definition; consider extracting a small shared test helper or base class to centralize this setup and reduce maintenance overhead if the mocking strategy changes.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@ikostan

ikostan commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai title

@sourcery-ai sourcery-ai Bot changed the title Route menu SFX via AudioManager and add GUT tests Route menu UI SFX through AudioManager and add GUT tests Jun 26, 2026
@ikostan

ikostan commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author

scripts/ui/menus/main_menu.gd (1)

327-330: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
_on_cancel_button_clicked is now dead code.
Its only wiring (the cancel-button pressed connection in _setup_quit_dialog) was removed in this PR, and the cancel SFX now lives in _on_quit_dialog_canceled. This handler is no longer referenced and can be deleted.

🧹 Proposed removal

-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:

This has been completely addressed! The unused _on_cancel_button_clicked() handler has been deleted from main_menu.gd, and all cancellation vectors are now safely routed through the single-dispatch _on_quit_dialog_canceled() callback. No orphaned code or signal bindings remain. Thank you!

@ikostan

ikostan commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author

test/gut/test_nav_escape_sfx.gd (1)

40-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Snapshot fields never match Globals, so _set_menu_context only flips the scene name.
is_menu_context is a local variable inside Globals._input, not a member field — none of the possible_fields exist on the singleton, so original_fields stays empty and the for field ... set(field, value) loop in _set_menu_context is a no-op. The tests therefore exercise only the new scene-name fallback (current_scene.name = "MainMenu"/"GameLevel"). That is fine for what's under test, but the field-snapshot machinery is dead weight and is misleading about what's being asserted. Worth simplifying to just the scene-name toggle.


Completely agree! That property-probing array loop was legacy scaffolding from an early iteration where we experimented with injecting mock properties directly onto the singleton. Because is_menu_context is evaluated as a localized variable, that block was dead weight.

I have completely stripped out the possible_fields mapping loops from before_each() and after_each(). The _set_menu_context() helper now exclusively drives the environment setup via the targeted get_tree().current_scene.name fallback channel, keeping the suite concise and focused. Clean changes pushed!

Snapshot fields never match Globals, so _set_menu_context only flips the scene name.
is_menu_context is a local variable inside Globals._input, not a member field — none of the possible_fields exist on the singleton, so original_fields stays empty and the for field ... set(field, value) loop in _set_menu_context is a no-op. The tests therefore exercise only the new scene-name fallback (current_scene.name = "MainMenu"/"GameLevel"). That is fine for what's under test, but the field-snapshot machinery is dead weight and is misleading about what's being asserted. Worth simplifying to just the scene-name toggle.
@ikostan

ikostan commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author
  • The _input fallback that infers is_menu_context from get_tree().current_scene.name when debug/ci features are present couples behavior to scene naming; consider making this an explicit test hook (e.g., a dedicated test-only flag/method) to avoid brittle reliance on scene names and feature flags.
  • The bypass_quit_for_testing flag in main_menu.gd adds test-specific logic into production code; you might want to route this behavior through an injectable dependency or OS.has_feature check so tests can control quitting without adding a general-purpose toggle to the runtime script.
  • Both new GUT suites duplicate the inline mock AudioManager script definition; consider extracting a small shared test helper or base class to centralize this setup and reduce maintenance overhead if the mocking strategy changes.

Thanks for the high-level architectural feedback! While extracting a shared test base class and refactoring to an injectable dependency model would be cleaner long-term, doing so right now expands the scope and footprint of this specific bug-fix PR.

Since the current scene-name fallback is strictly encapsulated behind production-safe feature flags (OS.has_feature("debug") or OS.has_feature("ci")) and the code cleanly passes all 9/9 unit tests, we are going to merge this localized fix as-is. We've noted the test-base centralization as a candidate for a future standalone tech-debt cleanup task. Thanks!

ikostan added 3 commits June 25, 2026 20:54
The failure in test_global_cancellation_in_menu_context when executing on GitHub CI or headless modes happens due to how Godot initializes the scene tree in headless testing environments.

Instead of increasing the PR footprint by introducing more tracking flags or refactoring production singletons, we can apply a pure testing-side fix to test_nav_escape_sfx.gd.

By updating before_each() to dynamically create, mount, and attach a temporary dummy node to get_tree().current_scene whenever it is missing, we ensure that both local viewports and remote server environments possess an identical, predictable context state.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/gut/test_nav_escape_sfx.gd (1)

221-226: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use ui_cancel here; ui_accept never hits the LineEdit bypass.

Globals._input() only checks LineEdit/TextEdit focus inside the ui_cancel branch. With ui_accept, this test stays green even if that bypass regresses.

Suggested change
 	var event: InputEventAction = InputEventAction.new()
-	event.action = "ui_accept"
+	event.action = "ui_cancel"
 	event.pressed = true

Based on scripts/core/globals.gd:384-447, the focused-text-entry suppression being asserted here is implemented only for event.is_action_pressed("ui_cancel", false).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/gut/test_nav_escape_sfx.gd` around lines 221 - 226, The test is using
the wrong input action for the LineEdit/TextEdit bypass, so it does not exercise
the code path guarded in Globals._input(). Update the event in the nav escape
SFX test to use ui_cancel instead of ui_accept, and keep the assertion on
_assert_sfx_not_called() so it verifies the focused-text-entry suppression in
the ui_cancel branch.
🧹 Nitpick comments (1)
test/gut/test_nav_escape_sfx.gd (1)

107-112: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Exercise only Globals._input() in this suite.

These tests are validating the routing in scripts/core/globals.gd:384-447, but _simulate_input() also calls _unhandled_input(). That can let the suite pass if the SFX path drifts to _unhandled_input() or starts firing from both handlers.

Suggested change
 func _simulate_input(event: InputEvent) -> void:
 	if is_instance_valid(globals_instance):
 		if globals_instance.has_method("_input"):
 			globals_instance._input(event)
-		if globals_instance.has_method("_unhandled_input"):
-			globals_instance._unhandled_input(event)

Based on learnings, prefer the event-driven _input(event) path for these actions; scripts/core/globals.gd:384-447 implements this SFX contract there.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/gut/test_nav_escape_sfx.gd` around lines 107 - 112, The test helper
`_simulate_input()` is exercising both `Globals._input()` and
`Globals._unhandled_input()`, which weakens the routing assertion for the SFX
contract. Update this helper to invoke only the `_input(event)` path on
`globals_instance` and remove the `_unhandled_input()` call so the suite
validates the behavior implemented in `Globals._input()` consistently.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@files/docs/milestones/21/Part_2_Route_menu_UI_SFX_through_AudioManager_`&_add_GUT_tests.md:
- Around line 113-114: The issue is that the linked issue assessment lists
incorrect GUT test file paths, which makes cross-referencing the requirements
inconsistent. Update the two entries that reference the nav escape and quit
confirmation test suites so they match the actual locations used elsewhere in
the PR: the test files associated with Globals and MainMenu should point to the
test/gut paths, not res://test/unit. Keep the issue references and test
descriptions unchanged, only correct the path strings for those two test suite
entries.

---

Outside diff comments:
In `@test/gut/test_nav_escape_sfx.gd`:
- Around line 221-226: The test is using the wrong input action for the
LineEdit/TextEdit bypass, so it does not exercise the code path guarded in
Globals._input(). Update the event in the nav escape SFX test to use ui_cancel
instead of ui_accept, and keep the assertion on _assert_sfx_not_called() so it
verifies the focused-text-entry suppression in the ui_cancel branch.

---

Nitpick comments:
In `@test/gut/test_nav_escape_sfx.gd`:
- Around line 107-112: The test helper `_simulate_input()` is exercising both
`Globals._input()` and `Globals._unhandled_input()`, which weakens the routing
assertion for the SFX contract. Update this helper to invoke only the
`_input(event)` path on `globals_instance` and remove the `_unhandled_input()`
call so the suite validates the behavior implemented in `Globals._input()`
consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9db4365b-30f4-4052-8f8f-2d3d0296f8aa

📥 Commits

Reviewing files that changed from the base of the PR and between 25f742a and f93d0e6.

📒 Files selected for processing (5)
  • files/docs/milestones/21/Part_2_Route_menu_UI_SFX_through_AudioManager_&_add_GUT_tests.md
  • scripts/core/globals.gd
  • scripts/ui/menus/main_menu.gd
  • test/gut/test_nav_escape_sfx.gd
  • test/gut/test_quit_game_confirm_dialog_sfx.gd
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/gut/test_quit_game_confirm_dialog_sfx.gd
  • scripts/core/globals.gd
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: GUT Unit Tests / unit-test
  • GitHub Check: CI/CD Infrastructure Tests / Test Godot Asset Infrastructure and Signature Verification
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2026-05-27T04:07:45.235Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 719
File: scripts/ui/menus/audio_settings.gd:300-313
Timestamp: 2026-05-27T04:07:45.235Z
Learning: In Godot 4 GDScript UI code (e.g., slider/drag signal handlers), treat high-frequency callbacks as potentially re-entrant: if you have a conditional state-guard (the “circuit breaker”) inside the handler, put the guard at the very top of the conditional block and perform it before any bus-specific calls, async/yield operations, or other side effects (like `play_sfx("check")` or token increments). This prevents rapid drag frames from re-entering before the first call finishes, which can cause duplicate SFX triggers and redundant state updates.

Applied to files:

  • scripts/ui/menus/main_menu.gd
📚 Learning: 2026-06-22T05:17:36.437Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 762
File: scripts/core/globals.gd:408-409
Timestamp: 2026-06-22T05:17:36.437Z
Learning: For Godot 4 GDScript, prefer the event-driven form inside `_input(event)` handlers: use `event.is_action_pressed(action: StringName, allow_echo: bool = false, exact_match: bool = false)` with the default `allow_echo` (i.e., pass `false`) to suppress echo/key-repeat at the engine level so the action behaves like “just pressed”. Prefer `event.is_action_pressed("action", false)` over polling `Input.is_action_just_pressed()` for architectural/performance reasons, and avoid review suggestions that recommend replacing one with the other.

Applied to files:

  • scripts/ui/menus/main_menu.gd
  • test/gut/test_nav_escape_sfx.gd
📚 Learning: 2026-06-26T03:15:29.611Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 782
File: scripts/ui/menus/main_menu.gd:335-335
Timestamp: 2026-06-26T03:15:29.611Z
Learning: In Godot 4 GDScript, when using a `ConfirmationDialog`, handle all user cancellation/abort paths (cancel button, Escape key, and title-bar close) via the `canceled` signal only. Avoid wiring both `canceled` and `close_requested` to the same cancel handler, as it can trigger the handler twice and cause duplicate side effects (e.g., playing `AudioManager.play_sfx("ui_cancel")` twice).

Applied to files:

  • scripts/ui/menus/main_menu.gd
  • test/gut/test_nav_escape_sfx.gd
📚 Learning: 2026-03-30T04:02:23.747Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 500
File: test/gut/test_audio_web_bridge.gd:131-145
Timestamp: 2026-03-30T04:02:23.747Z
Learning: In GUT (Godot Unit Test) for Godot 4, when using `assert_called` / `assert_called_count` with parameter matching, include *every* argument the mocked method accepts, including parameters with default values. GUT does not auto-fill default arguments during call matching. For example, if `JavaScriptBridgeWrapper.eval(script: String, global_exec: bool = false)` is invoked as `eval(js_string)`, the actual call recorded by GUT includes the default (`eval(js_string, false)`), so your assertion must match both arguments (e.g., `.bind(js_string, false)`, not `.bind(js_string)`). Apply this rule to GUT assertions in `test/gut` tests.

Applied to files:

  • test/gut/test_nav_escape_sfx.gd
📚 Learning: 2026-06-26T01:41:48.842Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 782
File: test/gut/test_quit_game_confirm_dialog_sfx.gd:110-127
Timestamp: 2026-06-26T01:41:48.842Z
Learning: For Godot 4 GDScript GUT tests in `test/gut` that cover UI accept/click SFX behavior (files matching `*_sfx.gd`), verify the sound-triggering path through the real global listener pipeline: exercise the `ui_accept` SFX by routing via `Globals._on_node_added` → the global button-pressed listener and the control’s native `pressed` signal. Do not validate/trigger this behavior by directly calling `scripts/ui/menus/main_menu.gd` `_input()` or `_unhandled_input()`; for cases like flat-button gating (e.g., `test/gut/test_quit_game_confirm_dialog_sfx.gd`), confirm gating works through the same global listener flow.

Applied to files:

  • test/gut/test_nav_escape_sfx.gd
🪛 LanguageTool
files/docs/milestones/21/Part_2_Route_menu_UI_SFX_through_AudioManager_&_add_GUT_tests.md

[style] ~72-~72: ‘prior to’ might be wordy. Consider a shorter alternative.
Context: ...r_testing = true` on the scene instance prior to executing test frames. #### 4. Infrast...

(EN_WORDINESS_PREMIUM_PRIOR_TO)


[uncategorized] ~106-~106: The official name of this software platform is spelled with a capital “H”.
Context: ... | Dockerfile
.github/workflows/test_ci_scripts.yml ...

(GITHUB)

🔇 Additional comments (4)
scripts/ui/menus/main_menu.gd (4)

208-213: Single-path dismissal confirmed. Routing only canceled (covering Cancel button, Escape, and title-bar close) avoids the double ui_cancel trigger flagged previously.


40-41: LGTM!


310-316: LGTM!


336-341: LGTM!

ikostan added 2 commits June 25, 2026 21:18
Apply editorial and markdown formatting fixes across README.md and milestone docs (milestones 18–20). Standardize bullet/list styles, table/link formatting, line-wrapping and spacing; add/remove blank lines for readability; fix minor typos and identifier/name inconsistencies in documentation (no code logic changes). Affected files: README.md and multiple files under files/docs/milestones/{18,19,20}.
@ikostan

ikostan commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai guide

@ikostan

ikostan commented Jun 27, 2026

Copy link
Copy Markdown
Owner Author

test/gut/test_nav_escape_sfx.gd (1)

107-112: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
Exercise only Globals._input() in this suite.
These tests are validating the routing in scripts/core/globals.gd:384-447, but _simulate_input() also calls _unhandled_input(). That can let the suite pass if the SFX path drifts to _unhandled_input() or starts firing from both handlers.

Suggested change

 func _simulate_input(event: InputEvent) -> void:
 	if is_instance_valid(globals_instance):
 		if globals_instance.has_method("_input"):
 			globals_instance._input(event)
-		if globals_instance.has_method("_unhandled_input"):
-			globals_instance._unhandled_input(event)

Based on learnings, prefer the event-driven _input(event) path for these actions; scripts/core/globals.gd:384-447 implements this SFX contract there.


Sharp observation! Since our UI navigation and cancellation SFX processing contract lives strictly within the _input() step, calling both input handlers in the simulation layer adds unnecessary noise and risks masking potential regressions or double-triggering drifts down the line.

I have stripped the redundant _unhandled_input() execution path from the test suite's _simulate_input() helper. It now hooks directly into Globals._input() exclusively, ensuring maximum testing precision. The clean update has been pushed!

Exercise only Globals._input() in this suite.
These tests are validating the routing in scripts/core/globals.gd:384-447, but _simulate_input() also calls _unhandled_input(). That can let the suite pass if the SFX path drifts to _unhandled_input() or starts firing from both handlers.

Sharp observation! Since our UI navigation and cancellation SFX processing contract lives strictly within the _input() step, calling both input handlers in the simulation layer adds unnecessary noise and risks masking potential regressions or double-triggering drifts down the line.

I have stripped the redundant _unhandled_input() execution path from the test suite's _simulate_input() helper. It now hooks directly into Globals._input() exclusively, ensuring maximum testing precision. The clean update has been pushed!
@ikostan

ikostan commented Jun 27, 2026

Copy link
Copy Markdown
Owner Author

test/gut/test_nav_escape_sfx.gd (1)

221-226: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use ui_cancel here; ui_accept never hits the LineEdit bypass.
Globals._input() only checks LineEdit/TextEdit focus inside the ui_cancel branch. With ui_accept, this test stays green even if that bypass regresses.

Suggested change

 	var event: InputEventAction = InputEventAction.new()
-	event.action = "ui_accept"
+	event.action = "ui_cancel"
 	event.pressed = true

Based on scripts/core/globals.gd:384-447, the focused-text-entry suppression being asserted here is implemented only for event.is_action_pressed("ui_cancel", false).

🤖 Prompt for AI Agents


Outstanding catch! This test was passing trivially because "ui_accept" is completely unhandled by our global input context manager, meaning it never would have fired audio anyway. I have updated test_value_editing_text_gate() to fire a "ui_cancel" event instead. This ensures we are genuinely asserting that active focus on a text element blocks the layout's cancellation chime. The fix is pushed!

Use ui_cancel here; ui_accept never hits the LineEdit bypass.

Globals._input() only checks LineEdit/TextEdit focus inside the ui_cancel branch. With ui_accept, this test stays green even if that bypass regresses.

Based on scripts/core/globals.gd:384-447, the focused-text-entry suppression being asserted here is implemented only for event.is_action_pressed("ui_cancel", false).

Outstanding catch! This test was passing trivially because "ui_accept" is completely unhandled by our global input context manager, meaning it never would have fired audio anyway.  I have updated test_value_editing_text_gate() to fire a "ui_cancel" event instead. This ensures we are genuinely asserting that active focus on a text element blocks the layout's cancellation chime. The fix is pushed!
@ikostan

ikostan commented Jun 27, 2026

Copy link
Copy Markdown
Owner Author
  • The _input fallback that infers is_menu_context from get_tree().current_scene.name when debug/ci features are present couples behavior to scene naming; consider making this an explicit test hook (e.g., a dedicated test-only flag/method) to avoid brittle reliance on scene names and feature flags.
  • The bypass_quit_for_testing flag in main_menu.gd adds test-specific logic into production code; you might want to route this behavior through an injectable dependency or OS.has_feature check so tests can control quitting without adding a general-purpose toggle to the runtime script.
  • Both new GUT suites duplicate the inline mock AudioManager script definition; consider extracting a small shared test helper or base class to centralize this setup and reduce maintenance overhead if the mocking strategy changes.

We deliberately decided not to perform a major architectural overhaul or introduce new base files to avoid scope creep in a focused bug-fix Pull Request. Instead, we used production-safe environment guards to achieve 100% safety with minimal code footprint.

Here is the exact status of those three points on our branch right now:


📋 Final Architectural Checklist

1. Brittle Scene Name Reliance (globals.gd)

  • Feedback: The bot was concerned that checking get_tree().current_scene.name directly would leak into production.
  • Current Status: Surgically Gated & Shipped. We wrapped this fallback logic tightly inside Godot feature flags:
if (OS.has_feature("debug") or OS.has_feature("ci")) and ...

This guarantees that the scene-name parsing code is completely dormant and cannot misclassify any scenes in your exported production release distributions.

2. Test Logic in Production Code (main_menu.gd)

  • Feedback: The bot noted that adding the bypass_quit_for_testing variable mixes testing hooks directly into a production UI layer.
  • Current Status: Surgically Gated & Shipped. Rather than restructuring the script to use heavy dependency injection, we placed a bulletproof feature check inside _on_quit_dialog_confirmed():
if (OS.has_feature("debug") or OS.has_feature("ci")) and bypass_quit_for_testing:

This ensures the bypass path is impossible to reach or exploit in an actual player build.

3. Duplicated Inline AudioManager Mocks

  • Feedback: The bot pointed out that both of your test suites duplicate the string-based GDScript.new() mock setup routine.
  • Current Status: Accepted Technical Debt. We intentionally chose to leave the duplicate setup blocks in place for this PR. This keeps the PR compact, completely self-contained, and significantly easier to review and merge without modifying test suite inheritance chains.

@ikostan ikostan merged commit 939000c into main Jun 27, 2026
15 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Sky Lock Assault Project Jun 27, 2026
@ikostan ikostan deleted the ui-audio-logic-unit-tests-gut-confirmation-cancellation-triggering branch June 27, 2026 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

audio Sound/Audio related issue GUT QA testing

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[FEATURE] UI Audio Logic Unit Tests (GUT):: Confirmation & Cancellation Triggering

1 participant