Fix Song of Storms: Rain persists across transitions, dismissal works from any area

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
scawful
2025-12-06 23:07:32 -05:00
parent d41dcdadb9
commit 841ef2d017
5 changed files with 84 additions and 162 deletions

View File

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

View File

@@ -114,6 +114,7 @@ assert pc() <= $1A922B
AddTravelBird = $0994FE AddTravelBird = $0994FE
AddWeathervaneExplosion = $098D11 AddWeathervaneExplosion = $098D11
Player_DoSfx1 = $078021 Player_DoSfx1 = $078021
Overworld_ReloadSubscreenOverlayAndAdvance_long = $02B1F4
org $07A3DB org $07A3DB
LinkItem_FluteHook: LinkItem_FluteHook:
@@ -246,10 +247,14 @@ print "Bank07 Free Space: ", pc
org $2B8000 org $2B8000
OcarinaEffect_SummonStorms: 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 LDA.w $8A : CMP.b #$00 : BEQ .check_for_magic_bean
CMP.b #$2E : BEQ .dismiss_storms CMP.b #$2E : BEQ .error_beep ; Zora areas already have rain
CMP.b #$2F : BEQ .dismiss_storms CMP.b #$2F : BEQ .error_beep
; Check for areas which should not be allowed to have rain ; Check for areas which should not be allowed to have rain
CMP.b #$05 : BEQ .error_beep CMP.b #$05 : BEQ .error_beep
CMP.b #$06 : BEQ .error_beep CMP.b #$06 : BEQ .error_beep
@@ -259,21 +264,29 @@ OcarinaEffect_SummonStorms:
CMP.b #$28 : BEQ .error_beep CMP.b #$28 : BEQ .error_beep
CMP.b #$29 : BEQ .error_beep CMP.b #$29 : BEQ .error_beep
.summon_or_dismiss ; Fall through to summon rain
; If the rain is already summoned, dismiss it JMP .summon_storms
LDA.l $7EE00E : BEQ .summon_storms
.dismiss_storms .dismiss_storms
LDA #$FF : STA $8C ; Clear the flag first so the reload routine loads default overlay
LDA #$00 : STA $7EE00E 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 $1D
STZ $9A STZ $9A
LDA #$FF : STA $8C
RTL RTL
.summon_storms .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 #$9F : STA $8C
LDA.b #$01 : STA.b $1D LDA.b #$01 : STA.b $1D
LDA.b #$72 : STA.b $9A LDA.b #$72 : STA.b $9A
LDA #$01 : STA $7EE00E
RTL RTL
.error_beep .error_beep
@@ -291,7 +304,7 @@ OcarinaEffect_SummonStorms:
STA.l MagicBeanProg STA.l MagicBeanProg
LDA.b #$2D : STA.w $012F LDA.b #$2D : STA.w $012F
+ +
JMP .summon_or_dismiss JMP .summon_storms
.not_active .not_active
RTL RTL
} }
@@ -321,12 +334,9 @@ PlayThunderAndRain:
ResetOcarinaFlag: ResetOcarinaFlag:
{ {
LDA.l GameState : BEQ .continue ; NOTE: Removed automatic clearing of $7EE00E on screen transitions.
CMP #$01 : BEQ .continue ; Rain flag is now only cleared when player plays Song of Storms again.
REP #$30 ; The visibility is controlled in ZSCustomOverworld.asm.
LDA #$0000 : STA.l $7EE00E
SEP #$30
.continue
LDA.w $0416 : ASL A LDA.w $0416 : ASL A
RTL RTL
} }

View File

@@ -551,7 +551,9 @@ org $0AFD0C
FloorIndicator: FloorIndicator:
{ {
REP #$30 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 INC A : CMP.w #$00C0 : BNE .dont_disable
; if the count up timer reaches 0x00BF frames ; if the count up timer reaches 0x00BF frames
; disable the floor indicator during the next frame. ; disable the floor indicator during the next frame.
@@ -575,7 +577,12 @@ FloorIndicator:
LDA $A0 : CMP.w #$0002 : BEQ .sanctuary_rat_room LDA $A0 : CMP.w #$0002 : BEQ .sanctuary_rat_room
SEP #$20 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 LDA $7EF3C5 : CMP.b #$02 : BCS .no_rain_state
; cause the ambient rain sound to occur (indoor version) ; cause the ambient rain sound to occur (indoor version)
LDA.b #$05 : STA $012D LDA.b #$05 : STA $012D

View File

@@ -460,11 +460,11 @@ Pool:
db $03 db $03
; When non 0 this will cause rain to appear on all areas in the beginning ; 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 org $288146 ; $140146
.EnableBeginningRain ; 0x01 .EnableBeginningRain ; 0x01
;if !UseVanillaPool > 0 ;if !UseVanillaPool > 0
db $FF db $00
;endif ;endif
; TODO: Add a place to change this in ZS. Once that is done add this to the ; 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 PHB : PHK : PLB
STZ.b $1D
PHX PHX
REP #$20 ; Set A in 16bit mode. REP #$20 ; Set A in 16bit mode.
@@ -1553,6 +1551,9 @@ ActivateSubScreen:
.notMire .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. ; Check if we are in the beginning phase, if not, no rain.
; If $7EF3C5 >= 0x02. ; If $7EF3C5 >= 0x02.
LDA.l $7EF3C5 : AND.w #$00FF : CMP.w #$0002 : BCS .noRain LDA.l $7EF3C5 : AND.w #$00FF : CMP.w #$0002 : BCS .noRain
@@ -1576,6 +1577,11 @@ ActivateSubScreen:
SEP #$20 ; Set A in 8bit mode. 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 PLX
PLB PLB
@@ -2295,20 +2301,22 @@ Overworld_ReloadSubscreenOverlay_Interupt:
.notMire .notMire
; Check if we are in the beginning phase, if not, no rain. ; 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 Pool_EnableBeginningRain : AND.w #$00FF : BEQ .noRain
LDA.l $7EF3C5 : AND.w #$00FF : CMP.w #$0002 : BCS .noRain LDA.l $7EF3C5 : AND.w #$00FF : CMP.w #$0002 : BCS .noRain
; The rain overlay.
LDX.w #$009F LDX.w #$009F
.noRain .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. ; Store the overlay for later.
PHX PHX
; If the value is 0xFF that means we didn't set any overlay so load the ; 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 ; pyramid one by default.
; BG during a normal transition from area 0x65 to the pyramid area.
CPX.w #$00FF : BNE .notFF CPX.w #$00FF : BNE .notFF
; The pyramid background. ; The pyramid background.
LDX.w #$0096 LDX.w #$0096
@@ -3617,6 +3625,12 @@ LoadAmbientSound:
{ {
PHB : PHK : PLB 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. ; Reset the ambient sound effect to what it was.
LDX.b $8A LDX.b $8A
LDA.l $7F5B00, X : LSR #4 : STA.w $012D LDA.l $7F5B00, X : LSR #4 : STA.w $012D

View File

@@ -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`). - *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. - *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: :PROPERTIES:
:ID: bug-zsow-daynight :ID: bug-zsow-daynight
:END: :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. - *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: :PROPERTIES:
:ID: bug-zsow-storms :ID: bug-zsow-storms
:END: :END:
- *Status:* In progress. - *Status:* Fixed. Rain overlay now persists across all transitions and loads immediately.
- *Blocker:* This task is blocked by the `ZSOW vs. Day/Night Sprites` regression, as they may be related. - *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: *** ACTIVE [#B] Zora Temple Tasks [0/2] :code:bugfix:
:PROPERTIES: :PROPERTIES: