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;
51 absl::StrFormat(
"[Sample: R=%d G=%d B=%d]", event.
sample_colors[0],
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};
159 absl::StrFormat(
" [Color56: R=%d G=%d B=%d]", c.r, c.g, c.b);
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] <<
"]";
278std::string PaletteDebugger::ExportColorComparisonsJSON()
const {
279 std::ostringstream
json;
284 json <<
"\"x\":" << c.x <<
",\"y\":" << c.y <<
",";
285 json <<
"\"palette_index\":" << (int)c.palette_index <<
",";
286 json <<
"\"actual\":[" << (int)c.actual_r <<
"," << (
int)c.actual_g <<
","
287 << (int)c.actual_b <<
"],";
288 json <<
"\"expected\":[" << (int)c.expected_r <<
"," << (
int)c.expected_g
289 <<
"," << (int)c.expected_b <<
"],";
290 json <<
"\"matches\":" << (c.matches ?
"true" :
"false");
299std::string PaletteDebugger::SamplePixelJSON(
int x,
int y)
const {
301 std::ostringstream
json;
303 json <<
"\"x\":" << comp.x <<
",\"y\":" << comp.y <<
",";
304 json <<
"\"palette_index\":" << (int)comp.palette_index <<
",";
305 json <<
"\"actual\":[" << (int)comp.actual_r <<
"," << (
int)comp.actual_g
306 <<
"," << (int)comp.actual_b <<
"],";
307 json <<
"\"expected\":[" << (int)comp.expected_r <<
","
308 << (
int)comp.expected_g <<
"," << (int)comp.expected_b <<
"],";
309 json <<
"\"matches\":" << (comp.matches ?
"true" :
"false");
314std::string PaletteDebugger::ExportFullStateJSON()
const {
315 std::ostringstream
json;
319 json <<
"\"events\":" << ExportToJSON() <<
",";
322 json <<
"\"comparisons\":" << ExportColorComparisonsJSON() <<
",";
325 json <<
"\"palette\":" << ExportPaletteDataJSON() <<
",";
328 json <<
"\"timeline\":" << ExportTimelineJSON() <<
",";
331 json <<
"\"diagnostic\":\"" << GetDiagnosticSummary() <<
"\",";
334 json <<
"\"hypothesis\":\"" << GetHypothesisAnalysis() <<
"\"";
340std::string PaletteDebugger::ExportPaletteDataJSON()
const {
341 std::ostringstream
json;
345 json <<
"\"colors\":[";
349 json <<
"{\"index\":" << i <<
",\"r\":" << (int)rgb.x
350 <<
",\"g\":" << (
int)rgb.y <<
",\"b\":" << (int)rgb.z <<
"}";
359std::string PaletteDebugger::ExportTimelineJSON()
const {
360 std::ostringstream
json;
364 uint64_t first_ts = 0, last_ts = 0;
366 first_ts =
events_[0].timestamp_ms;
370 json <<
"\"start_ms\":" << first_ts <<
",";
371 json <<
"\"end_ms\":" << last_ts <<
",";
372 json <<
"\"duration_ms\":" << (last_ts - first_ts) <<
",";
373 json <<
"\"event_count\":" <<
events_.size() <<
",";
376 json <<
"\"by_location\":{";
377 std::map<std::string, int> location_counts;
378 for (
const auto& e :
events_) {
379 location_counts[e.location]++;
382 for (
const auto& [loc, count] : location_counts) {
385 json <<
"\"" << loc <<
"\":" << count;
391 int info_count = 0, warn_count = 0, error_count = 0;
392 for (
const auto& e :
events_) {
400 json <<
"\"info_count\":" << info_count <<
",";
401 json <<
"\"warning_count\":" << warn_count <<
",";
402 json <<
"\"error_count\":" << error_count;
408std::string PaletteDebugger::GetDiagnosticSummary()
const {
409 std::ostringstream summary;
412 int warnings = 0, errors = 0;
413 bool has_palette_timing_issue =
false;
414 bool has_missing_palette =
false;
415 bool has_color_mismatch =
false;
417 for (
const auto& e :
events_) {
424 if (e.message.find(
"WITHOUT palette") != std::string::npos) {
425 has_missing_palette =
true;
427 if (e.message.find(
"only") != std::string::npos &&
428 e.message.find(
"colors") != std::string::npos) {
429 has_palette_timing_issue =
true;
436 has_color_mismatch =
true;
441 summary <<
"Events: " <<
events_.size() <<
", Warnings: " << warnings
442 <<
", Errors: " << errors <<
". ";
444 if (has_missing_palette) {
445 summary <<
"ISSUE: Texture created without palette. ";
447 if (has_palette_timing_issue) {
448 summary <<
"ISSUE: Palette may not be fully loaded. ";
450 if (has_color_mismatch) {
451 summary <<
"ISSUE: Color mismatch detected between expected and actual. ";
453 if (!has_missing_palette && !has_palette_timing_issue &&
454 !has_color_mismatch) {
455 summary <<
"No obvious issues detected. ";
458 return summary.str();
461std::string PaletteDebugger::GetHypothesisAnalysis()
const {
462 std::ostringstream analysis;
465 bool texture_before_palette =
false;
466 int last_palette_load_seq = -1;
467 int first_texture_create_seq = INT_MAX;
469 for (
const auto& e :
events_) {
470 if (e.message.find(
"Loaded palette") != std::string::npos) {
471 last_palette_load_seq =
472 std::max(last_palette_load_seq, e.sequence_number);
474 if (e.message.find(
"Creating texture") != std::string::npos) {
475 first_texture_create_seq =
476 std::min(first_texture_create_seq, e.sequence_number);
480 if (first_texture_create_seq < last_palette_load_seq) {
481 texture_before_palette =
true;
482 analysis <<
"TIMING HYPOTHESIS CONFIRMED: Texture created (seq "
483 << first_texture_create_seq <<
") before palette loaded (seq "
484 << last_palette_load_seq <<
"). ";
485 analysis <<
"FIX: Ensure palette is applied to surface BEFORE queuing "
486 "texture creation. ";
487 }
else if (last_palette_load_seq >= 0 && first_texture_create_seq < INT_MAX) {
488 analysis <<
"Timing appears correct: palette loaded (seq "
489 << last_palette_load_seq <<
") before texture created (seq "
490 << first_texture_create_seq <<
"). ";
494 bool wrong_palette_group =
false;
495 for (
const auto& e :
events_) {
496 if (e.color_count > 0 && e.color_count < 90) {
497 wrong_palette_group =
true;
498 analysis <<
"PALETTE GROUP ISSUE: Only " << e.color_count
499 <<
" colors (expected 90). Check palette group selection. ";
505 for (
const auto& e :
events_) {
506 if (e.message.find(
"NOT indexed") != std::string::npos) {
507 analysis <<
"FORMAT MISMATCH: Surface is not indexed, palette ignored. ";
512 if (analysis.str().empty()) {
513 analysis <<
"No hypothesis conditions detected. Render pipeline may be "
514 "correct. Check actual colors vs expected in comparisons. ";
517 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