Skip to content

Port tree node observation loop into audiomanager - #817

Merged
ikostan merged 23 commits into
mainfrom
port-tree-node-observation-loop-into-audiomanager
Jul 14, 2026
Merged

Port tree node observation loop into audiomanager#817
ikostan merged 23 commits into
mainfrom
port-tree-node-observation-loop-into-audiomanager

Conversation

@ikostan

@ikostan ikostan commented Jul 12, 2026

Copy link
Copy Markdown
Owner

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

PR #817 Summary: Port Tree Node Observation Loop into AudioManager

Overview

This pull request by @ikostan refactors the global UI button sound effects (SFX) handling in the SkyLockAssault Godot project. It migrates the automatic Button pressed signal hooking logic (previously in Globals) into the centralized AudioManager singleton. This improves architecture, lifecycle management, and maintainability while addressing related issues around UI audio consistency.

Key Changes

  • Core Refactoring (scripts/managers/audio_manager.gd, scripts/core/globals.gd):

    • AudioManager now registers a guarded listener to SceneTree.node_added in _ready().
    • Added _retroactive_ui_scan() for recursive traversal to hook pre-existing buttons created before the manager initializes.
    • Implemented filtering (flat buttons, no_global_sound metadata, buttons inside AcceptDialogs) and duplicate-connection guards using CONNECT_DEFERRED.
    • Centralized _on_global_button_pressed() to route ui_accept SFX via the configured menu bus.
    • Obsolete code in Globals is commented out/removed.
  • Testing Updates (test/gut/):

    • Expanded tests for retroactive scanning, idempotency, listener singularity, and AudioManager-specific behavior.
    • Updated mocks and helpers to target the new implementation (e.g., AudioManager._on_global_button_pressed).
    • Improved quit dialog SFX tests with better inheritance and spying.
  • CI/CD Improvements (.github/workflows/):

    • Normalized YAML quoting, indentation, and added yamllint directives for long lines/comments.
    • Updated Playwright cache key and artifact handling.
    • Switched gdlint workflow to direct gdtoolkit pip installation.

Benefits & Implications

AI/Bot Support

  • Summaries, reviews, and suggestions from @sourcery-ai, @coderabbitai, and @deepsource-io (see separate bot contributions summary for details).

Status: Ready for review/merge. Includes sequence diagrams and thorough test coverage. No breaking changes to existing UI audio behavior.


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

Move global UI button observation and audio hook logic from Globals into AudioManager, adding retroactive tree scanning and stronger listener guards, while updating tests and CI workflows accordingly.

New Features:

  • Add AudioManager-driven SceneTree listener that auto-wires Button pressed signals to centralized UI SFX playback.
  • Introduce a retroactive UI scan in AudioManager to hook up pre-existing buttons created before listener initialization.

Bug Fixes:

  • Ensure AudioManager registers a single node_added listener and prevents duplicate button audio connections.
  • Prevent flat buttons and dialog-internal buttons from triggering global confirmation sounds via AudioManager.
  • Fix Playwright cache key and artifact upload conditions to be more robust and YAML-lint compliant.

Enhancements:

  • Refine tests to validate AudioManager’s button wiring, retroactive scanning, and listener singularity behavior instead of relying on Globals.
  • Update AudioManager test doubles to inherit from the production script while safely spying on SFX calls.
  • Clean up redundant comments and dead button hook logic in Globals.

CI:

  • Normalize YAML quoting, indentation, and lint suppression in browser_test workflow steps.
  • Switch gdlint workflow from the Scony action to directly installing gdtoolkit via pip.

Summary by Sourcery

Centralize global UI button SFX wiring in AudioManager, adding retroactive scene-tree scanning and guarded listener registration while cleaning up Globals and aligning workflows and docs with the new architecture.

New Features:

  • Add AudioManager-managed SceneTree listener and retroactive UI scan to automatically wire Button pressed signals to centralized SFX playback.

Bug Fixes:

  • Ensure AudioManager registers only a single node_added listener and avoids duplicate button audio connections.
  • Maintain flat-button and dialog-internal button protection when routing global confirmation sounds through AudioManager.
  • Resolve YAML lint issues and cache key robustness in the browser_test workflow.

Enhancements:

  • Refocus GUT tests on AudioManager’s button wiring, retroactive scanning behavior, and listener singularity, using an inherited script double for safer SFX spying.
  • Remove obsolete button hook logic from Globals to reduce responsibilities and improve maintainability.

Build:

  • Switch gdlint workflow from the Scony action to installing gdtoolkit directly via pip.

CI:

  • Normalize quoting, condition syntax, and line-length handling in browser_test.yml to satisfy yamllint and improve artifact and Codecov uploads.

Documentation:

  • Add milestone documentation describing the port of the tree node observation loop into AudioManager, including architectural, testing, and CI changes.

Tests:

  • Add tests covering retroactive scans of pre-existing and nested buttons, idempotent behavior, and strict singular listener registration for AudioManager.
  • Adjust existing button hook and quit dialog SFX tests to assert against AudioManager’s implementations instead of Globals.

Chores:

  • Clean up duplicated comments in Globals and align encryption key helper docstring wording.

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes

    • Improved ui_accept sound hooking for buttons created during gameplay by using scene-tree monitoring plus a retroactive scan for pre-existing buttons.
    • Prevented unintended UI sounds for flat buttons, buttons that opt out of global audio, and buttons inside confirmation dialogs.
    • Avoided duplicate sound triggers and strengthened regression coverage around initialization and idempotency.
  • Quality Improvements

    • Updated quit confirmation SFX test spying to align with the production audio call signature.
  • Chores

    • Refreshed CI workflow formatting/linting (including Playwright/Godot lint YAML quoting) and updated Godot lint tooling installation.

Bots/AI Contributions Summary for PR #817

AI/Bot-Assisted Review and Automation

This PR benefited significantly from automated code review tools and AI assistants, which provided summaries, suggestions, walkthroughs, nitpicks, and quality checks. These contributions helped refine the refactoring (porting UI button observation logic to AudioManager), improve tests, clean up CI workflows, and address potential issues like duplicate connections and retroactive scanning.

