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} // namespace
21
22namespace yaze::zelda3 {
23
25 static PaletteDebugger instance;
26 return instance;
27}
28
29void PaletteDebugger::LogPaletteLoad(const std::string& location,
30 int palette_id,
31 const gfx::SnesPalette& palette) {
33 event.location = location;
34 event.palette_id = palette_id;
35 event.color_count = palette.size();
36 event.level = PaletteDebugLevel::INFO;
37 event.timestamp_ms = GetCurrentTimeMs();
38 event.sequence_number = sequence_counter_++;
39
40 // Sample first 3 colors
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));
46 }
47
48 std::string sample_str;
49 if (event.sample_colors.size() >= 3) {
50 sample_str =
51 absl::StrFormat("[Sample: R=%d G=%d B=%d]", event.sample_colors[0],
52 event.sample_colors[1], event.sample_colors[2]);
53 }
54
55 event.message = absl::StrFormat("Loaded palette %d with %d colors %s",
56 palette_id, event.color_count, sample_str);
57
58 AddEvent(event);
59 LOG_INFO("PaletteDebug", "%s", event.message);
60}
61
62void PaletteDebugger::LogPaletteApplication(const std::string& location,
63 int palette_id, bool success,
64 const std::string& reason) {
66 event.location = location;
67 event.palette_id = palette_id;
68 event.level = success ? PaletteDebugLevel::INFO : PaletteDebugLevel::ERROR;
69 event.timestamp_ms = GetCurrentTimeMs();
70 event.sequence_number = sequence_counter_++;
71 event.message =
72 success ? absl::StrFormat("Applied palette %d successfully", palette_id)
73 : absl::StrFormat("Failed to apply palette %d: %s", palette_id,
74 reason);
75
76 AddEvent(event);
77 if (success) {
78 LOG_INFO("PaletteDebug", "%s", event.message);
79 } else {
80 LOG_ERROR("PaletteDebug", "%s", event.message);
81 }
82}
83
84void PaletteDebugger::LogTextureCreation(const std::string& location,
85 bool has_palette, int color_count) {
87 event.location = location;
88 event.color_count = color_count;
89 event.level =
91 event.timestamp_ms = GetCurrentTimeMs();
92 event.sequence_number = sequence_counter_++;
93 event.message =
94 has_palette
95 ? absl::StrFormat("Creating texture with %d-color palette",
96 color_count)
97 : "WARNING: Creating texture WITHOUT palette - will use default "
98 "colors!";
99
100 AddEvent(event);
101 if (!has_palette) {
102 LOG_WARN("PaletteDebug", "%s", event.message);
103 }
104}
105
106void PaletteDebugger::LogSurfaceState(const std::string& location,
107 SDL_Surface* surface) {
108 PaletteDebugEvent event;
109 event.location = location;
110 event.timestamp_ms = GetCurrentTimeMs();
111 event.sequence_number = sequence_counter_++;
112
113 if (!surface) {
114 event.level = PaletteDebugLevel::ERROR;
115 event.message = "Surface is NULL!";
116 AddEvent(event);
117 LOG_ERROR("PaletteDebug", "%s", event.message);
118 return;
119 }
120
121 Uint32 fmt = platform::GetSurfaceFormat(surface);
122 if (fmt == SDL_PIXELFORMAT_UNKNOWN) {
123 event.level = PaletteDebugLevel::ERROR;
124 event.message = "Surface format is unknown!";
125 AddEvent(event);
126 LOG_ERROR("PaletteDebug", "%s", event.message);
127 return;
128 }
129
130 bool is_indexed = (fmt == SDL_PIXELFORMAT_INDEX8);
131 SDL_Palette* palette = platform::GetSurfacePalette(surface);
132 bool has_palette = (palette != nullptr);
133 int ncolors = has_palette ? palette->ncolors : 0;
134
135 event.color_count = ncolors;
136
137 if (!is_indexed) {
138 event.level = PaletteDebugLevel::WARNING;
139 event.message = absl::StrFormat(
140 "Surface is NOT indexed (format=0x%08X). Palette will be ignored!",
141 fmt);
142 } else if (!has_palette) {
143 event.level = PaletteDebugLevel::WARNING;
144 event.message = "Surface is indexed but has NO palette attached!";
145 } else if (ncolors < 90) {
146 event.level = PaletteDebugLevel::WARNING;
147 event.message = absl::StrFormat(
148 "Surface has palette with only %d colors (expected 90)", ncolors);
149 } else {
150 event.level = PaletteDebugLevel::INFO;
151 event.message = absl::StrFormat(
152 "Surface OK: indexed format, %d-color palette attached", ncolors);
153
154 // Sample color 56 (palette 7, offset 0) for verification
155 if (palette && ncolors > 56) {
156 auto& c = palette->colors[56];
157 event.sample_colors = {c.r, c.g, c.b};
158 event.message +=
159 absl::StrFormat(" [Color56: R=%d G=%d B=%d]", c.r, c.g, c.b);
160 }
161 }
162
163 AddEvent(event);
164 LOG_INFO("PaletteDebug", "%s: %s", location, event.message);
165}
166
170
174
176 ColorComparison comp;
177 comp.x = x;
178 comp.y = y;
179 comp.palette_index = 0;
180 comp.actual_r = comp.actual_g = comp.actual_b = 0;
181 comp.expected_r = comp.expected_g = comp.expected_b = 0;
182 comp.matches = false;
183
185 return comp;
186 }
187
188 auto* surface = current_bitmap_->surface();
189 int width = current_bitmap_->width();
190 int height = current_bitmap_->height();
191
192 if (x < 0 || x >= width || y < 0 || y >= height) {
193 return comp;
194 }
195
196 // Get palette index from bitmap data
197 const uint8_t* data = current_bitmap_->data();
198 size_t data_size = current_bitmap_->size();
199 if (!data || data_size == 0) {
200 return comp;
201 }
202
203 size_t idx = static_cast<size_t>(y * width + x);
204 if (idx >= data_size) {
205 return comp;
206 }
207
208 comp.palette_index = data[idx];
209
210 // Get expected color from our stored palette
211 if (comp.palette_index < current_palette_.size()) {
212 auto rgb = current_palette_[comp.palette_index].rgb();
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);
216 }
217
218 // Get actual color from SDL surface palette
219 SDL_Palette* palette = platform::GetSurfacePalette(surface);
220 if (palette && comp.palette_index < palette->ncolors) {
221 auto& c = palette->colors[comp.palette_index];
222 comp.actual_r = c.r;
223 comp.actual_g = c.g;
224 comp.actual_b = c.b;
225 }
226
227 // Check if they match
228 comp.matches =
229 (comp.actual_r == comp.expected_r && comp.actual_g == comp.expected_g &&
230 comp.actual_b == comp.expected_b);
231
232 return comp;
233}
234
236 const gfx::SnesPalette& palette) const {
237 uint32_t sum = 0;
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;
243 }
244 return sum;
245}
246
247#ifdef __EMSCRIPTEN__
248std::string PaletteDebugger::ExportToJSON() const {
249 std::ostringstream json;
250 json << "[";
251 for (size_t i = 0; i < events_.size(); i++) {
252 const auto& e = events_[i];
253 json << "{";
254 json << "\"location\":\"" << e.location << "\",";
255 json << "\"message\":\"" << e.message << "\",";
256 json << "\"level\":\""
257 << (e.level == PaletteDebugLevel::ERROR ? "error"
258 : e.level == PaletteDebugLevel::WARNING ? "warning"
259 : "info")
260 << "\",";
261 json << "\"palette_id\":" << e.palette_id << ",";
262 json << "\"color_count\":" << e.color_count;
263
264 // Include sample colors if available
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] << "]";
268 }
269
270 json << "}";
271 if (i < events_.size() - 1)
272 json << ",";
273 }
274 json << "]";
275 return json.str();
276}
277
278std::string PaletteDebugger::ExportColorComparisonsJSON() const {
279 std::ostringstream json;
280 json << "[";
281 for (size_t i = 0; i < comparisons_.size(); i++) {
282 const auto& c = comparisons_[i];
283 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");
291 json << "}";
292 if (i < comparisons_.size() - 1)
293 json << ",";
294 }
295 json << "]";
296 return json.str();
297}
298
299std::string PaletteDebugger::SamplePixelJSON(int x, int y) const {
300 auto comp = SamplePixelAt(x, y);
301 std::ostringstream json;
302 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");
310 json << "}";
311 return json.str();
312}
313
314std::string PaletteDebugger::ExportFullStateJSON() const {
315 std::ostringstream json;
316 json << "{";
317
318 // Events timeline
319 json << "\"events\":" << ExportToJSON() << ",";
320
321 // Color comparisons
322 json << "\"comparisons\":" << ExportColorComparisonsJSON() << ",";
323
324 // Current palette data
325 json << "\"palette\":" << ExportPaletteDataJSON() << ",";
326
327 // Timeline analysis
328 json << "\"timeline\":" << ExportTimelineJSON() << ",";
329
330 // Diagnostic summary
331 json << "\"diagnostic\":\"" << GetDiagnosticSummary() << "\",";
332
333 // Hypothesis analysis
334 json << "\"hypothesis\":\"" << GetHypothesisAnalysis() << "\"";
335
336 json << "}";
337 return json.str();
338}
339
340std::string PaletteDebugger::ExportPaletteDataJSON() const {
341 std::ostringstream json;
342 json << "{";
343 json << "\"size\":" << current_palette_.size() << ",";
344 json << "\"checksum\":" << ComputePaletteChecksum(current_palette_) << ",";
345 json << "\"colors\":[";
346
347 for (size_t i = 0; i < current_palette_.size(); i++) {
348 auto rgb = current_palette_[i].rgb();
349 json << "{\"index\":" << i << ",\"r\":" << (int)rgb.x
350 << ",\"g\":" << (int)rgb.y << ",\"b\":" << (int)rgb.z << "}";
351 if (i < current_palette_.size() - 1)
352 json << ",";
353 }
354
355 json << "]}";
356 return json.str();
357}
358
359std::string PaletteDebugger::ExportTimelineJSON() const {
360 std::ostringstream json;
361 json << "{";
362
363 // Find first and last timestamps
364 uint64_t first_ts = 0, last_ts = 0;
365 if (!events_.empty()) {
366 first_ts = events_[0].timestamp_ms;
367 last_ts = events_[events_.size() - 1].timestamp_ms;
368 }
369
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() << ",";
374
375 // Group events by location
376 json << "\"by_location\":{";
377 std::map<std::string, int> location_counts;
378 for (const auto& e : events_) {
379 location_counts[e.location]++;
380 }
381 bool first = true;
382 for (const auto& [loc, count] : location_counts) {
383 if (!first)
384 json << ",";
385 json << "\"" << loc << "\":" << count;
386 first = false;
387 }
388 json << "},";
389
390 // Count by level
391 int info_count = 0, warn_count = 0, error_count = 0;
392 for (const auto& e : events_) {
393 if (e.level == PaletteDebugLevel::INFO)
394 info_count++;
395 else if (e.level == PaletteDebugLevel::WARNING)
396 warn_count++;
397 else
398 error_count++;
399 }
400 json << "\"info_count\":" << info_count << ",";
401 json << "\"warning_count\":" << warn_count << ",";
402 json << "\"error_count\":" << error_count;
403
404 json << "}";
405 return json.str();
406}
407
408std::string PaletteDebugger::GetDiagnosticSummary() const {
409 std::ostringstream summary;
410
411 // Count warnings and errors
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;
416
417 for (const auto& e : events_) {
418 if (e.level == PaletteDebugLevel::WARNING)
419 warnings++;
420 if (e.level == PaletteDebugLevel::ERROR)
421 errors++;
422
423 // Detect specific issues
424 if (e.message.find("WITHOUT palette") != std::string::npos) {
425 has_missing_palette = true;
426 }
427 if (e.message.find("only") != std::string::npos &&
428 e.message.find("colors") != std::string::npos) {
429 has_palette_timing_issue = true;
430 }
431 }
432
433 // Check color comparisons for mismatches
434 for (const auto& c : comparisons_) {
435 if (!c.matches) {
436 has_color_mismatch = true;
437 break;
438 }
439 }
440
441 summary << "Events: " << events_.size() << ", Warnings: " << warnings
442 << ", Errors: " << errors << ". ";
443
444 if (has_missing_palette) {
445 summary << "ISSUE: Texture created without palette. ";
446 }
447 if (has_palette_timing_issue) {
448 summary << "ISSUE: Palette may not be fully loaded. ";
449 }
450 if (has_color_mismatch) {
451 summary << "ISSUE: Color mismatch detected between expected and actual. ";
452 }
453 if (!has_missing_palette && !has_palette_timing_issue &&
454 !has_color_mismatch) {
455 summary << "No obvious issues detected. ";
456 }
457
458 return summary.str();
459}
460
461std::string PaletteDebugger::GetHypothesisAnalysis() const {
462 std::ostringstream analysis;
463
464 // Analyze events for the timing hypothesis (75% confidence from plan)
465 bool texture_before_palette = false;
466 int last_palette_load_seq = -1;
467 int first_texture_create_seq = INT_MAX;
468
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);
473 }
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);
477 }
478 }
479
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 << "). ";
491 }
492
493 // Check for palette group selection hypothesis (60% confidence)
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. ";
500 break;
501 }
502 }
503
504 // Check for surface format mismatch (40% confidence)
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. ";
508 break;
509 }
510 }
511
512 if (analysis.str().empty()) {
513 analysis << "No hypothesis conditions detected. Render pipeline may be "
514 "correct. Check actual colors vs expected in comparisons. ";
515 }
516
517 return analysis.str();
518}
519#endif
520
521} // namespace yaze::zelda3
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const uint8_t * data() const
Definition bitmap.h:377
auto size() const
Definition bitmap.h:376
int height() const
Definition bitmap.h:374
int width() const
Definition bitmap.h:373
SDL_Surface * surface() const
Definition bitmap.h:379
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="")
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,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
Uint32 GetSurfaceFormat(SDL_Surface *surface)
Get the pixel format of a surface as Uint32.
Definition sdl_compat.h:390
SDL_Palette * GetSurfacePalette(SDL_Surface *surface)
Get the palette attached to a surface.
Definition sdl_compat.h:375
Zelda 3 specific classes and functions.
Definition editor.h:35
nlohmann::json json
SDL2/SDL3 compatibility layer.
std::vector< uint8_t > sample_colors