Fix Time System BG color tinting and overlay clearing bugs

- Fixed: BG color resetting to untinted value on screen transitions (InitColorLoad2).
- Fixed: Addressing mode error in ColorSubEffect causing incorrect tinting.
- Fixed: Active overlays being cleared when hour advances due to static table lookup and incomplete logic in Overworld_LoadBGColorAndSubscreenOverlay.
- Updated handoff documentation with resolution details.
This commit is contained in:
scawful
2025-11-22 16:41:58 -05:00
parent 8b23049e28
commit 2b504d987f
3 changed files with 150 additions and 19 deletions

View File

@@ -0,0 +1,129 @@
# 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

@@ -3671,7 +3671,8 @@ Overworld_LoadBGColorAndSubscreenOverlay:
.notMire .notMire
JSL.l ReadOverlayArray LDA.b $8C ; Use current active overlay instead of reading from static table
; JSL.l ReadOverlayArray
; Check for misery mire. ; Check for misery mire.
CMP.w #$009F : BNE .notRain CMP.w #$009F : BNE .notRain
@@ -3693,6 +3694,11 @@ Overworld_LoadBGColorAndSubscreenOverlay:
; Check for DW Death mountain. (not turtle rock?). ; Check for DW Death mountain. (not turtle rock?).
CMP.w #$009C : BEQ .setCustomFixedColor CMP.w #$009C : BEQ .setCustomFixedColor
; Safety check: If we have a valid overlay ID (not FF) that wasn't caught above
; (e.g. Canopy $9E, Fog $97), preserve it with default fixed colors instead of disabling.
CMP.w #$00FF : BNE .noCustomFixedColor
SEP #$30 ; Set A, X, and Y in 8bit mode. SEP #$30 ; Set A, X, and Y in 8bit mode.
; Don't set the subscreen during a warp to hide the transparent ; Don't set the subscreen during a warp to hide the transparent
@@ -3723,7 +3729,7 @@ Overworld_LoadBGColorAndSubscreenOverlay:
LDA.b $E2 : STA.b $E0 LDA.b $E2 : STA.b $E0
; Just because I need a bit more space. ; Just because I need a bit more space.
JSL.l ReadOverlayArray LDA.b $8C ; JSL.l ReadOverlayArray
; Are we at Hyrule Castle or Pyramid of Power? ; Are we at Hyrule Castle or Pyramid of Power?
CMP.w #$0096 : BNE .subscreenOnAndReturn CMP.w #$0096 : BNE .subscreenOnAndReturn
@@ -3734,7 +3740,8 @@ Overworld_LoadBGColorAndSubscreenOverlay:
.BRANCH_11 .BRANCH_11
; Check for the pyramid BG. ; Check for the pyramid BG.
JSL.l ReadOverlayArray : CMP.w #$0096 : BNE .subscreenOnAndReturn LDA.b $8C ; JSL.l ReadOverlayArray
CMP.w #$0096 : BNE .subscreenOnAndReturn
; Synchronize Y scrolls on BG0 and BG1. Same for X scrolls. ; Synchronize Y scrolls on BG0 and BG1. Same for X scrolls.
LDA.b $E8 : STA.b $E6 LDA.b $E8 : STA.b $E6
LDA.b $E2 : STA.b $E0 LDA.b $E2 : STA.b $E0
@@ -3949,13 +3956,8 @@ InitColorLoad2:
.storeColor .storeColor
; Set transparent color. ; Set transparent color.
STA.l $7EC300 STA.l $7EE018 ; Set temp color for tinting
STA.l $7EC340 ; Set transparent color. JSL Oracle_BackgroundFix ; Apply tint and write to buffers
; TODO: Based on the conditions as explained above, double check that this is
; not needed for any of them.
;STA.l $7EC500
;STA.l $7EC540
INC.b $15 INC.b $15

View File

@@ -387,22 +387,22 @@ ColorSubEffect:
AND #$00FF : TAX AND #$00FF : TAX
; Subtract amount to blue field based on a table ; Subtract amount to blue field based on a table
LDA.l !SubPalColor : AND #$7C00 : STA !BlueVal LDA.l !SubPalColor : AND #$7C00 : STA.l !BlueVal
SEC : SBC.l .blue, X : STA !TempPalColor SEC : SBC.l .blue, X : STA.l !TempPalColor
; mask out everything except the blue bits ; mask out everything except the blue bits
AND #$7C00 : CMP !TempPalColor : BEQ .no_blue_sign_change ; overflow ? AND #$7C00 : CMP.l !TempPalColor : BEQ .no_blue_sign_change ; overflow ?
LDA !SmallestBlue LDA.l !SmallestBlue
.no_blue_sign_change .no_blue_sign_change
STA.l !BlueVal STA.l !BlueVal
; Subtract amount to green field based on a table ; Subtract amount to green field based on a table
LDA !SubPalColor : AND #$03E0 : STA !GreenVal LDA.l !SubPalColor : AND #$03E0 : STA.l !GreenVal
SEC : SBC.l .green, X : STA.l !TempPalColor SEC : SBC.l .green, X : STA.l !TempPalColor
; Mask out everything except the green bits ; Mask out everything except the green bits
AND #$03E0 : CMP !TempPalColor : BEQ .no_green_sign_change ; overflow ? AND #$03E0 : CMP.l !TempPalColor : BEQ .no_green_sign_change ; overflow ?
LDA !SmallestGreen LDA.l !SmallestGreen
.no_green_sign_change .no_green_sign_change
STA.l !GreenVal STA.l !GreenVal
@@ -411,8 +411,8 @@ ColorSubEffect:
SEC : SBC.l .red, X : STA.l !TempPalColor SEC : SBC.l .red, X : STA.l !TempPalColor
; mask out everything except the red bits ; mask out everything except the red bits
AND #$001F : CMP !TempPalColor : BEQ .no_red_sign_change ; overflow ? AND #$001F : CMP.l !TempPalColor : BEQ .no_red_sign_change ; overflow ?
LDA !SmallestRed LDA.l !SmallestRed
.no_red_sign_change .no_red_sign_change
STA.l !RedVal STA.l !RedVal