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 gfx::Bitmap* bitmap, const gfx::SnesPalette& palette,
204 const std::vector<SDL_Color>& render_palette) {
205 current_bitmap_ = bitmap;
206 current_palette_ = palette;
207 current_render_palette_ = render_palette;
208}
209
211 if (current_bitmap_ == bitmap) {
212 current_bitmap_ = nullptr;
213 }
214}
215
217 ColorComparison comp;
218 comp.x = x;
219 comp.y = y;
220 comp.palette_index = 0;
221 comp.actual_r = comp.actual_g = comp.actual_b = 0;
222 comp.expected_r = comp.expected_g = comp.expected_b = 0;
223 comp.matches = false;
224
226 return comp;
227 }
228
229 auto* surface = current_bitmap_->surface();
230 int width = current_bitmap_->width();
231 int height = current_bitmap_->height();
232
233 if (x < 0 || x >= width || y < 0 || y >= height) {
234 return comp;
235 }
236
237 // Get palette index from bitmap data
238 const uint8_t* data = current_bitmap_->data();
239 size_t data_size = current_bitmap_->size();
240 if (!data || data_size == 0) {
241 return comp;
242 }
243
244 size_t idx = static_cast<size_t>(y * width + x);
245 if (idx >= data_size) {
246 return comp;
247 }
248
249 comp.palette_index = data[idx];
250
251 // Get expected color from the mapped dungeon render palette if available.
252 // Dungeon surfaces use the SDL/CGRAM-aligned 256-entry palette, not the raw
253 // 90-color dungeon ROM palette indices directly.
254 if (comp.palette_index < current_render_palette_.size()) {
255 const auto& c = current_render_palette_[comp.palette_index];
256 comp.expected_r = c.r;
257 comp.expected_g = c.g;
258 comp.expected_b = c.b;
259 } else if (comp.palette_index < current_palette_.size()) {
260 auto rgb = current_palette_[comp.palette_index].rgb();
261 comp.expected_r = static_cast<uint8_t>(rgb.x);
262 comp.expected_g = static_cast<uint8_t>(rgb.y);
263 comp.expected_b = static_cast<uint8_t>(rgb.z);
264 }
265
266 // Get actual color from SDL surface palette
267 SDL_Palette* palette = platform::GetSurfacePalette(surface);
268 if (palette && comp.palette_index < palette->ncolors) {
269 auto& c = palette->colors[comp.palette_index];
270 comp.actual_r = c.r;
271 comp.actual_g = c.g;
272 comp.actual_b = c.b;
273 }
274
275 // Check if they match
276 comp.matches =
277 (comp.actual_r == comp.expected_r && comp.actual_g == comp.expected_g &&
278 comp.actual_b == comp.expected_b);
279
280 return comp;
281}
282
284 const gfx::SnesPalette& palette) const {
285 uint32_t sum = 0;
286 for (size_t i = 0; i < palette.size(); i++) {
287 auto rgb = palette[i].rgb();
288 sum += static_cast<uint32_t>(rgb.x) * (i + 1);
289 sum += static_cast<uint32_t>(rgb.y) * (i + 1) * 256;
290 sum += static_cast<uint32_t>(rgb.z) * (i + 1) * 65536;
291 }
292 return sum;
293}
294
295#ifdef __EMSCRIPTEN__
296std::string PaletteDebugger::ExportToJSON() const {
297 std::ostringstream json;
298 json << "[";
299 for (size_t i = 0; i < events_.size(); i++) {
300 const auto& e = events_[i];
301 json << "{";
302 json << "\"location\":\"" << e.location << "\",";
303 json << "\"message\":\"" << e.message << "\",";
304 json << "\"level\":\""
305 << (e.level == PaletteDebugLevel::ERROR ? "error"
306 : e.level == PaletteDebugLevel::WARNING ? "warning"
307 : "info")
308 << "\",";
309 json << "\"palette_id\":" << e.palette_id << ",";
310 json << "\"color_count\":" << e.color_count;
311
312 // Include sample colors if available
313 if (!e.sample_colors.empty() && e.sample_colors.size() >= 3) {
314 json << ",\"sample_rgb\":[" << (int)e.sample_colors[0] << ","
315 << (int)e.sample_colors[1] << "," << (int)e.sample_colors[2] << "]";
316 }
317
318 json << "}";
319 if (i < events_.size() - 1)
320 json << ",";
321 }
322 json << "]";
323 return json.str();
324}
325
326std::string PaletteDebugger::ExportColorComparisonsJSON() const {
327 std::ostringstream json;
328 json << "[";
329 for (size_t i = 0; i < comparisons_.size(); i++) {
330 const auto& c = comparisons_[i];
331 json << "{";
332 json << "\"x\":" << c.x << ",\"y\":" << c.y << ",";
333 json << "\"palette_index\":" << (int)c.palette_index << ",";
334 json << "\"actual\":[" << (int)c.actual_r << "," << (int)c.actual_g << ","
335 << (int)c.actual_b << "],";
336 json << "\"expected\":[" << (int)c.expected_r << "," << (int)c.expected_g
337 << "," << (int)c.expected_b << "],";
338 json << "\"matches\":" << (c.matches ? "true" : "false");
339 json << "}";
340 if (i < comparisons_.size() - 1)
341 json << ",";
342 }
343 json << "]";
344 return json.str();
345}
346
347std::string PaletteDebugger::SamplePixelJSON(int x, int y) const {
348 auto comp = SamplePixelAt(x, y);
349 std::ostringstream json;
350 json << "{";
351 json << "\"x\":" << comp.x << ",\"y\":" << comp.y << ",";
352 json << "\"palette_index\":" << (int)comp.palette_index << ",";
353 json << "\"actual\":[" << (int)comp.actual_r << "," << (int)comp.actual_g
354 << "," << (int)comp.actual_b << "],";
355 json << "\"expected\":[" << (int)comp.expected_r << ","
356 << (int)comp.expected_g << "," << (int)comp.expected_b << "],";
357 json << "\"matches\":" << (comp.matches ? "true" : "false");
358 json << "}";
359 return json.str();
360}
361
362std::string PaletteDebugger::ExportFullStateJSON() const {
363 std::ostringstream json;
364 json << "{";
365
366 // Events timeline
367 json << "\"events\":" << ExportToJSON() << ",";
368
369 // Color comparisons
370 json << "\"comparisons\":" << ExportColorComparisonsJSON() << ",";
371
372 // Current palette data
373 json << "\"palette\":" << ExportPaletteDataJSON() << ",";
374
375 // Timeline analysis
376 json << "\"timeline\":" << ExportTimelineJSON() << ",";
377
378 // Diagnostic summary
379 json << "\"diagnostic\":\"" << GetDiagnosticSummary() << "\",";
380
381 // Hypothesis analysis
382 json << "\"hypothesis\":\"" << GetHypothesisAnalysis() << "\"";
383
384 json << "}";
385 return json.str();
386}
387
388std::string PaletteDebugger::ExportPaletteDataJSON() const {
389 std::ostringstream json;
390 json << "{";
391 json << "\"size\":" << current_palette_.size() << ",";
392 json << "\"render_size\":" << current_render_palette_.size() << ",";
393 json << "\"checksum\":" << ComputePaletteChecksum(current_palette_) << ",";
394 json << "\"colors\":[";
395
396 for (size_t i = 0; i < current_palette_.size(); i++) {
397 auto rgb = current_palette_[i].rgb();
398 json << "{\"index\":" << i << ",\"r\":" << (int)rgb.x
399 << ",\"g\":" << (int)rgb.y << ",\"b\":" << (int)rgb.z << "}";
400 if (i < current_palette_.size() - 1)
401 json << ",";
402 }
403
404 json << "],";
405 json << "\"render_colors\":[";
406
407 bool first_render = true;
408 for (size_t i = 0; i < current_render_palette_.size(); ++i) {
409 const auto& color = current_render_palette_[i];
410 if (color.a == 0) {
411 continue;
412 }
413 if (!first_render) {
414 json << ",";
415 }
416 json << "{\"index\":" << i << ",\"r\":" << static_cast<int>(color.r)
417 << ",\"g\":" << static_cast<int>(color.g)
418 << ",\"b\":" << static_cast<int>(color.b)
419 << ",\"a\":" << static_cast<int>(color.a) << "}";
420 first_render = false;
421 }
422
423 json << "]}";
424 return json.str();
425}
426
427std::string PaletteDebugger::ExportTimelineJSON() const {
428 std::ostringstream json;
429 json << "{";
430
431 // Find first and last timestamps
432 uint64_t first_ts = 0, last_ts = 0;
433 if (!events_.empty()) {
434 first_ts = events_[0].timestamp_ms;
435 last_ts = events_[events_.size() - 1].timestamp_ms;
436 }
437
438 json << "\"start_ms\":" << first_ts << ",";
439 json << "\"end_ms\":" << last_ts << ",";
440 json << "\"duration_ms\":" << (last_ts - first_ts) << ",";
441 json << "\"event_count\":" << events_.size() << ",";
442
443 // Group events by location
444 json << "\"by_location\":{";
445 std::map<std::string, int> location_counts;
446 for (const auto& e : events_) {
447 location_counts[e.location]++;
448 }
449 bool first = true;
450 for (const auto& [loc, count] : location_counts) {
451 if (!first)
452 json << ",";
453 json << "\"" << loc << "\":" << count;
454 first = false;
455 }
456 json << "},";
457
458 // Count by level
459 int info_count = 0, warn_count = 0, error_count = 0;
460 for (const auto& e : events_) {
461 if (e.level == PaletteDebugLevel::INFO)
462 info_count++;
463 else if (e.level == PaletteDebugLevel::WARNING)
464 warn_count++;
465 else
466 error_count++;
467 }
468 json << "\"info_count\":" << info_count << ",";
469 json << "\"warning_count\":" << warn_count << ",";
470 json << "\"error_count\":" << error_count;
471
472 json << "}";
473 return json.str();
474}
475
476std::string PaletteDebugger::GetDiagnosticSummary() const {
477 std::ostringstream summary;
478
479 // Count warnings and errors
480 int warnings = 0, errors = 0;
481 bool has_palette_timing_issue = false;
482 bool has_missing_palette = false;
483 bool has_color_mismatch = false;
484
485 for (const auto& e : events_) {
486 if (e.level == PaletteDebugLevel::WARNING)
487 warnings++;
488 if (e.level == PaletteDebugLevel::ERROR)
489 errors++;
490
491 // Detect specific issues
492 if (e.message.find("WITHOUT palette") != std::string::npos) {
493 has_missing_palette = true;
494 }
495 if (e.message.find("only") != std::string::npos &&
496 e.message.find("colors") != std::string::npos) {
497 has_palette_timing_issue = true;
498 }
499 }
500
501 // Check color comparisons for mismatches
502 for (const auto& c : comparisons_) {
503 if (!c.matches) {
504 has_color_mismatch = true;
505 break;
506 }
507 }
508
509 summary << "Events: " << events_.size() << ", Warnings: " << warnings
510 << ", Errors: " << errors << ". ";
511
512 if (has_missing_palette) {
513 summary << "ISSUE: Texture created without palette. ";
514 }
515 if (has_palette_timing_issue) {
516 summary << "ISSUE: Palette may not be fully loaded. ";
517 }
518 if (has_color_mismatch) {
519 summary << "ISSUE: Color mismatch detected between expected and actual. ";
520 }
521 if (!has_missing_palette && !has_palette_timing_issue &&
522 !has_color_mismatch) {
523 summary << "No obvious issues detected. ";
524 }
525
526 return summary.str();
527}
528
529std::string PaletteDebugger::GetHypothesisAnalysis() const {
530 std::ostringstream analysis;
531
532 // Analyze events for the timing hypothesis (75% confidence from plan)
533 bool texture_before_palette = false;
534 int last_palette_load_seq = -1;
535 int first_texture_create_seq = INT_MAX;
536
537 for (const auto& e : events_) {
538 if (e.message.find("Loaded palette") != std::string::npos) {
539 last_palette_load_seq =
540 std::max(last_palette_load_seq, e.sequence_number);
541 }
542 if (e.message.find("Creating texture") != std::string::npos) {
543 first_texture_create_seq =
544 std::min(first_texture_create_seq, e.sequence_number);
545 }
546 }
547
548 if (first_texture_create_seq < last_palette_load_seq) {
549 texture_before_palette = true;
550 analysis << "TIMING HYPOTHESIS CONFIRMED: Texture created (seq "
551 << first_texture_create_seq << ") before palette loaded (seq "
552 << last_palette_load_seq << "). ";
553 analysis << "FIX: Ensure palette is applied to surface BEFORE queuing "
554 "texture creation. ";
555 } else if (last_palette_load_seq >= 0 && first_texture_create_seq < INT_MAX) {
556 analysis << "Timing appears correct: palette loaded (seq "
557 << last_palette_load_seq << ") before texture created (seq "
558 << first_texture_create_seq << "). ";
559 }
560
561 // Check for palette group selection hypothesis (60% confidence)
562 bool wrong_palette_group = false;
563 for (const auto& e : events_) {
564 if (e.color_count > 0 && e.color_count < 90) {
565 wrong_palette_group = true;
566 analysis << "PALETTE GROUP ISSUE: Only " << e.color_count
567 << " colors (expected 90). Check palette group selection. ";
568 break;
569 }
570 }
571
572 // Check for surface format mismatch (40% confidence)
573 for (const auto& e : events_) {
574 if (e.message.find("NOT indexed") != std::string::npos) {
575 analysis << "FORMAT MISMATCH: Surface is not indexed, palette ignored. ";
576 break;
577 }
578 }
579
580 if (analysis.str().empty()) {
581 analysis << "No hypothesis conditions detected. Render pipeline may be "
582 "correct. Check actual colors vs expected in comparisons. ";
583 }
584
585 return analysis.str();
586}
587#endif
588
589} // namespace yaze::zelda3
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:69
const uint8_t * data() const
Definition bitmap.h:400
auto size() const
Definition bitmap.h:399
int height() const
Definition bitmap.h:397
int width() const
Definition bitmap.h:396
SDL_Surface * surface() const
Definition bitmap.h:402
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 SetCurrentPresentation(gfx::Bitmap *bitmap, const gfx::SnesPalette &palette, const std::vector< SDL_Color > &render_palette)
void LogSurfaceState(const std::string &location, SDL_Surface *surface)
void ClearCurrentBitmapIf(const gfx::Bitmap *bitmap)
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:110
#define LOG_WARN(category, format,...)
Definition log.h:108
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