diff --git a/Docs/Link.md b/Docs/Core/Link.md similarity index 100% rename from Docs/Link.md rename to Docs/Core/Link.md diff --git a/Docs/MemoryMap.md b/Docs/Core/MemoryMap.md similarity index 100% rename from Docs/MemoryMap.md rename to Docs/Core/MemoryMap.md diff --git a/Docs/Ram.md b/Docs/Core/Ram.md similarity index 100% rename from Docs/Ram.md rename to Docs/Core/Ram.md diff --git a/Docs/Core/SystemInteractions.md b/Docs/Core/SystemInteractions.md new file mode 100644 index 0000000..1a7b26b --- /dev/null +++ b/Docs/Core/SystemInteractions.md @@ -0,0 +1,140 @@ +# System Interaction & Compatibility Analysis + +References: +- `Docs/ZSCustomOverworld.md` +- `Docs/TimeSystem.md` + +## 1. Overview + +This document details the analysis of interactions between `ZSCustomOverworld` and other advanced systems in Oracle of Secrets. It outlines potential conflicts and proposes solutions to ensure they work together correctly. + +## 2. ZSCustomOverworld vs. Time System + +- **System:** `Overworld/time_system.asm` +- **Interaction Point:** Palette Modulation. + +### Analysis + +Both systems modify overworld palettes. ZSCustomOverworld sets the **base palette** for each area from its tables. The Time System applies a **color transformation** on top of the existing palette to simulate lighting changes. + +The conflict arises if the Time System reads the palette *before* ZSCustomOverworld has loaded the area-specific one, or if one system's writes completely overwrite the other's. + +The key routine is `LoadDayNightPaletteEffect` in `time_system.asm`, which is hooked into the game's main palette-loading functions. It intercepts every color write to CGRAM, applies its color subtraction logic, and then writes the final value. + +### Conclusion & Solution + +The current implementation is **mostly compatible by design**. The Time System's `LoadDayNightPaletteEffect` acts as a filter on all palette writes. When ZSCustomOverworld writes a new base palette to CGRAM, the Time System intercepts these writes and applies the day/night effect. + +**Recommendations:** +1. **No Code Change Needed for Compatibility (at this time):** The current hook-based approach should work. ZSCustomOverworld loads the base palette, and the Time System modifies it on the fly. +2. **Move Patches:** The `org` patches in `time_system.asm` should be moved to `Core/patches.asm` for consistency. This is a code organization improvement, not a compatibility fix. + +## 3. ZSCustomOverworld vs. Lost Woods Puzzle + +- **System:** `Overworld/lost_woods.asm` +- **Interaction Point:** Overworld Screen Transitions. + +### Analysis + +The Lost Woods puzzle works by intercepting the screen transition logic. When the player is in area `$29`, the `LostWoods` routine at `$A0F000` runs. It checks the player's exit direction against a predefined sequence. If the sequence is incorrect, it manually changes the player's and camera's coordinates to loop them back within the same screen, creating the maze effect. + +ZSCustomOverworld heavily modifies the screen transition logic via its hook at `OverworldHandleTransitions` (`$02A9C4`). The conflict is that ZSCustomOverworld's new, more complex transition logic does not account for the Lost Woods puzzle's override. + +### Conclusion & Solution + +This is a **direct conflict** that requires integration. The Lost Woods logic needs to be explicitly called from within ZSCustomOverworld's transition handler. + +**Recommendations:** +1. **Modify `OverworldHandleTransitions`:** In `ZSCustomOverworld.asm`, at the point where a transition is confirmed and the new screen ID is determined, add a check: + ```asm + ; Inside OverworldHandleTransitions, after a valid transition is detected + LDA.b $8A ; Current Area ID + CMP #$29 ; Is it the Lost Woods? + BNE .normal_transition + + ; If it is, call the Lost Woods logic + JSL LostWoods_PuzzleHandler + ; The handler should return with carry set if it handled the transition + BCS .transition_handled + +.normal_transition + ; ... existing ZS transition logic ... + +.transition_handled + ; ... code to finalize the transition after the puzzle logic runs ... + ``` +2. **Refactor `lost_woods.asm`:** The code in `lost_woods.asm` needs to be refactored into a proper subroutine (`LostWoods_PuzzleHandler`) that can be called via `JSL`. It should be modified to return a status (e.g., using the carry flag) to indicate whether it has overridden the transition or if the final, correct exit has been found. + +### Conclusion & Solution + +This is a **direct conflict** that requires integration. The Lost Woods logic needs to be explicitly called from within ZSCustomOverworld's transition handler. + +**Recommendations:** +1. **Modify `OverworldHandleTransitions`:** In `ZSCustomOverworld.asm`, at the point where a transition is confirmed and the new screen ID is determined, add a check: + ```asm + ; Inside OverworldHandleTransitions, after a valid transition is detected + LDA.b $8A ; Current Area ID + CMP #$29 ; Is it the Lost Woods? + BNE .normal_transition + + ; If it is, call the Lost Woods logic + JSL LostWoods_PuzzleHandler + ; The handler should return with carry set if it handled the transition + BCS .transition_handled + +.normal_transition + ; ... existing ZS transition logic ... + +.transition_handled + ; ... code to finalize the transition after the puzzle logic runs ... + ``` +2. **Refactor `lost_woods.asm`:** The code in `lost_woods.asm` needs to be refactored into a proper subroutine (`LostWoods_PuzzleHandler`) that can be called via `JSL`. It should be modified to return a status (e.g., using the carry flag) to indicate whether it has overridden the transition or if the final, correct exit has been found. + +## 4. ZSCustomOverworld vs. Song of Storms + +- **System:** `Items/ocarina.asm` +- **Interaction Point:** Overworld Screen Overlays. + +### Analysis + +The Song of Storms summons rain by directly writing the rain overlay ID (`#$9F`) to the overlay register (`$8C`). ZSCustomOverworld, however, determines the overlay for each screen via its `.OverlayTable`. A conflict occurs when: +1. The player plays the Song of Storms: The rain appears, but upon the next screen transition, ZSCustomOverworld will reload the area's default overlay, making the rain stop. +2. The player dismisses the storm: The code simply clears the overlay register, potentially removing a default overlay (like fog or clouds) that should be present in that area. + +### Conclusion & Solution + +This is a **direct conflict**. The Song of Storms logic must be made aware of ZSCustomOverworld's overlay system to properly override and restore the correct overlay. + +**Implemented Solution:** +1. **New SRAM Variable:** `SRAM_StormsActive` (`$7EF39D`) has been added to `Core/sram.asm` to persistently track whether the Song of Storms is active. +2. **Modified `OcarinaEffect_SummonStorms`:** + - This routine in `Items/ocarina.asm` now checks the current area's default overlay from `Pool_OverlayTable`. If the default is already rain (`#$9F`), it does nothing, preventing accidental cancellation of natural rain. + - Otherwise, it toggles the `SRAM_StormsActive` flag. Direct manipulation of the overlay register (`$8C`) has been removed from this routine. +3. **New `HandleStormsOverlay` Routine:** A new routine `HandleStormsOverlay` has been added to `Overworld/time_system.asm`. This routine is called from `RunClock` every frame the player is in the overworld. + - If `SRAM_StormsActive` is set, it forces the rain overlay (`$8C = #$9F`). + - If `SRAM_StormsActive` is not set, it does nothing, allowing ZSCustomOverworld's normal overlay logic to apply the area's default overlay. + +**Impact:** This solution ensures the rain state persists across transitions (dungeons, warps, screen changes) and correctly interacts with ZSCustomOverworld's overlay system without conflicts. It also prevents the Song of Storms from inadvertently canceling natural rain effects. + +## 5. ZSCustomOverworld vs. Day/Night Sprites + +- **System:** `Overworld/time_system.asm` and `Overworld/ZSCustomOverworld.asm` +- **Interaction Point:** Sprite Loading. + +### Analysis + +The original day/night sprite system relied on `CheckIfNight` and `CheckIfNight16Bit` routines to modify the game state (`$7EF3C5`) before vanilla sprite loading functions were called. This allowed different sprite sets to be loaded for day and night. + +With ZSOW v3, the vanilla sprite loading hook (`Overworld_LoadSprites` at `$09C4E3`) is replaced by ZSOW's `LoadOverworldSprites_Interupt` (`$09C4C7`). The conflict arose because the old day/night logic was no longer being called at the correct point in the execution flow. + +### Conclusion & Solution + +This conflict is **ongoing**. An attempted solution to integrate the `CheckIfNight` logic directly into ZSOW's sprite loading routine caused a regression, resulting in a `BRK` after returning from `LoadOverworldSprites_Interupt`. + +**Attempted Solution (Caused Regression):** +1. **Modified `LoadOverworldSprites_Interupt`:** In `ZSCustomOverworld.asm`, a `JSL CheckIfNight` call was inserted at the beginning of this routine. `CheckIfNight` returns a potentially modified game state (e.g., `GameState + 1` for night) in the accumulator. +2. **Adjusted Game State Usage:** The `LoadOverworldSprites_Interupt` routine then attempted to use this adjusted game state to look up the appropriate sprite set in ZSOW's `.Overworld_SpritePointers_state_..._New` tables. +3. **`CheckIfNight` and `CheckIfNight16Bit`:** These routines in `Overworld/time_system.asm` were uncommented and available. `CheckIfNight16Bit` is already integrated into ZSOW's `Sprite_LoadGfxProperties_Interupt` (`$00FC67`), ensuring sprite graphics properties are also adjusted for day/night. + +**Impact of Regression:** The game crashes with a `BRK` after `LoadOverworldSprites_Interupt` returns, indicating an issue with the state or stack after the `CheckIfNight` call. This solution is currently not viable. Further investigation is required to correctly integrate day/night sprite loading with ZSOW v3 without causing crashes. + diff --git a/Docs/Items.md b/Docs/Features/Items/Items.md similarity index 100% rename from Docs/Items.md rename to Docs/Features/Items/Items.md diff --git a/Docs/Masks.md b/Docs/Features/Masks/Masks.md similarity index 100% rename from Docs/Masks.md rename to Docs/Features/Masks/Masks.md diff --git a/Docs/Menu.md b/Docs/Features/Menu/Menu.md similarity index 100% rename from Docs/Menu.md rename to Docs/Features/Menu/Menu.md diff --git a/Docs/Music.md b/Docs/Features/Music/Music.md similarity index 100% rename from Docs/Music.md rename to Docs/Features/Music/Music.md diff --git a/Docs/General/AsarUsage.md b/Docs/General/AsarUsage.md new file mode 100644 index 0000000..5bbdb99 --- /dev/null +++ b/Docs/General/AsarUsage.md @@ -0,0 +1,51 @@ +# Asar Usage and ROM Management + +This document outlines the best practices for using Asar and managing ROM files within the Oracle of Secrets project. + +## Safety First: Preserve the Clean ROM + +The most important rule is to **never modify the clean ROM directly**. The clean ROM for this project is expected to be `Roms/oos169.sfc`. All patches must be applied to a *copy* of this file. This ensures that you always have a pristine base to work from and prevents irreversible changes to the original game file. + +The `Roms/` directory is ignored by git, so you don't have to worry about accidentally committing large ROM files. + +## The Build Script + +A `build.sh` script is provided to automate the build process and enforce safe ROM management. + +### Usage + +To build the ROM, run the script from the project root. You can optionally provide a version number. + +**Build with a version number:** +```sh +./build.sh 1.0 +``` +This will create a patched ROM named `Roms/oos-v1.0.sfc`. + +**Build without a version number:** +```sh +./build.sh +``` +This will create a patched ROM named `Roms/oos-patched.sfc`. + +### What it Does + +1. **Copies the ROM**: It creates a copy of `Roms/oos169.sfc`. +2. **Applies the Patch**: It runs `asar` to apply the main patch file (`Oracle_main.asm`) to the newly created ROM copy. +3. **Output**: The final, patched ROM is placed in the `Roms/` directory. + +## Manual Build Process (Not Recommended) + +If you need to run the build process manually, follow these steps: + +1. **Create a copy of the clean ROM**: + ```sh + cp Roms/oos169.sfc Roms/my_patched_rom.sfc + ``` + +2. **Run Asar**: + ```sh + asar Oracle_main.asm Roms/my_patched_rom.sfc + ``` + +Using the `build.sh` script is highly recommended to avoid mistakes. diff --git a/Docs/DevelopmentGuidelines.md b/Docs/General/DevelopmentGuidelines.md similarity index 100% rename from Docs/DevelopmentGuidelines.md rename to Docs/General/DevelopmentGuidelines.md diff --git a/Docs/QuestFlow.md b/Docs/Guides/QuestFlow.md similarity index 100% rename from Docs/QuestFlow.md rename to Docs/Guides/QuestFlow.md diff --git a/Docs/SpriteCreationGuide.md b/Docs/Guides/SpriteCreationGuide.md similarity index 100% rename from Docs/SpriteCreationGuide.md rename to Docs/Guides/SpriteCreationGuide.md diff --git a/Docs/README.md b/Docs/README.md new file mode 100644 index 0000000..a67dc47 --- /dev/null +++ b/Docs/README.md @@ -0,0 +1,26 @@ +# Oracle of Secrets Documentation + +Welcome to the documentation for Oracle of Secrets. This directory is organized to help you find information about the project's architecture, systems, and content. + +## Directory Structure + +- `./General/`: High-level project information, including development guidelines and build instructions. +- `./Core/`: Documentation for the core engine components, such as memory maps, the player engine (`Link.md`), and system interaction analysis. +- `./Features/`: Detailed analysis of major gameplay features. + - `./Features/Items/`: Information on custom and modified items. + - `./Features/Masks/`: Details on the mask transformation system. + - `./Features/Menu/`: Analysis of the custom menu and HUD. + - `./Features/Music/`: Guide to the music system and composition workflow. +- `./World/`: Information about the game world's construction. + - `./World/Overworld/`: Documentation for the overworld engine, including `ZSCustomOverworld` and the time system. + - `./World/Dungeons/`: Details on dungeon mechanics and custom features. +- `./Sprites/`: Analysis of all sprite types, including bosses, NPCs, and interactive objects. +- `./Guides/`: Step-by-step guides and tutorials, such as the sprite creation guide and the main quest flowchart. + +## Key Documents + +- **`General/DevelopmentGuidelines.md`**: The primary guide for coding standards, architecture, and best practices. Start here to understand the project's philosophy. +- **`Core/MemoryMap.md`**: A comprehensive map of custom WRAM and SRAM variables. +- **`Guides/QuestFlow.md`**: A detailed walkthrough of the main story and side-quest progression. +- **`Guides/SpriteCreationGuide.md`**: A tutorial for creating new custom sprites. +- **`World/Overworld/ZSCustomOverworld.md`**: A deep dive into the data-driven overworld engine that powers the game world. diff --git a/Docs/Bosses.md b/Docs/Sprites/Bosses.md similarity index 100% rename from Docs/Bosses.md rename to Docs/Sprites/Bosses.md diff --git a/Docs/Sprites/IceBlock.md b/Docs/Sprites/IceBlock.md new file mode 100644 index 0000000..eeeacf7 --- /dev/null +++ b/Docs/Sprites/IceBlock.md @@ -0,0 +1,78 @@ +# Ice Block System (`Sprites/Objects/ice_block.asm`) + +## Overview + +This file contains the logic for the pushable ice block sprite. It's a statue-like object that, when pushed by Link, slides in one direction until it collides with a wall, another object, or a switch. + +## Key Functionality + +- **Pushing Mechanics:** When Link makes contact, the block's `SprMiscA` register stores Link's facing direction. A timer (`SprTimerA`) is used to confirm the push, after which the block is set in motion. +- **Sliding:** Once pushed, the block moves at a constant velocity until a collision is detected. +- **Collision:** The block stops moving when it collides with a wall (detected via `SprCollision`) or another sprite (handled in `Statue_BlockSprites`). It also stops when it hits a specific switch tile. +- **State Management:** The sprite uses `SprMisc` registers to track its state, such as whether it's currently being pushed (`SprMiscC`) or is in motion (`SprMiscB`). + +## Analysis & Areas for Improvement + +The ice block is a classic Zelda puzzle element, but its current implementation suffers from over-sensitivity and unpredictable behavior, leading to frustration for beta testers. The goal is to achieve a "Pokémon-style" grid-based sliding puzzle feel, requiring intentional pushes and predictable movement. + +### Current Problems: +- **Over-sensitivity:** The block initiates movement on simple hitbox overlap with Link, leading to accidental pushes. +- **Unpredictable Direction:** Link's slight movements or diagonal contact can result in the block sliding in unintended directions. +- **Non-grid-aligned movement:** The block does not snap to a grid, making its stopping positions feel imprecise. + +### Proposed Improvements: + +#### 1. Intent-Based Push Mechanics (Addressing Sensitivity & Direction) +The current `JSL Sprite_CheckDamageToPlayerSameLayer` is too broad. It will be replaced with a more robust system: + +- **Directional Alignment Check:** A new subroutine (`IceBlock_CheckLinkPushAlignment`) will be implemented. This routine will verify that Link is: + - Directly adjacent to the ice block. + - Facing the ice block (e.g., if Link is facing right, the block must be to his right). + - Aligned within a small pixel tolerance (e.g., +/- 4 pixels) on the non-pushing axis (e.g., for a horizontal push, Link's Y-coordinate must be close to the block's Y-coordinate). +- **Push Confirmation Timer:** Instead of an immediate push, a short timer (e.g., 10-15 frames) will be introduced. Link must maintain the correct directional alignment and contact for this duration to confirm an intentional push. If contact or alignment is broken, the timer resets. +- **Locked Direction:** Once a push is confirmed, the block's movement direction will be locked to a single cardinal direction (horizontal or vertical) until it collides with an obstacle. + +#### 2. Predictable Movement & Stopping (Grid Alignment) +- **Grid Snapping on Push:** When a push is confirmed, the ice block's coordinates (`SprX, SprY`) will be snapped to the nearest 8-pixel grid boundary before movement begins. This ensures that all slides start and end cleanly on the game's tile grid. + +#### 3. Code Refactoring & Readability (General Improvements) +While implementing the above, the following existing suggestions from the previous analysis will also be applied: + +- **Use `subroutine` for All Code Blocks:** Convert all major logical blocks within `Sprites/Objects/ice_block.asm` into proper `subroutine`s for better scope management and readability. +- **Replace Magic Numbers with Constants:** Define named constants for all hardcoded values (speeds, timers, tile IDs, alignment tolerances) to improve code clarity and maintainability. +- **Refactor `Sprite_ApplyPush`:** Convert the `Sprite_ApplyPush` routine to use a lookup table for setting `SprXSpeed` and `SprYSpeed` based on the determined push direction. This will make the code more compact and easier to modify. +- **Clarify `Statue_BlockSprites`:** Rename this routine to `IceBlock_HandleSpriteToSpriteCollision` and add detailed comments to explain its logic, especially concerning sprite-to-sprite collision and recoil calculations. + +## Deeper Analysis: Sprite Solidity and Link's Collision + +To understand why the ice block is not solid, we need to look at both the sprite's properties and Link's collision detection code. + +### Sprite Property Analysis + +Several flags in the sprite's RAM data structure control its physical properties. These are the most relevant for solidity: + +* **`!Statue = 01` Property:** In the sprite's header, `!Statue = 01` is set. This is a high-level property that should be translated into one or more of the low-level RAM flags by the `%Set_Sprite_Properties` macro when the sprite is initialized. + +* **`SprDefl` (`$0CAA`):** This is the "Sprite Deflection" register and appears to be critical. + * **Bit 2 (`$04`):** The `Core/symbols.asm` file labels this the "pushable interaction flag". Although the comment says it's "Never queried," this is highly suspect and is the most likely candidate for enabling pushable-statue physics. + * **Our Bug:** Our previous attempts to modify `Sprite_IceBlock_Prep` either cleared this register entirely (`STZ.w SprDefl, X`) or left it alone, both of which resulted in Link walking through the block. This indicates that this register's value is crucial and must be set correctly, likely by the engine's default property loading routines. Our code was interfering with this. + +* **`SprHitbox` (`$0F60`):** This register contains the `I` (Ignore Collisions) bit. + * **Bit 7 (`$80`):** If this bit is set, the sprite will ignore all collisions with Link. We must ensure our code does not accidentally set this bit. + +### Link's Collision Logic (Hypothesis) + +The core of Link's interaction with the world is handled in `bank_07.asm`. + +* **`Link_HandleCardinalCollision` (`JSR` at `#_0782C2`):** This is the key function that processes Link's movement against solid objects. A full analysis is pending a complete reading of `bank_07.asm`, but we can hypothesize its behavior. + * **Hypothesis:** This routine likely loops through all active sprites on screen. For each sprite, it checks a combination of flags (e.g., `SprDefl`, `SprHitbox`) to determine if the sprite is solid. If it is, it performs a bounding box check. If Link's next position would overlap with a solid sprite, his movement is halted. The fact that Link walks through the ice block proves that the block is not being flagged as solid correctly. + +### Revised Troubleshooting Plan + +Based on this deeper analysis, the plan is to work *with* the game engine's properties, not against them. + +1. **Analyze `Link_HandleCardinalCollision`:** The top priority is to find and fully analyze this function in `bank_07.asm` to understand exactly which sprite flags it checks to identify a solid, pushable object. +2. **Analyze `SpritePrep_LoadProperties`:** Understand how the `%Set_Sprite_Properties` macro and the subsequent `JSL SpritePrep_LoadProperties` function translate the `!Statue = 01` property into RAM flags. This will reveal the correct default values for a statue. +3. **Correct `Sprite_IceBlock_Prep`:** With the knowledge from the steps above, write a definitive `Sprite_IceBlock_Prep` routine that correctly initializes all necessary flags (`SprDefl`, etc.) for a pushable statue, without overriding engine defaults. +4. **Verify Solidity:** Build and test to confirm Link collides with the block. +5. **Re-evaluate Push Logic:** Once the block is solid, re-evaluate the push initiation logic, which uses `JSL Sprite_CheckDamageToPlayerSameLayer`. If it still fails, we will have a solid object to debug against, which is a much better state. diff --git a/Docs/Sprites/Minecart.md b/Docs/Sprites/Minecart.md new file mode 100644 index 0000000..78c216d --- /dev/null +++ b/Docs/Sprites/Minecart.md @@ -0,0 +1,72 @@ +# Minecart System (`Sprites/Objects/minecart.asm`) + +## Overview + +The minecart is a highly complex and stateful sprite. It functions as a vehicle for the player, following predefined tracks, handling intersections, and persisting its location across rooms. This system is one of the most intricate custom sprites in the project. + +## Key Functionality + +- **State Machine:** The sprite operates on a state machine (`Minecart_WaitHoriz`, `Minecart_WaitVert`, `Minecart_Move...`) to handle waiting, player interaction, and movement. +- **Track System:** A custom system using a large block of SRAM (starting at `!MinecartTrackRoom = $0728`) allows up to 32 unique minecart "tracks" to exist in the world. The sprite saves its position and room index to this table, allowing it to reappear where the player left it. +- **Custom Collision:** The minecart does not use standard sprite collision. Instead, it reads the tile ID at its center to determine its behavior, following a complex set of rules for straight tracks, corners, intersections, and stops. +- **Player Interaction:** The player can start, stop, and change the direction of the cart at specific junctions. + +## Analysis & Areas for Improvement + +This is a very impressive piece of engineering for an SNES game. The code is dense and showcases advanced techniques. The main areas for improvement are in readability, data organization, and reducing code duplication. + +### 1. Use a `struct` for the Track System + +- **Observation:** The minecart tracking system is managed by a series of parallel arrays in SRAM (`!MinecartTrackRoom`, `!MinecartTrackX`, `!MinecartTrackY`). This is functional but can be confusing to read and maintain. +- **Suggestion:** This is a perfect use case for asar's `struct` directive. Define a structure for a single track and then create a table of those structures. + + *Example:* + ```asm + struct MinecartTrack + Room dw + XPos dw + YPos dw + endstruct + + ; In your RAM definitions + MinecartTracks table[32] of MinecartTrack + ``` +- **Benefit:** This makes the data structure explicit and far more readable. Accessing data becomes `MinecartTracks.Room[track_index]` instead of calculating offsets into a generic block of RAM. + +### 2. Refactor Movement and Direction-Setting Code + +- **Observation:** + - The routines `Minecart_MoveNorth`, `Minecart_MoveEast`, `Minecart_MoveSouth`, and `Minecart_MoveWest` contain very similar logic, differing only in the speed value and axis. + - The direction-setting routines (`Minecart_SetDirectionNorth`, etc.) also contain significant duplication. +- **Suggestion:** + - Create a single `Minecart_Move` subroutine that takes a direction as an argument. It could use a lookup table to fetch the correct speed and axis. + - Create a single `Minecart_SetDirection` subroutine that takes a direction argument and sets the `SprMiscB`, `!MinecartDirection`, and animation state from lookup tables. +- **Benefit:** Massively reduces code duplication, making the logic easier to debug and modify. A change to movement logic would only need to be made in one place. + +### 3. Use `subroutine` for All Code Blocks + +- **Observation:** The file uses a mix of labels and macros (`%GotoAction`) for its state machine. The main logic is a large jump table. +- **Suggestion:** Convert all logical blocks (`Minecart_WaitHoriz`, `HandleTileDirections`, `CheckForCornerTiles`, etc.) into proper `subroutine`s. +- **Benefit:** Enforces local scoping for labels, improves readability, and makes the code's structure much clearer. + +### 4. Replace Magic Numbers with Constants + +- **Observation:** The code is full of hardcoded values for directions, speeds, tile IDs, and sprite states. +- **Suggestion:** Define constants for all of these. + + *Example:* + ```asm + !CART_SPEED = 20 + !TILE_TRACK_CORNER_TL = $B2 + + !DIR_NORTH = 0 + !DIR_EAST = 1 + ; etc. + ``` +- **Benefit:** This is crucial for a system this complex. It makes the code self-documenting and dramatically reduces the chance of introducing bugs from typos in numerical values. + +### 5. Add High-Level Comments + +- **Observation:** The code has some comments, but they mostly describe *what* a single line is doing. +- **Suggestion:** Add block comments at the top of major subroutines (especially `Sprite_Minecart_Prep` and `HandleTileDirections`) explaining the overall *purpose* and *logic* of the code block. Explain the caching/tracking system in detail. +- **Benefit:** This is essential for future maintainability, especially for a system this intricate. It will make it possible for you or others to understand the code after being away from it for a while. diff --git a/Docs/NPCs.md b/Docs/Sprites/NPCs.md similarity index 100% rename from Docs/NPCs.md rename to Docs/Sprites/NPCs.md diff --git a/Docs/Objects.md b/Docs/Sprites/Objects.md similarity index 100% rename from Docs/Objects.md rename to Docs/Sprites/Objects.md diff --git a/Docs/Overlord.md b/Docs/Sprites/Overlord.md similarity index 100% rename from Docs/Overlord.md rename to Docs/Sprites/Overlord.md diff --git a/Docs/Dungeons.md b/Docs/World/Dungeons/Dungeons.md similarity index 100% rename from Docs/Dungeons.md rename to Docs/World/Dungeons/Dungeons.md diff --git a/Docs/Overworld.md b/Docs/World/Overworld/Overworld.md similarity index 100% rename from Docs/Overworld.md rename to Docs/World/Overworld/Overworld.md diff --git a/Docs/World/Overworld/TimeSystem.md b/Docs/World/Overworld/TimeSystem.md new file mode 100644 index 0000000..8bec2a6 --- /dev/null +++ b/Docs/World/Overworld/TimeSystem.md @@ -0,0 +1,74 @@ +# Time System (`Overworld/time_system.asm`) + +## Overview + +This system manages the in-game clock, day/night cycle, and associated palette effects. It runs continuously, updating the time and adjusting visual elements like the sky color and sprite palettes based on the current hour. + +## Key Functionality + +- **Clock:** A 24-hour clock is maintained in SRAM (`Hours` at `$7EE000`, `Minutes` at `$7EE001`). +- **Palette Modulation:** The core of the system is `ColorSubEffect`, which subtracts values from the red, green, and blue components of palettes based on the time of day, using lookup tables. +- **Time-Based Events:** The system checks for daily events (like the Magic Bean quest) and handles time manipulation effects (like the Song of Time). +- **HUD Display:** It includes logic to draw the current time to the HUD. + +## Analysis & Areas for Improvement + +The time system is functional but could be significantly improved in terms of structure, readability, and maintainability. + +### 1. Move Patches to `Core/patches.asm` + +- **Observation:** The file contains numerous `org` patches that modify vanilla game logic to hook in the time system. +- **Suggestion:** Relocate all `org` blocks to the centralized `Core/patches.asm` file. This is the most important cleanup step. +- **Benefit:** This will separate the new system's implementation from the act of patching it into the original code, making both parts easier to understand and manage. + +### 2. Use a `struct` for Time-Related Variables + +- **Observation:** Time-related variables are defined as individual labels pointing to SRAM addresses (e.g., `Hours`, `Minutes`, `TimeSpeed`, `!BlueVal`). +- **Suggestion:** Group these related variables into a single `struct`. + + *Example:* + ```asm + struct TimeState + Hours db + Minutes db + TimeSpeed db + ; ... other vars ... + BlueVal dw + GreenVal dw + RedVal dw + endstruct + + ; Then access with: + LDA TimeState.Hours, X + ``` +- **Benefit:** This provides a clear, high-level definition of the data structure, improves readability, and makes it easier to manage memory layout. + +### 3. Use `subroutine` for Code Blocks + +- **Observation:** The file consists of many large, labeled blocks of code (e.g., `RunClock`, `DrawClockToHud`, `ColorSubEffect`). +- **Suggestion:** Convert these blocks to use `subroutine`/`endsubroutine`. +- **Benefit:** This clearly defines the scope of each piece of logic, makes labels within them local by default, and improves overall code structure. + +### 4. Refactor Large Subroutines + +- **Observation:** `RunClock` is a very large and complex subroutine with multiple responsibilities and deep nesting. +- **Suggestion:** Break `RunClock` into smaller, more focused subroutines. + - `TimeSystem_CheckCanRun`: A subroutine to check the game state (`$10`, `$11`) and decide if the clock should tick. + - `TimeSystem_IncrementTime`: A subroutine to handle the core logic of incrementing minutes and hours. + - `TimeSystem_UpdatePalettes`: A subroutine to call the palette update logic when the hour changes. +- **Benefit:** Smaller, single-purpose functions are easier to read, debug, and maintain. + +### 5. Replace Magic Numbers with Constants + +- **Observation:** The code is replete with hardcoded values for time, palettes, and game states. +- **Suggestion:** Define constants for these values using `!` or `define()`. + + *Example:* + ```asm + !TIME_SPEED_NORMAL = $3F + !GAME_STATE_OVERWORLD = $09 + + LDA.b #!TIME_SPEED_NORMAL : STA.l TimeSpeed + LDA $10 : CMP #!GAME_STATE_OVERWORLD : BEQ .overworld + ``` +- **Benefit:** Makes the code self-documenting and reduces the risk of errors when modifying these values. diff --git a/Docs/World/Overworld/ZSCustomOverworld.md b/Docs/World/Overworld/ZSCustomOverworld.md new file mode 100644 index 0000000..bbfe868 --- /dev/null +++ b/Docs/World/Overworld/ZSCustomOverworld.md @@ -0,0 +1,59 @@ +# ZScream Custom Overworld (`Overworld/ZSCustomOverworld.asm`) + +## 1. Overview + +ZSCustomOverworld is a powerful and extensive system that replaces large parts of the vanilla *A Link to the Past* overworld engine. Its primary purpose is to remove hardcoded behaviors and replace them with a data-driven approach, allowing for a highly customizable overworld. + +Instead of relying on hardcoded logic for palettes, graphics, and layouts, ZSCustomOverworld reads this information from a large pool of data tables located in expanded ROM space (starting at `$288000`). These tables are designed to be edited by the ZScream overworld editor. + +## 2. Key Features + +- **Custom Palettes & Colors:** Assign a unique main palette and background color to every overworld screen. +- **Custom Graphics:** Assign custom static tile graphics (GFX groups) and animated tile sets to each area. +- **Custom Overlays:** Add or remove subscreen overlays (like rain, fog, and clouds) on a per-area basis. +- **Flexible Layouts:** Fixes vanilla bugs related to screen transitions and adds support for new area sizes, such as 2x1 "wide" and 1x2 "tall" areas, in addition to the standard 1x1 and 2x2. +- **Expanded Special Worlds:** Allows the normally limited "special world" areas (like the Master Sword grove) to be used as full-featured overworld screens. + +## 3. Core Architecture: Data Tables + +The system's flexibility comes from a large data pool starting at `org $288000`. Key tables include: + +- **`.BGColorTable`:** A table of 16-bit color values for the background of each overworld screen. +- **`.EnableTable`:** A series of flags to enable or disable specific features of ZSCustomOverworld, such as custom palettes or overlays. +- **`.MainPaletteTable`:** An index (`$00` to `$05`) into the game's main overworld palette sets for each screen. +- **`.MosaicTable`:** A bitfield for each screen to control mosaic transitions on a per-direction basis. +- **`.AnimatedTable`:** The GFX sheet ID for animated tiles for each screen. +- **`.OverlayTable`:** The overlay ID (e.g., `$9F` for rain) for each screen. `$FF` means no overlay. +- **`.OWGFXGroupTable`:** A large table defining the 8 GFX group sheets to be loaded for each overworld screen. +- **`.Overworld_ActualScreenID_New`:** A table that defines the "parent" screen for multi-screen areas (e.g., for a 2x2 area, all four screens point to the top-left screen's ID). +- **`.ByScreen..._New` Tables:** Four tables (`ByScreen1` for right, `2` for left, `3` for down, `4` for up) that define the camera boundaries for screen transitions. These are crucial for supporting non-standard area sizes. +- **`.Overworld_SpritePointers_state_..._New` Tables:** These tables define which sprite set to load for each overworld area based on the game state (`state_0` for the intro, `state_1` for post-Agahnim 1, `state_2` for post-Ganon). This allows for different enemy and NPC populations as the story progresses. + +## 4. Key Hooks & Functions + +ZSCustomOverworld replaces dozens of vanilla routines. Some of the most critical hooks are: + +- `org $0283EE` (**`PreOverworld_LoadProperties_Interupt`**): + - **Original:** `Overworld_LoadProperties`. This function loads music, palettes, and GFX when transitioning from a dungeon/house to the overworld. + - **New Logic:** The ZS version is heavily modified to read from the custom data tables for palettes and GFX instead of using hardcoded logic. It also removes hardcoded music changes for certain exits. + +- `org $02C692` (**`Overworld_LoadAreaPalettes`**): + - **Original:** A routine to load overworld palettes. + - **New Logic:** Reads the main palette index from the `.MainPaletteTable` instead of using a hardcoded value. + +- `org $02A9C4` (**`OverworldHandleTransitions`**): + - **Original:** The main logic for handling screen-to-screen transitions on the overworld. + - **New Logic:** This is one of the most heavily modified sections. The new logic uses the custom tables (`.ByScreen...`, `.Overworld_ActualScreenID_New`, etc.) to handle transitions between areas of different sizes, fixing vanilla bugs and allowing for new layouts. + +- `org $02AF58` (**`Overworld_ReloadSubscreenOverlay_Interupt`**): + - **Original:** Logic for loading subscreen overlays. + - **New Logic:** Reads the overlay ID from the `.OverlayTable` instead of using hardcoded checks for specific areas (like the Misery Mire rain). + +- `org $09C4C7` (**`LoadOverworldSprites_Interupt`**): + - **Original:** `LoadOverworldSprites`. This function determines which sprites to load for the current overworld screen. + - **New Logic:** The ZS version reads from the `.Overworld_SpritePointers_state_..._New` tables based on the current game state (`$7EF3C5`) to get a pointer to the correct sprite set for the area. This allows for dynamic sprite populations. + +## 5. Configuration + +- **`!UseVanillaPool`:** A flag that, when set to 1, forces the system to use data tables that mimic the vanilla game's behavior. This is useful for debugging. +- **`!Func...` Flags:** A large set of individual flags that allow for enabling or disabling specific hooks. This provides granular control for debugging and compatibility testing.