Key bots/AI contributors (in GitHub-mentionable format):

  • @sourcery-ai: Generated the PR summary, reviewer's guide, sequence diagrams, and provided high-level feedback (e.g., suggestions on removing dead code in globals.gd and guarding the node_added listener). It also flagged the need for retroactive UI scanning for pre-existing buttons.
  • @coderabbitai: Delivered a detailed walkthrough, change summaries, nitpick comments (e.g., removing commented-out dead code), and quality/maintainability suggestions. It also produced a fun poem summarizing the changes.
  • @deepsource-io (via DeepSourceReview): Performed automated code analysis on the changes, providing a PR report card with grades on security, reliability, complexity, and hygiene, plus inline review comments and links to full Python/JavaScript reviews.

These tools enhanced code quality, ensured consistency with project standards (e.g., GDScript/Godot practices), and streamlined the review process without direct code commits (all commits were authored by the human contributor).

@ikostan Contributions (Human Maintainer)

@ikostan drove the entire implementation through multiple targeted commits, handling the core refactoring, test updates, and CI fixes. Key efforts include:

This PR centralizes UI audio management, improves maintainability, and resolves several CI/lint issues while preserving existing behavior for flat buttons, dialog buttons, and metadata exclusions.

Overall Impact: Strong collaboration between human engineering and AI tooling resulted in a polished, well-tested change ready for merge. No dependency updates or external bot commits (e.g., no @dependabot activity here).

