Sub-menu system is complete and the audio system finally works the way it was supposed to. This session is the catch-up work after the responsive UI shipped — the smaller details that made the menus feel like a finished product instead of an early prototype.
7 SUB-MENUS, ONE SHARED VISUAL LANGUAGE
Every sub-menu reached the "matches the rest of the family" bar. The previous notebook-paper look on stats got swapped for the same VHS cover + scanline overlay as the other menus, because the user-facing feedback was unambiguous: "looks out of place, doesn't match". Same call on the in-game settings overlay — the spec said "Ink & Paper" cream + navy, the screen looked like a Microsoft Office dialog, and the fix was a dark sepia paper with a blood-red border that reads "horror notebook page" rather than "admin dialog".
All seven sub-screens are now spec-compliant: main_menu, continue_run, achievements, new_game, prestige_tree, stats, settings, plus the modal in_game_settings_overlay. Shared foundations across all of them: 1280×720, Patrick Hand / Special Elite / Gloria Hallelujah fonts, sepia palette, the same back-button convention, and the "content priority" rule that decorations never block UI.
THE IN-GAME SETTINGS OVERHAUL
The in-game settings overlay was the messiest of the bunch. Two things landed in the same session:
- Back-to-menu button at top-left (per spec rule "Back button is always top-left — even on in-game settings"). Previously the only way out was the X close button, which just resumed gameplay. The
← MAIN MENUbutton saves the run and returns to the main menu. - Dark horror palette: dark sepia paper
Color(0.18, 0.13, 0.08)+ blood-red borderColor(0.55, 0.12, 0.1)+ cream ink. The X button stayed as a contrasting blood-red square in the top-right.
The save flow had to be explicit: Main.save_current_state() (renamed from _save_current_state to make it public) runs before change_scene_to_file(), otherwise Main is freed before the save can read its state. The music pause/resume is via the "music" group — Main.tscn's MusicPlayer is in the group, the overlay iterates the group on show/hide. Decoupled from any direct path access.
MENU MUSIC NOW LOOPS AND UPDATES IN REAL-TIME
Two related bugs, same root cause: a static music player parented to the wrong node.
Bug #1 — music cut on every scene change. MainMenu had its own music_player child node that got freed on every scene transition; the sub-menus had no music at all. Fix: centralized via MenuAudio (a static helper that parents the music player to tree.root so it survives every scene change). Every sub-menu's _ready() now calls MenuAudio.start_menu_music(get_tree()). Main.gd._ready() still calls MenuAudio.stop_menu_music() to silence the menu music when entering gameplay. Net result: music is continuous from Main Menu through Continue Run through New Game through Settings through back to Main Menu. No cuts.
Bug #2 — the music didn't loop. This one took three attempts. First I tried forcing stream.loop_mode = AudioStreamWAV.LOOP_FORWARD in code after load — that broke music entirely (user reported "no music anywhere in the game"). Then I tried tracking the .import files via git add -f to bypass the *.import gitignore — that didn't work either. The final answer was the finished signal.
Why loop_mode didn't work: VRAD-compressed WAV streams (which is what every music .wav in this project uses, compress/mode=2) silently ignore the loop_mode property — both the .import setting and the runtime override. This is a Godot 4 quirk. The only reliable loop is to connect to AudioStreamPlayer.finished and call play() from the handler. Applied to MenuAudio._menu_player and Main.gd.music_player. Now both menu music and level music loop indefinitely regardless of compression mode.
The side-effect fix that came with the loop: MenuAudio.start_menu_music() now also subscribes to AudioSettings.music_volume_changed (with an is_connected guard for repeated scene entries). So moving the music slider in the menu settings panel updates the menu music loudness in real-time — not on the next menu entry, not on the next game start, right now. Previously the listener only ran during gameplay, so menu changes didn't apply until Main.gd took over again.
CLEAN DIFFICULTY CARDS
The three difficulty cards on the new-game screen each had their own baked-in difficulty_easy.png / difficulty_normal.png / difficulty_hard.png textures with "20-30 MIN, RELAXED, ROOM TO EXPERIMENT" and similar text rendered into the image. Two problems with that approach: the AI image generators kept putting extra text on the surface despite prompt iterations, and any text change meant a full asset regeneration.
Fix: generated one clean yellow sticky note (no text), then put the labels in-engine as Label nodes — EASY / NORMAL / HARD in blood-red Patrick Hand 48pt at the top, the time / description in Special Elite 16pt at the bottom, both with autowrap and a pre-wrapped two-line layout that fits the 360×200 card bounds.
The three difficulty cards share one clean yellow texture. Title and subtitle text are Label children, in Patrick Hand blood red and Special Elite typewriter. The slight rotation on the left and right cards gives the hand-pinned feel.
Same pattern was applied to the main menu: the btn_continue_paper.png and btn_newgame_paper.png textures used to have "▶ CONTINUE" / "▶ NEW GAME" baked into the image. Both got cleaned up to a generic paper texture, and the text is now in-engine via Label children. Two buttons, one texture, in-engine text — and any future button text change is a 5-second .tscn edit instead of a 30-minute asset regeneration.
SMALLER WINS, INDEXED
- MainActionButton Label enabled. Was
visible=falsewith no font/color/size overrides — only the paper texture was visible. Enabled, styled Patrick Hand 36pt gold + black 4px outline. Now "▶ CONTINUE" / "▶ NEW GAME" reads clearly. - New
NewGameButtonbelow the MainActionButton at the right side. Always visible, always routes to the difficulty picker — even if a save exists. So once you've played once, the main menu has both CONTINUE and NEW GAME, side by side. - DifficultyContainer re-centered (anchors 15 → 8, ±560 / ±100) so the three cards sit in the middle of the viewport, not pushed to the bottom.
- Sub-menus + main menu now use the
MenuAudiocentralization, so a single play call at game-start sets up the menu music for the entire session. - Stats
PageOverlaykept (the cream "page" surface where the stats list sits) so the numbers stay readable on top of the VHS background.
WHAT'S NEXT
Sub-menu polishing is done. The next workstream is the L5–L9 content pack: 40+ new upgrades themed per level, 50+ achievements, the Prestige skill tree expansion from 7 to 30+ nodes across 4 branches. None of that is blocked on UI or audio anymore — it's content work.
After that, Steam page setup. The build is closer to launch-ready than ever, but the L5–L9 pack is the difference between "early prototype" and "feature-complete demo". Once that lands, the Steam announcement cycle can start.
Follow along on itch.io and the devlog index. If you have feedback on the audio or the dark horror UI direction, drop it in the comments.