feat: Enhance Dungeon Editor with Advanced Room Properties and Layout Visualization

- Introduced advanced room properties UI in the Dungeon Editor, allowing users to set effects and tags for rooms through dropdown menus.
- Updated the rendering logic to visualize room layouts, including walls, floors, pits, water, and doors, with appropriate color coding for clarity.
- Improved texture management by processing texture updates immediately to ensure objects appear correctly in the editor.
- Organized ROM address constants into a new header file for better maintainability and clarity in the codebase.
- Refactored existing code to utilize the new constants, ensuring a cleaner and more consistent naming convention.
This commit is contained in:
scawful
2025-10-09 22:14:33 -04:00
parent c1e69ce03e
commit f9753532a4
7 changed files with 485 additions and 252 deletions

View File

@@ -85,23 +85,69 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
ImGui::EndTable(); ImGui::EndTable();
} }
// Layer visibility controls in compact table // Advanced room properties (Effect, Tags, Layer Merge)
ImGui::Separator(); ImGui::Separator();
if (ImGui::BeginTable("##LayerControls", 3, ImGuiTableFlags_SizingStretchSame)) { if (ImGui::BeginTable("##AdvancedProperties", 3, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("Effect");
ImGui::TableSetupColumn("Tag 1");
ImGui::TableSetupColumn("Tag 2");
ImGui::TableHeadersRow();
ImGui::TableNextRow();
// Effect dropdown
ImGui::TableNextColumn();
const char* effect_names[] = {"Nothing", "One", "Moving Floor", "Moving Water", "Four", "Red Flashes", "Torch Show Floor", "Ganon Room"};
int effect_val = static_cast<int>(room.effect());
if (ImGui::Combo("##Effect", &effect_val, effect_names, 8)) {
room.SetEffect(static_cast<zelda3::EffectKey>(effect_val));
}
// Tag 1 dropdown (abbreviated for space)
ImGui::TableNextColumn();
const char* tag_names[] = {"Nothing", "NW Kill", "NE Kill", "SW Kill", "SE Kill", "W Kill", "E Kill", "N Kill", "S Kill",
"Clear Quad", "Clear Room", "NW Push", "NE Push", "SW Push", "SE Push", "W Push", "E Push",
"N Push", "S Push", "Push Block", "Pull Lever", "Clear Level", "Switch Hold", "Switch Toggle"};
int tag1_val = static_cast<int>(room.tag1());
if (ImGui::Combo("##Tag1", &tag1_val, tag_names, 24)) {
room.SetTag1(static_cast<zelda3::TagKey>(tag1_val));
}
// Tag 2 dropdown
ImGui::TableNextColumn();
int tag2_val = static_cast<int>(room.tag2());
if (ImGui::Combo("##Tag2", &tag2_val, tag_names, 24)) {
room.SetTag2(static_cast<zelda3::TagKey>(tag2_val));
}
ImGui::EndTable();
}
// Layer visibility and merge controls
ImGui::Separator();
if (ImGui::BeginTable("##LayerControls", 4, ImGuiTableFlags_SizingStretchSame)) {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
auto& layer_settings = GetRoomLayerSettings(room_id); auto& layer_settings = GetRoomLayerSettings(room_id);
ImGui::Checkbox("Show BG1", &layer_settings.bg1_visible); ImGui::Checkbox("BG1", &layer_settings.bg1_visible);
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Checkbox("Show BG2", &layer_settings.bg2_visible); ImGui::Checkbox("BG2", &layer_settings.bg2_visible);
ImGui::TableNextColumn(); ImGui::TableNextColumn();
// BG2 layer type dropdown // BG2 layer type
const char* bg2_layer_types[] = {"Normal", "Trans", "Add", "Dark", "Off"}; const char* bg2_types[] = {"Norm", "Trans", "Add", "Dark", "Off"};
ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::Combo("##BG2Type", &layer_settings.bg2_layer_type, bg2_layer_types, 5); ImGui::Combo("##BG2Type", &layer_settings.bg2_layer_type, bg2_types, 5);
ImGui::TableNextColumn();
// Layer merge type
const char* merge_types[] = {"Off", "Parallax", "Dark", "On top", "Translucent", "Addition", "Normal", "Transparent", "Dark room"};
int merge_val = room.layer_merging().ID;
if (ImGui::Combo("##Merge", &merge_val, merge_types, 9)) {
room.SetLayerMerging(zelda3::kLayerMergeTypeList[merge_val]);
}
ImGui::EndTable(); ImGui::EndTable();
} }
@@ -391,20 +437,61 @@ void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& obje
} }
// Room layout visualization // Room layout visualization
void DungeonCanvasViewer::DrawRoomLayout(const zelda3::Room& room) { void DungeonCanvasViewer::DrawRoomLayout(zelda3::Room& room) {
// Draw room layout structural elements (walls, floors, pits) // Draw room layout structural elements (walls, floors, pits)
// This provides visual context for where objects should be placed // This is the immovable BASE LAYER that defines room structure
const auto& layout = room.GetLayout(); auto& layout = room.GetLayout();
// Get dimensions (64x64 tiles = 512x512 pixels) // Ensure layout is loaded (critical!)
auto [width_tiles, height_tiles] = layout.GetDimensions(); if (layout.GetObjects().empty()) {
auto status = layout.LoadLayout(room.id());
if (!status.ok()) {
LOG_DEBUG("[DrawRoomLayout]", "Failed to load layout: %s", status.message().data());
return;
}
}
// TODO: Get layout objects by type // Get structural elements by type
// For now, draw a grid overlay to show the room structure auto walls = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kWall);
// Future: Implement GetObjectsByType() in RoomLayout auto floors = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kFloor);
auto pits = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kPit);
auto water = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kWater);
auto doors = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kDoor);
LOG_DEBUG("[DrawRoomLayout]", "Room layout: %dx%d tiles", width_tiles, height_tiles); LOG_DEBUG("[DrawRoomLayout]", "Layout elements: %zu walls, %zu floors, %zu pits, %zu water, %zu doors",
walls.size(), floors.size(), pits.size(), water.size(), doors.size());
// Get canvas scale for proper sizing
float scale = canvas_.global_scale();
int tile_size = static_cast<int>(8 * scale); // 8x8 pixels scaled
// Draw walls (dark gray, semi-transparent) - immovable structure
for (const auto& wall : walls) {
auto [x, y] = RoomToCanvasCoordinates(wall.x(), wall.y());
canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(0.2f, 0.2f, 0.2f, 0.5f));
}
// Draw pits (orange warning) - damage zones
for (const auto& pit : pits) {
auto [x, y] = RoomToCanvasCoordinates(pit.x(), pit.y());
canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(1.0f, 0.4f, 0.0f, 0.6f));
}
// Draw water (blue, semi-transparent)
for (const auto& water_tile : water) {
auto [x, y] = RoomToCanvasCoordinates(water_tile.x(), water_tile.y());
canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(0.2f, 0.4f, 0.8f, 0.5f));
}
// Draw doors (purple) - connection points
for (const auto& door : doors) {
auto [x, y] = RoomToCanvasCoordinates(door.x(), door.y());
canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(0.8f, 0.2f, 0.8f, 0.7f));
if (scale >= 1.0f) { // Only show label if zoomed in enough
canvas_.DrawText("DOOR", x + 2, y + 2);
}
}
} }
// Object visualization methods // Object visualization methods
@@ -414,6 +501,9 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) {
const auto& objects = room.GetTileObjects(); const auto& objects = room.GetTileObjects();
// Get canvas scale for proper sizing
float scale = canvas_.global_scale();
for (const auto& obj : objects) { for (const auto& obj : objects) {
// Convert object position (tile coordinates) to canvas pixel coordinates // Convert object position (tile coordinates) to canvas pixel coordinates
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(obj.x(), obj.y()); auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(obj.x(), obj.y());
@@ -431,6 +521,10 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) {
width = (size_h + 1) * 8; width = (size_h + 1) * 8;
height = (size_v + 1) * 8; height = (size_v + 1) * 8;
// Apply canvas scale
width = static_cast<int>(width * scale);
height = static_cast<int>(height * scale);
// Clamp to reasonable sizes // Clamp to reasonable sizes
width = std::min(width, 512); width = std::min(width, 512);
height = std::min(height, 512); height = std::min(height, 512);

View File

@@ -105,7 +105,7 @@ class DungeonCanvasViewer {
void CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height); void CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height);
// Visualization // Visualization
void DrawRoomLayout(const zelda3::Room& room); void DrawRoomLayout(zelda3::Room& room); // Non-const to call LoadLayout
void DrawObjectPositionOutlines(const zelda3::Room& room); void DrawObjectPositionOutlines(const zelda3::Room& room);
// Room graphics management // Room graphics management

View File

@@ -1,12 +1,11 @@
#include "app/gfx/graphics_optimizer.h" #include "app/gfx/graphics_optimizer.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <iomanip>
#include <iostream>
#include <sstream> #include <sstream>
#include "app/gfx/bpp_format_manager.h" #include "app/gfx/bpp_format_manager.h"
#include "app/gfx/atlas_renderer.h"
#include "util/log.h"
namespace yaze { namespace yaze {
namespace gfx { namespace gfx {
@@ -20,181 +19,201 @@ void GraphicsOptimizer::Initialize() {
max_quality_loss_ = 0.1f; max_quality_loss_ = 0.1f;
min_memory_savings_ = 1024; min_memory_savings_ = 1024;
performance_threshold_ = 0.05f; performance_threshold_ = 0.05f;
optimization_stats_.clear(); optimization_stats_.clear();
optimization_cache_.clear(); optimization_cache_.clear();
} }
OptimizationResult GraphicsOptimizer::OptimizeSheet(const std::vector<uint8_t>& sheet_data, OptimizationResult GraphicsOptimizer::OptimizeSheet(
int sheet_id, const std::vector<uint8_t>& sheet_data, int sheet_id,
const SnesPalette& palette, const SnesPalette& palette, OptimizationStrategy strategy) {
OptimizationStrategy strategy) {
ScopedTimer timer("graphics_optimize_sheet"); ScopedTimer timer("graphics_optimize_sheet");
OptimizationResult result; OptimizationResult result;
try { try {
// Analyze the sheet // Analyze the sheet
SheetOptimizationData data = AnalyzeSheet(sheet_data, sheet_id, palette); SheetOptimizationData data = AnalyzeSheet(sheet_data, sheet_id, palette);
if (!data.is_convertible) { if (!data.is_convertible) {
result.success = false; result.success = false;
result.message = "Sheet is not suitable for optimization"; result.message = "Sheet is not suitable for optimization";
return result; return result;
} }
// Check if optimization meets criteria // Check if optimization meets criteria
if (!ShouldOptimize(data, strategy)) { if (!ShouldOptimize(data, strategy)) {
result.success = false; result.success = false;
result.message = "Optimization does not meet criteria"; result.message = "Optimization does not meet criteria";
return result; return result;
} }
// Calculate potential savings // Calculate potential savings
result.memory_saved = data.current_size - data.optimized_size; result.memory_saved = data.current_size - data.optimized_size;
result.performance_gain = CalculatePerformanceGain(data.current_format, data.recommended_format); result.performance_gain =
result.quality_loss = CalculateQualityLoss(data.current_format, data.recommended_format, sheet_data); CalculatePerformanceGain(data.current_format, data.recommended_format);
result.quality_loss = CalculateQualityLoss(
data.current_format, data.recommended_format, sheet_data);
// Check if optimization is worthwhile // Check if optimization is worthwhile
if (result.memory_saved < min_memory_savings_ && if (result.memory_saved < min_memory_savings_ &&
result.performance_gain < performance_threshold_ && result.performance_gain < performance_threshold_ &&
result.quality_loss > max_quality_loss_) { result.quality_loss > max_quality_loss_) {
result.success = false; result.success = false;
result.message = "Optimization benefits do not justify quality loss"; result.message = "Optimization benefits do not justify quality loss";
return result; return result;
} }
result.success = true; result.success = true;
result.message = "Optimization recommended"; result.message = "Optimization recommended";
result.recommended_formats.push_back(data.recommended_format); result.recommended_formats.push_back(data.recommended_format);
result.sheet_recommendations[sheet_id] = data.recommended_format; result.sheet_recommendations[sheet_id] = data.recommended_format;
UpdateOptimizationStats("sheets_optimized", 1.0); UpdateOptimizationStats("sheets_optimized", 1.0);
UpdateOptimizationStats("memory_saved", static_cast<double>(result.memory_saved)); UpdateOptimizationStats("memory_saved",
UpdateOptimizationStats("performance_gain", static_cast<double>(result.performance_gain)); static_cast<double>(result.memory_saved));
UpdateOptimizationStats("performance_gain",
static_cast<double>(result.performance_gain));
} catch (const std::exception& e) { } catch (const std::exception& e) {
result.success = false; result.success = false;
result.message = "Optimization failed: " + std::string(e.what()); result.message = "Optimization failed: " + std::string(e.what());
SDL_Log("GraphicsOptimizer::OptimizeSheet failed: %s", e.what()); SDL_Log("GraphicsOptimizer::OptimizeSheet failed: %s", e.what());
} }
return result; return result;
} }
OptimizationResult GraphicsOptimizer::OptimizeSheets(const std::unordered_map<int, std::vector<uint8_t>>& sheets, OptimizationResult GraphicsOptimizer::OptimizeSheets(
const std::unordered_map<int, SnesPalette>& palettes, const std::unordered_map<int, std::vector<uint8_t>>& sheets,
OptimizationStrategy strategy) { const std::unordered_map<int, SnesPalette>& palettes,
OptimizationStrategy strategy) {
ScopedTimer timer("graphics_optimize_sheets"); ScopedTimer timer("graphics_optimize_sheets");
OptimizationResult result; OptimizationResult result;
result.success = true; result.success = true;
size_t total_memory_saved = 0; size_t total_memory_saved = 0;
float total_performance_gain = 0.0f; float total_performance_gain = 0.0f;
float total_quality_loss = 0.0f; float total_quality_loss = 0.0f;
int optimized_sheets = 0; int optimized_sheets = 0;
for (const auto& [sheet_id, sheet_data] : sheets) { for (const auto& [sheet_id, sheet_data] : sheets) {
auto palette_it = palettes.find(sheet_id); auto palette_it = palettes.find(sheet_id);
if (palette_it == palettes.end()) { if (palette_it == palettes.end()) {
continue; // Skip sheets without palettes continue; // Skip sheets without palettes
} }
auto sheet_result = OptimizeSheet(sheet_data, sheet_id, palette_it->second, strategy); auto sheet_result =
OptimizeSheet(sheet_data, sheet_id, palette_it->second, strategy);
if (sheet_result.success) { if (sheet_result.success) {
total_memory_saved += sheet_result.memory_saved; total_memory_saved += sheet_result.memory_saved;
total_performance_gain += sheet_result.performance_gain; total_performance_gain += sheet_result.performance_gain;
total_quality_loss += sheet_result.quality_loss; total_quality_loss += sheet_result.quality_loss;
optimized_sheets++; optimized_sheets++;
// Merge recommendations // Merge recommendations
result.recommended_formats.insert(result.recommended_formats.end(), result.recommended_formats.insert(
sheet_result.recommended_formats.begin(), result.recommended_formats.end(),
sheet_result.recommended_formats.end()); sheet_result.recommended_formats.begin(),
result.sheet_recommendations.insert(sheet_result.sheet_recommendations.begin(), sheet_result.recommended_formats.end());
sheet_result.sheet_recommendations.end()); result.sheet_recommendations.insert(
sheet_result.sheet_recommendations.begin(),
sheet_result.sheet_recommendations.end());
} }
} }
result.memory_saved = total_memory_saved; result.memory_saved = total_memory_saved;
result.performance_gain = optimized_sheets > 0 ? total_performance_gain / optimized_sheets : 0.0f; result.performance_gain =
result.quality_loss = optimized_sheets > 0 ? total_quality_loss / optimized_sheets : 0.0f; optimized_sheets > 0 ? total_performance_gain / optimized_sheets : 0.0f;
result.quality_loss =
optimized_sheets > 0 ? total_quality_loss / optimized_sheets : 0.0f;
if (optimized_sheets > 0) { if (optimized_sheets > 0) {
result.message = "Optimized " + std::to_string(optimized_sheets) + " sheets"; result.message =
"Optimized " + std::to_string(optimized_sheets) + " sheets";
} else { } else {
result.success = false; result.success = false;
result.message = "No sheets could be optimized"; result.message = "No sheets could be optimized";
} }
UpdateOptimizationStats("batch_optimizations", 1.0); UpdateOptimizationStats("batch_optimizations", 1.0);
UpdateOptimizationStats("total_sheets_processed", static_cast<double>(sheets.size())); UpdateOptimizationStats("total_sheets_processed",
static_cast<double>(sheets.size()));
return result; return result;
} }
SheetOptimizationData GraphicsOptimizer::AnalyzeSheet(const std::vector<uint8_t>& sheet_data, SheetOptimizationData GraphicsOptimizer::AnalyzeSheet(
int sheet_id, const std::vector<uint8_t>& sheet_data, int sheet_id,
const SnesPalette& palette) { const SnesPalette& palette) {
// Check cache first // Check cache first
std::string cache_key = GenerateCacheKey(sheet_data, sheet_id); std::string cache_key = GenerateCacheKey(sheet_data, sheet_id);
auto cache_it = optimization_cache_.find(cache_key); auto cache_it = optimization_cache_.find(cache_key);
if (cache_it != optimization_cache_.end()) { if (cache_it != optimization_cache_.end()) {
return cache_it->second; return cache_it->second;
} }
ScopedTimer timer("graphics_analyze_sheet"); ScopedTimer timer("graphics_analyze_sheet");
SheetOptimizationData data; SheetOptimizationData data;
data.sheet_id = sheet_id; data.sheet_id = sheet_id;
data.current_size = sheet_data.size(); data.current_size = sheet_data.size();
// Detect current format // Detect current format
data.current_format = BppFormatManager::Get().DetectFormat(sheet_data, 128, 32); // Standard sheet size data.current_format = BppFormatManager::Get().DetectFormat(
sheet_data, 128, 32); // Standard sheet size
// Analyze color usage // Analyze color usage
data.colors_used = CountUsedColors(sheet_data, palette); data.colors_used = CountUsedColors(sheet_data, palette);
// Determine optimal format // Determine optimal format
data.recommended_format = DetermineOptimalFormat(sheet_data, palette, OptimizationStrategy::kBalanced); data.recommended_format = DetermineOptimalFormat(
sheet_data, palette, OptimizationStrategy::kBalanced);
// Calculate potential savings // Calculate potential savings
const auto& current_info = BppFormatManager::Get().GetFormatInfo(data.current_format); const auto& current_info =
const auto& recommended_info = BppFormatManager::Get().GetFormatInfo(data.recommended_format); BppFormatManager::Get().GetFormatInfo(data.current_format);
const auto& recommended_info =
data.optimized_size = (sheet_data.size() * recommended_info.bits_per_pixel) / current_info.bits_per_pixel; BppFormatManager::Get().GetFormatInfo(data.recommended_format);
data.compression_ratio = static_cast<float>(data.current_size) / data.optimized_size;
data.optimized_size = (sheet_data.size() * recommended_info.bits_per_pixel) /
current_info.bits_per_pixel;
data.compression_ratio =
static_cast<float>(data.current_size) / data.optimized_size;
// Determine if conversion is beneficial // Determine if conversion is beneficial
data.is_convertible = (data.current_format != data.recommended_format) && data.is_convertible =
(data.colors_used <= recommended_info.max_colors) && (data.current_format != data.recommended_format) &&
(data.compression_ratio > 1.1f); // At least 10% savings (data.colors_used <= recommended_info.max_colors) &&
(data.compression_ratio > 1.1f); // At least 10% savings
data.optimization_reason = GenerateOptimizationReason(data); data.optimization_reason = GenerateOptimizationReason(data);
// Cache the result // Cache the result
optimization_cache_[cache_key] = data; optimization_cache_[cache_key] = data;
return data; return data;
} }
std::unordered_map<int, SheetOptimizationData> GraphicsOptimizer::GetOptimizationRecommendations( std::unordered_map<int, SheetOptimizationData>
GraphicsOptimizer::GetOptimizationRecommendations(
const std::unordered_map<int, std::vector<uint8_t>>& sheets, const std::unordered_map<int, std::vector<uint8_t>>& sheets,
const std::unordered_map<int, SnesPalette>& palettes) { const std::unordered_map<int, SnesPalette>& palettes) {
std::unordered_map<int, SheetOptimizationData> recommendations; std::unordered_map<int, SheetOptimizationData> recommendations;
for (const auto& [sheet_id, sheet_data] : sheets) { for (const auto& [sheet_id, sheet_data] : sheets) {
auto palette_it = palettes.find(sheet_id); auto palette_it = palettes.find(sheet_id);
if (palette_it == palettes.end()) { if (palette_it == palettes.end()) {
continue; continue;
} }
recommendations[sheet_id] = AnalyzeSheet(sheet_data, sheet_id, palette_it->second); recommendations[sheet_id] =
AnalyzeSheet(sheet_data, sheet_id, palette_it->second);
} }
return recommendations; return recommendations;
} }
@@ -202,33 +221,34 @@ OptimizationResult GraphicsOptimizer::ApplyOptimizations(
const std::unordered_map<int, SheetOptimizationData>& recommendations, const std::unordered_map<int, SheetOptimizationData>& recommendations,
std::unordered_map<int, std::vector<uint8_t>>& sheets, std::unordered_map<int, std::vector<uint8_t>>& sheets,
std::unordered_map<int, SnesPalette>& palettes) { std::unordered_map<int, SnesPalette>& palettes) {
ScopedTimer timer("graphics_apply_optimizations"); ScopedTimer timer("graphics_apply_optimizations");
OptimizationResult result; OptimizationResult result;
result.success = true; result.success = true;
size_t total_memory_saved = 0; size_t total_memory_saved = 0;
int optimized_sheets = 0; int optimized_sheets = 0;
for (const auto& [sheet_id, data] : recommendations) { for (const auto& [sheet_id, data] : recommendations) {
if (!data.is_convertible) { if (!data.is_convertible) {
continue; continue;
} }
auto sheet_it = sheets.find(sheet_id); auto sheet_it = sheets.find(sheet_id);
if (sheet_it == sheets.end()) { if (sheet_it == sheets.end()) {
continue; continue;
} }
try { try {
// Convert the sheet data // Convert the sheet data
auto converted_data = BppFormatManager::Get().ConvertFormat( auto converted_data = BppFormatManager::Get().ConvertFormat(
sheet_it->second, data.current_format, data.recommended_format, 128, 32); sheet_it->second, data.current_format, data.recommended_format, 128,
32);
// Update the sheet // Update the sheet
sheet_it->second = converted_data; sheet_it->second = converted_data;
// Optimize palette if needed // Optimize palette if needed
auto palette_it = palettes.find(sheet_id); auto palette_it = palettes.find(sheet_id);
if (palette_it != palettes.end()) { if (palette_it != palettes.end()) {
@@ -236,31 +256,34 @@ OptimizationResult GraphicsOptimizer::ApplyOptimizations(
for (int i = 0; i < data.colors_used; ++i) { for (int i = 0; i < data.colors_used; ++i) {
used_colors.push_back(i); used_colors.push_back(i);
} }
palette_it->second = BppFormatManager::Get().OptimizePaletteForFormat( palette_it->second = BppFormatManager::Get().OptimizePaletteForFormat(
palette_it->second, data.recommended_format, used_colors); palette_it->second, data.recommended_format, used_colors);
} }
total_memory_saved += data.current_size - data.optimized_size; total_memory_saved += data.current_size - data.optimized_size;
optimized_sheets++; optimized_sheets++;
result.sheet_recommendations[sheet_id] = data.recommended_format; result.sheet_recommendations[sheet_id] = data.recommended_format;
} catch (const std::exception& e) { } catch (const std::exception& e) {
SDL_Log("Failed to optimize sheet %d: %s", sheet_id, e.what()); SDL_Log("Failed to optimize sheet %d: %s", sheet_id, e.what());
} }
} }
result.memory_saved = total_memory_saved; result.memory_saved = total_memory_saved;
result.message = "Optimized " + std::to_string(optimized_sheets) + " sheets"; result.message = "Optimized " + std::to_string(optimized_sheets) + " sheets";
UpdateOptimizationStats("optimizations_applied", static_cast<double>(optimized_sheets)); UpdateOptimizationStats("optimizations_applied",
UpdateOptimizationStats("total_memory_saved", static_cast<double>(total_memory_saved)); static_cast<double>(optimized_sheets));
UpdateOptimizationStats("total_memory_saved",
static_cast<double>(total_memory_saved));
return result; return result;
} }
std::unordered_map<std::string, double> GraphicsOptimizer::GetOptimizationStats() const { std::unordered_map<std::string, double>
GraphicsOptimizer::GetOptimizationStats() const {
return optimization_stats_; return optimization_stats_;
} }
@@ -270,8 +293,8 @@ void GraphicsOptimizer::ClearCache() {
} }
void GraphicsOptimizer::SetOptimizationParameters(float max_quality_loss, void GraphicsOptimizer::SetOptimizationParameters(float max_quality_loss,
size_t min_memory_savings, size_t min_memory_savings,
float performance_threshold) { float performance_threshold) {
max_quality_loss_ = max_quality_loss; max_quality_loss_ = max_quality_loss;
min_memory_savings_ = min_memory_savings; min_memory_savings_ = min_memory_savings;
performance_threshold_ = performance_threshold; performance_threshold_ = performance_threshold;
@@ -279,166 +302,197 @@ void GraphicsOptimizer::SetOptimizationParameters(float max_quality_loss,
// Helper method implementations // Helper method implementations
BppFormat GraphicsOptimizer::DetermineOptimalFormat(const std::vector<uint8_t>& data, BppFormat GraphicsOptimizer::DetermineOptimalFormat(
const SnesPalette& palette, const std::vector<uint8_t>& data, const SnesPalette& palette,
OptimizationStrategy strategy) { OptimizationStrategy strategy) {
int colors_used = CountUsedColors(data, palette); int colors_used = CountUsedColors(data, palette);
// Determine optimal format based on color usage and strategy // Determine optimal format based on color usage and strategy
switch (strategy) { switch (strategy) {
case OptimizationStrategy::kMemoryOptimized: case OptimizationStrategy::kMemoryOptimized:
if (colors_used <= 4) return BppFormat::kBpp2; if (colors_used <= 4)
if (colors_used <= 8) return BppFormat::kBpp3; return BppFormat::kBpp2;
if (colors_used <= 16) return BppFormat::kBpp4; if (colors_used <= 8)
return BppFormat::kBpp3;
if (colors_used <= 16)
return BppFormat::kBpp4;
break; break;
case OptimizationStrategy::kPerformanceOptimized: case OptimizationStrategy::kPerformanceOptimized:
// Prefer formats that work well with atlas rendering // Prefer formats that work well with atlas rendering
if (colors_used <= 16) return BppFormat::kBpp4; if (colors_used <= 16)
return BppFormat::kBpp4;
break; break;
case OptimizationStrategy::kQualityOptimized: case OptimizationStrategy::kQualityOptimized:
// Only optimize if significant memory savings // Only optimize if significant memory savings
if (colors_used <= 4) return BppFormat::kBpp2; if (colors_used <= 4)
return BppFormat::kBpp2;
break; break;
case OptimizationStrategy::kBalanced: case OptimizationStrategy::kBalanced:
if (colors_used <= 4) return BppFormat::kBpp2; if (colors_used <= 4)
if (colors_used <= 8) return BppFormat::kBpp3; return BppFormat::kBpp2;
if (colors_used <= 16) return BppFormat::kBpp4; if (colors_used <= 8)
return BppFormat::kBpp3;
if (colors_used <= 16)
return BppFormat::kBpp4;
break; break;
} }
return BppFormat::kBpp8; // Default to 8BPP return BppFormat::kBpp8; // Default to 8BPP
} }
float GraphicsOptimizer::CalculateQualityLoss(BppFormat from_format, BppFormat to_format, float GraphicsOptimizer::CalculateQualityLoss(
const std::vector<uint8_t>& data) { BppFormat from_format, BppFormat to_format,
if (from_format == to_format) return 0.0f; const std::vector<uint8_t>& data) {
if (from_format == to_format)
return 0.0f;
// Higher BPP to lower BPP conversions may lose quality // Higher BPP to lower BPP conversions may lose quality
if (static_cast<int>(from_format) > static_cast<int>(to_format)) { if (static_cast<int>(from_format) > static_cast<int>(to_format)) {
int bpp_diff = static_cast<int>(from_format) - static_cast<int>(to_format); int bpp_diff = static_cast<int>(from_format) - static_cast<int>(to_format);
return std::min(1.0f, static_cast<float>(bpp_diff) * 0.1f); // 10% loss per BPP level return std::min(
1.0f, static_cast<float>(bpp_diff) * 0.1f); // 10% loss per BPP level
} }
return 0.0f; // Lower to higher BPP is lossless return 0.0f; // Lower to higher BPP is lossless
} }
size_t GraphicsOptimizer::CalculateMemorySavings(BppFormat from_format, BppFormat to_format, size_t GraphicsOptimizer::CalculateMemorySavings(
const std::vector<uint8_t>& data) { BppFormat from_format, BppFormat to_format,
if (from_format == to_format) return 0; const std::vector<uint8_t>& data) {
if (from_format == to_format)
return 0;
const auto& from_info = BppFormatManager::Get().GetFormatInfo(from_format); const auto& from_info = BppFormatManager::Get().GetFormatInfo(from_format);
const auto& to_info = BppFormatManager::Get().GetFormatInfo(to_format); const auto& to_info = BppFormatManager::Get().GetFormatInfo(to_format);
size_t from_size = data.size(); size_t from_size = data.size();
size_t to_size = (from_size * to_info.bits_per_pixel) / from_info.bits_per_pixel; size_t to_size =
(from_size * to_info.bits_per_pixel) / from_info.bits_per_pixel;
return from_size - to_size; return from_size - to_size;
} }
float GraphicsOptimizer::CalculatePerformanceGain(BppFormat from_format, BppFormat to_format) { float GraphicsOptimizer::CalculatePerformanceGain(BppFormat from_format,
if (from_format == to_format) return 0.0f; BppFormat to_format) {
if (from_format == to_format)
return 0.0f;
// Lower BPP formats generally render faster // Lower BPP formats generally render faster
if (static_cast<int>(from_format) > static_cast<int>(to_format)) { if (static_cast<int>(from_format) > static_cast<int>(to_format)) {
int bpp_diff = static_cast<int>(from_format) - static_cast<int>(to_format); int bpp_diff = static_cast<int>(from_format) - static_cast<int>(to_format);
return std::min(0.5f, static_cast<float>(bpp_diff) * 0.1f); // 10% gain per BPP level return std::min(
0.5f, static_cast<float>(bpp_diff) * 0.1f); // 10% gain per BPP level
} }
return 0.0f; return 0.0f;
} }
bool GraphicsOptimizer::ShouldOptimize(const SheetOptimizationData& data, OptimizationStrategy strategy) { bool GraphicsOptimizer::ShouldOptimize(const SheetOptimizationData& data,
if (!data.is_convertible) return false; OptimizationStrategy strategy) {
if (!data.is_convertible)
return false;
switch (strategy) { switch (strategy) {
case OptimizationStrategy::kMemoryOptimized: case OptimizationStrategy::kMemoryOptimized:
return data.compression_ratio > 1.2f; // At least 20% savings return data.compression_ratio > 1.2f; // At least 20% savings
case OptimizationStrategy::kPerformanceOptimized: case OptimizationStrategy::kPerformanceOptimized:
return data.compression_ratio > 1.1f; // At least 10% savings return data.compression_ratio > 1.1f; // At least 10% savings
case OptimizationStrategy::kQualityOptimized: case OptimizationStrategy::kQualityOptimized:
return data.compression_ratio > 1.5f; // At least 50% savings return data.compression_ratio > 1.5f; // At least 50% savings
case OptimizationStrategy::kBalanced: case OptimizationStrategy::kBalanced:
return data.compression_ratio > 1.15f; // At least 15% savings return data.compression_ratio > 1.15f; // At least 15% savings
} }
return false; return false;
} }
std::string GraphicsOptimizer::GenerateOptimizationReason(const SheetOptimizationData& data) { std::string GraphicsOptimizer::GenerateOptimizationReason(
const SheetOptimizationData& data) {
std::ostringstream reason; std::ostringstream reason;
reason << "Convert from " << BppFormatManager::Get().GetFormatInfo(data.current_format).name reason << "Convert from "
<< " to " << BppFormatManager::Get().GetFormatInfo(data.recommended_format).name << BppFormatManager::Get().GetFormatInfo(data.current_format).name
<< " (uses " << data.colors_used << " colors, " << " to "
<< std::fixed << std::setprecision(1) << (data.compression_ratio - 1.0f) * 100.0f << BppFormatManager::Get().GetFormatInfo(data.recommended_format).name
<< " (uses " << data.colors_used << " colors, " << std::fixed
<< std::setprecision(1) << (data.compression_ratio - 1.0f) * 100.0f
<< "% memory savings)"; << "% memory savings)";
return reason.str(); return reason.str();
} }
int GraphicsOptimizer::CountUsedColors(const std::vector<uint8_t>& data, const SnesPalette& palette) { int GraphicsOptimizer::CountUsedColors(const std::vector<uint8_t>& data,
const SnesPalette& palette) {
std::vector<bool> used_colors(palette.size(), false); std::vector<bool> used_colors(palette.size(), false);
for (uint8_t pixel : data) { for (uint8_t pixel : data) {
if (pixel < palette.size()) { if (pixel < palette.size()) {
used_colors[pixel] = true; used_colors[pixel] = true;
} }
} }
int count = 0; int count = 0;
for (bool used : used_colors) { for (bool used : used_colors) {
if (used) count++; if (used)
count++;
} }
return count; return count;
} }
float GraphicsOptimizer::CalculateColorEfficiency(const std::vector<uint8_t>& data, const SnesPalette& palette) { float GraphicsOptimizer::CalculateColorEfficiency(
const std::vector<uint8_t>& data, const SnesPalette& palette) {
int used_colors = CountUsedColors(data, palette); int used_colors = CountUsedColors(data, palette);
return static_cast<float>(used_colors) / palette.size(); return static_cast<float>(used_colors) / palette.size();
} }
std::vector<int> GraphicsOptimizer::AnalyzeColorDistribution(const std::vector<uint8_t>& data) { std::vector<int> GraphicsOptimizer::AnalyzeColorDistribution(
const std::vector<uint8_t>& data) {
std::vector<int> distribution(256, 0); std::vector<int> distribution(256, 0);
for (uint8_t pixel : data) { for (uint8_t pixel : data) {
distribution[pixel]++; distribution[pixel]++;
} }
return distribution; return distribution;
} }
std::string GraphicsOptimizer::GenerateCacheKey(const std::vector<uint8_t>& data, int sheet_id) { std::string GraphicsOptimizer::GenerateCacheKey(
const std::vector<uint8_t>& data, int sheet_id) {
std::ostringstream key; std::ostringstream key;
key << "sheet_" << sheet_id << "_" << data.size(); key << "sheet_" << sheet_id << "_" << data.size();
// Add hash of data for uniqueness // Add hash of data for uniqueness
size_t hash = 0; size_t hash = 0;
for (size_t i = 0; i < std::min(data.size(), size_t(1024)); ++i) { for (size_t i = 0; i < std::min(data.size(), size_t(1024)); ++i) {
hash = hash * 31 + data[i]; hash = hash * 31 + data[i];
} }
key << "_" << hash; key << "_" << hash;
return key.str(); return key.str();
} }
void GraphicsOptimizer::UpdateOptimizationStats(const std::string& operation, double value) { void GraphicsOptimizer::UpdateOptimizationStats(const std::string& operation,
double value) {
optimization_stats_[operation] += value; optimization_stats_[operation] += value;
} }
// GraphicsOptimizationScope implementation // GraphicsOptimizationScope implementation
GraphicsOptimizationScope::GraphicsOptimizationScope(OptimizationStrategy strategy, int sheet_count) GraphicsOptimizationScope::GraphicsOptimizationScope(
: strategy_(strategy), sheet_count_(sheet_count), OptimizationStrategy strategy, int sheet_count)
: strategy_(strategy),
sheet_count_(sheet_count),
timer_("graphics_optimize_scope") { timer_("graphics_optimize_scope") {
std::ostringstream op_name; std::ostringstream op_name;
op_name << "graphics_optimize_" << static_cast<int>(strategy) << "_" << sheet_count; op_name << "graphics_optimize_" << static_cast<int>(strategy) << "_"
<< sheet_count;
operation_name_ = op_name.str(); operation_name_ = op_name.str();
} }
@@ -446,7 +500,8 @@ GraphicsOptimizationScope::~GraphicsOptimizationScope() {
// Timer automatically ends in destructor // Timer automatically ends in destructor
} }
void GraphicsOptimizationScope::AddSheet(int sheet_id, size_t original_size, size_t optimized_size) { void GraphicsOptimizationScope::AddSheet(int sheet_id, size_t original_size,
size_t optimized_size) {
result_.memory_saved += (original_size - optimized_size); result_.memory_saved += (original_size - optimized_size);
} }

View File

@@ -3,12 +3,9 @@
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <memory>
#include <string> #include <string>
#include "app/gfx/bitmap.h"
#include "app/gfx/bpp_format_manager.h" #include "app/gfx/bpp_format_manager.h"
#include "app/gfx/atlas_renderer.h"
#include "app/gfx/performance_profiler.h" #include "app/gfx/performance_profiler.h"
namespace yaze { namespace yaze {

View File

@@ -0,0 +1,100 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H
#define YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H
#include <cstdint>
namespace yaze {
namespace zelda3 {
// ============================================================================
// Dungeon ROM Address Constants
// ============================================================================
// This file contains all ROM addresses for dungeon-related data in ALTTP.
// Organized by category for readability and maintainability.
// === Room Data Pointers ===
constexpr int kRoomObjectLayoutPointer = 0x882D; // Layout pointer table
constexpr int kRoomObjectPointer = 0x874C; // Object data pointer (Long)
constexpr int kRoomHeaderPointer = 0xB5DD; // Room header pointer (LONG)
constexpr int kRoomHeaderPointerBank = 0xB5E7; // Room header bank byte
// === Palette Data ===
constexpr int kDungeonsMainBgPalettePointers = 0xDEC4B; // JP Same
constexpr int kDungeonsPalettes = 0xDD734; // Dungeon palette data
// === Item & Sprite Data ===
constexpr int kRoomItemsPointers = 0xDB69; // JP 0xDB67
constexpr int kRoomsSpritePointer = 0x4C298; // JP Same (2-byte bank 09D62E)
constexpr int kSpriteBlocksetPointer = 0x5B57; // Sprite graphics pointer
// === Graphics Data ===
constexpr int kGfxGroupsPointer = 0x6237; // Graphics group table
constexpr int kTileAddress = 0x001B52; // Main tile graphics
constexpr int kTileAddressFloor = 0x001B5A; // Floor tile graphics
// === Block Data ===
constexpr int kBlocksLength = 0x8896; // Word value
constexpr int kBlocksPointer1 = 0x15AFA; // Block data pointer 1
constexpr int kBlocksPointer2 = 0x15B01; // Block data pointer 2
constexpr int kBlocksPointer3 = 0x15B08; // Block data pointer 3
constexpr int kBlocksPointer4 = 0x15B0F; // Block data pointer 4
// === Chests ===
constexpr int kChestsLengthPointer = 0xEBF6; // Chest count pointer
constexpr int kChestsDataPointer1 = 0xEBFB; // Chest data start
// === Torches ===
constexpr int kTorchData = 0x2736A; // JP 0x2704A
constexpr int kTorchesLengthPointer = 0x88C1; // Torch count pointer
// === Pits & Warps ===
constexpr int kPitPointer = 0x394AB; // Pit/hole data
constexpr int kPitCount = 0x394A6; // Number of pits
// === Doors ===
constexpr int kDoorPointers = 0xF83C0; // Door data table
constexpr int kDoorGfxUp = 0x4D9E; // Door graphics (up)
constexpr int kDoorGfxDown = 0x4E06; // Door graphics (down)
constexpr int kDoorGfxCaveExitDown = 0x4E06; // Cave exit door
constexpr int kDoorGfxLeft = 0x4E66; // Door graphics (left)
constexpr int kDoorGfxRight = 0x4EC6; // Door graphics (right)
constexpr int kDoorPosUp = 0x197E; // Door position (up)
constexpr int kDoorPosDown = 0x1996; // Door position (down)
constexpr int kDoorPosLeft = 0x19AE; // Door position (left)
constexpr int kDoorPosRight = 0x19C6; // Door position (right)
// === Sprites ===
constexpr int kSpritesData = 0x4D8B0; // Sprite data start
constexpr int kSpritesDataEmptyRoom = 0x4D8AE; // Empty room sprite marker
constexpr int kSpritesEndData = 0x4EC9E; // Sprite data end
constexpr int kDungeonSpritePointers = 0x090000; // Dungeon sprite pointer table
// === Messages ===
constexpr int kMessagesIdDungeon = 0x3F61D; // Dungeon message IDs
// === Room Metadata ===
constexpr int kNumberOfRooms = 296; // Total dungeon rooms (0x00-0x127)
// Stair objects (special handling)
constexpr uint16_t kStairsObjects[] = {0x139, 0x138, 0x13B, 0x12E, 0x12D};
// === Layout Pointers (referenced in comments) ===
// Layout00 ptr: 0x47EF04
// Layout01 ptr: 0xAFEF04
// Layout02 ptr: 0xF0EF04
// Layout03 ptr: 0x4CF004
// Layout04 ptr: 0xA8F004
// Layout05 ptr: 0xECF004
// Layout06 ptr: 0x48F104
// Layout07 ptr: 0xA4F104
// === Notes ===
// - Layout arrays are NOT exactly the same as rooms
// - Object array is terminated by 0xFFFF (no layers)
// - In normal room, 0xFFFF goes to next layer (layers 0, 1, 2)
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H

View File

@@ -331,17 +331,24 @@ void Room::RenderRoomGraphics() {
// Update textures with all the data (floor + background + objects + palette) // Update textures with all the data (floor + background + objects + palette)
if (bg1_bmp.texture()) { if (bg1_bmp.texture()) {
// Texture exists - UPDATE it with new object data // Texture exists - UPDATE it with new object data
LOG_DEBUG("[RenderRoomGraphics]", "Queueing UPDATE for existing textures");
gfx::Arena::Get().QueueTextureCommand( gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &bg1_bmp); gfx::Arena::TextureCommandType::UPDATE, &bg1_bmp);
gfx::Arena::Get().QueueTextureCommand( gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &bg2_bmp); gfx::Arena::TextureCommandType::UPDATE, &bg2_bmp);
} else { } else {
// No texture yet - CREATE it // No texture yet - CREATE it
LOG_DEBUG("[RenderRoomGraphics]", "Queueing CREATE for new textures");
gfx::Arena::Get().QueueTextureCommand( gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg1_bmp); gfx::Arena::TextureCommandType::CREATE, &bg1_bmp);
gfx::Arena::Get().QueueTextureCommand( gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg2_bmp); gfx::Arena::TextureCommandType::CREATE, &bg2_bmp);
} }
// CRITICAL: Process texture queue immediately so objects appear!
// Don't wait for next frame - update NOW!
gfx::Arena::Get().ProcessTextureQueue(nullptr);
LOG_DEBUG("[RenderRoomGraphics]", "Processed texture queue immediately");
} }
void Room::RenderObjectsToBackground() { void Room::RenderObjectsToBackground() {

View File

@@ -9,6 +9,7 @@
#include "app/rom.h" #include "app/rom.h"
#include "app/gfx/background_buffer.h" #include "app/gfx/background_buffer.h"
#include "app/zelda3/dungeon/dungeon_rom_addresses.h"
#include "app/zelda3/dungeon/room_layout.h" #include "app/zelda3/dungeon/room_layout.h"
#include "app/zelda3/dungeon/room_object.h" #include "app/zelda3/dungeon/room_object.h"
#include "app/zelda3/sprite/sprite.h" #include "app/zelda3/sprite/sprite.h"
@@ -16,72 +17,51 @@
namespace yaze { namespace yaze {
namespace zelda3 { namespace zelda3 {
// room_object_layout_pointer 0x882D // ROM addresses moved to dungeon_rom_addresses.h for better organization
// room_object_pointer 0x874C // Use kPrefixedNames for new code (clean naming convention)
// 0x882D -> readlong() -> 2FEF04 (04EF2F -> toPC->026F2F) ->
// 47EF04 ; layout00 ptr
// AFEF04 ; layout01 ptr
// F0EF04 ; layout02 ptr
// 4CF004 ; layout03 ptr
// A8F004 ; layout04 ptr
// ECF004 ; layout05 ptr
// 48F104 ; layout06 ptr
// A4F104 ; layout07 ptr
// also they are not exactly the same as rooms
// the object array is terminated by a 0xFFFF there's no layers
// in normal room when you encounter a 0xFFFF it goes to the next layer
constexpr int room_object_layout_pointer = 0x882D;
constexpr int room_object_pointer = 0x874C; // Long pointer
constexpr int dungeons_main_bg_palette_pointers = 0xDEC4B; // JP Same
constexpr int dungeons_palettes = 0xDD734;
constexpr int room_items_pointers = 0xDB69; // JP 0xDB67
constexpr int rooms_sprite_pointer = 0x4C298; // JP Same //2byte bank 09D62E
constexpr int kRoomHeaderPointer = 0xB5DD; // LONG
constexpr int kRoomHeaderPointerBank = 0xB5E7; // JP Same
constexpr int gfx_groups_pointer = 0x6237;
constexpr int chests_length_pointer = 0xEBF6;
constexpr int chests_data_pointer1 = 0xEBFB;
constexpr int messages_id_dungeon = 0x3F61D;
constexpr int blocks_length = 0x8896; // Word value
constexpr int blocks_pointer1 = 0x15AFA;
constexpr int blocks_pointer2 = 0x15B01;
constexpr int blocks_pointer3 = 0x15B08;
constexpr int blocks_pointer4 = 0x15B0F;
constexpr int torch_data = 0x2736A; // JP 0x2704A
constexpr int torches_length_pointer = 0x88C1;
constexpr int sprite_blockset_pointer = 0x5B57;
constexpr int sprites_data = 0x4D8B0;
constexpr int sprites_data_empty_room = 0x4D8AE;
constexpr int sprites_end_data = 0x4EC9E;
constexpr int pit_pointer = 0x394AB;
constexpr int pit_count = 0x394A6;
constexpr int doorPointers = 0xF83C0;
// doors
constexpr int door_gfx_up = 0x4D9E;
constexpr int door_gfx_down = 0x4E06;
constexpr int door_gfx_cavexit_down = 0x4E06;
constexpr int door_gfx_left = 0x4E66;
constexpr int door_gfx_right = 0x4EC6;
constexpr int door_pos_up = 0x197E;
constexpr int door_pos_down = 0x1996;
constexpr int door_pos_left = 0x19AE;
constexpr int door_pos_right = 0x19C6;
constexpr int dungeon_spr_ptrs = 0x090000;
constexpr int NumberOfRooms = 296;
// Legacy aliases for backward compatibility (gradual migration)
constexpr int room_object_layout_pointer = kRoomObjectLayoutPointer;
constexpr int room_object_pointer = kRoomObjectPointer;
constexpr int dungeons_main_bg_palette_pointers = kDungeonsMainBgPalettePointers;
constexpr int dungeons_palettes = kDungeonsPalettes;
constexpr int room_items_pointers = kRoomItemsPointers;
constexpr int rooms_sprite_pointer = kRoomsSpritePointer;
constexpr int gfx_groups_pointer = kGfxGroupsPointer;
constexpr int chests_length_pointer = kChestsLengthPointer;
constexpr int chests_data_pointer1 = kChestsDataPointer1;
constexpr int messages_id_dungeon = kMessagesIdDungeon;
constexpr int blocks_length = kBlocksLength;
constexpr int blocks_pointer1 = kBlocksPointer1;
constexpr int blocks_pointer2 = kBlocksPointer2;
constexpr int blocks_pointer3 = kBlocksPointer3;
constexpr int blocks_pointer4 = kBlocksPointer4;
constexpr int torch_data = kTorchData;
constexpr int torches_length_pointer = kTorchesLengthPointer;
constexpr int sprite_blockset_pointer = kSpriteBlocksetPointer;
constexpr int sprites_data = kSpritesData;
constexpr int sprites_data_empty_room = kSpritesDataEmptyRoom;
constexpr int sprites_end_data = kSpritesEndData;
constexpr int pit_pointer = kPitPointer;
constexpr int pit_count = kPitCount;
constexpr int doorPointers = kDoorPointers;
constexpr int door_gfx_up = kDoorGfxUp;
constexpr int door_gfx_down = kDoorGfxDown;
constexpr int door_gfx_cavexit_down = kDoorGfxCaveExitDown;
constexpr int door_gfx_left = kDoorGfxLeft;
constexpr int door_gfx_right = kDoorGfxRight;
constexpr int door_pos_up = kDoorPosUp;
constexpr int door_pos_down = kDoorPosDown;
constexpr int door_pos_left = kDoorPosLeft;
constexpr int door_pos_right = kDoorPosRight;
constexpr int dungeon_spr_ptrs = kDungeonSpritePointers;
constexpr int tile_address = kTileAddress;
constexpr int tile_address_floor = kTileAddressFloor;
constexpr int NumberOfRooms = kNumberOfRooms;
constexpr uint16_t stairsObjects[] = {0x139, 0x138, 0x13B, 0x12E, 0x12D}; constexpr uint16_t stairsObjects[] = {0x139, 0x138, 0x13B, 0x12E, 0x12D};
constexpr int tile_address = 0x001B52; // TODO: Gradually migrate all code to use kPrefixedNames directly
constexpr int tile_address_floor = 0x001B5A; // Then remove these legacy aliases
struct LayerMergeType { struct LayerMergeType {
uint8_t ID; uint8_t ID;