ikostan added 2 commits July 11, 2026 20:40
Port automatic Button pressed hooking from Globals to AudioManager (Issue #800). Globals no longer connects node_added and its handler is commented out. AudioManager now registers node_added in _ready and provides _on_node_added/_on_global_button_pressed (preserving flat/dialog protections and CONNECT_DEFERRED). Tests updated to call AudioManager hooks and include a stub _on_node_added in the audio mock used by the suite. This centralizes UI SFX routing and addresses lifecycle/connection concerns referenced in #763/#800.
Clean up .github/workflows/browser_test.yml: add yamllint disable/enable around long lines, update Playwright cache comment, standardize quoting for if expressions and string fields (node-version, artifact names, file paths, codecov params), and re-indent the embedded Python server block for readability. These are formatting/linting improvements only; no functional changes intended.
@ikostan ikostan self-assigned this Jul 12, 2026
@ikostan ikostan added CI/CD github actions Pull requests that update GitHub Actions code github_actions Pull requests that update GitHub Actions code labels Jul 12, 2026
@ikostan ikostan linked an issue Jul 12, 2026 that may be closed by this pull request
@ikostan ikostan moved this to In Progress in Sky Lock Assault Project Jul 12, 2026
@sourcery-ai

sourcery-ai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Ports global UI button audio wiring and SceneTree observation from Globals into AudioManager, adds retroactive scene scanning and strict listener guards, and updates tests and CI workflows to validate the new behavior.

Sequence diagram for AudioManager-driven global button SFX wiring

sequenceDiagram
    participant SceneTree
    participant AudioManager
    participant Button
    participant AudioConstants

    Note over AudioManager: Initialization
    AudioManager->>SceneTree: get_tree()
    AudioManager->>SceneTree: node_added.is_connected(_on_node_added)
    alt [listener not connected]
        AudioManager->>SceneTree: node_added.connect(_on_node_added)
    end
    AudioManager->>SceneTree: _retroactive_ui_scan(root)

    loop Retroactive scan
        AudioManager->>SceneTree: _retroactive_ui_scan(node)
        alt [node.get_class() == Button]
            AudioManager->>Button: _on_node_added(node)
        end
    end

    Note over SceneTree,AudioManager: Runtime node addition
    SceneTree-->>AudioManager: node_added(node)
    AudioManager->>AudioManager: _on_node_added(node)
    alt [node.get_class() == Button]
        AudioManager->>Button: cast to Button
        alt [btn.flat or btn.has_meta(no_global_sound)]
            AudioManager-->>Button: return (no hook)
        else [btn inside AcceptDialog]
            AudioManager-->>Button: return (no hook)
        else [eligible button]
            AudioManager->>Button: btn.pressed.is_connected(_on_global_button_pressed)
            alt [not connected]
                AudioManager->>Button: btn.pressed.connect(_on_global_button_pressed, CONNECT_DEFERRED)
            end
        end
    end

    Note over Button,AudioManager: User presses button
    Button-->>AudioManager: pressed -> _on_global_button_pressed()
    AudioManager->>AudioManager: _on_global_button_pressed()
    AudioManager->>AudioConstants: use BUS_SFX_MENU
    AudioManager->>AudioManager: play_sfx(ui_accept, AudioConstants.BUS_SFX_MENU)
Loading

File-Level Changes

Change Details Files
Centralize SceneTree node_added observation and global Button pressed SFX wiring inside AudioManager, including retroactive scanning and filtering.
  • Connect AudioManager._on_node_added to SceneTree.node_added in _ready() with a guard to ensure only one listener is registered.
  • Introduce _retroactive_ui_scan to recursively traverse the scene tree and call the same button-hook logic on pre-existing nodes.
  • Implement _on_node_added to filter out flat buttons, buttons with no_global_sound metadata, and buttons inside AcceptDialog, then connect pressed to _on_global_button_pressed with CONNECT_DEFERRED.
  • Implement _on_global_button_pressed in AudioManager to play the ui_accept sound on the configured SFX menu bus.
  • Remove the Globals-based node_added listener and its button hook/audio callback functions so AudioManager owns the behavior.
scripts/managers/audio_manager.gd
scripts/core/globals.gd
Align unit tests with the new AudioManager-centric button wiring, and add coverage for retroactive scanning and listener singularity.
  • Update connection-count helpers to look for AudioManager._on_global_button_pressed instead of Globals._on_global_button_pressed.
  • Change duplicate-scan tests to drive AudioManager._on_node_added and assert that connection counts remain single.
  • Add tests verifying retroactive scan hooks pre-existing buttons, remains idempotent on already wired nodes, and traverses mixed hierarchies without wiring non-button containers.
  • Add a test to ensure AudioManager’s node_added listener registration remains strictly singular even if _ready() is re-entered.
  • Refactor quit dialog SFX tests to use an inherited AudioManager script double that overrides play_sfx for spying while preserving real node wiring, and drop the obsolete flat-button test tied to Globals.
test/gut/test_globals_button_hooks.gd
test/gut/test_quit_game_confirm_dialog_sfx.gd
Tighten GitHub Actions workflows for browser tests and GDScript linting/formatting to satisfy yamllint and modern tooling requirements.
  • Normalize quoting and indentation in browser_test.yml, including double-quoting if conditions, artifact names, paths, and Codecov arguments.
  • Add yamllint disable/enable comments around long lines like Playwright cache key comments and the embedded Python HTTP server script.
  • Adjust the Playwright cache key to depend on the resolved Playwright package version and fix related condition expressions.
  • Switch gdlint workflow from the Scony/godot-gdscript-toolkit action to installing gdtoolkit via pip directly.
.github/workflows/browser_test.yml
.github/workflows/gdlint.yml
Add milestone documentation describing the AudioManager port and summarizing architectural, testing, and CI changes.
  • Create a new markdown milestone file explaining the move of button SFX wiring from Globals to AudioManager.
  • Document the key refactoring, test updates, and CI workflow changes along with benefits and implications.
  • Include a short reviewer’s guide and notes on AI/bot contribution for the milestone.
files/docs/milestones/21/Part_8_Port_tree_node_observation_loop_into_audio_manager.md

Assessment against linked issues

Issue Objective Addressed Explanation
#800 Move the active tree observer mechanism _on_node_added from scripts/core/globals.gd into scripts/managers/audio_manager.gd and connect it directly to SceneTree.node_added during AudioManager._ready().
#800 Preserve the existing node filtering contracts in the observer (get_class() == "Button", flat button and no_global_sound exclusions, and internal AcceptDialog button exclusions) when implemented inside AudioManager.
#800 Ensure button pressed signal connections created by the observer use CONNECT_DEFERRED and route to a local AudioManager playback callback rather than Globals, making the audio singleton autonomous.
#813 Quote all previously bare string values and conditional if: expressions in .github/workflows/browser_test.yml so they conform to yamllint’s quoted-strings rule.
#813 Eliminate yamllint [quoted-strings] warnings for .github/workflows/browser_test.yml in the CI YAML Lint / build workflow.
#814 Resolve yamllint line-length violations in .github/workflows/browser_test.yml (specifically the long cache key comment/key and embedded Python HTTP server block) by keeping lines under 90 characters or using explicit yamllint disable/enable directives.
#815 Ensure all direct uses of actions/setup-python in local GitHub workflow files are explicitly set to version v6 (not v5).
#815 Clarify in CI/CD configuration or comments that any remaining Node.js 20 deprecation warnings originate from third-party/composite actions and that local workflows are already on setup-python@v6.

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

@ikostan ikostan linked an issue Jul 12, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@ikostan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ddf5be6a-bda6-43ee-9ec7-5c020c8bd957

📥 Commits

Reviewing files that changed from the base of the PR and between cdc7695 and 37e9ea4.

📒 Files selected for processing (1)
  • files/docs/milestones/21/Part_8_Port_tree_node_observation_loop_into_audio_manager.md
📝 Walkthrough

Walkthrough

AudioManager now owns automatic sound wiring for eligible buttons, including retroactive scanning and duplicate-connection protection. Globals’ previous handlers are removed, tests follow the new ownership, milestone documentation is added, and CI workflows receive formatting and GDToolkit installation updates.

Changes

Audio button wiring

Layer / File(s) Summary
AudioManager runtime hook
scripts/managers/audio_manager.gd
AudioManager registers eligible buttons from runtime and existing scene trees, filters excluded buttons, and plays ui_accept through the menu SFX bus.
Globals hook removal
scripts/core/globals.gd
Globals no longer observes added nodes or provides global button audio handlers.
Button hook lifecycle tests
test/gut/test_globals_button_hooks.gd, test/gut/test_quit_game_confirm_dialog_sfx.gd
Tests validate AudioManager ownership, retroactive and idempotent registration, listener uniqueness, and the production AudioManager mock signature.
Audio button milestone documentation
files/docs/milestones/21/Part_8_Port_tree_node_observation_loop_into_audio_manager.md
Documents the AudioManager migration, filtering, scanning, tests, and workflow updates.

CI workflow updates

Layer / File(s) Summary
Browser workflow formatting
.github/workflows/browser_test.yml
YAML lint directives, quoted expressions and values, and inline server formatting are updated without changing workflow behavior.
GDToolkit installation
.github/workflows/gdlint.yml
GDToolkit installation changes from a referenced action to pip install "gdtoolkit==4.*".

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SceneTree
  participant AudioManager
  participant Button
  participant MenuSFX
  SceneTree->>AudioManager: node_added(Button)
  AudioManager->>Button: connect pressed signal
  Button->>AudioManager: pressed
  AudioManager->>MenuSFX: play ui_accept on BUS_SFX_MENU
Loading

Possibly related PRs

Suggested labels: audio, testing, GUT, documentation

Poem

A bunny moved the button chime,
To AudioManager right on time.
Tests guard each signal’s flight,
While YAML quotes shine bright.
Hop, hop—sounds just right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main refactor from Globals to AudioManager.
Description check ✅ Passed The description covers the overview, changes, testing, related issue, and extra notes, with only template formatting differences.
Linked Issues check ✅ Passed The PR implements #800 by moving node_added handling into AudioManager while preserving filtering, deferred connections, and playback.
Out of Scope Changes check ✅ Passed All changes support the AudioManager port, its tests, docs, or CI cleanup, with no clearly unrelated code introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch port-tree-node-observation-loop-into-audiomanager

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.

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

  • The old button hook implementation in globals.gd is now fully commented out but still present; consider either removing it or replacing it with a brief comment pointing to the new AudioManager implementation to avoid future divergence and confusion.
  • With get_tree().node_added now connected in AudioManager._ready, this implicitly relies on a single AudioManager being instantiated early and only once; if there’s any chance of multiple instances or alternative boot paths, it may be worth adding a guard or assertion to ensure only one listener is attached.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The old button hook implementation in `globals.gd` is now fully commented out but still present; consider either removing it or replacing it with a brief comment pointing to the new `AudioManager` implementation to avoid future divergence and confusion.
- With `get_tree().node_added` now connected in `AudioManager._ready`, this implicitly relies on a single AudioManager being instantiated early and only once; if there’s any chance of multiple instances or alternative boot paths, it may be worth adding a guard or assertion to ensure only one listener is attached.

## Individual Comments

### Comment 1
<location path="scripts/managers/audio_manager.gd" line_range="65-66" />
<code_context>
 	# Initialize the SFX object pool
 	_initialize_sfx_pool()

+	# NEW: Connect global listener to monitor all runtime UI instantiation tracks (Issue #800)
+	get_tree().node_added.connect(_on_node_added)
+

</code_context>
<issue_to_address>
**issue (bug_risk):** Buttons instantiated before AudioManager._ready() will never be wired up to global SFX.

By moving this listener from `Globals` to `AudioManager`, any `Button` nodes created before `AudioManager._ready()` fires will never trigger the `node_added` handler and thus won’t get their `pressed` signal hooked up to global SFX. If that’s a problem for your UI flow, either guarantee `AudioManager` is initialized very early in the scene tree, or add a one-time scan in `_ready()` to connect existing `Button` instances retroactively.
</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/managers/audio_manager.gd Outdated
@deepsource-io

deepsource-io Bot commented Jul 12, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 9389df0...37e9ea4 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 Jul 13, 2026 5:11a.m. Review ↗
JavaScript Jul 13, 2026 5:11a.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.

@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

🧹 Nitpick comments (2)
scripts/core/globals.gd (1)

683-710: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove commented-out dead code.

The old _on_node_added and _on_global_button_pressed methods are fully commented out and now live in AudioManager. Keeping 28 lines of stale code creates confusion and risks accidental reactivation. Remove them entirely.

♻️ Proposed cleanup
-
-## Automatically hooks up base Button elements for confirmation sfx
-#func _on_node_added(node: Node) -> void:
-#	# FIXED: Use strict string evaluation to satisfy the Issue `#763` contract
-#	if node.get_class() == "Button":
-#		var btn := node as Button
-#		if is_instance_valid(btn):
-#			# Flat Button Protection: Avoid superimposing global audio over theme audio
-#			if btn.flat or btn.has_meta("no_global_sound"):
-#				return
-#
-#			# Dialog Protection: Exclude internal buttons of Accept/ConfirmationDialogs
-#			var parent := btn.get_parent()
-#			while parent:
-#				if parent is AcceptDialog:
-#					return
-#				parent = parent.get_parent()
-#
-#			# Guard against duplicate connections using the explicit named callable.
-#			# NOTE: CONNECT_DEFERRED is strictly required here to pass the Issue `#763`
-#			# verification contract and guarantee thread-safe scene tree execution.
-#			if not btn.pressed.is_connected(_on_global_button_pressed):
-#				btn.pressed.connect(_on_global_button_pressed, CONNECT_DEFERRED)
-
-## Centralized button audio execution target to prevent lambda churn
-#func _on_global_button_pressed() -> void:
-#	# Explicitly route button accepts through the shared UI SFX bus to guarantee consistent
-#	# muting behavior
-#	AudioManager.play_sfx("ui_accept", AudioConstants.BUS_SFX_MENU)
🤖 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 683 - 710, Remove the fully
commented-out _on_node_added and _on_global_button_pressed method blocks from
globals.gd, including their associated comments, while leaving the active
globals implementation unchanged.
test/gut/test_quit_game_confirm_dialog_sfx.gd (1)

26-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mock's _on_node_added stub makes test_flat_button_anti_trigger_protection ineffective.

The mock script replaces AudioManager's entire script with a _on_node_added that does pass. When test_flat_button_anti_trigger_protection calls AudioManager._on_node_added(start_button), it invokes the mock's no-op stub — no connection is made regardless of whether the button is flat. The test then emits pressed and asserts ui_accept was not called, which passes trivially because no handler was ever connected, not because the flat-flag guard worked.

This means the test cannot detect a regression in the flat-button protection logic. The real coverage exists in test_globals_button_hooks.gd::test_flat_button_is_ignored_by_hook, so consider either removing this redundant test or updating the mock to replicate the real _on_node_added gating logic.

Based on learnings from PR 782: for *_sfx.gd GUT tests, verify the sound-triggering path through the real global listener pipeline rather than a stub that bypasses it.

♻️ Proposed option: remove redundant test
-
-## 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
-	# OLD: Globals._on_node_added(start_button)
-	AudioManager._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")

Also applies to: 126-127

🤖 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 26 - 29, Remove
the redundant test_flat_button_anti_trigger_protection coverage from
test_quit_game_confirm_dialog_sfx.gd, since it invokes the no-op _on_node_added
stub and cannot validate flat-button protection. Keep the existing real pipeline
coverage in test_globals_button_hooks.gd::test_flat_button_is_ignored_by_hook,
and remove the mock stub only if no remaining tests require it.

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 @.github/workflows/browser_test.yml:
- Around line 256-265: Quote the Codecov token expression in the “Upload
Coverage to Codecov” step by updating the token field to a quoted string, while
leaving the surrounding action configuration unchanged.

---

Nitpick comments:
In `@scripts/core/globals.gd`:
- Around line 683-710: Remove the fully commented-out _on_node_added and
_on_global_button_pressed method blocks from globals.gd, including their
associated comments, while leaving the active globals implementation unchanged.

In `@test/gut/test_quit_game_confirm_dialog_sfx.gd`:
- Around line 26-29: Remove the redundant
test_flat_button_anti_trigger_protection coverage from
test_quit_game_confirm_dialog_sfx.gd, since it invokes the no-op _on_node_added
stub and cannot validate flat-button protection. Keep the existing real pipeline
coverage in test_globals_button_hooks.gd::test_flat_button_is_ignored_by_hook,
and remove the mock stub only if no remaining tests require it.
🪄 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: 0667d936-c0d9-4a44-a8bb-e0b121c652de

📥 Commits

Reviewing files that changed from the base of the PR and between 9389df0 and 7d87f30.

📒 Files selected for processing (5)
  • .github/workflows/browser_test.yml
  • scripts/core/globals.gd
  • scripts/managers/audio_manager.gd
  • test/gut/test_globals_button_hooks.gd
  • test/gut/test_quit_game_confirm_dialog_sfx.gd
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: GUT Unit Tests / unit-test
  • GitHub Check: GDUnit4 Unit Tests / unit-test
  • GitHub Check: CI/CD Infrastructure Tests / Test Godot Asset Infrastructure and Signature Verification
  • GitHub Check: CodeRabbit
  • GitHub Check: Sourcery review
🧰 Additional context used
🧠 Learnings (7)
📚 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_globals_button_hooks.gd
  • test/gut/test_quit_game_confirm_dialog_sfx.gd
📚 Learning: 2026-06-29T03:24:09.331Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 784
File: test/gut/test_globals_button_hooks.gd:206-209
Timestamp: 2026-06-29T03:24:09.331Z
Learning: In this repository’s GUT-based Godot 4 tests (files under `test/gut/`), note that a failed assertion aborts the remainder of the test body immediately, so cleanup code placed later in the test may not run. For nodes created during test setup in `test/gut` tests, prefer `add_child_autofree()` instead of plain `add_child()` when there could be a later assertion failure; this ensures the node is freed automatically even if the test exits early (manual freeing later is acceptable but should not be the only cleanup mechanism).

Applied to files:

  • test/gut/test_globals_button_hooks.gd
  • 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_globals_button_hooks.gd
  • scripts/core/globals.gd
  • scripts/managers/audio_manager.gd
  • test/gut/test_quit_game_confirm_dialog_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:

  • test/gut/test_globals_button_hooks.gd
  • scripts/core/globals.gd
  • scripts/managers/audio_manager.gd
  • 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
📚 Learning: 2026-04-28T02:11:45.806Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 588
File: .github/workflows/deploy_to_itch.yml:44-56
Timestamp: 2026-04-28T02:11:45.806Z
Learning: When a CI workflow edits Godot's `project.godot` (INI) to inject custom ProjectSettings values, insert the setting key under the correct section header that matches the `game/` (or other) root in the ProjectSettings path. For example, `ProjectSettings.get_setting("game/security/save_salt", ...)` expects the INI entry under `[game]` with key `security/save_salt` (i.e., `[game]` then `security/save_salt=...`), not under `[application]`. Otherwise the lookup will fall back to the default value at runtime.

Applied to files:

  • .github/workflows/browser_test.yml
📚 Learning: 2026-05-20T00:01:27.632Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 654
File: .github/workflows/browser_test.yml:99-101
Timestamp: 2026-05-20T00:01:27.632Z
Learning: In this repository’s GitHub Actions workflows, treat supply-chain pinning as follows: 
- **Do not flag** steps that use **first-party** GitHub-owned actions under `actions/*` (e.g., `actions/checkout`, `actions/cache`) when they use a **major version tag** like `v6` / `v5`.
- **Do flag** **third-party** actions (anything not under `actions/*`, e.g., `firebelley/godot-export`, `codecov/codecov-action`) when they use an unpinned ref such as `vX` or `main` instead of being pinned to a **commit SHA** (i.e., `@<commit-sha>`).

Applied to files:

  • .github/workflows/browser_test.yml
🪛 GitHub Check: YAML Lint / build (3.x)
.github/workflows/browser_test.yml

[warning] 264-264:
264:18 [quoted-strings] string value is not quoted with double quotes

🔇 Additional comments (6)
.github/workflows/browser_test.yml (1)

109-138: LGTM!

Also applies to: 164-164, 215-218, 241-255, 267-275

scripts/managers/audio_manager.gd (2)

62-67: LGTM!


556-583: LGTM!

scripts/core/globals.gd (1)

55-55: LGTM!

test/gut/test_globals_button_hooks.gd (1)

41-42: LGTM!

Also applies to: 63-64, 234-235

test/gut/test_quit_game_confirm_dialog_sfx.gd (1)

14-14: LGTM!

Comment thread .github/workflows/browser_test.yml
ikostan added 2 commits July 11, 2026 20:55
Connects get_tree().node_added in _ready and adds a recursive _retroactive_ui_scan(node) to walk the existing scene tree and hook UI nodes instantiated before the autoload was ready (addresses Issue #800). Also adds two GUT tests to ensure pre-existing buttons are captured and that the scan is idempotent and does not double-bind connections.
@ikostan

ikostan commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author
  • The old button hook implementation in globals.gd is now fully commented out but still present; consider either removing it or replacing it with a brief comment pointing to the new AudioManager implementation to avoid future divergence and confusion.

Fixed

@ikostan

ikostan commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author
  • With get_tree().node_added now connected in AudioManager._ready, this implicitly relies on a single AudioManager being instantiated early and only once; if there’s any chance of multiple instances or alternative boot paths, it may be worth adding a guard or assertion to ensure only one listener is attached.

Excellent point. Relying on an Autoload structure usually guarantees a single initialization, but alternate boot paths, script doubling during unit tests, or manual re-initialization workflows could easily cause duplicate listener registrations.

I have added an explicit .is_connected() defensive guard directly inside _ready() before binding the scene tree signal to guarantee that only a single instance of the listener is ever attached. Additionally, I've appended a new GUT architectural integration test to verify that this guard successfully prevents double-binding if secondary setup loops run. Resolving

ikostan added 2 commits July 11, 2026 21:01
Add an is_connected() guard in AudioManager._ready to prevent duplicate connections to SceneTree.node_added (addresses Issue #800). Also add a GUT test (test_audiomanager_listener_registration_is_strictly_singular) that asserts the listener is registered exactly once and that subsequent connect attempts do not create duplicate bindings.
@ikostan

ikostan commented Jul 12, 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 reviewed your changes and they look great!


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 Jul 12, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@ikostan

ikostan commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

test/gut/test_quit_game_confirm_dialog_sfx.gd (1)

26-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Mock's _on_node_added stub makes test_flat_button_anti_trigger_protection ineffective.
The mock script replaces AudioManager's entire script with a _on_node_added that does pass. When test_flat_button_anti_trigger_protection calls AudioManager._on_node_added(start_button), it invokes the mock's no-op stub — no connection is made regardless of whether the button is flat. The test then emits pressed and asserts ui_accept was not called, which passes trivially because no handler was ever connected, not because the flat-flag guard worked.
This means the test cannot detect a regression in the flat-button protection logic. The real coverage exists in test_globals_button_hooks.gd::test_flat_button_is_ignored_by_hook, so consider either removing this redundant test or updating the mock to replicate the real _on_node_added gating logic.
Based on learnings from PR 782: for *_sfx.gd GUT tests, verify the sound-triggering path through the real global listener pipeline rather than a stub that bypasses it.


Agreed. While I recently updated the mock to inherit from the production script (which fixed the immediate blind spot), you are right that this specific test is redundant here. test_globals_button_hooks.gd already covers flat-button isolation comprehensively. I have removed the redundant test to keep this suite strictly focused on the quit dialog logic. Resolving!

Mock's _on_node_added stub makes test_flat_button_anti_trigger_protection ineffective.
The mock script replaces AudioManager's entire script with a _on_node_added that does pass. When test_flat_button_anti_trigger_protection calls AudioManager._on_node_added(start_button), it invokes the mock's no-op stub — no connection is made regardless of whether the button is flat. The test then emits pressed and asserts ui_accept was not called, which passes trivially because no handler was ever connected, not because the flat-flag guard worked.
This means the test cannot detect a regression in the flat-button protection logic. The real coverage exists in test_globals_button_hooks.gd::test_flat_button_is_ignored_by_hook, so consider either removing this redundant test or updating the mock to replicate the real _on_node_added gating logic.
Based on learnings from PR 782: for *_sfx.gd GUT tests, verify the sound-triggering path through the real global listener pipeline rather than a stub that bypasses it.
@ikostan

ikostan commented Jul 13, 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:

  • In the GitHub Actions workflows, several if: conditions and inputs are now wrapped in quotes (e.g. if: "steps.playwright-cache.outputs.cache-hit != 'true'", if: "always()", token: "${{ secrets.CODECOV_TOKEN }}"), which may cause GitHub to treat them as literal strings rather than expressions; consider reverting these to the unquoted expression form to avoid breaking conditional execution and interpolation.
  • The _retroactive_ui_scan helper currently calls _on_node_added on every node in the tree, even non-UI nodes; you might want to early-return based on node.get_class() or a type check before calling _on_node_added to reduce unnecessary work on large scene trees.
  • In test_audiomanager_listener_registration_is_strictly_singular, the second connect is guarded by if not get_tree().node_added.is_connected(AudioManager._on_node_added), so the test never actually attempts a duplicate registration; consider forcing a second connect unconditionally to verify that the guard in _ready() truly prevents multiple listener attachments.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the GitHub Actions workflows, several `if:` conditions and inputs are now wrapped in quotes (e.g. `if: "steps.playwright-cache.outputs.cache-hit != 'true'"`, `if: "always()"`, `token: "${{ secrets.CODECOV_TOKEN }}"`), which may cause GitHub to treat them as literal strings rather than expressions; consider reverting these to the unquoted expression form to avoid breaking conditional execution and interpolation.
- The `_retroactive_ui_scan` helper currently calls `_on_node_added` on every node in the tree, even non-UI nodes; you might want to early-return based on `node.get_class()` or a type check before calling `_on_node_added` to reduce unnecessary work on large scene trees.
- In `test_audiomanager_listener_registration_is_strictly_singular`, the second `connect` is guarded by `if not get_tree().node_added.is_connected(AudioManager._on_node_added)`, so the test never actually attempts a duplicate registration; consider forcing a second `connect` unconditionally to verify that the guard in `_ready()` truly prevents multiple listener attachments.

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.

@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

🧹 Nitpick comments (1)
.github/workflows/gdlint.yml (1)

31-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pin GDToolkit to the repository’s declared version.

The workflow installs any future 4.x release, while requirements.txt pins gdtoolkit==4.5.0. This can make lint results and CI reliability change without a repository update. Use the same exact version, or update the dependency pin and workflow together.

Proposed fix
-          pip install "gdtoolkit==4.*"
+          pip install "gdtoolkit==4.5.0"
🤖 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 @.github/workflows/gdlint.yml around lines 31 - 33, Update the workflow’s
gdtoolkit installation command to use the exact version declared in
requirements.txt, gdtoolkit==4.5.0, instead of the floating 4.* constraint; keep
the repository dependency pin and CI installation synchronized.
🤖 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 `@test/gut/test_globals_button_hooks.gd`:
- Around line 336-376: Update the shared after_each() cleanup to verify that
AudioManager._on_node_added is connected to get_tree().node_added and reconnect
it when missing. Keep this guard idempotent so it restores listener state after
test_retroactive_scan_captures_pre_existing_buttons without creating duplicate
connections.

---

Nitpick comments:
In @.github/workflows/gdlint.yml:
- Around line 31-33: Update the workflow’s gdtoolkit installation command to use
the exact version declared in requirements.txt, gdtoolkit==4.5.0, instead of the
floating 4.* constraint; keep the repository dependency pin and CI installation
synchronized.
🪄 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: 941a5a1e-703f-49f9-9af9-00c265053ce2

📥 Commits

Reviewing files that changed from the base of the PR and between 7d87f30 and 1888b81.

📒 Files selected for processing (6)
  • .github/workflows/browser_test.yml
  • .github/workflows/gdlint.yml
  • scripts/core/globals.gd
  • scripts/managers/audio_manager.gd
  • test/gut/test_globals_button_hooks.gd
  • test/gut/test_quit_game_confirm_dialog_sfx.gd
💤 Files with no reviewable changes (1)
  • scripts/core/globals.gd
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/managers/audio_manager.gd
  • .github/workflows/browser_test.yml
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: Sourcery review
  • GitHub Check: GUT Unit Tests / unit-test
  • GitHub Check: CI/CD Infrastructure Tests / Test Godot Asset Infrastructure and Signature Verification
  • GitHub Check: Sourcery review
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2026-04-28T02:11:45.806Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 588
File: .github/workflows/deploy_to_itch.yml:44-56
Timestamp: 2026-04-28T02:11:45.806Z
Learning: When a CI workflow edits Godot's `project.godot` (INI) to inject custom ProjectSettings values, insert the setting key under the correct section header that matches the `game/` (or other) root in the ProjectSettings path. For example, `ProjectSettings.get_setting("game/security/save_salt", ...)` expects the INI entry under `[game]` with key `security/save_salt` (i.e., `[game]` then `security/save_salt=...`), not under `[application]`. Otherwise the lookup will fall back to the default value at runtime.

Applied to files:

  • .github/workflows/gdlint.yml
📚 Learning: 2026-05-20T00:01:27.632Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 654
File: .github/workflows/browser_test.yml:99-101
Timestamp: 2026-05-20T00:01:27.632Z
Learning: In this repository’s GitHub Actions workflows, treat supply-chain pinning as follows: 
- **Do not flag** steps that use **first-party** GitHub-owned actions under `actions/*` (e.g., `actions/checkout`, `actions/cache`) when they use a **major version tag** like `v6` / `v5`.
- **Do flag** **third-party** actions (anything not under `actions/*`, e.g., `firebelley/godot-export`, `codecov/codecov-action`) when they use an unpinned ref such as `vX` or `main` instead of being pinned to a **commit SHA** (i.e., `@<commit-sha>`).

Applied to files:

  • .github/workflows/gdlint.yml
📚 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_globals_button_hooks.gd
  • test/gut/test_quit_game_confirm_dialog_sfx.gd
📚 Learning: 2026-06-29T03:24:09.331Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 784
File: test/gut/test_globals_button_hooks.gd:206-209
Timestamp: 2026-06-29T03:24:09.331Z
Learning: In this repository’s GUT-based Godot 4 tests (files under `test/gut/`), note that a failed assertion aborts the remainder of the test body immediately, so cleanup code placed later in the test may not run. For nodes created during test setup in `test/gut` tests, prefer `add_child_autofree()` instead of plain `add_child()` when there could be a later assertion failure; this ensures the node is freed automatically even if the test exits early (manual freeing later is acceptable but should not be the only cleanup mechanism).

Applied to files:

  • test/gut/test_globals_button_hooks.gd
  • 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_globals_button_hooks.gd
  • test/gut/test_quit_game_confirm_dialog_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:

  • test/gut/test_globals_button_hooks.gd
  • 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
🔇 Additional comments (2)
test/gut/test_globals_button_hooks.gd (1)

41-42: LGTM!

Also applies to: 63-64, 234-235, 378-403, 405-438

test/gut/test_quit_game_confirm_dialog_sfx.gd (1)

14-34: LGTM!

Comment thread test/gut/test_globals_button_hooks.gd
@ikostan

ikostan commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author
  • In the GitHub Actions workflows, several if: conditions and inputs are now wrapped in quotes (e.g. if: "steps.playwright-cache.outputs.cache-hit != 'true'", if: "always()", token: "${{ secrets.CODECOV_TOKEN }}"), which may cause GitHub to treat them as literal strings rather than expressions; consider reverting these to the unquoted expression form to avoid breaking conditional execution and interpolation.

This configuration is intentional. Our repository's strict yamllint configuration enforces the quoted-strings rule, which flags any unquoted or bare expressions in YAML properties. We are intentionally wrapping these in double quotes to prevent linter violations and keep the CI green."

Spot-on catch regarding the early abort behavior in GUT. If the precondition assertion fails, the engine listener would remain detached, polluting the state of all subsequent test sequences.

I have implemented the proposed fix by moving the idempotent reconnection guard directly into the shared after_each() lifecycle cleanup routine. This guarantees the listener is always restored cleanly between runs, avoiding runtime state leakage. Resolving!
@ikostan

ikostan commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author
  • The _retroactive_ui_scan helper currently calls _on_node_added on every node in the tree, even non-UI nodes; you might want to early-return based on node.get_class() or a type check before calling _on_node_added to reduce unnecessary work on large scene trees.

Excellent point regarding scene tree scale efficiency. Passing thousands of non-UI spatial, structural, or timing nodes into the full validation function creates unnecessary call stack overhead during scene initialization.

I have updated _retroactive_ui_scan to perform a lightweight inline class filter check before invoking _on_node_added(). This eliminates function call churn on non-button elements while ensuring the recursive loop still safely crawls through nested container hierarchies. Resolving!

ikostan added 2 commits July 12, 2026 21:13
Skip unnecessary function calls on non-Button components in the retroactive scan by checking node class before processing. This reduces overhead while maintaining full tree traversal to discover nested buttons.

Adds comprehensive test verifying the optimization correctly handles mixed hierarchies with structural nodes, containers, and leaf buttons.
Updated test to directly call _ready() instead of manually connecting the signal, providing a more accurate simulation of how duplicate listeners could occur in production code when _ready() is invoked multiple times.
@ikostan

ikostan commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author
  • In test_audiomanager_listener_registration_is_strictly_singular, the second connect is guarded by if not get_tree().node_added.is_connected(AudioManager._on_node_added), so the test never actually attempts a duplicate registration; consider forcing a second connect unconditionally to verify that the guard in _ready() truly prevents multiple listener attachments.

"Great catch. Replicating the defensive if guard inside the test block turned it into a false positive since the test code was short-circuiting before doing any real work[cite: 11].

I have updated the test to invoke AudioManager._ready() unconditionally. This accurately simulates a secondary initialization/alternate boot sequence, verifying that the production guard inside _ready() successfully drops duplicate connection requests. Resolving!

@ikostan

ikostan commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai guide

@ikostan

ikostan commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai guide

@ikostan

ikostan commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai guide

@ikostan

ikostan commented Jul 13, 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:

  • In both _on_node_added and _retroactive_ui_scan, using node.get_class() == "Button" means subclasses of Button will not be wired for SFX; if you expect custom button types to behave like standard buttons, consider switching to node is Button or a more flexible type check.
  • The retroactive scan currently calls _on_node_added for every Button it encounters, which re-runs all filtering and connection logic; consider extracting the core wiring into a dedicated helper so that the node-added handler and retroactive scan share a single, easier-to-maintain implementation.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In both `_on_node_added` and `_retroactive_ui_scan`, using `node.get_class() == "Button"` means subclasses of `Button` will not be wired for SFX; if you expect custom button types to behave like standard buttons, consider switching to `node is Button` or a more flexible type check.
- The retroactive scan currently calls `_on_node_added` for every `Button` it encounters, which re-runs all filtering and connection logic; consider extracting the core wiring into a dedicated helper so that the node-added handler and retroactive scan share a single, easier-to-maintain implementation.

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 Jul 13, 2026

Copy link
Copy Markdown
Owner Author
  • In both _on_node_added and _retroactive_ui_scan, using node.get_class() == "Button" means subclasses of Button will not be wired for SFX; if you expect custom button types to behave like standard buttons, consider switching to node is Button or a more flexible type check.
  • The retroactive scan currently calls _on_node_added for every Button it encounters, which re-runs all filtering and connection logic; consider extracting the core wiring into a dedicated helper so that the node-added handler and retroactive scan share a single, easier-to-maintain implementation.

  • Regarding the first point, the strict get_class() == "Button" check is actually an intentional architectural design choice for this project. We want to explicitly exclude engine button subclasses (like CheckButton or OptionButton) from the global sound layer to prevent audio tracking conflicts against their own native component triggers. This policy is actively validated and enforced by our test_button_subclass_is_ignored_by_strict_class_contract unit test.
  • Regarding the second point, keeping the class check gated inside _retroactive_ui_scan serves as a clean, early-return optimization boundary. It stops non-button nodes from unnecessarily churning through the heavier validation stack during the boot frame traversal. Since the loop is highly efficient and already 100% verified by our integration tests, I'm keeping the implementation as-is to keep the PR scope focused entirely on the core migration requirements of Issue [FEATURE] Port Tree Node Observation Loop into AudioManager #800

@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

🧹 Nitpick comments (1)
files/docs/milestones/21/Part_8_Port_tree_node_observation_loop_into_audio_manager.md (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use consistent "AudioManager" casing in the title.

The heading uses "audiomanager" (lowercase, single word) while the rest of the document consistently uses "AudioManager" (PascalCase, two words). Align the title for consistency.

✏️ Proposed fix
-# 🏁 Port tree node observation loop into audiomanager
+# 🏁 Port tree node observation loop into AudioManager
🤖 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
`@files/docs/milestones/21/Part_8_Port_tree_node_observation_loop_into_audio_manager.md`
at line 1, Update the document’s heading to use the consistent “AudioManager”
casing and spacing instead of “audiomanager,” leaving the rest of the title
unchanged.
🤖 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_8_Port_tree_node_observation_loop_into_audio_manager.md`:
- Line 15: Update the documentation statements at the “Globals” obsolete-code
summary and “commenting out obsolete hooks” entry to consistently state that the
button-hook implementation and previous handlers were removed from globals.gd,
not commented out. Preserve the surrounding milestone content.

---

Nitpick comments:
In
`@files/docs/milestones/21/Part_8_Port_tree_node_observation_loop_into_audio_manager.md`:
- Line 1: Update the document’s heading to use the consistent “AudioManager”
casing and spacing instead of “audiomanager,” leaving the rest of the title
unchanged.
🪄 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: 8ef6cafa-d239-46d6-a2d2-deba5eb03134

📥 Commits

Reviewing files that changed from the base of the PR and between 1888b81 and cdc7695.

📒 Files selected for processing (4)
  • .github/workflows/browser_test.yml
  • files/docs/milestones/21/Part_8_Port_tree_node_observation_loop_into_audio_manager.md
  • scripts/managers/audio_manager.gd
  • test/gut/test_globals_button_hooks.gd
🚧 Files skipped from review as they are similar to previous changes (3)
  • scripts/managers/audio_manager.gd
  • test/gut/test_globals_button_hooks.gd
  • .github/workflows/browser_test.yml
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: CI/CD Infrastructure Tests / Test Godot Asset Infrastructure and Signature Verification
🧰 Additional context used
🪛 LanguageTool
files/docs/milestones/21/Part_8_Port_tree_node_observation_loop_into_audio_manager.md

[uncategorized] ~22-~22: The official name of this software platform is spelled with a capital “H”.
Context: ... and spying. - CI/CD Improvements (.github/workflows/): - Normalized YAML quoti...

(GITHUB)


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

(GITHUB)


[uncategorized] ~60-~60: The official name of this software platform is spelled with a capital “H”.
Context: ...es and conditional if: expressions in .github/workflows/browser_test.yml so they con...

(GITHUB)


[uncategorized] ~61-~61: The official name of this software platform is spelled with a capital “H”.
Context: ...amllint [quoted-strings] warnings for .github/workflows/browser_test.yml in the CI Y...

(GITHUB)


[uncategorized] ~62-~62: The official name of this software platform is spelled with a capital “H”.
Context: ...lve yamllint line-length violations in .github/workflows/browser_test.yml (specificall...

(GITHUB)


[uncategorized] ~93-~93: The official name of this software platform is spelled with a capital “H”.
Context: ...wright cache, gdtoolkit installation in .github/workflows/). - Addressing linked issue...

(GITHUB)

@ikostan

ikostan commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

Nitpick comments (1)

files/docs/milestones/21/Part_8_Port_tree_node_observation_loop_into_audio_manager.md (1)> 1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use consistent "AudioManager" casing in the title.
The heading uses "audiomanager" (lowercase, single word) while the rest of the document consistently uses "AudioManager" (PascalCase, two words). Align the title for consistency.


Fixed

@ikostan
ikostan merged commit 5da50a5 into main Jul 14, 2026
14 checks passed
@ikostan
ikostan deleted the port-tree-node-observation-loop-into-audiomanager branch July 14, 2026 00:14
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Sky Lock Assault Project Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD github actions Pull requests that update GitHub Actions code github_actions Pull requests that update GitHub Actions code refactoring

Projects

Status: Done

1 participant