11#include "absl/strings/str_format.h"
16 return std::chrono::duration_cast<std::chrono::milliseconds>(
17 std::chrono::steady_clock::now().time_since_epoch())
34 event.palette_id = palette_id;
35 event.color_count = palette.
size();
37 event.timestamp_ms = GetCurrentTimeMs();
41 for (
size_t i = 0; i < std::min(
size_t(3), palette.
size()); i++) {
42 auto rgb = palette[i].rgb();
43 event.sample_colors.push_back(
static_cast<uint8_t
>(rgb.x));
44 event.sample_colors.push_back(
static_cast<uint8_t
>(rgb.y));
45 event.sample_colors.push_back(
static_cast<uint8_t
>(rgb.z));
48 std::string sample_str;
50 sample_str = absl::StrFormat(
"[Sample: R=%d G=%d B=%d]",
55 event.message = absl::StrFormat(
"Loaded palette %d with %d colors %s",
63 int palette_id,
bool success,
64 const std::string& reason) {
67 event.palette_id = palette_id;
69 event.timestamp_ms = GetCurrentTimeMs();
72 success ? absl::StrFormat(
"Applied palette %d successfully", palette_id)
73 : absl::StrFormat(
"Failed to apply palette %d: %s", palette_id,
85 bool has_palette,
int color_count) {
88 event.color_count = color_count;
91 event.timestamp_ms = GetCurrentTimeMs();
95 ? absl::StrFormat(
"Creating texture with %d-color palette",
97 :
"WARNING: Creating texture WITHOUT palette - will use default "
107 SDL_Surface* surface) {
110 event.timestamp_ms = GetCurrentTimeMs();
115 event.message =
"Surface is NULL!";
122 if (fmt == SDL_PIXELFORMAT_UNKNOWN) {
124 event.message =
"Surface format is unknown!";
130 bool is_indexed = (fmt == SDL_PIXELFORMAT_INDEX8);
132 bool has_palette = (palette !=
nullptr);
133 int ncolors = has_palette ? palette->ncolors : 0;
135 event.color_count = ncolors;
139 event.message = absl::StrFormat(
140 "Surface is NOT indexed (format=0x%08X). Palette will be ignored!",
142 }
else if (!has_palette) {
144 event.message =
"Surface is indexed but has NO palette attached!";
145 }
else if (ncolors < 90) {
147 event.message = absl::StrFormat(
148 "Surface has palette with only %d colors (expected 90)", ncolors);
151 event.message = absl::StrFormat(
152 "Surface OK: indexed format, %d-color palette attached", ncolors);
155 if (palette && ncolors > 56) {
156 auto& c = palette->colors[56];
157 event.sample_colors = {c.r, c.g, c.b};
158 event.message += absl::StrFormat(
" [Color56: R=%d G=%d B=%d]", c.r, c.g,
192 if (x < 0 || x >= width || y < 0 || y >= height) {
199 if (!data || data_size == 0) {
203 size_t idx =
static_cast<size_t>(y * width + x);
204 if (idx >= data_size) {
213 comp.
expected_r =
static_cast<uint8_t
>(rgb.x);
214 comp.
expected_g =
static_cast<uint8_t
>(rgb.y);
215 comp.
expected_b =
static_cast<uint8_t
>(rgb.z);
238 for (
size_t i = 0; i < palette.
size(); i++) {
239 auto rgb = palette[i].rgb();
240 sum +=
static_cast<uint32_t
>(rgb.x) * (i + 1);
241 sum +=
static_cast<uint32_t
>(rgb.y) * (i + 1) * 256;
242 sum +=
static_cast<uint32_t
>(rgb.z) * (i + 1) * 65536;
248std::string PaletteDebugger::ExportToJSON()
const {
249 std::ostringstream
json;
251 for (
size_t i = 0; i <
events_.size(); i++) {
254 json <<
"\"location\":\"" << e.location <<
"\",";
255 json <<
"\"message\":\"" << e.message <<
"\",";
256 json <<
"\"level\":\""
261 json <<
"\"palette_id\":" << e.palette_id <<
",";
262 json <<
"\"color_count\":" << e.color_count;
265 if (!e.sample_colors.empty() && e.sample_colors.size() >= 3) {
266 json <<
",\"sample_rgb\":[" << (int)e.sample_colors[0] <<
","
267 << (
int)e.sample_colors[1] <<
"," << (int)e.sample_colors[2] <<
"]";
277std::string PaletteDebugger::ExportColorComparisonsJSON()
const {
278 std::ostringstream
json;
283 json <<
"\"x\":" << c.x <<
",\"y\":" << c.y <<
",";
284 json <<
"\"palette_index\":" << (int)c.palette_index <<
",";
285 json <<
"\"actual\":[" << (int)c.actual_r <<
"," << (
int)c.actual_g <<
","
286 << (int)c.actual_b <<
"],";
287 json <<
"\"expected\":[" << (int)c.expected_r <<
"," << (
int)c.expected_g
288 <<
"," << (int)c.expected_b <<
"],";
289 json <<
"\"matches\":" << (c.matches ?
"true" :
"false");
297std::string PaletteDebugger::SamplePixelJSON(
int x,
int y)
const {
299 std::ostringstream
json;
301 json <<
"\"x\":" << comp.x <<
",\"y\":" << comp.y <<
",";
302 json <<
"\"palette_index\":" << (int)comp.palette_index <<
",";
303 json <<
"\"actual\":[" << (int)comp.actual_r <<
"," << (
int)comp.actual_g
304 <<
"," << (int)comp.actual_b <<
"],";
305 json <<
"\"expected\":[" << (int)comp.expected_r <<
","
306 << (
int)comp.expected_g <<
"," << (int)comp.expected_b <<
"],";
307 json <<
"\"matches\":" << (comp.matches ?
"true" :
"false");
312std::string PaletteDebugger::ExportFullStateJSON()
const {
313 std::ostringstream
json;
317 json <<
"\"events\":" << ExportToJSON() <<
",";
320 json <<
"\"comparisons\":" << ExportColorComparisonsJSON() <<
",";
323 json <<
"\"palette\":" << ExportPaletteDataJSON() <<
",";
326 json <<
"\"timeline\":" << ExportTimelineJSON() <<
",";
329 json <<
"\"diagnostic\":\"" << GetDiagnosticSummary() <<
"\",";
332 json <<
"\"hypothesis\":\"" << GetHypothesisAnalysis() <<
"\"";
338std::string PaletteDebugger::ExportPaletteDataJSON()
const {
339 std::ostringstream
json;
343 json <<
"\"colors\":[";
347 json <<
"{\"index\":" << i <<
",\"r\":" << (int)rgb.x <<
",\"g\":"
348 << (
int)rgb.y <<
",\"b\":" << (int)rgb.z <<
"}";
356std::string PaletteDebugger::ExportTimelineJSON()
const {
357 std::ostringstream
json;
361 uint64_t first_ts = 0, last_ts = 0;
363 first_ts =
events_[0].timestamp_ms;
367 json <<
"\"start_ms\":" << first_ts <<
",";
368 json <<
"\"end_ms\":" << last_ts <<
",";
369 json <<
"\"duration_ms\":" << (last_ts - first_ts) <<
",";
370 json <<
"\"event_count\":" <<
events_.size() <<
",";
373 json <<
"\"by_location\":{";
374 std::map<std::string, int> location_counts;
375 for (
const auto& e :
events_) {
376 location_counts[e.location]++;
379 for (
const auto& [loc, count] : location_counts) {
380 if (!first)
json <<
",";
381 json <<
"\"" << loc <<
"\":" << count;
387 int info_count = 0, warn_count = 0, error_count = 0;
388 for (
const auto& e :
events_) {
396 json <<
"\"info_count\":" << info_count <<
",";
397 json <<
"\"warning_count\":" << warn_count <<
",";
398 json <<
"\"error_count\":" << error_count;
404std::string PaletteDebugger::GetDiagnosticSummary()
const {
405 std::ostringstream summary;
408 int warnings = 0, errors = 0;
409 bool has_palette_timing_issue =
false;
410 bool has_missing_palette =
false;
411 bool has_color_mismatch =
false;
413 for (
const auto& e :
events_) {
418 if (e.message.find(
"WITHOUT palette") != std::string::npos) {
419 has_missing_palette =
true;
421 if (e.message.find(
"only") != std::string::npos &&
422 e.message.find(
"colors") != std::string::npos) {
423 has_palette_timing_issue =
true;
430 has_color_mismatch =
true;
435 summary <<
"Events: " <<
events_.size() <<
", Warnings: " << warnings
436 <<
", Errors: " << errors <<
". ";
438 if (has_missing_palette) {
439 summary <<
"ISSUE: Texture created without palette. ";
441 if (has_palette_timing_issue) {
442 summary <<
"ISSUE: Palette may not be fully loaded. ";
444 if (has_color_mismatch) {
445 summary <<
"ISSUE: Color mismatch detected between expected and actual. ";
447 if (!has_missing_palette && !has_palette_timing_issue && !has_color_mismatch) {
448 summary <<
"No obvious issues detected. ";
451 return summary.str();
454std::string PaletteDebugger::GetHypothesisAnalysis()
const {
455 std::ostringstream analysis;
458 bool texture_before_palette =
false;
459 int last_palette_load_seq = -1;
460 int first_texture_create_seq = INT_MAX;
462 for (
const auto& e :
events_) {
463 if (e.message.find(
"Loaded palette") != std::string::npos) {
464 last_palette_load_seq =
465 std::max(last_palette_load_seq, e.sequence_number);
467 if (e.message.find(
"Creating texture") != std::string::npos) {
468 first_texture_create_seq =
469 std::min(first_texture_create_seq, e.sequence_number);
473 if (first_texture_create_seq < last_palette_load_seq) {
474 texture_before_palette =
true;
475 analysis <<
"TIMING HYPOTHESIS CONFIRMED: Texture created (seq "
476 << first_texture_create_seq <<
") before palette loaded (seq "
477 << last_palette_load_seq <<
"). ";
478 analysis <<
"FIX: Ensure palette is applied to surface BEFORE queuing "
479 "texture creation. ";
480 }
else if (last_palette_load_seq >= 0 && first_texture_create_seq < INT_MAX) {
481 analysis <<
"Timing appears correct: palette loaded (seq "
482 << last_palette_load_seq <<
") before texture created (seq "
483 << first_texture_create_seq <<
"). ";
487 bool wrong_palette_group =
false;
488 for (
const auto& e :
events_) {
489 if (e.color_count > 0 && e.color_count < 90) {
490 wrong_palette_group =
true;
491 analysis <<
"PALETTE GROUP ISSUE: Only " << e.color_count
492 <<
" colors (expected 90). Check palette group selection. ";
498 for (
const auto& e :
events_) {
499 if (e.message.find(
"NOT indexed") != std::string::npos) {
500 analysis <<
"FORMAT MISMATCH: Surface is not indexed, palette ignored. ";
505 if (analysis.str().empty()) {
506 analysis <<
"No hypothesis conditions detected. Render pipeline may be "
507 "correct. Check actual colors vs expected in comparisons. ";
510 return analysis.str();
Represents a bitmap image optimized for SNES ROM hacking.
const uint8_t * data() const
SDL_Surface * surface() const
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)
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="")
gfx::Bitmap * current_bitmap_
void SetCurrentPalette(const gfx::SnesPalette &palette)
std::vector< PaletteDebugEvent > events_
std::vector< ColorComparison > comparisons_
void LogSurfaceState(const std::string &location, SDL_Surface *surface)
ColorComparison SamplePixelAt(int x, int y) const
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
uint64_t GetCurrentTimeMs()
Zelda 3 specific classes and functions.
SDL2/SDL3 compatibility layer.
std::vector< uint8_t > sample_colors