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
; Area checks only apply when trying to SUMMON rain
LDA.w $8A : CMP.b #$00 : BEQ .check_for_magic_bean
CMP.b #$2E : BEQ .error_beep ; Zora areas already have rain
CMP.b #$2F : BEQ .error_beep
LDA.w $8A : CMP.b #$00 : BNE +
JMP .check_for_magic_bean
+
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
CMP.b #$05 : BEQ .error_beep
CMP.b #$06 : BEQ .error_beep
CMP.b #$07 : BEQ .error_beep
CMP.b #$10 : BEQ .error_beep
CMP.b #$18 : BEQ .error_beep
CMP.b #$28 : BEQ .error_beep
CMP.b #$29 : BEQ .error_beep
CMP.b #$05 : BEQ .jump_error_beep
CMP.b #$06 : BEQ .jump_error_beep
CMP.b #$07 : BEQ .jump_error_beep
CMP.b #$10 : BEQ .jump_error_beep
CMP.b #$18 : BEQ .jump_error_beep
CMP.b #$28 : BEQ .jump_error_beep
CMP.b #$29 : BNE .no_error_beep
.jump_error_beep
JMP .error_beep
.no_error_beep
; Fall through to summon rain
JMP .summon_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
LDA #$00 : STA $7EE00E
; Trigger overlay reload - will load area default (pyramid or other)
@@ -278,6 +306,22 @@ OcarinaEffect_SummonStorms:
LDA #$FF : STA $8C
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
; Set the flag first so the reload routine sees it
LDA #$01 : STA $7EE00E

View File

