Remove outdated Dungeon Graphics Rendering Pipeline Analysis document and update z3ed README with new CMake preset usage and streamlined core documentation references.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<uint8_t> 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<uint8_t> 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<uint8_t>(current_gfx16_));
|
||||
Arena::Get().bg2().DrawBackground(span<uint8_t>(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<const Tile16>
|
||||
|
||||
// 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<int>(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.
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user