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:
129
Docs/handoff_time_system_bgcolor.md
Normal file
129
Docs/handoff_time_system_bgcolor.md
Normal 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.
|
||||
@@ -3671,7 +3671,8 @@ Overworld_LoadBGColorAndSubscreenOverlay:
|
||||
|
||||
.notMire
|
||||
|
||||
JSL.l ReadOverlayArray
|
||||
LDA.b $8C ; Use current active overlay instead of reading from static table
|
||||
; JSL.l ReadOverlayArray
|
||||
|
||||
; Check for misery mire.
|
||||
CMP.w #$009F : BNE .notRain
|
||||
@@ -3693,6 +3694,11 @@ Overworld_LoadBGColorAndSubscreenOverlay:
|
||||
|
||||
; Check for DW Death mountain. (not turtle rock?).
|
||||
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.
|
||||
|
||||
; Don't set the subscreen during a warp to hide the transparent
|
||||
@@ -3723,7 +3729,7 @@ Overworld_LoadBGColorAndSubscreenOverlay:
|
||||
LDA.b $E2 : STA.b $E0
|
||||
|
||||
; 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?
|
||||
CMP.w #$0096 : BNE .subscreenOnAndReturn
|
||||
@@ -3734,7 +3740,8 @@ Overworld_LoadBGColorAndSubscreenOverlay:
|
||||
.BRANCH_11
|
||||
|
||||
; 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.
|
||||
LDA.b $E8 : STA.b $E6
|
||||
LDA.b $E2 : STA.b $E0
|
||||
@@ -3949,13 +3956,8 @@ InitColorLoad2:
|
||||
.storeColor
|
||||
|
||||
; Set transparent color.
|
||||
STA.l $7EC300
|
||||
STA.l $7EC340 ; Set transparent color.
|
||||
|
||||
; 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
|
||||
STA.l $7EE018 ; Set temp color for tinting
|
||||
JSL Oracle_BackgroundFix ; Apply tint and write to buffers
|
||||
|
||||
INC.b $15
|
||||
|
||||
|
||||
@@ -387,22 +387,22 @@ ColorSubEffect:
|
||||
AND #$00FF : TAX
|
||||
|
||||
; Subtract amount to blue field based on a table
|
||||
LDA.l !SubPalColor : AND #$7C00 : STA !BlueVal
|
||||
SEC : SBC.l .blue, X : STA !TempPalColor
|
||||
LDA.l !SubPalColor : AND #$7C00 : STA.l !BlueVal
|
||||
SEC : SBC.l .blue, X : STA.l !TempPalColor
|
||||
|
||||
; mask out everything except the blue bits
|
||||
AND #$7C00 : CMP !TempPalColor : BEQ .no_blue_sign_change ; overflow ?
|
||||
LDA !SmallestBlue
|
||||
AND #$7C00 : CMP.l !TempPalColor : BEQ .no_blue_sign_change ; overflow ?
|
||||
LDA.l !SmallestBlue
|
||||
.no_blue_sign_change
|
||||
STA.l !BlueVal
|
||||
|
||||
; 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
|
||||
|
||||
; Mask out everything except the green bits
|
||||
AND #$03E0 : CMP !TempPalColor : BEQ .no_green_sign_change ; overflow ?
|
||||
LDA !SmallestGreen
|
||||
AND #$03E0 : CMP.l !TempPalColor : BEQ .no_green_sign_change ; overflow ?
|
||||
LDA.l !SmallestGreen
|
||||
.no_green_sign_change
|
||||
STA.l !GreenVal
|
||||
|
||||
@@ -411,8 +411,8 @@ ColorSubEffect:
|
||||
SEC : SBC.l .red, X : STA.l !TempPalColor
|
||||
|
||||
; mask out everything except the red bits
|
||||
AND #$001F : CMP !TempPalColor : BEQ .no_red_sign_change ; overflow ?
|
||||
LDA !SmallestRed
|
||||
AND #$001F : CMP.l !TempPalColor : BEQ .no_red_sign_change ; overflow ?
|
||||
LDA.l !SmallestRed
|
||||
.no_red_sign_change
|
||||
STA.l !RedVal
|
||||
|
||||
|
||||
Reference in New Issue
Block a user