@@ -35,111 +35,172 @@ LostWoods:
STZ !ComboCounter
RTL
normalfinish:
LDA.l Pool_Overworld_ActualScreenID_New, X
STZ !ComboCounter
RTL
begincode:
; Return from where we came from
CPX !EastArea : BEQ normalfinish
; from here onwards, use the ram address to determine which combo you're up to
; this code is pretty repeatable
LDA !ComboCounter : CMP #$00 : BNE combo1
; did you get it right?
CPX !NorthArea : BEQ UP_CORRECT
STZ !ComboCounter
BRA RESOLVE_INCORRECT
combo1:
CMP #$01 : BNE combo2
CPX !WestArea : BEQ LEFT_CORRECT
STZ !ComboCounter
BRA RESOLVE_INCORRECT
combo2:
CMP #$02 : BNE combo3
CPX !SouthArea : BEQ DOWN_CORRECT
STZ !ComboCounter
BRA RESOLVE_INCORRECT
combo3:
; we want to load the down area, since we complete the combos
CPX !WestArea : BNE RESOLVE_INCORRECT
LDA #$1B : STA $012F ; play fanfare
BRA normalfinish
RESOLVE_INCORRECT:
CPX !NorthArea : BEQ CASE_UP
CPX !WestArea : BEQ CASE_LEFT
BRA CASE_DOWN
DOWN_CORRECT:
normalfinish:
JSL LostWoods_ResetCoordinates
LDA.l Pool_Overworld_ActualScreenID_New, X
STZ !ComboCounter
RTL
begincode:
; Return from where we came from
CPX !EastArea : BEQ normalfinish
; from here onwards, use the ram address to determine which combo you're up to
; this code is pretty repeatable
LDA !ComboCounter : CMP #$00 : BNE combo1
; did you get it right?
CPX !NorthArea : BEQ UP_CORRECT
STZ !ComboCounter
BRA RESOLVE_INCORRECT
combo1:
CMP #$01 : BNE combo2
CPX !WestArea : BEQ LEFT_CORRECT
STZ !ComboCounter
BRA RESOLVE_INCORRECT
combo2:
CMP #$02 : BNE combo3
CPX !SouthArea : BEQ DOWN_CORRECT
STZ !ComboCounter
BRA RESOLVE_INCORRECT
combo3:
; we want to load the down area, since we complete the combos
CPX !WestArea : BNE RESOLVE_INCORRECT
LDA #$1B : STA $012F ; play fanfare
BRA normalfinish
RESOLVE_INCORRECT:
CPX !NorthArea : BEQ CASE_UP
CPX !WestArea : BEQ CASE_LEFT
BRA CASE_DOWN
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
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
; Only run if we are in area 0x29
LDA.b $8A : CMP.b #$29 : BNE .done
REP #$20
; Check Target Area (in X register)
CPX !EastArea : BEQ .snap_east
CPX !WestArea : BEQ .snap_west
CPX !NorthArea : BEQ .snap_north
CPX !SouthArea : BEQ .snap_south
BRA .done_coords ; Fallback if unknown exit
.snap_east ; Target 0x2A (Right)
; Snap X to Right Edge of 0x29 (0x0400)
LDA.w #$0400 : STA.b $22
; Modulo Y to 0x29 Base (0x0A00)
LDA.b $20 : AND.w #$01FF : ORA.w #$0A00 : STA.b $20
BRA .reset_scroll
.snap_west ; Target 0x28 (Left)
; Snap X to Left Edge of 0x29 (0x0200)
LDA.w #$0200 : STA.b $22
; Modulo Y to 0x29 Base (0x0A00)
LDA.b $20 : AND.w #$01FF : ORA.w #$0A00 : STA.b $20
BRA .reset_scroll
.snap_north ; Target 0x21 (Up)
; Snap Y to Top Edge of 0x29 (0x0A00)
LDA.w #$0A00 : STA.b $20
; Modulo X to 0x29 Base (0x0200)
LDA.b $22 : AND.w #$01FF : ORA.w #$0200 : STA.b $22
BRA .reset_scroll
.snap_south ; Target 0x31 (Down)
; Snap Y to Bottom Edge of 0x29 (0x0C00)
LDA.w #$0C00 : STA.b $20
; Modulo X to 0x29 Base (0x0200)
LDA.b $22 : AND.w #$01FF : ORA.w #$0200 : STA.b $22
BRA .reset_scroll
.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
LDA.b $22 : AND.w #$01FF : ORA.w #$0200 : STA.b $22
.reset_scroll
SEP #$20
; Reset Overlay Scroll Drifts introduced by puzzle
STZ.b $E1
STZ.b $E3
STZ.b $E7
STZ.b $E9
.done
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] 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:
:ID: bug-time-system-bg-color
:END:
- *Symptom:* Custom background color not working correctly after recent changes.
- *Likely Cause:* Regression introduced during system refactoring or ZSCustomOverworld changes.
- *Files to Investigate:* =Overworld/time_system.asm=, =Overworld/ZSCustomOverworld.asm=
- *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.
- *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=)
*** TODO [#B] Refactor Minecart System [0/4]
*** ACTIVE [#B] Refactor Minecart System [1/4]
:PROPERTIES:
:ID: refactor-minecart
:END:
- *Analysis:* An impressive but highly complex system that would benefit greatly from better organization and data-driven design.
- *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.
- [ ] 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.
@@ -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.
- *Status:* Awaiting emulator verification.
*** TODO [#A] Resolve ZSOW vs. Lost Woods Conflict
*** DONE [#A] Resolve ZSOW vs. Lost Woods Conflict
:PROPERTIES:
:ID: bug-zsow-lostwoods
:END:
- *Analysis:* The `lost_woods.asm` puzzle directly conflicts with `ZSCustomOverworld.asm`'s transition handler.
- *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.
- *Analysis:* The `lost_woods.asm` puzzle logic was executing prematurely on entry, corrupting coordinates.
- *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.
- *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
: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.
** Quests & Narrative Sequences
*** ACTIVE [#A] Zora Sanctuary Questline [2/3] :quest:
*** ACTIVE [#A] Zora Sanctuary Questline [3/3] :quest:
:PROPERTIES:
:ID: quest-zora-sanctuary
:END:
- [X] Meet lone Sea Zora left at the Sanctuary, learn of Zora Princess.
- [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:
:PROPERTIES: