From 851da896446745c70d433b30f4d229ca466bc6df Mon Sep 17 00:00:00 2001 From: scawful Date: Mon, 8 Dec 2025 16:42:19 -0500 Subject: [PATCH] Fix: Zora Sanctuary Waterfall trigger and Lost Woods transition logic --- Docs/Issues/LostWoods_Transition_Desync.md | 45 ++++ Items/ocarina.asm | 64 ++++- Overworld/lost_woods.asm | 273 +++++++++++++-------- oracle.org | 25 +- 4 files changed, 281 insertions(+), 126 deletions(-) create mode 100644 Docs/Issues/LostWoods_Transition_Desync.md diff --git a/Docs/Issues/LostWoods_Transition_Desync.md b/Docs/Issues/LostWoods_Transition_Desync.md new file mode 100644 index 0000000..82d4ef2 --- /dev/null +++ b/Docs/Issues/LostWoods_Transition_Desync.md @@ -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) diff --git a/Items/ocarina.asm b/Items/ocarina.asm index 4d4c340..945190a 100644 --- a/Items/ocarina.asm +++ b/Items/ocarina.asm @@ -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 diff --git a/Overworld/lost_woods.asm b/Overworld/lost_woods.asm index a9007bb..cd2a929 100644 --- a/Overworld/lost_woods.asm +++ b/Overworld/lost_woods.asm @@ -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 diff --git a/oracle.org b/oracle.org index 2390342..98507f7 100644 --- a/oracle.org +++ b/oracle.org @@ -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: