Fix: Zora Sanctuary Waterfall trigger and Lost Woods transition logic

This commit is contained in:
scawful
2025-12-08 16:42:19 -05:00
parent 9a8c6d919a
commit 851da89644
4 changed files with 281 additions and 126 deletions

View File

@@ -0,0 +1,45 @@
# Issue: Lost Woods Transition Coordinate Desync
## Status: Active / Low Priority
**Created:** March 2026
**Impact:** Visual/Gameplay discontinuity when exiting the Lost Woods (Area 0x29) back to the West (0x28).
## Problem Description
The custom Lost Woods puzzle uses a coordinate manipulation trick (`INC/DEC $21`, `INC/DEC $E7`) to simulate an infinite loop.
- **Symptoms:**
- When completing the puzzle (Exit East -> 0x2A), the fix implemented (`LostWoods_ResetCoordinates`) correctly snaps Link to the left edge of the new screen, preventing him from skipping the map.
- **Regression:** When *returning* to the previous map (Exit West -> 0x28), Link may appear at incorrect coordinates or the camera may be misaligned relative to the player.
- The "Snapping" logic forces Link's X/Y to the base of Area 0x29 (e.g., X=0x0200). However, the transition logic in `ZSCustomOverworld.asm` uses these coordinates to calculate the *destination* position in the new area. If the snap happens too early or incorrectly, the destination calculation (Start X - Offset) might underflow or misalign.
## Technical Analysis
### Custom Logic (`lost_woods.asm`)
The puzzle modifies:
- `$21` / `$23`: Link's High-Byte Coordinates (World Grid Position).
- `$E1` / `$E7` / `$E9`: Overlay and BG Scroll Registers.
This desynchronizes the "Visible" position from the "Logical" position expected by the standard Overworld engine.
### ZSOW Transition Logic
`OverworldHandleTransitions` in `ZSCustomOverworld.asm` relies on:
- `$20` / `$22`: Link's 16-bit absolute coordinates.
- `Pool_OverworldTransitionPositionX/Y`: Lookup tables for screen boundaries.
### Root Cause Hypothesis
1. **Coordinate Mismatch:** The `LostWoods_ResetCoordinates` routine snaps Link to `X=0x0200` (Left edge of 0x29).
2. **Transition Calc:** When moving West to 0x28, the engine expects Link to be crossing the boundary.
3. **Vanilla vs. Custom:** Vanilla ALTTP does not use infinite looping coordinates in the overworld. This mechanic is entirely custom and fights the static grid nature of the engine.
## Future Investigation Strategy (Reference `usdasm`)
1. **Vanilla Transitions:** Study `Bank02.asm` in `usdasm` to see how `Module09_Overworld` handles coordinate handoffs.
- Look for `Overworld_ScrollMap` and `Overworld_HandleCardinalCollision`.
2. **Camera Re-centering:** Search for routines that "center" the camera on Link after a transition (`Overworld_SetCameraBoundaries`). We may need to manually invoke this *after* the transition logic finishes, rather than snapping coordinates *before*.
3. **Scroll Register Reset:** Instead of zeroing `$E1` etc., we might need to recalculate them based on the *new* area's properties immediately upon load.
## Workaround
The bug is non-fatal. Players can navigate out of the area, though the visual transition may be jarring.
## Related Files
- `Overworld/lost_woods.asm`
- `Overworld/ZSCustomOverworld.asm`
- `usdasm/bank_02.asm` (Reference)

View File

@@ -252,22 +252,50 @@ OcarinaEffect_SummonStorms:
LDA.l $7EE00E : BNE .dismiss_storms LDA.l $7EE00E : BNE .dismiss_storms
; Area checks only apply when trying to SUMMON rain ; 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 : BNE +
CMP.b #$2E : BEQ .error_beep ; Zora areas already have rain JMP .check_for_magic_bean
CMP.b #$2F : BEQ .error_beep +
CMP.b #$2E : BEQ .jump_error_beep ; Zora areas already have rain
CMP.b #$2F : BEQ .jump_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 .jump_error_beep
CMP.b #$06 : BEQ .error_beep CMP.b #$06 : BEQ .jump_error_beep
CMP.b #$07 : BEQ .error_beep CMP.b #$07 : BEQ .jump_error_beep
CMP.b #$10 : BEQ .error_beep CMP.b #$10 : BEQ .jump_error_beep
CMP.b #$18 : BEQ .error_beep CMP.b #$18 : BEQ .jump_error_beep
CMP.b #$28 : BEQ .error_beep CMP.b #$28 : BEQ .jump_error_beep
CMP.b #$29 : BEQ .error_beep CMP.b #$29 : BNE .no_error_beep
.jump_error_beep
JMP .error_beep
.no_error_beep
; Fall through to summon rain ; Fall through to summon rain
JMP .summon_storms JMP .summon_storms
.dismiss_storms .dismiss_storms
; Check for Zora Temple Waterfall Trigger
; Map 1E, High Precision Zone (16x16 pixels)
; Target: Y=$06A8, X=$0CB7 (At the statue)
; Range: Y=$06A0-$06B0, X=$0CB0-$0CC0
LDA.w $8A : CMP.b #$1E : BNE .normal_dismiss
; Y Coordinate Check
LDA.b $21 : CMP.b #$06 : BNE .normal_dismiss ; High Byte
LDA.b $20 : CMP.b #$A0 : BCC .normal_dismiss ; Low Byte < $A0 (Too North/Close)
CMP.b #$B0 : BCS .normal_dismiss ; Low Byte >= $B0 (Too South)
; X Coordinate Check
LDA.b $23 : CMP.b #$0C : BNE .normal_dismiss ; High Byte
LDA.b $22 : CMP.b #$B0 : BCC .normal_dismiss ; Low Byte < $B0 (Too West)
CMP.b #$C0 : BCS .normal_dismiss ; Low Byte >= $C0 (Too East)
; Trigger Found!
JMP .trigger_zora_waterfall
.normal_dismiss
; Clear the flag first so the reload routine loads default overlay ; 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) ; Trigger overlay reload - will load area default (pyramid or other)
@@ -278,6 +306,22 @@ OcarinaEffect_SummonStorms:
LDA #$FF : STA $8C LDA #$FF : STA $8C
RTL RTL
.trigger_zora_waterfall
; Clear Rain State
LDA #$00 : STA $7EE00E
STZ $1D ; Hide Rain Overlay
STZ $9A ; Clear Color Math
LDA #$FF : STA $8C ; Clear Overlay ID
; Setup Zora Temple Cutscene
STZ.b $B0 ; Reset Animation Timer
LDA.b #$01 : STA.w $04C6 ; Set Overlay Index (01 = Zora Temple)
LDA.b #$16 : STA.b $11 ; Set Submodule to "Open Entrance" ($16)
INC.b $15 ; Force Palette Refresh
RTL
.summon_storms .summon_storms
; Set the flag first so the reload routine sees it ; Set the flag first so the reload routine sees it
LDA #$01 : STA $7EE00E LDA #$01 : STA $7EE00E

View File

@@ -35,111 +35,172 @@ LostWoods:
STZ !ComboCounter STZ !ComboCounter
RTL RTL
normalfinish: normalfinish:
LDA.l Pool_Overworld_ActualScreenID_New, X JSL LostWoods_ResetCoordinates
STZ !ComboCounter LDA.l Pool_Overworld_ActualScreenID_New, X
RTL STZ !ComboCounter
RTL
begincode:
; Return from where we came from begincode:
CPX !EastArea : BEQ normalfinish ; Return from where we came from
; from here onwards, use the ram address to determine which combo you're up to CPX !EastArea : BEQ normalfinish
; this code is pretty repeatable ; from here onwards, use the ram address to determine which combo you're up to
LDA !ComboCounter : CMP #$00 : BNE combo1 ; this code is pretty repeatable
; did you get it right? LDA !ComboCounter : CMP #$00 : BNE combo1
CPX !NorthArea : BEQ UP_CORRECT ; did you get it right?
STZ !ComboCounter CPX !NorthArea : BEQ UP_CORRECT
BRA RESOLVE_INCORRECT STZ !ComboCounter
BRA RESOLVE_INCORRECT
combo1:
CMP #$01 : BNE combo2 combo1:
CPX !WestArea : BEQ LEFT_CORRECT CMP #$01 : BNE combo2
STZ !ComboCounter CPX !WestArea : BEQ LEFT_CORRECT
BRA RESOLVE_INCORRECT STZ !ComboCounter
BRA RESOLVE_INCORRECT
combo2:
CMP #$02 : BNE combo3 combo2:
CPX !SouthArea : BEQ DOWN_CORRECT CMP #$02 : BNE combo3
STZ !ComboCounter CPX !SouthArea : BEQ DOWN_CORRECT
BRA RESOLVE_INCORRECT STZ !ComboCounter
BRA RESOLVE_INCORRECT
combo3:
; we want to load the down area, since we complete the combos combo3:
CPX !WestArea : BNE RESOLVE_INCORRECT ; we want to load the down area, since we complete the combos
LDA #$1B : STA $012F ; play fanfare CPX !WestArea : BNE RESOLVE_INCORRECT
BRA normalfinish LDA #$1B : STA $012F ; play fanfare
BRA normalfinish
RESOLVE_INCORRECT:
CPX !NorthArea : BEQ CASE_UP RESOLVE_INCORRECT:
CPX !WestArea : BEQ CASE_LEFT CPX !NorthArea : BEQ CASE_UP
BRA CASE_DOWN CPX !WestArea : BEQ CASE_LEFT
BRA CASE_DOWN
DOWN_CORRECT:
DOWN_CORRECT:
{
INC !ComboCounter
CASE_DOWN:
DEC $21
DEC $21
DEC $E7
DEC $E7
DEC $E9
DEC $E9
DEC $611
DEC $611
DEC $613
DEC $613
LDA $700
SEC
SBC #$10
STA $700
BRA all
} ; label DOWN_CORRECT
UP_CORRECT:
{
INC !ComboCounter
CASE_UP:
INC $21
INC $21
INC $E7
INC $E7
INC $E9
INC $E9
INC $611
INC $611
INC $613
INC $613
LDA $700
CLC
ADC #$10
STA $700
LDA.b #$01 : STA !RestoreCam
BRA all
} ; label UP_CORRECT
LEFT_CORRECT:
{
INC !ComboCounter
CASE_LEFT:
INC $23
INC $23
INC $E1
INC $E1
INC $E3
INC $E3
INC $615
INC $615
INC $617
INC $617
INC $700
INC $700
} ; label LEFT_CORRECT
all:
{
LDA #$29 ; load the same area.
RTL
}
} ; label LOST_WOOD_HOOK
LostWoods_ResetCoordinates:
{ {
INC !ComboCounter ; Only run if we are in area 0x29
CASE_DOWN: LDA.b $8A : CMP.b #$29 : BNE .done
DEC $21
DEC $21 REP #$20
DEC $E7
DEC $E7 ; Check Target Area (in X register)
DEC $E9 CPX !EastArea : BEQ .snap_east
DEC $E9 CPX !WestArea : BEQ .snap_west
DEC $611 CPX !NorthArea : BEQ .snap_north
DEC $611 CPX !SouthArea : BEQ .snap_south
DEC $613 BRA .done_coords ; Fallback if unknown exit
DEC $613
LDA $700 .snap_east ; Target 0x2A (Right)
SEC ; Snap X to Right Edge of 0x29 (0x0400)
SBC #$10 LDA.w #$0400 : STA.b $22
STA $700 ; Modulo Y to 0x29 Base (0x0A00)
BRA all LDA.b $20 : AND.w #$01FF : ORA.w #$0A00 : STA.b $20
} ; label DOWN_CORRECT BRA .reset_scroll
.snap_west ; Target 0x28 (Left)
UP_CORRECT: ; Snap X to Left Edge of 0x29 (0x0200)
{ LDA.w #$0200 : STA.b $22
INC !ComboCounter ; Modulo Y to 0x29 Base (0x0A00)
CASE_UP: LDA.b $20 : AND.w #$01FF : ORA.w #$0A00 : STA.b $20
INC $21 BRA .reset_scroll
INC $21
INC $E7 .snap_north ; Target 0x21 (Up)
INC $E7 ; Snap Y to Top Edge of 0x29 (0x0A00)
INC $E9 LDA.w #$0A00 : STA.b $20
INC $E9 ; Modulo X to 0x29 Base (0x0200)
INC $611 LDA.b $22 : AND.w #$01FF : ORA.w #$0200 : STA.b $22
INC $611 BRA .reset_scroll
INC $613
INC $613 .snap_south ; Target 0x31 (Down)
LDA $700 ; Snap Y to Bottom Edge of 0x29 (0x0C00)
CLC LDA.w #$0C00 : STA.b $20
ADC #$10 ; Modulo X to 0x29 Base (0x0200)
STA $700 LDA.b $22 : AND.w #$01FF : ORA.w #$0200 : STA.b $22
LDA.b #$01 : STA !RestoreCam BRA .reset_scroll
BRA all
} ; label UP_CORRECT .done_coords
; If we didn't match an exit, fallback to just modulo-ing both
LDA.b $20 : AND.w #$01FF : ORA.w #$0A00 : STA.b $20
LEFT_CORRECT: LDA.b $22 : AND.w #$01FF : ORA.w #$0200 : STA.b $22
{
INC !ComboCounter .reset_scroll
CASE_LEFT: SEP #$20
INC $23
INC $23 ; Reset Overlay Scroll Drifts introduced by puzzle
INC $E1 STZ.b $E1
INC $E1 STZ.b $E3
INC $E3 STZ.b $E7
INC $E3 STZ.b $E9
INC $615
INC $615 .done
INC $617 RTL
INC $617
INC $700
INC $700
} ; label LEFT_CORRECT
all:
{
LDA #$29 ; load the same area.
RTL
} }
} ; label LOST_WOOD_HOOK

View File

@@ -84,21 +84,22 @@ This section tracks tasks focused on improving the existing codebase's structure
- [X] Convert all logic blocks (`RunClock`, `DrawClockToHud`, `ColorSubEffect`) into proper `subroutine`s. - [X] Convert all logic blocks (`RunClock`, `DrawClockToHud`, `ColorSubEffect`) into proper `subroutine`s.
- [X] Break down the large `RunClock` routine into smaller, single-purpose functions (e.g., `TimeSystem_CheckCanRun`, `TimeSystem_IncrementTime`, `TimeSystem_UpdatePalettes`). - [X] Break down the large `RunClock` routine into smaller, single-purpose functions (e.g., `TimeSystem_CheckCanRun`, `TimeSystem_IncrementTime`, `TimeSystem_UpdatePalettes`).
*** TODO [#A] Fix Time System Custom BG Color Regression *** DONE [#A] Fix Time System Custom BG Color Regression
:PROPERTIES: :PROPERTIES:
:ID: bug-time-system-bg-color :ID: bug-time-system-bg-color
:END: :END:
- *Symptom:* Custom background color not working correctly after recent changes. - *Symptom:* Custom background color not working correctly after recent changes.
- *Likely Cause:* Regression introduced during system refactoring or ZSCustomOverworld changes. - *Root Cause:* The Color Math Control Register ($9A) was persisting when transitioning from an overlay area (Rain/Storms) to a normal area, causing additive color math to apply to the background.
- *Files to Investigate:* =Overworld/time_system.asm=, =Overworld/ZSCustomOverworld.asm= - *Solution:* Explicitly cleared $9A in `Overworld_LoadBGColorAndSubscreenOverlay` and `Overworld_ReloadSubscreenOverlay_Interupt` in `ZSCustomOverworld.asm` when the overlay ID is $FF. Also ensured Time System tint persistence via `Oracle_CgramAuxToMain_Impl` in `mask_routines.asm`.
** Minecart System (=Sprites/Objects/minecart.asm=) ** Minecart System (=Sprites/Objects/minecart.asm=)
*** TODO [#B] Refactor Minecart System [0/4] *** ACTIVE [#B] Refactor Minecart System [1/4]
:PROPERTIES: :PROPERTIES:
:ID: refactor-minecart :ID: refactor-minecart
:END: :END:
- *Analysis:* An impressive but highly complex system that would benefit greatly from better organization and data-driven design. - *Analysis:* An impressive but highly complex system that would benefit greatly from better organization and data-driven design.
- *Tasks:* - *Tasks:*
- [X] Externalize track data into `data/minecart_tracks.asm` (Partial step toward struct/table conversion).
- [ ] Define a `MinecartTrack` struct and convert the SRAM tracking arrays into a `table` of these structs. - [ ] Define a `MinecartTrack` struct and convert the SRAM tracking arrays into a `table` of these structs.
- [ ] Refactor the four `Minecart_Move...` routines into a single `Minecart_Move` subroutine that uses a lookup table for speed and axis. - [ ] Refactor the four `Minecart_Move...` routines into a single `Minecart_Move` subroutine that uses a lookup table for speed and axis.
- [ ] Refactor the `Minecart_SetDirection...` routines into a single `Minecart_SetDirection` subroutine that uses lookup tables. - [ ] Refactor the `Minecart_SetDirection...` routines into a single `Minecart_SetDirection` subroutine that uses lookup tables.
@@ -157,13 +158,14 @@ This section tracks known conflicts between systems and outstanding bugs.
- *Task:* Refactored the `Sprite_ApplyPush` routine to use a lookup table for setting speed based on direction. Converted `IceBlock_CheckForGround` and `Sprite_IceBlock_CheckForSwitch` to subroutines. Replaced magic numbers with constants. - *Task:* Refactored the `Sprite_ApplyPush` routine to use a lookup table for setting speed based on direction. Converted `IceBlock_CheckForGround` and `Sprite_IceBlock_CheckForSwitch` to subroutines. Replaced magic numbers with constants.
- *Status:* Awaiting emulator verification. - *Status:* Awaiting emulator verification.
*** TODO [#A] Resolve ZSOW vs. Lost Woods Conflict *** DONE [#A] Resolve ZSOW vs. Lost Woods Conflict
:PROPERTIES: :PROPERTIES:
:ID: bug-zsow-lostwoods :ID: bug-zsow-lostwoods
:END: :END:
- *Analysis:* The `lost_woods.asm` puzzle directly conflicts with `ZSCustomOverworld.asm`'s transition handler. - *Analysis:* The `lost_woods.asm` puzzle logic was executing prematurely on entry, corrupting coordinates.
- *Task:* Refactor `lost_woods.asm` into a proper `JSL`-callable subroutine (`LostWoods_PuzzleHandler`). - *Fix:* Added a check to ensure puzzle logic only runs when *inside* Area 0x29. Added `LostWoods_ResetCoordinates` to snap Link's position and clear scroll drifts on exit.
- *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. - *Status:* Main "skipping" bug resolved.
- *Regression:* Minor camera/coordinate desync when returning West (0x28). Documented in [[file:Docs/Issues/LostWoods_Transition_Desync.md][LostWoods_Transition_Desync.md]]. Low priority.
*** DONE [#A] ZSOW vs. Day/Night Sprites *** DONE [#A] ZSOW vs. Day/Night Sprites
:PROPERTIES: :PROPERTIES:
@@ -270,13 +272,16 @@ This section tracks known conflicts between systems and outstanding bugs.
- [ ] *Controlled Collapse:* A puzzle where you must intentionally make a floor tile crumble to fall down to a specific spot on the floor below. - [ ] *Controlled Collapse:* A puzzle where you must intentionally make a floor tile crumble to fall down to a specific spot on the floor below.
** Quests & Narrative Sequences ** Quests & Narrative Sequences
*** ACTIVE [#A] Zora Sanctuary Questline [2/3] :quest: *** ACTIVE [#A] Zora Sanctuary Questline [3/3] :quest:
:PROPERTIES: :PROPERTIES:
:ID: quest-zora-sanctuary :ID: quest-zora-sanctuary
:END: :END:
- [X] Meet lone Sea Zora left at the Sanctuary, learn of Zora Princess. - [X] Meet lone Sea Zora left at the Sanctuary, learn of Zora Princess.
- [X] Conflict over territory lead to Zora Princesses imprisonment. - [X] Conflict over territory lead to Zora Princesses imprisonment.
- [ ] Implement waterfall opening event using Song of Storms. This will require coordination between =Items/ocarina.asm= and =Overworld/overlays.asm=. - [X] Implement waterfall opening event using Song of Storms.
- Trigger: Song of Storms dismissal at the statue (Map 1E, Top-Left).
- Logic: Strict 16x16 pixel trigger zone (Y=$06A0-$06B0, X=$0CB0-$0CC0) to prevent camera desync.
- Fixes: Restored `overlays.asm` camera logic and added palette refresh (`INC $15`) to `ocarina.asm`.
*** TODO [#A] Kalyxo Castle Prison Sequence [0/4] :sequence:code: *** TODO [#A] Kalyxo Castle Prison Sequence [0/4] :sequence:code:
:PROPERTIES: :PROPERTIES: