From 841ef2d01738d1b5c5924ce68030f2a7a275a4b3 Mon Sep 17 00:00:00 2001 From: scawful Date: Sat, 6 Dec 2025 23:07:32 -0500 Subject: [PATCH] Fix Song of Storms: Rain persists across transitions, dismissal works from any area MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- Docs/handoff_time_system_bgcolor.md | 129 ---------------------------- Items/ocarina.asm | 42 +++++---- Menu/menu_hud.asm | 11 ++- Overworld/ZSCustomOverworld.asm | 34 +++++--- oracle.org | 30 +++++-- 5 files changed, 84 insertions(+), 162 deletions(-) delete mode 100644 Docs/handoff_time_system_bgcolor.md diff --git a/Docs/handoff_time_system_bgcolor.md b/Docs/handoff_time_system_bgcolor.md deleted file mode 100644 index 56b0b86..0000000 --- a/Docs/handoff_time_system_bgcolor.md +++ /dev/null @@ -1,129 +0,0 @@ -# Handoff: Time System Custom BG Color Tinting Bug - -## Problem Statement - -When the time system advances the hour in areas with overlays (e.g., Lost Woods with canopy overlay `$9E`), the **custom background color turns bright green (untinted)** instead of being darkened by the time-of-day tinting system. - -The time system should apply a color subtraction effect to all palettes, including the custom BG color, based on the current hour. This works correctly for sprites and most BG tiles, but the custom BG color (at palette position 0) remains at its original bright value. - -## Symptoms - -1. Enter Lost Woods (or any area with custom BG color + overlay) -2. Wait for the hour to advance (or use Song of Time) -3. **Expected**: BG color darkens with time of day -4. **Actual**: BG color becomes bright/untinted green - -Additionally, some attempted fixes caused the **overlay to disappear entirely** (controlled by `$1D` register). - -## Architecture Overview - -### Palette Buffer System - -| Buffer | Address | Purpose | -|--------|---------|---------| -| Staging HUD | `$7EC300` | Source palette data | -| Staging BG | `$7EC340` | Source palette data | -| Staging Sprite | `$7EC400` | Source palette data | -| Effective HUD | `$7EC500` | What gets DMA'd to CGRAM | -| Effective BG | `$7EC540` | What gets DMA'd to CGRAM | -| Effective Sprite | `$7EC600` | What gets DMA'd to CGRAM | - -The BG color is stored at **position 0** of these buffers (`$7EC500`, `$7EC540`, etc.). - -### Key Variables - -| Variable | Address | Purpose | -|----------|---------|---------| -| `Hours` | `$7EE000` | Current hour (0-23) | -| `!SubPalColor` | `$7EE018` | Temp storage for ColorSubEffect input | -| `$8C` | WRAM | Current overlay type ($9E = canopy, $9F = rain, etc.) | -| `$1D` | WRAM | Subscreen/overlay enable flag | - -### Key Routines - -1. **`RunClock`** (`time_system.asm:99`) - Increments time, triggers palette updates -2. **`ColorSubEffect`** (`time_system.asm:382`) - Applies RGB subtraction based on hour -3. **`BackgroundFix`** (`time_system.asm:448`) - Wrapper that calls ColorSubEffect for BG color -4. **`ReplaceBGColor`** (`ZSCustomOverworld.asm:3816`) - ZS hook that loads custom BG color -5. **`InitColorLoad2`** (`ZSCustomOverworld.asm:3941`) - Another path that sets BG color -6. **`Overworld_SetFixedColAndScroll`** (`$0BFE70`) - Vanilla routine for BG color setup - -## Code Flow When Hour Advances - -``` -RunClock - | - v -.increase_hours - | - v -JSL RomToPaletteBuffer ; Reload palettes from ROM -JSL PaletteBufferToEffective ; Copy staging -> effective (with tint hooks) - | - v -Check overlay ($8C) - |-- $9E (canopy) --> JSL Overworld_SetFixedColAndScroll_AltEntry - |-- $9F (rain) ----> JSL Overworld_SetFixedColAndScroll_AltEntry - |-- other ---------> JSL Overworld_SetFixedColAndScroll - | - v -ZS Hook at $0BFEB6 intercepts - | - v -Overworld_LoadBGColorAndSubscreenOverlay - | - v -JSL ReplaceBGColor - |-- Loads color from Pool_BGColorTable - |-- Stores to $7EE018 - |-- JSL Oracle_BackgroundFix - | |-- Should apply ColorSubEffect - | |-- Write to $7EC500, $7EC300, $7EC540, $7EC340 - v -Continue with overlay setup -``` - -## Root Causes Identified by Agent Swarm - -### 1. Untinted Color Overwrite in InitColorLoad2 -The `InitColorLoad2` routine in `Overworld/ZSCustomOverworld.asm` (hooked at `$0ED627` for screen transitions) was loading the raw background color from the `Pool_BGColorTable` and writing it directly to the palette buffers, bypassing the time-of-day tinting logic. This caused the color to revert to its base value whenever a screen loaded. - -### 2. Addressing Mode Error in ColorSubEffect -The `ColorSubEffect` routine in `Overworld/time_system.asm` was accessing global RAM variables (`$7EE016`, `$7EE018`) using short addressing modes (e.g., `CMP !TempPalColor`) without ensuring the Data Bank Register (DB) was set to `$7E`. This caused it to read garbage data when called from `ZSCustomOverworld.asm` (where DB is typically not `$7E`), resulting in incorrect or failing tint calculations. - -### 3. Overlay Clearing Logic -The `Overworld_LoadBGColorAndSubscreenOverlay` routine (which hooks the screen update logic) had two flaws that caused it to disable valid overlays when the hour changed: -1. **Static vs. Dynamic**: It was reading the overlay ID from a static table (`ReadOverlayArray`) instead of the active runtime variable (`$8C`). This meant dynamic overlays (like Rain toggled by an item) were ignored. -2. **Incomplete Logic**: The routine only explicitly handled a subset of overlay IDs (`$9F`, `$9D`, `$96`, `$95`, `$9C`). Any valid overlay *not* in this list (such as Canopy `$9E`, Fog `$97`, or Bridge `$94`) would fall through to a "disable" block that executed `STZ $1D`, turning off the overlay layer. - -## Fixes Applied - -### 1. Fixed Addressing in `time_system.asm` -Added `.l` (long) suffixes to all variable accesses in `ColorSubEffect` (e.g., `LDA.l !SubPalColor`, `CMP.l !TempPalColor`) to ensure correct memory access regardless of the current Data Bank. - -### 2. Integrated Tinting in `ZSCustomOverworld.asm` -Updated `InitColorLoad2` to call `Oracle_BackgroundFix` (which wraps `ColorSubEffect`) instead of writing raw values. This ensures that the background color is properly tinted for the current time of day immediately upon screen load. - -### 3. Overlay Preservation in `ZSCustomOverworld.asm` -In `Overworld_LoadBGColorAndSubscreenOverlay`: -1. **Dynamic ID Check**: Replaced the static table lookup with `LDA.b $8C` to ensure the *currently active* overlay is evaluated. -2. **Fallthrough Protection**: Added a safety check (`CMP.w #$00FF : BNE .noCustomFixedColor`) just before the disable block. This ensures that *any* valid overlay ID (anything that isn't `$00FF`) will bypass the disable instruction and instead proceed to enable the subscreen with default fixed colors. - -## Verification Results - -- **Time Tinting**: The background color now correctly darkens as time passes and persists across screen transitions. -- **Overlay Persistence**: Overlays (Canopy, Rain, Fog, etc.) are no longer cleared when the hour advances. -- **Overlay Stability**: `$1D` register is correctly managed, preventing "disappearing overlay" regressions. - -## Files Involved - -- `Overworld/time_system.asm` - Time system and tinting routines -- `Overworld/ZSCustomOverworld.asm` - Custom BG color system, hooks - -## Test Case - -1. Start game, enter Lost Woods -2. Set time to evening (hour 18+) using debug or Song of Time -3. Observe: BG color should be darkened -4. Wait for hour to advance -5. Observe: BG color should remain darkened AND overlay (canopy) should remain visible. \ No newline at end of file diff --git a/Items/ocarina.asm b/Items/ocarina.asm index ada69b1..4d4c340 100644 --- a/Items/ocarina.asm +++ b/Items/ocarina.asm @@ -114,6 +114,7 @@ assert pc() <= $1A922B AddTravelBird = $0994FE AddWeathervaneExplosion = $098D11 Player_DoSfx1 = $078021 +Overworld_ReloadSubscreenOverlayAndAdvance_long = $02B1F4 org $07A3DB LinkItem_FluteHook: @@ -246,10 +247,14 @@ print "Bank07 Free Space: ", pc org $2B8000 OcarinaEffect_SummonStorms: { - ; Dismiss the rain in the Zora area where it is already raining + ; FIRST: Check if rain is already active - always allow dismissal + ; This must come before area checks so you can dismiss from any area + LDA.l $7EE00E : BNE .dismiss_storms + + ; Area checks only apply when trying to SUMMON rain LDA.w $8A : CMP.b #$00 : BEQ .check_for_magic_bean - CMP.b #$2E : BEQ .dismiss_storms - CMP.b #$2F : BEQ .dismiss_storms + CMP.b #$2E : BEQ .error_beep ; Zora areas already have rain + CMP.b #$2F : BEQ .error_beep ; Check for areas which should not be allowed to have rain CMP.b #$05 : BEQ .error_beep CMP.b #$06 : BEQ .error_beep @@ -259,21 +264,29 @@ OcarinaEffect_SummonStorms: CMP.b #$28 : BEQ .error_beep CMP.b #$29 : BEQ .error_beep - .summon_or_dismiss - ; If the rain is already summoned, dismiss it - LDA.l $7EE00E : BEQ .summon_storms - .dismiss_storms - LDA #$FF : STA $8C + ; Fall through to summon rain + JMP .summon_storms + + .dismiss_storms + ; Clear the flag first so the reload routine loads default overlay LDA #$00 : STA $7EE00E + ; Trigger overlay reload - will load area default (pyramid or other) + JSL Overworld_ReloadSubscreenOverlayAndAdvance_long + ; Hide the subscreen and disable color math STZ $1D STZ $9A + LDA #$FF : STA $8C RTL .summon_storms + ; Set the flag first so the reload routine sees it + LDA #$01 : STA $7EE00E + ; Trigger overlay reload - will load rain due to $7EE00E flag + JSL Overworld_ReloadSubscreenOverlayAndAdvance_long + ; Set up visibility and color math LDA #$9F : STA $8C LDA.b #$01 : STA.b $1D LDA.b #$72 : STA.b $9A - LDA #$01 : STA $7EE00E RTL .error_beep @@ -291,7 +304,7 @@ OcarinaEffect_SummonStorms: STA.l MagicBeanProg LDA.b #$2D : STA.w $012F + - JMP .summon_or_dismiss + JMP .summon_storms .not_active RTL } @@ -321,12 +334,9 @@ PlayThunderAndRain: ResetOcarinaFlag: { - LDA.l GameState : BEQ .continue - CMP #$01 : BEQ .continue - REP #$30 - LDA #$0000 : STA.l $7EE00E - SEP #$30 - .continue + ; NOTE: Removed automatic clearing of $7EE00E on screen transitions. + ; Rain flag is now only cleared when player plays Song of Storms again. + ; The visibility is controlled in ZSCustomOverworld.asm. LDA.w $0416 : ASL A RTL } diff --git a/Menu/menu_hud.asm b/Menu/menu_hud.asm index 2637b0b..e117a00 100644 --- a/Menu/menu_hud.asm +++ b/Menu/menu_hud.asm @@ -551,7 +551,9 @@ org $0AFD0C FloorIndicator: { REP #$30 - LDA $04A0 : AND.w #$00FF : BEQ .hide_indicator + LDA $04A0 : AND.w #$00FF : BNE + + JMP .hide_indicator + + INC A : CMP.w #$00C0 : BNE .dont_disable ; if the count up timer reaches 0x00BF frames ; disable the floor indicator during the next frame. @@ -575,7 +577,12 @@ FloorIndicator: LDA $A0 : CMP.w #$0002 : BEQ .sanctuary_rat_room SEP #$20 - ; Check the world state + ; Check if Song of Storms rain is active + LDA.l $7EE00E : BEQ .checkIntroRain + LDA.b #$05 : STA $012D ; Indoor rain SFX + BRA .no_rain_state + .checkIntroRain + ; Check the world state (intro rain) LDA $7EF3C5 : CMP.b #$02 : BCS .no_rain_state ; cause the ambient rain sound to occur (indoor version) LDA.b #$05 : STA $012D diff --git a/Overworld/ZSCustomOverworld.asm b/Overworld/ZSCustomOverworld.asm index f1b04f0..48a8c7a 100644 --- a/Overworld/ZSCustomOverworld.asm +++ b/Overworld/ZSCustomOverworld.asm @@ -460,11 +460,11 @@ Pool: db $03 ; When non 0 this will cause rain to appear on all areas in the beginning - ; phase. Default is $FF. + ; phase. Default is $FF. Disabled for Oracle of Secrets. org $288146 ; $140146 .EnableBeginningRain ; 0x01 ;if !UseVanillaPool > 0 - db $FF + db $00 ;endif ; TODO: Add a place to change this in ZS. Once that is done add this to the @@ -1530,8 +1530,6 @@ ActivateSubScreen: { PHB : PHK : PLB - STZ.b $1D - PHX REP #$20 ; Set A in 16bit mode. @@ -1553,6 +1551,9 @@ ActivateSubScreen: .notMire + ; Check if Song of Storms rain is active + LDA.l $7EE00E : AND.w #$00FF : BNE .turnOn + ; Check if we are in the beginning phase, if not, no rain. ; If $7EF3C5 >= 0x02. LDA.l $7EF3C5 : AND.w #$00FF : CMP.w #$0002 : BCS .noRain @@ -1576,6 +1577,11 @@ ActivateSubScreen: SEP #$20 ; Set A in 8bit mode. + ; Only clear $1D if not in menu (module $0E) + LDA.b $10 : CMP.b #$0E : BEQ .skipClear + STZ.b $1D ; Only clear if no overlay needed AND not in menu + .skipClear + PLX PLB @@ -2295,20 +2301,22 @@ Overworld_ReloadSubscreenOverlay_Interupt: .notMire ; Check if we are in the beginning phase, if not, no rain. - ; If $7EF3C5 >= 0x02. LDA.l Pool_EnableBeginningRain : AND.w #$00FF : BEQ .noRain LDA.l $7EF3C5 : AND.w #$00FF : CMP.w #$0002 : BCS .noRain - ; The rain overlay. LDX.w #$009F - + .noRain - + + ; Check if Song of Storms rain is active - force rain overlay + LDA.l $7EE00E : AND.w #$00FF : BEQ .noSongOfStorms + LDX.w #$009F + .noSongOfStorms + ; Store the overlay for later. PHX ; If the value is 0xFF that means we didn't set any overlay so load the - ; pyramid one by default. This is done in vanilla to not have to load the - ; BG during a normal transition from area 0x65 to the pyramid area. + ; pyramid one by default. CPX.w #$00FF : BNE .notFF ; The pyramid background. LDX.w #$0096 @@ -3617,6 +3625,12 @@ LoadAmbientSound: { PHB : PHK : PLB + ; Check if Song of Storms rain is active + LDA.l $7EE00E : BEQ .noSongOfStorms + LDA.b #$01 : STA.w $012D ; Rain SFX + BRA .disableRainSound + .noSongOfStorms + ; Reset the ambient sound effect to what it was. LDX.b $8A LDA.l $7F5B00, X : LSR #4 : STA.w $012D diff --git a/oracle.org b/oracle.org index f0c6fed..fe2d086 100644 --- a/oracle.org +++ b/oracle.org @@ -157,19 +157,39 @@ This section tracks known conflicts between systems and outstanding bugs. - *Task:* Refactor `lost_woods.asm` into a proper `JSL`-callable subroutine (`LostWoods_PuzzleHandler`). - *Implementation:* Modify the `OverworldHandleTransitions` routine in `ZSCustomOverworld.asm` to check if the current area is the Lost Woods (`#$29`) and call the new handler. The handler should return a status indicating if it has overridden the transition. -*** ACTIVE [#A] ZSOW vs. Day/Night Sprites +*** DONE [#A] ZSOW vs. Day/Night Sprites :PROPERTIES: :ID: bug-zsow-daynight :END: - - *Status:* In progress. + - *Status:* Fixed. - *Task:* Triage the crash to identify the root cause and develop a new solution for integrating day/night sprite loading with ZSOW. -*** ACTIVE [#A] ZSOW vs. Song of Storms +*** DONE [#A] ZSOW vs. Song of Storms :PROPERTIES: :ID: bug-zsow-storms :END: - - *Status:* In progress. - - *Blocker:* This task is blocked by the `ZSOW vs. Day/Night Sprites` regression, as they may be related. + - *Status:* Fixed. Rain overlay now persists across all transitions and loads immediately. + - *Solution:* Multi-part fix across several files: + 1. *ZSCustomOverworld.asm (lines 2307-2310):* Added $7EE00E check before default overlay, forces rain ($9F) when flag is set. + 2. *ZSCustomOverworld.asm (lines 1556-1557):* ActivateSubScreen checks $7EE00E for menu transition visibility. + 3. *ZSCustomOverworld.asm (lines 3625-3629):* LoadAmbientSound forces rain SFX ($01) when flag is set. + 4. *menu_hud.asm (lines 580-583):* Indoor rain SFX ($05) when Song of Storms active. + 5. *ocarina.asm:* OcarinaEffect_SummonStorms now calls JSL Overworld_ReloadSubscreenOverlayAndAdvance_long to properly load/unload rain overlay graphics immediately. + 6. *ocarina.asm:* ResetOcarinaFlag no longer clears $7EE00E on screen transitions. + 7. *ocarina.asm:* Reordered logic - check $7EE00E FIRST so dismissal works from any area. Area checks only block summoning. + - *Note:* Pool_EnableBeginningRain set to $00 to disable intro rain for Oracle of Secrets. + - *Known Issues:* + - Minor graphical artifacts in rain tiles may need editor fix or could be menu layer conflict. + - Zora areas ($2E, $2F) now error beep on summon attempt. The Zora's Domain waterfall event (dismiss to trigger cutscene) needs position check - should only work when near the event in top-left of large map. + - Edge cases with synchronization may exist but are deferred. + +*** ACTIVE [#B] Menu Empty Items Bug :code:bugfix: + :PROPERTIES: + :ID: bug-menu-empty + :END: + - *Problem:* Menu system has issues when a section has no items. + - *Cause:* Likely related to menu refactoring in commit 52a5ed4. + - *File:* =Menu/menu.asm= *** ACTIVE [#B] Zora Temple Tasks [0/2] :code:bugfix: :PROPERTIES: