diff --git a/docs/dungeon_canvas_fix_summary.md b/docs/dungeon_canvas_fix_summary.md deleted file mode 100644 index a4bffdc4..00000000 --- a/docs/dungeon_canvas_fix_summary.md +++ /dev/null @@ -1,92 +0,0 @@ -# Dungeon Canvas Blank Screen - Fix Summary - -## Issue -The dungeon editor canvas was displaying only UI elements but the room graphics were blank, despite the rendering pipeline appearing to be set up correctly. - -## Root Cause -The background buffers (`bg1` and `bg2`) were never being populated with tile indices from room objects before calling `DrawBackground()`. - -### The Missing Step -1. `Room::RenderRoomGraphics()` would: - - Copy graphics data to `current_gfx16_` - - Draw floor pattern via `DrawFloor()` - - Call `DrawBackground()` to render tiles to pixels - -2. **Problem**: `DrawBackground()` expects the buffer to contain tile indices, but only the floor pattern was in the buffer. Room objects were loaded but never converted to tile indices in the buffer! - -### How ZScream Does It -- ZScream's `Room.loadLayoutObjects()` and object drawing methods populate `tilesBg1Buffer`/`tilesBg2Buffer` with tile indices -- `GraphicsManager.DrawBackground()` then converts those buffer indices to pixels -- Result: Complete room with floors, walls, and objects - -## Solution Implemented - -### Changes Made - -**File**: `src/app/zelda3/dungeon/room.cc` - -1. **Enhanced `RenderRoomGraphics()`** (line 286-315) - - Added call to `RenderObjectsToBackground()` after `DrawFloor()` but before `DrawBackground()` - -2. **Implemented `RenderObjectsToBackground()`** (line 317-383) - - Iterates through all `tile_objects_` - - Ensures each object has tiles loaded via `EnsureTilesLoaded()` - - For each object: - - Determines correct layer (BG1 vs BG2) - - Converts each `Tile16` to 4 `TileInfo` sub-tiles - - Converts each `TileInfo` to word format using `TileInfoToWord()` - - Places tiles in background buffer using `SetTileAt()` - -### Technical Details - -**Tile Structure**: -- `Tile16` = 16x16 pixel tile made of 4 `TileInfo` (8x8) tiles -- Arranged as: `[tile0_][tile1_]` (top row), `[tile2_][tile3_]` (bottom row) - -**Word Format** (per SNES specs): -``` -Bit 15: Vertical flip -Bit 14: Horizontal flip -Bit 13: Priority/over -Bits 10-12: Palette (3 bits) -Bits 0-9: Tile ID (10 bits) -``` - -**Buffer Layout**: -- 64x64 tiles (512x512 pixels) -- Buffer index = `y * 64 + x` -- Each entry is a uint16_t word - -## Testing - -To verify the fix: -1. Build yaze with: `cmake --build build --target yaze` -2. Run yaze and open the dungeon editor -3. Select any room -4. Canvas should now display: - - ✅ Floor pattern - - ✅ Wall structures - - ✅ Room objects - - ✅ Correct layer separation - -## Future Enhancements - -- Add layout object rendering (walls from room layout data) -- Optimize tile placement algorithm for different object types -- Add support for animated tiles -- Implement object-specific draw routines (similar to ZScream's `DungeonObjectDraw.cs`) - -## Related Files - -- **Implementation**: `src/app/zelda3/dungeon/room.cc` -- **Header**: `src/app/zelda3/dungeon/room.h` -- **Buffer**: `src/app/gfx/background_buffer.cc` -- **Tiles**: `src/app/gfx/snes_tile.h` -- **Reference**: `ZeldaFullEditor/GraphicsManager.cs` (ZScream) -- **Reference**: `ZeldaFullEditor/Data/Types/DungeonObjectDraw.cs` (ZScream) - -## Build Status - -✅ **Compilation**: Success (0 errors) -⚠️ **Warnings**: 21 warnings (pre-existing, unrelated to this fix) - diff --git a/docs/dungeon_developer_guide.md b/docs/dungeon_developer_guide.md deleted file mode 100644 index 732e9d03..00000000 --- a/docs/dungeon_developer_guide.md +++ /dev/null @@ -1,96 +0,0 @@ -# Dungeon Editor Developer Guide - -## 1. Implementation Plan - -**Goal**: Implement fully functional dungeon room editing based on ZScream's proven approach. - -### 1.1. Current State Analysis - -- **Working**: Room header loading, basic class structures (`Room`, `RoomObject`), graphics loading, and UI framework. -- **Not Working**: Object parsing, drawing, placement, and saving. The core issue is the incomplete implementation of the 3-byte object encoding/decoding scheme and the failure to render object tiles to the background buffer. - -### 1.2. ZScream's Approach - -ZScream's implementation relies on several key concepts that need to be ported: - -- **Object Encoding**: Three different 3-byte encoding formats for objects based on their ID range (Type 1, Type 2, Type 3). -- **Layer Separation**: Objects are organized into three layers (BG1, BG2, BG3) separated by `0xFFFF` markers in the ROM. -- **Object Loading**: A loop parses the object data, decodes each 3-byte entry based on its type, and handles layer and door markers. -- **Object Drawing**: Each object's constituent tiles are drawn to the correct background buffer (`tilesBg1Buffer` or `tilesBg2Buffer`), which is then used to render the final room image. - -### 1.3. Implementation Tasks - -1. **Core Object System (High Priority)**: - * Implement the three different object encoding and decoding schemes. - * Enhance the room object parser to correctly handle layers and door sections. - * Implement tile loading for objects from the ROM's subtype tables. - * Create an object drawing system that renders the loaded tiles to the correct background buffer. -2. **Editor UI Integration (Medium Priority)**: - * Implement object placement, selection, and drag-and-drop on the canvas. - * Create UI panels for editing object properties (position, size, layer). -3. **Save System (High Priority)**: - * Implement the serialization of room objects back into the 3-byte ROM format, respecting layers and markers. - * Implement the logic to write the serialized data back to the ROM, handling potential room size expansion. - ---- - -## 2. Graphics Rendering Pipeline - -This section provides a comprehensive analysis of how the dungeon editor renders room graphics. - -### 2.1. Architecture Diagram - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ ROM Data │ -│ ┌────────────────────────┐ ┌──────────────────────────────────┐│ -│ │ Room Headers │ │ Dungeon Palettes ││ -│ │ - Palette ID │ │ - dungeon_main[id] ││ -│ │ - Blockset ID │ │ - sprites_aux1[id] ││ -│ └────────────────────────┘ └──────────────────────────────────┘│ -└───────────────────────────┬─────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ Room Loading (room.cc) │ -│ ├─ Load 16 blocks (graphics sheets) │ -│ └─ Copy graphics to a 32KB buffer (`current_gfx16_`) │ -└───────────────────────────┬─────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ Room::RenderRoomGraphics() Pipeline │ -│ │ -│ Step 1: DrawFloor() on BG1 and BG2 buffers │ -│ └─ Populates buffers with repeating floor tile pattern. │ -│ │ -│ Step 2: RenderObjectsToBackground() │ -│ └─ Iterates room objects and draws their tiles into the BG1/BG2 buffers.│ -│ │ -│ Step 3: DrawBackground() on BG1 and BG2 │ -│ └─ Converts tile words in buffers to 8bpp indexed pixel data in a Bitmap.│ -│ │ -│ Step 4: Apply Palette & Create/Update Texture │ -│ ├─ Get dungeon_main palette for the room. │ -│ ├─ Apply palette to the Bitmap. │ -│ └─ Create or update an SDL_Texture from the paletted Bitmap. │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 2.2. Blank Canvas Bug: Root Cause Analysis - -A critical "blank canvas" bug was diagnosed and fixed. The root cause was multifaceted: - -1. **Missing Object Rendering**: The `RenderObjectsToBackground()` step was missing entirely. The background buffers only contained the floor pattern, so walls and objects were never drawn. - * **Fix**: Implemented `RenderObjectsToBackground()` to iterate through all loaded room objects and draw their constituent tiles into the correct background buffer (`bg1` or `bg2`). - -2. **Missing Palette Application**: The indexed-pixel `Bitmap` was being converted to a texture without its palette. SDL had no information to map the color indices to actual colors, resulting in a blank texture. - * **Fix**: Added a call to `bitmap.SetPaletteWithTransparent()` *before* the `CreateAndRenderBitmap()` or `UpdateBitmap()` call. - -3. **Incorrect Tile Word Format**: The floor drawing logic was writing only the tile ID to the buffer, discarding the necessary palette information within the 16-bit tile word. - * **Fix**: Modified the code to use `gfx::TileInfoToWord()` to ensure the full 16-bit value, including palette index, was written. - -4. **Incorrect Palette ID**: The system was hardcoded to use palette `0` for all rooms, causing most dungeons to render with incorrect, "gargoyle-y" colors. - * **Fix**: Modified the code to use the specific `palette` ID loaded from each room's header. - -**Key Takeaway**: The entire rendering pipeline, from buffer population to palette application, must be executed in the correct order for graphics to appear. diff --git a/docs/dungeon_editing_implementation_plan.md b/docs/dungeon_editing_implementation_plan.md deleted file mode 100644 index 86cf7af0..00000000 --- a/docs/dungeon_editing_implementation_plan.md +++ /dev/null @@ -1,1172 +0,0 @@ -# Dungeon Editing Implementation Plan for Yaze - -**Status**: Planning Phase -**Goal**: Implement fully functional dungeon room editing based on ZScream's proven approach -**Created**: October 4, 2025 - -## Executive Summary - -The dungeon object system in yaze currently does not work properly. ZScream's dungeon editing is fully functional and provides a proven reference implementation. This document outlines a comprehensive plan to implement working dungeon editing in yaze by analyzing ZScream's approach and adapting it to yaze's C++ architecture. - -## Table of Contents - -1. [Current State Analysis](#current-state-analysis) -2. [ZScream's Working Approach](#zscreams-working-approach) -3. [ROM Data Structure Reference](#rom-data-structure-reference) -4. [Implementation Tasks](#implementation-tasks) -5. [Technical Architecture](#technical-architecture) -6. [Testing Strategy](#testing-strategy) -7. [References](#references) - ---- - -## Current State Analysis - -### What Works in Yaze - -- ✅ Room header loading (basic properties: blockset, palette, spriteset) -- ✅ Room class structure exists -- ✅ RoomObject class with basic properties -- ✅ Graphics loading system (blocks, palettes) -- ✅ Room layout system for structural elements -- ✅ Sprite loading (separate from objects) -- ✅ UI framework with canvas, selectors, and viewers - -### What Doesn't Work - -- ❌ **Object Parsing**: Objects are loaded but not properly decoded -- ❌ **Object Drawing**: Objects don't render correctly on canvas -- ❌ **Object Placement**: No working system to place/edit objects -- ❌ **Object Encoding**: Can't save objects back to ROM format -- ❌ **Object Selection**: Can't select/move/delete objects properly -- ❌ **Layer Management**: Layer separation not working correctly -- ❌ **Door Objects**: Door section (0xF0 FF) not handled -- ❌ **Object Types**: Type1, Type2, Type3 distinctions not properly implemented -- ❌ **Tile Loading**: Objects don't load their tile data correctly -- ❌ **Collision/Properties**: Object-specific properties not tracked - -### Key Problems Identified - -1. **Object Data Format**: The 3-byte object encoding/decoding is incomplete -2. **Layer Separation**: Objects on BG1, BG2, BG3 not properly separated -3. **Object Rendering**: Object tile data not being loaded and drawn -4. **Editor Integration**: No working UI flow for object placement -5. **Save System**: No encoding of edited objects back to ROM format - ---- - -## ZScream's Working Approach - -### Room Data Structure (ZScream) - -```csharp -public class Room -{ - // Header Data (14 bytes at header_location) - public byte layout; // Byte 1 (bits 2-4) - public byte floor1; // Byte 0 (bits 0-3) - public byte floor2; // Byte 0 (bits 4-7) - public byte blockset; // Byte 2 - public byte spriteset; // Byte 3 - public byte palette; // Byte 1 (bits 0-5) - public Background2 bg2; // Byte 0 (bits 5-7) - public CollisionKey collision; // Byte 0 (bits 2-4) - public EffectKey effect; // Byte 4 - public TagKey tag1; // Byte 5 - public TagKey tag2; // Byte 6 - public byte holewarp; // Byte 9 - public byte[] staircase_rooms; // Bytes 10-13 (4 stairs) - public byte[] staircase_plane; // Byte 7-8 (plane info) - - // Object Data - public List tilesObjects; // Objects in room - public List tilesLayoutObjects; // Layout/structural objects - public List sprites; // Sprites (separate system) - public List chest_list; // Chests in room - public byte[] blocks; // 16 GFX blocks for room -} -``` - -### Object Data Structure (ZScream) - -```csharp -public class Room_Object -{ - // ROM Data (encoded in 3 bytes) - public byte X; // Position X (0-63) - public byte Y; // Position Y (0-63) - public byte Size; // Size parameter (0-15) - public ushort id; // Object ID (0x000-0xFFF) - public LayerType Layer; // BG1, BG2, or BG3 - - // Rendering Data - public List tiles; // Tile data for drawing - public int width, height; // Computed object size - public int offsetX, offsetY; // Drawing offset - - // Editor Data - public string name; // Object name for UI - public ObjectOption options; // Flags: Door, Chest, Block, Torch, Stairs - public DungeonLimits LimitClass; // Object limits tracking - public int uniqueID; // Unique ID for undo/redo -} -``` - -### Object Encoding Format (Critical!) - -ZScream uses **three different encoding formats** based on object ID range: - -#### Type 1 Objects (ID 0x000-0x0FF) - Standard Objects - -**Encoding**: `xxxxxxss yyyyyyss iiiiiiii` - -``` -Byte 1: (X << 2) | ((Size >> 2) & 0x03) -Byte 2: (Y << 2) | (Size & 0x03) -Byte 3: Object ID (0x00-0xFF) -``` - -**Decoding**: -``` -X = (Byte1 & 0xFC) >> 2 // Bits 2-7 = X position -Y = (Byte2 & 0xFC) >> 2 // Bits 2-7 = Y position -Size = ((Byte1 & 0x03) << 2) | (Byte2 & 0x03) // Size 0-15 -ID = Byte3 // Object ID -``` - -#### Type 2 Objects (ID 0x100-0x1FF) - Special Objects - -**Encoding**: `111111xx xxxxyyyy yyiiiiii` - -``` -Byte 1: 0xFC | ((X & 0x30) >> 4) // 0xFC-0xFF -Byte 2: ((X & 0x0F) << 4) | ((Y & 0x3C) >> 2) -Byte 3: ((Y & 0x03) << 6) | (ID & 0x3F) -``` - -**Decoding**: -``` -X = ((Byte1 & 0x03) << 4) | ((Byte2 & 0xF0) >> 4) // X position -Y = ((Byte2 & 0x0F) << 2) | ((Byte3 & 0xC0) >> 6) // Y position -ID = (Byte3 & 0x3F) | 0x100 // Object ID -Size = 0 // No size parameter -``` - -#### Type 3 Objects (ID 0xF00-0xFFF) - Extended Objects - -**Encoding**: `xxxxxxii yyyyyyii 11111iii` - -``` -Byte 1: (X << 2) | (ID & 0x03) -Byte 2: (Y << 2) | ((ID >> 2) & 0x03) -Byte 3: (ID >> 4) & 0xFF // 0xF0-0xFF -``` - -**Decoding**: -``` -X = (Byte1 & 0xFC) >> 2 -Y = (Byte2 & 0xFC) >> 2 -ID = ((Byte3 << 4) & 0xF00) | ((Byte2 & 0x03) << 2) | (Byte1 & 0x03) | 0x80 -``` - -### Layer and Door Markers - -Objects are organized in **layers** separated by special markers: - -``` -[Floor/Layout byte] -[Layout configuration byte] - -0xFF 0xFF <- Layer 1 End - -0xFF 0xFF <- Layer 2 End - -[Optional Door Section] -0xF0 0xFF <- Door Section Marker - -0xFF 0xFF <- End of Room -``` - -### Object Loading Process (ZScream) - -```csharp -// 1. Get room object pointer (3 bytes) -int object_pointer = ROM at (room_object_pointer + room_id * 3) -int objects_location = SnesToPc(object_pointer) - -// 2. Read floor/layout -byte floor1 = data[objects_location] & 0x0F -byte floor2 = (data[objects_location] >> 4) & 0x0F -byte layout = (data[objects_location + 1] >> 2) & 0x07 - -// 3. Parse objects -int pos = objects_location + 2 -int layer = 0 -bool door = false - -while (true) { - byte b1 = data[pos] - byte b2 = data[pos + 1] - - // Check for layer end - if (b1 == 0xFF && b2 == 0xFF) { - layer++ - pos += 2 - if (layer == 3) break - continue - } - - // Check for door section - if (b1 == 0xF0 && b2 == 0xFF) { - door = true - pos += 2 - continue - } - - byte b3 = data[pos + 2] - - if (!door) { - // Decode object based on type - RoomObject obj = DecodeObject(b1, b2, b3, layer) - tilesObjects.Add(obj) - pos += 3 - } else { - // Decode door (2 bytes) - door_pos = b1 >> 3 - door_dir = b1 & 0x07 - door_type = b2 - // Create door object - pos += 2 - } -} -``` - -### Object Saving Process (ZScream) - -```csharp -public byte[] getTilesBytes() -{ - List objectsBytes = new List(); - List doorsBytes = new List(); - - // 1. Write floor/layout - byte floorbyte = (byte)((floor2 << 4) + floor1); - byte layoutbyte = (byte)(layout << 2); - objectsBytes.Add(floorbyte); - objectsBytes.Add(layoutbyte); - - // 2. Write Layer 1 - foreach (object in layer0_objects) { - byte[] encoded = EncodeObject(object); - objectsBytes.AddRange(encoded); - } - objectsBytes.Add(0xFF); - objectsBytes.Add(0xFF); - - // 3. Write Layer 2 - foreach (object in layer1_objects) { - byte[] encoded = EncodeObject(object); - objectsBytes.AddRange(encoded); - } - objectsBytes.Add(0xFF); - objectsBytes.Add(0xFF); - - // 4. Write Layer 3 - foreach (object in layer2_objects) { - byte[] encoded = EncodeObject(object); - objectsBytes.AddRange(encoded); - } - - // 5. Write Doors if any - if (has_doors) { - objectsBytes.Add(0xF0); - objectsBytes.Add(0xFF); - objectsBytes.AddRange(doorsBytes); - } - - objectsBytes.Add(0xFF); - objectsBytes.Add(0xFF); - - return objectsBytes.ToArray(); -} -``` - -### Object Tile Loading (ZScream) - -Each object references tile data from ROM tables: - -```csharp -// Get tile pointer from subtype table -int tile_ptr = GetSubtypeTablePointer(object_id); -int tile_pos = ROM[tile_ptr] + ROM[tile_ptr+1] << 8; - -// Load tiles (each tile is 2 bytes: ID and properties) -for (int i = 0; i < tile_count; i++) { - byte tile_id = ROM[tile_pos + i*2]; - byte tile_prop = ROM[tile_pos + i*2 + 1]; - - Tile tile = new Tile(tile_id, tile_prop); - object.tiles.Add(tile); -} -``` - -### Object Drawing (ZScream) - -```csharp -public void Draw() -{ - // Calculate object dimensions based on size - width = basewidth + (sizewidth * Size); - height = baseheight + (sizeheight * Size); - - // Draw each tile at computed position - foreach (Tile tile in tiles) { - draw_tile(tile, tile_x * 8, tile_y * 8); - } -} - -// Tile drawing writes to background buffers -public void draw_tile(Tile t, int xx, int yy) -{ - int buffer_index = ((xx / 8) + offsetX + nx) + - ((ny + offsetY + (yy / 8)) * 64); - - if (Layer == BG1) { - tilesBg1Buffer[buffer_index] = t.GetTileInfo(); - } - if (Layer == BG2) { - tilesBg2Buffer[buffer_index] = t.GetTileInfo(); - } -} -``` - ---- - -## ROM Data Structure Reference - -### Key ROM Addresses (from ALTTP Disassembly) - -``` -# Room Headers -room_object_pointer = 0x874C # Long pointer to room object data -room_object_layout_pointer = 0x882D # Pointer to layout data -kRoomHeaderPointer = 0xB5DD # Long pointer to room headers -kRoomHeaderPointerBank = 0xB5E7 # Bank byte - -# Graphics -gfx_groups_pointer = 0x6237 # Graphics groups -blocks_pointer1-4 = 0x15AFA-0x15B0F # Block data pointers -dungeons_palettes = 0xDD734 # Palette data -sprite_blockset_pointer = 0x5B57 # Sprite blocksets - -# Sprites -rooms_sprite_pointer = 0x4C298 # 2-byte bank pointer -sprites_data = 0x4D8B0 # Sprite data -sprites_end_data = 0x4EC9E # End of sprite data - -# Chests -chests_length_pointer = 0xEBF6 # Chest count -chests_data_pointer1 = 0xEBFB # Chest data pointer - -# Doors -door_gfx_up/down/left/right = 0x4D9E-0x4EC6 # Door graphics -door_pos_up/down/left/right = 0x197E-0x19C6 # Door positions - -# Torches, Blocks, Pits -torch_data = 0x2736A # Torch data -torches_length_pointer = 0x88C1 # Torch count -blocks_length = 0x8896 # Block count -pit_pointer = 0x394AB # Pit data -pit_count = 0x394A6 # Pit count - -# Messages -messages_id_dungeon = 0x3F61D # Message IDs - -# Object Subtypes (Tile Data Tables) -kRoomObjectSubtype1 = 0x8000 # Type 1 table -kRoomObjectSubtype2 = 0x83F0 # Type 2 table -kRoomObjectSubtype3 = 0x84F0 # Type 3 table -kRoomObjectTileAddress = 0x1B52 # Base tile address -``` - -### Room Header Format (14 bytes) - -``` -Offset | Bits | Description --------|-----------|------------------------------------------ -0 | 0-0 | Is Light (1 = light room, 0 = dark) -0 | 2-4 | Collision type -0 | 5-7 | Background2 type -1 | 0-5 | Palette ID -2 | 0-7 | Blockset ID -3 | 0-7 | Spriteset ID -4 | 0-7 | Effect -5 | 0-7 | Tag 1 -6 | 0-7 | Tag 2 -7 | 2-3 | Staircase 1 plane -7 | 4-5 | Staircase 2 plane -7 | 6-7 | Staircase 3 plane -8 | 0-1 | Staircase 4 plane -9 | 0-7 | Holewarp room -10 | 0-7 | Staircase 1 room -11 | 0-7 | Staircase 2 room -12 | 0-7 | Staircase 3 room -13 | 0-7 | Staircase 4 room -``` - -### Object Data Format - -``` -[Floor/Layout - 2 bytes] - Byte 0: floor1 (bits 0-3), floor2 (bits 4-7) - Byte 1: layout (bits 2-4) - -[Layer 1 Objects] - For each object: 3 bytes (Type1/Type2/Type3 encoding) - -[Layer 1 End Marker] - 0xFF 0xFF - -[Layer 2 Objects] - For each object: 3 bytes (Type1/Type2/Type3 encoding) - -[Layer 2 End Marker] - 0xFF 0xFF - -[Layer 3 Objects] - For each object: 3 bytes (Type1/Type2/Type3 encoding) - -[Optional: Door Section] - 0xF0 0xFF (Door section marker) - For each door: 2 bytes - Byte 1: door_pos (bits 3-7), door_dir (bits 0-2) - Byte 2: door_type - -[End Marker] - 0xFF 0xFF -``` - -### Tile Data Format - -``` -Each Tile16 (metatile) consists of 4 Tile8s (8x8 tiles): - Word 0: Top-left tile - Word 1: Top-right tile - Word 2: Bottom-left tile - Word 3: Bottom-right tile - -Each Word encodes: - Bits 0-9: Tile ID (0-1023) - Bit 10: Horizontal flip - Bit 11: Vertical flip - Bits 12-14: Palette (0-7) - Bit 15: Priority -``` - ---- - -## Implementation Tasks - -### Phase 1: Core Object System (HIGH PRIORITY) - -#### Task 1.1: Object Encoding/Decoding ✅ CRITICAL - -**Files**: `src/app/zelda3/dungeon/room_object.cc`, `room_object.h` - -**Objectives**: -- Implement `DecodeObjectFromBytes(byte b1, byte b2, byte b3, layer)` -- Implement `EncodeObjectToBytes(RoomObject) -> [byte, byte, byte]` -- Handle Type1, Type2, Type3 encoding schemes correctly -- Add unit tests for encoding/decoding round-trips - -**Dependencies**: None - -**Estimated Time**: 3-4 hours - -**Implementation Details**: - -```cpp -// Add to room_object.h -struct ObjectBytes { - uint8_t b1; - uint8_t b2; - uint8_t b3; -}; - -// Decode object from 3-byte ROM format -static RoomObject DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3, - uint8_t layer); - -// Encode object to 3-byte ROM format -ObjectBytes EncodeObjectToBytes() const; - -// Determine object type from bytes -static int DetermineObjectType(uint8_t b1, uint8_t b3); -``` - -#### Task 1.2: Enhanced Object Parsing ✅ CRITICAL - -**Files**: `src/app/zelda3/dungeon/room.cc`, `room.h` - -**Objectives**: -- Refactor `ParseObjectsFromLocation()` to handle all 3 layers -- Implement door section parsing (0xF0 FF marker) -- Track objects by layer properly -- Handle layer end markers (0xFF FF) - -**Dependencies**: Task 1.1 - -**Estimated Time**: 4-5 hours - -**Implementation Details**: - -```cpp -// Add to room.h -struct LayeredObjects { - std::vector layer1; - std::vector layer2; - std::vector layer3; - std::vector doors; -}; - -// Enhanced parsing with layer separation -absl::Status ParseLayeredObjects(int objects_location, - LayeredObjects& out_objects); -``` - -#### Task 1.3: Object Tile Loading ✅ CRITICAL - -**Files**: `src/app/zelda3/dungeon/room_object.cc`, `object_parser.cc` - -**Objectives**: -- Implement proper tile loading from subtype tables -- Load tiles based on object ID and subtype -- Cache tile data for performance -- Handle animated tiles - -**Dependencies**: Task 1.1 - -**Estimated Time**: 4-5 hours - -**Implementation Details**: - -```cpp -// Enhance RoomObject::EnsureTilesLoaded() -absl::Status LoadTilesFromSubtypeTable(int object_id); - -// Get subtype table info -SubtypeTableInfo GetSubtypeTable(int object_id); - -// Load tile data from ROM -absl::StatusOr> LoadTileData(int tile_ptr); -``` - -#### Task 1.4: Object Drawing System ⚠️ HIGH PRIORITY - -**Files**: `src/app/zelda3/dungeon/object_renderer.cc` - -**Objectives**: -- Implement proper object rendering with tiles -- Draw objects at correct positions with proper offsets -- Handle object sizing based on Size parameter -- Render to correct background layer (BG1/BG2/BG3) - -**Dependencies**: Task 1.3 - -**Estimated Time**: 5-6 hours - -**Implementation Details**: - -```cpp -// Enhanced rendering -absl::Status RenderObjectWithTiles(const RoomObject& object, - const gfx::SnesPalette& palette, - std::vector& bg1_buffer, - std::vector& bg2_buffer); - -// Compute object dimensions -void ComputeObjectDimensions(RoomObject& object); - -// Draw individual tile -void DrawTileToBuffer(const gfx::Tile16& tile, int x, int y, - std::vector& buffer); -``` - ---- - -### Phase 2: Editor UI Integration (MEDIUM PRIORITY) - -#### Task 2.1: Object Placement System - -**Files**: `src/app/editor/dungeon/dungeon_object_interaction.cc` - -**Objectives**: -- Implement click-to-place object system -- Snap objects to grid -- Show preview of object before placement -- Validate object placement (limits, collisions) - -**Dependencies**: Phase 1 complete - -**Estimated Time**: 4-5 hours - -#### Task 2.2: Object Selection System - -**Files**: `src/app/editor/dungeon/dungeon_object_interaction.cc` - -**Objectives**: -- Click to select objects -- Drag to move objects -- Multi-select with box selection -- Show selection highlight - -**Dependencies**: Task 2.1 - -**Estimated Time**: 3-4 hours - -#### Task 2.3: Object Properties Editor - -**Files**: `src/app/editor/dungeon/dungeon_object_selector.cc` - -**Objectives**: -- Show object properties in sidebar -- Edit object properties (X, Y, Size, Layer) -- Change object type -- Real-time preview of changes - -**Dependencies**: Task 2.2 - -**Estimated Time**: 3-4 hours - -#### Task 2.4: Layer Management UI - -**Files**: `src/app/editor/dungeon/dungeon_editor.cc` - -**Objectives**: -- Layer visibility toggles -- Active layer selection -- Layer opacity controls -- Show layer indicators - -**Dependencies**: Task 2.1 - -**Estimated Time**: 2-3 hours - ---- - -### Phase 3: Save System (HIGH PRIORITY) - -#### Task 3.1: Room Object Encoding ✅ CRITICAL - -**Files**: `src/app/zelda3/dungeon/room.cc` - -**Objectives**: -- Implement `SerializeRoomObjects() -> byte[]` -- Encode objects by layer with proper markers -- Handle door section separately -- Maintain floor/layout bytes - -**Dependencies**: Task 1.1 - -**Estimated Time**: 4-5 hours - -**Implementation Details**: - -```cpp -// Add to room.h -struct SerializedRoomData { - std::vector data; - int size; - int original_size; -}; - -// Serialize all room objects to ROM format -absl::StatusOr SerializeRoomObjects(); - -// Encode objects for one layer -std::vector EncodeLayer(const std::vector& objects); - -// Encode door section -std::vector EncodeDoors(const std::vector& doors); -``` - -#### Task 3.2: ROM Writing System - -**Files**: `src/app/rom.cc`, `src/app/zelda3/dungeon/room.cc` - -**Objectives**: -- Write serialized object data back to ROM -- Update room pointers if size changed -- Handle room size expansion -- Validate written data - -**Dependencies**: Task 3.1 - -**Estimated Time**: 5-6 hours - -#### Task 3.3: Save Validation - -**Files**: `src/app/zelda3/dungeon/room.cc` - -**Objectives**: -- Verify encoded data matches original format -- Check object limits (sprites, doors, chests) -- Validate room size constraints -- Round-trip testing (load -> save -> load) - -**Dependencies**: Task 3.2 - -**Estimated Time**: 3-4 hours - ---- - -### Phase 4: Advanced Features (LOW PRIORITY) - -#### Task 4.1: Undo/Redo System - -**Files**: `src/app/zelda3/dungeon/dungeon_editor_system.cc` - -**Objectives**: -- Track object changes -- Implement undo stack -- Implement redo stack -- Handle multiple operations - -**Dependencies**: Phase 2, Phase 3 - -**Estimated Time**: 5-6 hours - -#### Task 4.2: Copy/Paste Objects - -**Files**: `src/app/editor/dungeon/dungeon_object_interaction.cc` - -**Objectives**: -- Copy selected objects -- Paste with offset -- Duplicate objects -- Paste across rooms - -**Dependencies**: Task 2.2 - -**Estimated Time**: 2-3 hours - -#### Task 4.3: Object Library/Templates - -**Files**: `src/app/editor/dungeon/dungeon_object_selector.cc` - -**Objectives**: -- Save common object configurations -- Load object templates -- Share templates across rooms -- Import/export templates - -**Dependencies**: Phase 2 - -**Estimated Time**: 4-5 hours - -#### Task 4.4: Room Import/Export - -**Files**: `src/app/zelda3/dungeon/room.cc` - -**Objectives**: -- Export room to JSON/binary -- Import room from file -- Batch export/import -- Room comparison tools - -**Dependencies**: Phase 3 - -**Estimated Time**: 5-6 hours - ---- - -## Technical Architecture - -### Class Hierarchy - -``` -Rom -├── Room (296 rooms) -│ ├── RoomHeader (14 bytes) -│ ├── RoomLayout (structural elements) -│ ├── std::vector (tile objects) -│ │ ├── RoomObject (base) -│ │ ├── Subtype1 (Type1 objects) -│ │ ├── Subtype2 (Type2 objects) -│ │ └── Subtype3 (Type3 objects) -│ ├── std::vector (doors) -│ ├── std::vector (sprites) -│ └── std::vector (chests) -└── DungeonEditor - ├── DungeonEditorSystem (editing logic) - ├── DungeonObjectEditor (object manipulation) - ├── ObjectRenderer (rendering) - ├── ObjectParser (parsing) - ├── DungeonCanvasViewer (display) - ├── DungeonObjectSelector (object palette) - └── DungeonObjectInteraction (mouse/keyboard) -``` - -### Data Flow - -``` -ROM File - ↓ -[LoadRoomFromRom] - ↓ -Room Header → Room Properties - ↓ -[ParseObjectsFromLocation] - ↓ -Object Bytes (3-byte format) - ↓ -[DecodeObjectFromBytes] - ↓ -RoomObject instances - ↓ -[LoadTilesFromSubtypeTable] - ↓ -Tile data loaded - ↓ -[RenderObjectWithTiles] - ↓ -Display on Canvas - ↓ -User Edits - ↓ -[SerializeRoomObjects] - ↓ -Object Bytes (3-byte format) - ↓ -[WriteToRom] - ↓ -ROM File Updated -``` - -### Key Algorithms - -#### Object Type Detection - -```cpp -int DetermineObjectType(uint8_t b1, uint8_t b3) { - // Type 3: b3 >= 0xF8 or b1 >= 0xFC with b3 >= 0xF0 - if (b3 >= 0xF8) return 3; - - // Type 2: b1 >= 0xFC - if (b1 >= 0xFC) return 2; - - // Type 1: default - return 1; -} -``` - -#### Object Decoding - -```cpp -RoomObject DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t layer) { - int type = DetermineObjectType(b1, b3); - - uint8_t x, y, size; - uint16_t id; - - switch (type) { - case 1: // Type1: xxxxxxss yyyyyyss iiiiiiii - x = (b1 & 0xFC) >> 2; - y = (b2 & 0xFC) >> 2; - size = ((b1 & 0x03) << 2) | (b2 & 0x03); - id = b3; - break; - - case 2: // Type2: 111111xx xxxxyyyy yyiiiiii - x = ((b1 & 0x03) << 4) | ((b2 & 0xF0) >> 4); - y = ((b2 & 0x0F) << 2) | ((b3 & 0xC0) >> 6); - size = 0; - id = (b3 & 0x3F) | 0x100; - break; - - case 3: // Type3: xxxxxxii yyyyyyii 11111iii - x = (b1 & 0xFC) >> 2; - y = (b2 & 0xFC) >> 2; - size = 0; - id = ((b3 & 0xFF) << 4) | ((b2 & 0x03) << 2) | (b1 & 0x03) | 0x80; - break; - } - - return RoomObject(id, x, y, size, layer); -} -``` - -#### Object Encoding - -```cpp -ObjectBytes EncodeObjectToBytes(const RoomObject& obj) { - ObjectBytes bytes; - - if (obj.id_ >= 0xF00) { // Type 3 - bytes.b1 = (obj.x_ << 2) | (obj.id_ & 0x03); - bytes.b2 = (obj.y_ << 2) | ((obj.id_ >> 2) & 0x03); - bytes.b3 = (obj.id_ >> 4) & 0xFF; - } - else if (obj.id_ >= 0x100) { // Type 2 - bytes.b1 = 0xFC | ((obj.x_ & 0x30) >> 4); - bytes.b2 = ((obj.x_ & 0x0F) << 4) | ((obj.y_ & 0x3C) >> 2); - bytes.b3 = ((obj.y_ & 0x03) << 6) | (obj.id_ & 0x3F); - } - else { // Type 1 - bytes.b1 = (obj.x_ << 2) | ((obj.size_ >> 2) & 0x03); - bytes.b2 = (obj.y_ << 2) | (obj.size_ & 0x03); - bytes.b3 = obj.id_; - } - - return bytes; -} -``` - ---- - -## Testing Strategy - -### Unit Tests - -#### Encoding/Decoding Tests - -```cpp -TEST(RoomObjectTest, EncodeDecodeType1) { - // Create Type1 object - RoomObject obj(0x42, 10, 20, 3, 0); - - // Encode - auto bytes = obj.EncodeObjectToBytes(); - - // Decode - auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0); - - // Verify - EXPECT_EQ(decoded.id_, obj.id_); - EXPECT_EQ(decoded.x_, obj.x_); - EXPECT_EQ(decoded.y_, obj.y_); - EXPECT_EQ(decoded.size_, obj.size_); -} - -TEST(RoomObjectTest, EncodeDecodeType2) { - // Type2 object - RoomObject obj(0x125, 15, 30, 0, 1); - - auto bytes = obj.EncodeObjectToBytes(); - auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1); - - EXPECT_EQ(decoded.id_, obj.id_); - EXPECT_EQ(decoded.x_, obj.x_); - EXPECT_EQ(decoded.y_, obj.y_); -} - -TEST(RoomObjectTest, EncodeDecodeType3) { - // Type3 object (chest) - RoomObject obj(0xF99, 5, 10, 0, 0); - - auto bytes = obj.EncodeObjectToBytes(); - auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0); - - EXPECT_EQ(decoded.id_, obj.id_); - EXPECT_EQ(decoded.x_, obj.x_); - EXPECT_EQ(decoded.y_, obj.y_); -} -``` - -#### Room Parsing Tests - -```cpp -TEST(RoomTest, ParseObjectsWithLayers) { - // Create test data with 3 layers - std::vector data = { - 0x12, // floor1/floor2 - 0x04, // layout - // Layer 1 - 0x28, 0x50, 0x10, // Object 1 - 0xFF, 0xFF, // Layer 1 end - // Layer 2 - 0x30, 0x60, 0x20, // Object 2 - 0xFF, 0xFF, // Layer 2 end - 0xFF, 0xFF // End - }; - - Room room; - auto status = room.ParseObjectsFromData(data); - EXPECT_TRUE(status.ok()); - EXPECT_EQ(room.GetTileObjects().size(), 2); -} -``` - -### Integration Tests - -#### Round-Trip Tests - -```cpp -TEST(IntegrationTest, LoadSaveLoadRoom) { - Rom rom("test_rom.sfc"); - - // Load room - Room room1 = LoadRoomFromRom(&rom, 0x001); - size_t original_objects = room1.GetTileObjects().size(); - - // Save room - auto serialized = room1.SerializeRoomObjects(); - EXPECT_TRUE(serialized.ok()); - - // Write to ROM - auto write_status = rom.WriteRoomObjects(0x001, serialized.value()); - EXPECT_TRUE(write_status.ok()); - - // Load again - Room room2 = LoadRoomFromRom(&rom, 0x001); - - // Verify same objects - EXPECT_EQ(room2.GetTileObjects().size(), original_objects); - for (size_t i = 0; i < original_objects; i++) { - EXPECT_EQ(room2.GetTileObject(i).id_, room1.GetTileObject(i).id_); - EXPECT_EQ(room2.GetTileObject(i).x_, room1.GetTileObject(i).x_); - EXPECT_EQ(room2.GetTileObject(i).y_, room1.GetTileObject(i).y_); - } -} -``` - -### Manual Testing - -#### Test Cases - -1. **Basic Object Loading** - - Load room with various object types - - Verify all objects appear - - Check object positions match ZScream - -2. **Object Placement** - - Place new objects in empty room - - Move existing objects - - Delete objects - - Verify changes persist - -3. **Layer Management** - - Place objects on different layers - - Toggle layer visibility - - Verify layer separation - -4. **Save/Load** - - Edit multiple rooms - - Save changes - - Reload ROM - - Verify all changes present - -5. **Edge Cases** - - Full rooms (many objects) - - Empty rooms - - Rooms with only doors - - Large objects (max size) - ---- - -## References - -### Documentation - -- **ZScream Source Code**: `/Users/scawful/Code/ZScreamDungeon/ZeldaFullEditor/Rooms/` -- **Yaze Source Code**: `/Users/scawful/Code/yaze/src/app/zelda3/dungeon/` -- **ALTTP Disassembly**: `/Users/scawful/Code/yaze/assets/asm/usdasm/` -- **This Document**: `/Users/scawful/Code/yaze/docs/dungeon_editing_implementation_plan.md` - -### Key Files - -#### ZScream (Reference) -- `Room.cs` - Main room class with object loading/saving -- `Room_Object.cs` - Object class with encoding/decoding -- `DungeonObjectData.cs` - Object tile data tables -- `GFX.cs` - Graphics management - -#### Yaze (Implementation) -- `src/app/zelda3/dungeon/room.h/cc` - Room class -- `src/app/zelda3/dungeon/room_object.h/cc` - RoomObject class -- `src/app/zelda3/dungeon/object_parser.h/cc` - Object parsing -- `src/app/zelda3/dungeon/object_renderer.h/cc` - Object rendering -- `src/app/editor/dungeon/dungeon_editor.h/cc` - Main editor -- `src/app/editor/dungeon/dungeon_object_interaction.h/cc` - Object interaction - -### External Resources - -- **ALTTP Hacking Resources**: https://github.com/spannerisms/z3doc -- **ROM Map**: https://www.zeldacodes.org/ -- **Disassembly**: https://github.com/camthesaxman/zelda3 - ---- - -## Appendix: Object ID Reference - -### Type 1 Objects (0x00-0xFF) - -Common objects with size parameter: -- 0x00-0x09: Walls (ceiling, top, bottom) -- 0x0A-0x20: Diagonal walls -- 0x21: Platform stairs -- 0x22: Rail -- 0x23-0x2E: Pit edges -- 0x2F-0x30: Rail walls -- 0x33: Carpet -- 0x34: Carpet trim -- 0x36-0x3E: Drapes, statues, columns -- 0x3F-0x45: Water edges -- 0x50-0x5F: Dungeon-specific decorations - -### Type 2 Objects (0x100-0x1FF) - -Special fixed-size objects: -- 0x100-0x10F: Corners (concave, convex) -- 0x110-0x11F: Kinked corners -- 0x120-0x12F: Decorative objects (braziers, statues) -- 0x130-0x13F: Functional objects (blocks, stairs, chests) -- 0x140-0x14F: Interactive objects (switches, torches) - -### Type 3 Objects (0xF00-0xFFF) - -Extended special objects: -- 0xF99: Small chest -- 0xFB1: Large chest -- 0xF80-0xF8F: Various stairs -- 0xF90-0xF9F: Special effects -- 0xFA0-0xFAF: Interactive elements - ---- - -## Status Tracking - -### Current Progress - -- [x] Document created -- [ ] Phase 1: Core Object System - - [ ] Task 1.1: Object Encoding/Decoding - - [ ] Task 1.2: Enhanced Object Parsing - - [ ] Task 1.3: Object Tile Loading - - [ ] Task 1.4: Object Drawing System -- [ ] Phase 2: Editor UI Integration -- [ ] Phase 3: Save System -- [ ] Phase 4: Advanced Features - -### Next Steps - -1. **Start with Task 1.1** - Implement object encoding/decoding -2. **Create unit tests** - Test all three object types -3. **Verify with ZScream** - Compare results with known working implementation -4. **Proceed to Task 1.2** - Once encoding/decoding is solid - -### Timeline Estimate - -- **Phase 1**: 16-20 hours (CRITICAL - Must be done first) -- **Phase 2**: 12-16 hours (Required for usability) -- **Phase 3**: 12-15 hours (Required for saving) -- **Phase 4**: 16-20 hours (Nice to have) - -**Total**: 56-71 hours (7-9 full working days) - ---- - -*Document Version: 1.0* -*Last Updated: October 4, 2025* -*Author: AI Assistant with scawful* - diff --git a/docs/dungeon_graphics_pipeline_analysis.md b/docs/dungeon_graphics_pipeline_analysis.md deleted file mode 100644 index c37f442b..00000000 --- a/docs/dungeon_graphics_pipeline_analysis.md +++ /dev/null @@ -1,420 +0,0 @@ -# Dungeon Graphics Rendering Pipeline Analysis - -## Overview - -This document provides a comprehensive analysis of how the YAZE dungeon editor renders room graphics, including the interaction between bitmaps, arena buffers, palettes, and palette groups. - -## Architecture Diagram - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ ROM Data │ -│ ┌────────────────────────┐ ┌──────────────────────────────────┐│ -│ │ Room Headers │ │ Dungeon Palettes ││ -│ │ - Palette ID │ │ - dungeon_main[id][180 colors] ││ -│ │ - Blockset ID │ │ - sprites_aux1[id][colors] ││ -│ │ - Spriteset ID │ │ - Palette Groups ││ -│ │ - Background ID │ │ ││ -│ └────────────────────────┘ └──────────────────────────────────┘│ -└───────────────────────────┬─────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ Room Loading (room.cc) │ -│ │ -│ LoadRoomFromRom() → LoadRoomGraphics() → Copy RoomGraphics │ -│ ├─ Load 16 blocks (graphics sheets) │ -│ ├─ blocks[0-7]: Main blockset │ -│ ├─ blocks[8-11]: Static sprites (fairies, pots, etc.) │ -│ └─ blocks[12-15]: Spriteset sprites │ -└───────────────────────────┬─────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ Graphics Arena (arena.h/.cc) │ -│ │ -│ ┌──────────────────┐ ┌──────────────────┐ │ -│ │ gfx_sheets_[223] │ │ Background │ │ -│ │ (Bitmap objects) │ │ Buffers │ │ -│ │ │ │ - bg1_ │ │ -│ │ Each holds: │ │ - bg2_ │ │ -│ │ - Pixel data │ │ │ │ -│ │ - SDL Surface │ │ layer1_buffer_ │ │ -│ │ - SDL Texture │ │ layer2_buffer_ │ │ -│ │ - Palette │ │ [64x64 = 4096 │ │ -│ └──────────────────┘ │ tile words] │ │ -│ └──────────────────┘ │ -└───────────────────────────┬─────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ Room::RenderRoomGraphics() Pipeline │ -│ │ -│ Step 1: CopyRoomGraphicsToBuffer() │ -│ └─ Copy 16 blocks to current_gfx16_[32768] buffer │ -│ │ -│ Step 2: DrawFloor() on both BG1 and BG2 │ -│ ├─ Read floor tile IDs from ROM │ -│ ├─ Create TileInfo objects (id, palette, mirror flags) │ -│ └─ SetTileAt() in background buffers (repeating pattern) │ -│ │ -│ Step 3: RenderObjectsToBackground() ⚠️ NEW │ -│ ├─ Iterate through tile_objects_ │ -│ ├─ For each object, get its Tile16 array │ -│ ├─ Each Tile16 contains 4 TileInfo (8x8 tiles) │ -│ ├─ Convert TileInfo → 16-bit word: │ -│ │ (vflip<<15) | (hflip<<14) | (palette<<10) | tile_id │ -│ └─ SetTileAt() in correct layer (BG1 or BG2) │ -│ │ -│ Step 4: DrawBackground() on both BG1 and BG2 │ -│ ├─ BackgroundBuffer::DrawBackground(current_gfx16_) │ -│ ├─ For each tile in buffer_[4096]: │ -│ │ ├─ Extract 16-bit word │ -│ │ ├─ WordToTileInfo() → TileInfo │ -│ │ └─ DrawTile() → Write 8x8 pixels to bitmap_.data_ │ -│ └─ bitmap_.Create(512, 512, 8, pixel_data) │ -│ │ -│ Step 5: Apply Palette & Create/Update Texture │ -│ ├─ Get dungeon_main palette for this room │ -│ ├─ bitmap_.SetPaletteWithTransparent(palette, 0) │ -│ ├─ If first time: │ -│ │ └─ CreateAndRenderBitmap() → Create SDL_Texture │ -│ └─ Else: │ -│ └─ UpdateBitmap() → Update existing SDL_Texture │ -└─────────────────────────────────────────────────────────────────┘ -``` - -## Component Breakdown - -### 1. Arena (gfx/arena.h/.cc) - -**Purpose**: Global graphics resource manager using singleton pattern. - -**Key Members**: -- `gfx_sheets_[223]`: Array of Bitmap objects (one for each graphics sheet in ROM) -- `bg1_`, `bg2_`: BackgroundBuffer objects for SNES layer 1 and layer 2 -- `layer1_buffer_[4096]`, `layer2_buffer_[4096]`: Raw tile word arrays - -**Responsibilities**: -- Resource pooling for SDL textures and surfaces -- Batch texture updates for performance -- Centralized access to graphics sheets - -### 2. BackgroundBuffer (gfx/background_buffer.h/.cc) - -**Purpose**: Manages a single SNES background layer (512x512 pixels = 64x64 tiles). - -**Key Members**: -- `buffer_[4096]`: Array of 16-bit tile words (vflip|hflip|palette|tile_id) -- `bitmap_`: The Bitmap object that holds the rendered pixel data -- `width_`, `height_`: Dimensions in pixels (typically 512x512) - -**Key Methods**: - -#### `SetTileAt(int x, int y, uint16_t value)` -```cpp -// Sets a tile word at tile coordinates (x, y) -// x, y are in tile units (0-63) -buffer_[y * tiles_w + x] = value; -``` - -#### `DrawBackground(std::span gfx16_data)` -```cpp -// Renders all tiles in buffer_ to bitmap_ -1. Create bitmap (512x512, 8bpp) -2. For each tile (64x64 grid): - - Get tile word from buffer_[xx + yy * 64] - - WordToTileInfo() to extract: id, palette, hflip, vflip - - DrawTile() writes 64 pixels to bitmap at correct position -``` - -#### `DrawFloor()` -```cpp -// Special case: Draws floor pattern from ROM data -1. Read 8 floor tile IDs from ROM (2 rows of 4) -2. Repeat pattern across entire 64x64 grid -3. SetTileAt() for each position -``` - -### 3. Bitmap (gfx/bitmap.h/.cc) - -**Purpose**: Represents a 2D image with SNES-specific features. - -**Key Members**: -- `data_[width * height]`: Raw indexed pixel data (palette indices) -- `palette_`: SnesPalette object (15-bit RGB colors) -- `surface_`: SDL_Surface for pixel manipulation -- `texture_`: SDL_Texture for rendering to screen -- `active_`, `modified_`: State flags - -**Key Methods**: - -#### `Create(int w, int h, int depth, vector data)` -```cpp -// Initialize bitmap with pixel data -width_ = w; -height_ = h; -depth_ = depth; // Usually 8 (bits per pixel) -data_ = data; -active_ = true; -``` - -#### `SetPaletteWithTransparent(SnesPalette palette, size_t index)` -```cpp -// Apply palette and make color[index] transparent -palette_ = palette; -// Update surface_->format->palette with SDL_Colors -// Set color[index] alpha to 0 for transparency -``` - -#### `CreateTexture(SDL_Renderer* renderer)` / `UpdateTexture()` -```cpp -// Convert surface_ to hardware-accelerated texture_ -texture_ = SDL_CreateTextureFromSurface(renderer, surface_); -// or -SDL_UpdateTexture(texture_, nullptr, surface_->pixels, surface_->pitch); -``` - -### 4. Room (zelda3/dungeon/room.h/.cc) - -**Purpose**: Represents a single dungeon room with all its data. - -**Key Members**: -- `room_id_`: Room index (0-295) -- `palette`, `blockset`, `spriteset`: IDs from ROM header -- `blocks_[16]`: Graphics sheet indices for this room -- `current_gfx16_[32768]`: Raw graphics data for this room -- `tile_objects_`: Vector of RoomObject instances -- `rom_`: Pointer to ROM data - -**Key Methods**: - -#### `LoadRoomGraphics(uint8_t entrance_blockset)` -```cpp -// Load 16 graphics sheets for this room -blocks_[0-7]: Main blockset sheets -blocks_[8-11]: Static sprites (fairies, pots, etc.) -blocks_[12-15]: Spriteset sprites -``` - -#### `CopyRoomGraphicsToBuffer()` -```cpp -// Copy 16 blocks of 2KB each into current_gfx16_[32KB] -for (int i = 0; i < 16; i++) { - int block = blocks_[i]; - memcpy(current_gfx16_ + i*2048, - graphics_buffer[block*2048], - 2048); -} -LoadAnimatedGraphics(); // Overlay animated frames -``` - -#### `RenderRoomGraphics()` ⭐ **Main Rendering Method** -```cpp -void Room::RenderRoomGraphics() { - // Step 1: Copy graphics data from ROM - CopyRoomGraphicsToBuffer(); - - // Step 2: Draw floor pattern - Arena::Get().bg1().DrawFloor(rom->vector(), tile_address, - tile_address_floor, floor1_graphics_); - Arena::Get().bg2().DrawFloor(rom->vector(), tile_address, - tile_address_floor, floor2_graphics_); - - // Step 3: ⚠️ NEW - Render room objects to buffers - RenderObjectsToBackground(); - - // Step 4: Convert tile buffers to bitmaps - Arena::Get().bg1().DrawBackground(span(current_gfx16_)); - Arena::Get().bg2().DrawBackground(span(current_gfx16_)); - - // Step 5: Apply palette and create/update textures - auto palette = rom->palette_group().dungeon_main[palette_id][0]; - if (!Arena::Get().bg1().bitmap().is_active()) { - Renderer::Get().CreateAndRenderBitmap(..., Arena::Get().bg1().bitmap(), palette); - Renderer::Get().CreateAndRenderBitmap(..., Arena::Get().bg2().bitmap(), palette); - } else { - Renderer::Get().UpdateBitmap(&Arena::Get().bg1().bitmap()); - Renderer::Get().UpdateBitmap(&Arena::Get().bg2().bitmap()); - } -} -``` - -#### `RenderObjectsToBackground()` ⚠️ **Critical New Method** ✅ **Fixed** -```cpp -void Room::RenderObjectsToBackground() { - auto& bg1 = Arena::Get().bg1(); - auto& bg2 = Arena::Get().bg2(); - - for (const auto& obj : tile_objects_) { - // Ensure object has tiles loaded - obj.EnsureTilesLoaded(); - auto tiles_result = obj.GetTiles(); // Returns span - - // Calculate the width of the object in Tile16 units - // Most objects are arranged in a grid, typically 1-8 tiles wide - int tiles_wide = 1; - if (tiles.size() > 1) { - // Try to determine optimal layout based on tile count - // Common patterns: 1x1, 2x2, 4x1, 2x4, 4x4, 8x1, etc. - int sq = static_cast(std::sqrt(tiles.size())); - if (sq * sq == tiles.size()) { - tiles_wide = sq; // Perfect square (4, 9, 16, etc.) - } else if (tiles.size() <= 4) { - tiles_wide = tiles.size(); // Small objects laid out horizontally - } else { - // For larger objects, try common widths (4 or 8) - tiles_wide = (tiles.size() >= 8) ? 8 : 4; - } - } - - // Each Tile16 is 16x16 (4 TileInfo of 8x8) - for (size_t i = 0; i < tiles.size(); i++) { - const auto& tile16 = tiles[i]; - - // Calculate base position using calculated width (in 8x8 units) - int base_x = obj.x_ + ((i % tiles_wide) * 2); - int base_y = obj.y_ + ((i / tiles_wide) * 2); - - // Tile16.tiles_info[4] contains the 4 sub-tiles: - // [0][1] (top-left, top-right) - // [2][3] (bottom-left, bottom-right) - for (int sub = 0; sub < 4; sub++) { - int tile_x = base_x + (sub % 2); - int tile_y = base_y + (sub / 2); - - // Bounds check - if (tile_x < 0 || tile_x >= 64 || tile_y < 0 || tile_y >= 64) { - continue; - } - - // Convert TileInfo to 16-bit word - uint16_t word = TileInfoToWord(tile16.tiles_info[sub]); - - // Set in correct layer - bool is_bg2 = (obj.layer_ == RoomObject::LayerType::BG2); - auto& buffer = is_bg2 ? bg2 : bg1; - buffer.SetTileAt(tile_x, tile_y, word); - } - } - } -} -``` - -## Palette System - -### Palette Hierarchy - -``` -ROM Palette Data -│ -├─ dungeon_main[palette_group_id] -│ └─ Large palette (180 colors) -│ └─ Split into PaletteGroup: -│ ├─ palette(0): Main dungeon palette -│ ├─ palette(1): Alternate palette 1 -│ └─ palette(2-n): More palettes -│ -└─ sprites_aux1[palette_id] - └─ Sprite auxiliary palettes -``` - -### Palette Loading Flow - -```cpp -// In DungeonEditor::Load() -auto dungeon_pal_group = rom->palette_group().dungeon_main; -full_palette_ = dungeon_pal_group[current_palette_group_id_]; -ASSIGN_OR_RETURN(current_palette_group_, - CreatePaletteGroupFromLargePalette(full_palette_)); - -// In DungeonCanvasViewer::LoadAndRenderRoomGraphics() -auto dungeon_palette_ptr = rom->paletteset_ids[room.palette][0]; -auto palette_id = rom->ReadWord(0xDEC4B + dungeon_palette_ptr); -current_palette_group_id_ = palette_id.value() / 180; -full_palette = rom->palette_group().dungeon_main[current_palette_group_id_]; - -// Apply to graphics sheets -for (int i = 0; i < 8; i++) { // BG1 layers - int block = room.blocks()[i]; - Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent( - current_palette_group_[current_palette_id_], 0); -} - -for (int i = 8; i < 16; i++) { // BG2 layers (sprites) - int block = room.blocks()[i]; - Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent( - sprites_aux1_pal_group[current_palette_id_], 0); -} -``` - ---- - -# Appendix: Blank Canvas Bug - Detailed Root Cause Analysis - -This section, merged from `dungeon_canvas_blank_fix.md`, details the investigation and resolution of the blank canvas bug. - -## Problem - -The DungeonEditor canvas displayed as blank white despite the rendering pipeline appearing to execute correctly. - -## Diagnostic Output Analysis - -Using a comprehensive diagnostic system, the data flow was traced through 8 steps. Steps 1-6 (ROM loading, buffer population, bitmap creation, texture creation) were all passing. The failure was in Step 7. - -### ❌ Step 7: PALETTE MISSING - -``` -=== Step 7: Palette === - Palette size: 0 colors ❌❌❌ ROOT CAUSE! -``` - -## Root Cause Analysis - -Three distinct issues were identified and fixed in sequence: - -### Cause 1: Missing Palette Application - -**The palette was never applied to the bitmap objects!** The bitmaps contained indexed pixel data (e.g., color indices 8, 9, 12), but without a color palette, SDL couldn't map these indices to actual colors, resulting in a blank texture. - -**The Fix (Location: `src/app/zelda3/dungeon/room.cc:319-322`)**: -Added `SetPaletteWithTransparent()` calls to the bitmaps *before* creating the SDL textures. This ensures the renderer has the color information it needs. - -```cpp -// CRITICAL: Apply palette to bitmaps BEFORE creating/updating textures -bg1_bmp.SetPaletteWithTransparent(bg1_palette, 0); -bg2_bmp.SetPaletteWithTransparent(bg1_palette, 0); - -// Now create/update textures (palette is already set in bitmap) -Renderer::Get().CreateAndRenderBitmap(..., bg1_bmp, bg1_palette); -``` - -### Cause 2: All Black Canvas (Incorrect Tile Word) - -After the first fix, the canvas was all black. This was because `DrawFloor()` was only passing the tile ID to the background buffer, losing the crucial palette information. - -**The Fix**: -Converted the `TileInfo` struct to a full 16-bit word (which includes palette bits) before writing it to the buffer. - -```cpp -// CORRECT: Convert TileInfo to word with all metadata -uint16_t word1 = gfx::TileInfoToWord(floorTile1); -SetTileAt((xx * 4), (yy * 2), word1); // ✅ Now includes palette! -``` - -### Cause 3: Wrong Palette (All Rooms Look "Gargoyle-y") - -After the second fix, all rooms rendered, but with the same incorrect palette (from the first dungeon). - -**The Fix**: -Used the room's specific `palette` ID loaded from the ROM header instead of hardcoding palette index `0`. - -```cpp -// ✅ CORRECT: Use the room's palette ID -auto bg1_palette = - rom()->mutable_palette_group()->get_group("dungeon_main")[0].palette(palette); -``` - -## Key Takeaway - -**Always apply a palette to indexed-color bitmaps before creating SDL textures.** The rendering pipeline requires this step to translate color indices into visible pixels. Each subsequent fix ensured the *correct* palette information was being passed at each stage. \ No newline at end of file diff --git a/docs/z3ed/README.md b/docs/z3ed/README.md index 04bf1e3a..22d2b81b 100644 --- a/docs/z3ed/README.md +++ b/docs/z3ed/README.md @@ -183,6 +183,8 @@ cmake --build build --target z3ed cmake -B build -DZ3ED_AI=ON cmake --build build --target z3ed +# use macos-dev-z3ed-ai cmake preset + # Full build with AI agent AND testing suite cmake -B build -DZ3ED_AI=ON -DYAZE_WITH_GRPC=ON cmake --build build --target z3ed @@ -297,13 +299,6 @@ Here are some example prompts you can try with either Ollama or Gemini: ## Core Documentation -### Essential Reads -1. **[BUILD_QUICK_REFERENCE.md](BUILD_QUICK_REFERENCE.md)** - **NEW!** Fast build guide with Z3ED_AI flag examples -2. **[AGENT-ROADMAP.md](AGENT-ROADMAP.md)** - The primary source of truth for the AI agent's strategic vision, architecture, and next steps -3. **[Z3ED_AI_FLAG_MIGRATION.md](Z3ED_AI_FLAG_MIGRATION.md)** - **NEW!** Complete guide to Z3ED_AI flag and crash fixes -4. **[E6-z3ed-cli-design.md](E6-z3ed-cli-design.md)** - Detailed architecture and design philosophy -5. **[E6-z3ed-reference.md](E6-z3ed-reference.md)** - Complete command reference and API documentation - ## Current Status (October 3, 2025) ### ✅ Production Ready @@ -330,9 +325,6 @@ Here are some example prompts you can try with either Ollama or Gemini: 2. **GUI Chat Widget** (6-8h): ImGui integration (TUI exists as reference) 3. **Tool Coverage Expansion** (8-10h): Dialogue, sprites, regions -### 📋 Next Steps -See [AGENT-ROADMAP.md](AGENT-ROADMAP.md) for detailed technical roadmap. - ## AI Editing Focus Areas z3ed is optimized for practical ROM editing workflows: