yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
palette_debug.cc
Go to the documentation of this file.
2
4
5#include <algorithm>
6#include <chrono>
7#include <climits>
8#include <map>
9#include <sstream>
10
11#include "absl/strings/str_format.h"
12#include "util/log.h"
13
14namespace {
15uint64_t GetCurrentTimeMs() {
16 return std::chrono::duration_cast<std::chrono::milliseconds>(
17 std::chrono::steady_clock::now().time_since_epoch())
18 .count();
19}
20
22#ifdef YAZE_PALETTE_DEBUG_VERBOSE
23 return true;
24#else
25 return false;
26#endif
27}
28
31 LOG_ERROR("PaletteDebug", "%s: %s", event.location.c_str(),
32 event.message.c_str());
33 return;
34 }
36 LOG_WARN("PaletteDebug", "%s: %s", event.location.c_str(),
37 event.message.c_str());
38 return;
39 }
41 LOG_DEBUG("PaletteDebug", "%s: %s", event.location.c_str(),
42 event.message.c_str());
43 }
44}
45} // namespace
46
47namespace yaze::zelda3 {
48
50 static PaletteDebugger instance;
51 return instance;
52}
53
54void PaletteDebugger::LogPaletteLoad(const std::string& location,
55 int palette_id,
56 const gfx::SnesPalette& palette) {
58 event.location = location;
59 event.palette_id = palette_id;
60 event.color_count = palette.size();
61 event.level = PaletteDebugLevel::INFO;
62 event.timestamp_ms = GetCurrentTimeMs();
63 event.sequence_number = sequence_counter_++;
64
65 // Sample first 3 colors
66 for (size_t i = 0; i < std::min(size_t(3), palette.size()); i++) {
67 auto rgb = palette[i].rgb();
68 event.sample_colors.push_back(static_cast<uint8_t>(rgb.x));
69 event.sample_colors.push_back(static_cast<uint8_t>(rgb.y));
70 event.sample_colors.push_back(static_cast<uint8_t>(rgb.z));
71 }
72
73 std::string sample_str;
74 if (event.sample_colors.size() >= 3) {
75 sample_str =
76 absl::StrFormat("[Sample: R=%d G=%d B=%d]", event.sample_colors[0],
77 event.sample_colors[1], event.sample_colors[2]);
78 }
79
80 event.message = absl::StrFormat("Loaded palette %d with %d colors %s",
81 palette_id, event.color_count, sample_str);
82
83 AddEvent(event);
84 LogPaletteDebugEvent(event);
85}
86
87void PaletteDebugger::LogPaletteApplication(const std::string& location,
88 int palette_id, bool success,
89 const std::string& reason) {
91 event.location = location;
92 event.palette_id = palette_id;
93 event.color_count = !current_render_palette_.empty()
94 ? static_cast<int>(current_render_palette_.size())
95 : static_cast<int>(current_palette_.size());
96 event.level = success ? PaletteDebugLevel::INFO : PaletteDebugLevel::ERROR;
97 event.timestamp_ms = GetCurrentTimeMs();
98 event.sequence_number = sequence_counter_++;
99 event.message =
100 success ? absl::StrFormat("Applied palette %d successfully", palette_id)
101 : absl::StrFormat("Failed to apply palette %d: %s", palette_id,
102 reason);
103
104 AddEvent(event);
105 LogPaletteDebugEvent(event);
106}
107
108void PaletteDebugger::LogTextureCreation(const std::string& location,
109 bool has_palette, int color_count) {
110 PaletteDebugEvent event;
111 event.location = location;
112 event.color_count = color_count;
113 event.level =
115 event.timestamp_ms = GetCurrentTimeMs();
116 event.sequence_number = sequence_counter_++;
117 event.message =
118 has_palette
119 ? absl::StrFormat("Creating texture with %d-color palette",
120 color_count)
121 : "WARNING: Creating texture WITHOUT palette - will use default "
122 "colors!";
123
124 AddEvent(event);
125 LogPaletteDebugEvent(event);
126}
127
128void PaletteDebugger::LogSurfaceState(const std::string& location,
129 SDL_Surface* surface) {
130 PaletteDebugEvent event;
131 event.location = location;
132 event.timestamp_ms = GetCurrentTimeMs();
133 event.sequence_number = sequence_counter_++;
134
135 if (!surface) {
136 event.level = PaletteDebugLevel::ERROR;
137 event.message = "Surface is NULL!";
138 AddEvent(event);
139 LogPaletteDebugEvent(event);
140 return;
141 }
142
143 Uint32 fmt = platform::GetSurfaceFormat(surface);
144 if (fmt == SDL_PIXELFORMAT_UNKNOWN) {
145 event.level = PaletteDebugLevel::ERROR;
146 event.message = "Surface format is unknown!";
147 AddEvent(event);
148 LogPaletteDebugEvent(event);
149 return;
150 }
151
152 bool is_indexed = (fmt == SDL_PIXELFORMAT_INDEX8);
153 SDL_Palette* palette = platform::GetSurfacePalette(surface);
154 bool has_palette = (palette != nullptr);
155 int ncolors = has_palette ? palette->ncolors : 0;
156
157 event.color_count = ncolors;
158
159 if (!is_indexed) {
160 event.level = PaletteDebugLevel::WARNING;
161 event.message = absl::StrFormat(
162 "Surface is NOT indexed (format=0x%08X). Palette will be ignored!",
163 fmt);
164 } else if (!has_palette) {
165 event.level = PaletteDebugLevel::WARNING;
166 event.message = "Surface is indexed but has NO palette attached!";
167 } else if (ncolors < 90) {
168 event.level = PaletteDebugLevel::WARNING;
169 event.message = absl::StrFormat(
170 "Surface has palette with only %d colors (expected 90)", ncolors);
171 } else {
172 event.level = PaletteDebugLevel::INFO;
173 event.message = absl::StrFormat(
174 "Surface OK: indexed format, %d-color palette attached", ncolors);
175
176 // Sample color 56 (palette 7, offset 0) for verification
177 if (palette && ncolors > 56) {
178 auto& c = palette->colors[56];
179 event.sample_colors = {c.r, c.g, c.b};
180 event.message +=
181 absl::StrFormat(" [Color56: R=%d G=%d B=%d]", c.r, c.g, c.b);
182 }
183 }
184
185 AddEvent(event);
186 LogPaletteDebugEvent(event);
187}
188
192
194 const std::vector<SDL_Color>& palette) {
195 current_render_palette_ = palette;
196}
197
201
203 ColorComparison comp;
204 comp.x = x;
205 comp.y = y;
206 comp.palette_index = 0;
207 comp.actual_r = comp.actual_g = comp.actual_b = 0;
208 comp.expected_r = comp.expected_g = comp.expected_b = 0;
209 comp.matches = false;
210
212 return comp;
213 }
214
215 auto* surface = current_bitmap_->surface();
216 int width = current_bitmap_->width();
217 int height = current_bitmap_->height();
218
219 if (x < 0 || x >= width || y < 0 || y >= height) {
220 return comp;
221 }
222
223 // Get palette index from bitmap data
224 const uint8_t* data = current_bitmap_->data();
225 size_t data_size = current_bitmap_->size();
226 if (!data || data_size == 0) {
227 return comp;
228 }
229
230 size_t idx = static_cast<size_t>(y * width + x);
231 if (idx >= data_size) {
232 return comp;
233 }
234
235 comp.palette_index = data[idx];
236
237 // Get expected color from the mapped dungeon render palette if available.
238 // Dungeon surfaces use the SDL/CGRAM-aligned 256-entry palette, not the raw
239 // 90-color dungeon ROM palette indices directly.
240 if (comp.palette_index < current_render_palette_.size()) {
241 const auto& c = current_render_palette_[comp.palette_index];
242 comp.expected_r = c.r;
243 comp.expected_g = c.g;
244 comp.expected_b = c.b;
245 } else if (comp.palette_index < current_palette_.size()) {
246 auto rgb = current_palette_[comp.palette_index].rgb();
247 comp.expected_r = static_cast<uint8_t>(rgb.x);
248 comp.expected_g = static_cast<uint8_t>(rgb.y);
249 comp.expected_b = static_cast<uint8_t>(rgb.z);
250 }
251
252 // Get actual color from SDL surface palette
253 SDL_Palette* palette = platform::GetSurfacePalette(surface);
254 if (palette && comp.palette_index < palette->ncolors) {
255 auto& c = palette->colors[comp.palette_index];
256 comp.actual_r = c.r;
257 comp.actual_g = c.g;
258 comp.actual_b = c.b;
259 }
260
261 // Check if they match
262 comp.matches =
263 (comp.actual_r == comp.expected_r && comp.actual_g == comp.expected_g &&
264 comp.actual_b == comp.expected_b);
265
266 return comp;
267}
268
270 const gfx::SnesPalette& palette) const {
271 uint32_t sum = 0;
272 for (size_t i = 0; i < palette.size(); i++) {
273 auto rgb = palette[i].rgb();
274 sum += static_cast<uint32_t>(rgb.x) * (i + 1);
275 sum += static_cast<uint32_t>(rgb.y) * (i + 1) * 256;
276 sum += static_cast<uint32_t>(rgb.z) * (i + 1) * 65536;
277 }
278 return sum;
279}
280
281#ifdef __EMSCRIPTEN__
282std::string PaletteDebugger::ExportToJSON() const {
283 std::ostringstream json;
284 json << "[";
285 for (size_t i = 0; i < events_.size(); i++) {
286 const auto& e = events_[i];
287 json << "{";
288 json << "\"location\":\"" << e.location << "\",";
289 json << "\"message\":\"" << e.message << "\",";
290 json << "\"level\":\""
291 << (e.level == PaletteDebugLevel::ERROR ? "error"
292 : e.level == PaletteDebugLevel::WARNING ? "warning"
293 : "info")
294 << "\",";
295 json << "\"palette_id\":" << e.palette_id << ",";
296 json << "\"color_count\":" << e.color_count;
297
298 // Include sample colors if available
299 if (!e.sample_colors.empty() && e.sample_colors.size() >= 3) {
300 json << ",\"sample_rgb\":[" << (int)e.sample_colors[0] << ","
301 << (int)e.sample_colors[1] << "," << (int)e.sample_colors[2] << "]";
302 }
303
304 json << "}";
305 if (i < events_.size() - 1)
306 json << ",";
307 }
308 json << "]";
309 return json.str();
310}
311
312std::string PaletteDebugger::ExportColorComparisonsJSON() const {
313 std::ostringstream json;
314 json << "[";
315 for (size_t i = 0; i < comparisons_.size(); i++) {
316 const auto& c = comparisons_[i];
317 json << "{";
318 json << "\"x\":" << c.x << ",\"y\":" << c.y << ",";
319 json << "\"palette_index\":" << (int)c.palette_index << ",";
320 json << "\"actual\":[" << (int)c.actual_r << "," << (int)c.actual_g << ","
321 << (int)c.actual_b << "],";
322 json << "\"expected\":[" << (int)c.expected_r << "," << (int)c.expected_g
323 << "," << (int)c.expected_b << "],";
324 json << "\"matches\":" << (c.matches ? "true" : "false");
325 json << "}";
326 if (i < comparisons_.size() - 1)
327 json << ",";
328 }
329 json << "]";
330 return json.str();
331}
332
333std::string PaletteDebugger::SamplePixelJSON(int x, int y) const {
334 auto comp = SamplePixelAt(x, y);
335 std::ostringstream json;
336 json << "{";
337 json << "\"x\":" << comp.x << ",\"y\":" << comp.y << ",";
338 json << "\"palette_index\":" << (int)comp.palette_index << ",";
339 json << "\"actual\":[" << (int)comp.actual_r << "," << (int)comp.actual_g
340 << "," << (int)comp.actual_b << "],";
341 json << "\"expected\":[" << (int)comp.expected_r << ","
342 << (int)comp.expected_g << "," << (int)comp.expected_b << "],";
343 json << "\"matches\":" << (comp.matches ? "true" : "false");
344 json << "}";
345 return json.str();
346}
347
348std::string PaletteDebugger::ExportFullStateJSON() const {
349 std::ostringstream json;
350 json << "{";
351
352 // Events timeline
353 json << "\"events\":" << ExportToJSON() << ",";
354
355 // Color comparisons
356 json << "\"comparisons\":" << ExportColorComparisonsJSON() << ",";
357
358 // Current palette data
359 json << "\"palette\":" << ExportPaletteDataJSON() << ",";
360
361 // Timeline analysis
362 json << "\"timeline\":" << ExportTimelineJSON() << ",";
363
364 // Diagnostic summary
365 json << "\"diagnostic\":\"" << GetDiagnosticSummary() << "\",";
366
367 // Hypothesis analysis
368 json << "\"hypothesis\":\"" << GetHypothesisAnalysis() << "\"";
369
370 json << "}";
371 return json.str();
372}
373
374std::string PaletteDebugger::ExportPaletteDataJSON() const {
375 std::ostringstream json;
376 json << "{";
377 json << "\"size\":" << current_palette_.size() << ",";
378 json << "\"render_size\":" << current_render_palette_.size() << ",";
379 json << "\"checksum\":" << ComputePaletteChecksum(current_palette_) << ",";
380 json << "\"colors\":[";
381
382 for (size_t i = 0; i < current_palette_.size(); i++) {
383 auto rgb = current_palette_[i].rgb();
384 json << "{\"index\":" << i << ",\"r\":" << (int)rgb.x
385 << ",\"g\":" << (int)rgb.y << ",\"b\":" << (int)rgb.z << "}";
386 if (i < current_palette_.size() - 1)
387 json << ",";
388 }
389
390 json << "],";
391 json << "\"render_colors\":[";
392
393 bool first_render = true;
394 for (size_t i = 0; i < current_render_palette_.size(); ++i) {
395 const auto& color = current_render_palette_[i];
396 if (color.a == 0) {
397 continue;
398 }
399 if (!first_render) {
400 json << ",";
401 }
402 json << "{\"index\":" << i << ",\"r\":" << static_cast<int>(color.r)
403 << ",\"g\":" << static_cast<int>(color.g)
404 << ",\"b\":" << static_cast<int>(color.b)
405 << ",\"a\":" << static_cast<int>(color.a) << "}";
406 first_render = false;
407 }
408
409 json << "]}";
410 return json.str();
411}
412
413std::string PaletteDebugger::ExportTimelineJSON() const {
414 std::ostringstream json;
415 json << "{";
416
417 // Find first and last timestamps
418 uint64_t first_ts = 0, last_ts = 0;
419 if (!events_.empty()) {
420 first_ts = events_[0].timestamp_ms;
421 last_ts = events_[events_.size() - 1].timestamp_ms;
422 }
423
424 json << "\"start_ms\":" << first_ts << ",";
425 json << "\"end_ms\":" << last_ts << ",";
426 json << "\"duration_ms\":" << (last_ts - first_ts) << ",";
427 json << "\"event_count\":" << events_.size() << ",";
428
429 // Group events by location
430 json << "\"by_location\":{";
431 std::map<std::string, int> location_counts;
432 for (const auto& e : events_) {
433 location_counts[e.location]++;
434 }
435 bool first = true;
436 for (const auto& [loc, count] : location_counts) {
437 if (!first)
438 json << ",";
439 json << "\"" << loc << "\":" << count;
440 first = false;
441 }
442 json << "},";
443
444 // Count by level
445 int info_count = 0, warn_count = 0, error_count = 0;
446 for (const auto& e : events_) {
447 if (e.level == PaletteDebugLevel::INFO)
448 info_count++;
449 else if (e.level == PaletteDebugLevel::WARNING)
450 warn_count++;
451 else
452 error_count++;
453 }
454 json << "\"info_count\":" << info_count << ",";
455 json << "\"warning_count\":" << warn_count << ",";
456 json << "\"error_count\":" << error_count;
457
458 json << "}";
459 return json.str();
460}
461
462std::string PaletteDebugger::GetDiagnosticSummary() const {
463 std::ostringstream summary;
464
465 // Count warnings and errors
466 int warnings = 0, errors = 0;
467 bool has_palette_timing_issue = false;
468 bool has_missing_palette = false;
469 bool has_color_mismatch = false;
470
471 for (const auto& e : events_) {
472 if (e.level == PaletteDebugLevel::WARNING)
473 warnings++;
474 if (e.level == PaletteDebugLevel::ERROR)
475 errors++;
476
477 // Detect specific issues
478 if (e.message.find("WITHOUT palette") != std::string::npos) {
479 has_missing_palette = true;
480 }
481 if (e.message.find("only") != std::string::npos &&
482 e.message.find("colors") != std::string::npos) {
483 has_palette_timing_issue = true;
484 }
485 }
486
487 // Check color comparisons for mismatches
488 for (const auto& c : comparisons_) {
489 if (!c.matches) {
490 has_color_mismatch = true;
491 break;
492 }
493 }
494
495 summary << "Events: " << events_.size() << ", Warnings: " << warnings
496 << ", Errors: " << errors << ". ";
497
498 if (has_missing_palette) {
499 summary << "ISSUE: Texture created without palette. ";
500 }
501 if (has_palette_timing_issue) {
502 summary << "ISSUE: Palette may not be fully loaded. ";
503 }
504 if (has_color_mismatch) {
505 summary << "ISSUE: Color mismatch detected between expected and actual. ";
506 }
507 if (!has_missing_palette && !has_palette_timing_issue &&
508 !has_color_mismatch) {
509 summary << "No obvious issues detected. ";
510 }
511
512 return summary.str();
513}
514
515std::string PaletteDebugger::GetHypothesisAnalysis() const {
516 std::ostringstream analysis;
517
518 // Analyze events for the timing hypothesis (75% confidence from plan)
519 bool texture_before_palette = false;
520 int last_palette_load_seq = -1;
521 int first_texture_create_seq = INT_MAX;
522
523 for (const auto& e : events_) {
524 if (e.message.find("Loaded palette") != std::string::npos) {
525 last_palette_load_seq =
526 std::max(last_palette_load_seq, e.sequence_number);
527 }
528 if (e.message.find("Creating texture") != std::string::npos) {
529 first_texture_create_seq =
530 std::min(first_texture_create_seq, e.sequence_number);
531 }
532 }
533
534 if (first_texture_create_seq < last_palette_load_seq) {
535 texture_before_palette = true;
536 analysis << "TIMING HYPOTHESIS CONFIRMED: Texture created (seq "
537 << first_texture_create_seq << ") before palette loaded (seq "
538 << last_palette_load_seq << "). ";
539 analysis << "FIX: Ensure palette is applied to surface BEFORE queuing "
540 "texture creation. ";
541 } else if (last_palette_load_seq >= 0 && first_texture_create_seq < INT_MAX) {
542 analysis << "Timing appears correct: palette loaded (seq "
543 << last_palette_load_seq << ") before texture created (seq "
544 << first_texture_create_seq << "). ";
545 }
546
547 // Check for palette group selection hypothesis (60% confidence)
548 bool wrong_palette_group = false;
549 for (const auto& e : events_) {
550 if (e.color_count > 0 && e.color_count < 90) {
551 wrong_palette_group = true;
552 analysis << "PALETTE GROUP ISSUE: Only " << e.color_count
553 << " colors (expected 90). Check palette group selection. ";
554 break;
555 }
556 }
557
558 // Check for surface format mismatch (40% confidence)
559 for (const auto& e : events_) {
560 if (e.message.find("NOT indexed") != std::string::npos) {
561 analysis << "FORMAT MISMATCH: Surface is not indexed, palette ignored. ";
562 break;
563 }
564 }
565
566 if (analysis.str().empty()) {
567 analysis << "No hypothesis conditions detected. Render pipeline may be "
568 "correct. Check actual colors vs expected in comparisons. ";
569 }
570
571 return analysis.str();
572}
573#endif
574
575} // namespace yaze::zelda3
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const uint8_t * data() const
Definition bitmap.h:398
auto size() const
Definition bitmap.h:397
int height() const
Definition bitmap.h:395
int width() const
Definition bitmap.h:394
SDL_Surface * surface() const
Definition bitmap.h:400
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void SetCurrentBitmap(gfx::Bitmap *bitmap)
void LogTextureCreation(const std::string &location, bool has_palette, int color_count)
uint32_t ComputePaletteChecksum(const gfx::SnesPalette &palette) const
void AddEvent(const PaletteDebugEvent &event)
std::vector< SDL_Color > current_render_palette_
void LogPaletteLoad(const std::string &location, int palette_id, const gfx::SnesPalette &palette)
static PaletteDebugger & Get()
gfx::SnesPalette current_palette_
void LogPaletteApplication(const std::string &location, int palette_id, bool success, const std::string &reason="")
void SetCurrentPalette(const gfx::SnesPalette &palette)
std::vector< PaletteDebugEvent > events_
std::vector< ColorComparison > comparisons_
void LogSurfaceState(const std::string &location, SDL_Surface *surface)
void SetCurrentRenderPalette(const std::vector< SDL_Color > &palette)
ColorComparison SamplePixelAt(int x, int y) const
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
void LogPaletteDebugEvent(const yaze::zelda3::PaletteDebugEvent &event)
Uint32 GetSurfaceFormat(SDL_Surface *surface)
Get the pixel format of a surface as Uint32.
Definition sdl_compat.h:408
SDL_Palette * GetSurfacePalette(SDL_Surface *surface)
Get the palette attached to a surface.
Definition sdl_compat.h:392
Zelda 3 specific classes and functions.
nlohmann::json json
SDL2/SDL3 compatibility layer.
std::vector< uint8_t > sample_colors