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 = absl::StrFormat("[Sample: R=%d G=%d B=%d]",
51 event.sample_colors[0], event.sample_colors[1],
52 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 += absl::StrFormat(" [Color56: R=%d G=%d B=%d]", c.r, c.g,
159 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 = (comp.actual_r == comp.expected_r &&
229 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) json << ",";
272 }
273 json << "]";
274 return json.str();
275}
276
277std::string PaletteDebugger::ExportColorComparisonsJSON() const {
278 std::ostringstream json;
279 json << "[";
280 for (size_t i = 0; i < comparisons_.size(); i++) {
281 const auto& c = comparisons_[i];
282 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");
290 json << "}";
291 if (i < comparisons_.size() - 1) json << ",";
292 }
293 json << "]";
294 return json.str();
295}
296
297std::string PaletteDebugger::SamplePixelJSON(int x, int y) const {
298 auto comp = SamplePixelAt(x, y);
299 std::ostringstream json;
300 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");
308 json << "}";
309 return json.str();
310}
311
312std::string PaletteDebugger::ExportFullStateJSON() const {
313 std::ostringstream json;
314 json << "{";
315
316 // Events timeline
317 json << "\"events\":" << ExportToJSON() << ",";
318
319 // Color comparisons
320 json << "\"comparisons\":" << ExportColorComparisonsJSON() << ",";
321
322 // Current palette data
323 json << "\"palette\":" << ExportPaletteDataJSON() << ",";
324
325 // Timeline analysis
326 json << "\"timeline\":" << ExportTimelineJSON() << ",";
327
328 // Diagnostic summary
329 json << "\"diagnostic\":\"" << GetDiagnosticSummary() << "\",";
330
331 // Hypothesis analysis
332 json << "\"hypothesis\":\"" << GetHypothesisAnalysis() << "\"";
333
334 json << "}";
335 return json.str();
336}
337
338std::string PaletteDebugger::ExportPaletteDataJSON() const {
339 std::ostringstream json;
340 json << "{";
341 json << "\"size\":" << current_palette_.size() << ",";
342 json << "\"checksum\":" << ComputePaletteChecksum(current_palette_) << ",";
343 json << "\"colors\":[";
344
345 for (size_t i = 0; i < current_palette_.size(); i++) {
346 auto rgb = current_palette_[i].rgb();
347 json << "{\"index\":" << i << ",\"r\":" << (int)rgb.x << ",\"g\":"
348 << (int)rgb.y << ",\"b\":" << (int)rgb.z << "}";
349 if (i < current_palette_.size() - 1) json << ",";
350 }
351
352 json << "]}";
353 return json.str();
354}
355
356std::string PaletteDebugger::ExportTimelineJSON() const {
357 std::ostringstream json;
358 json << "{";
359
360 // Find first and last timestamps
361 uint64_t first_ts = 0, last_ts = 0;
362 if (!events_.empty()) {
363 first_ts = events_[0].timestamp_ms;
364 last_ts = events_[events_.size() - 1].timestamp_ms;
365 }
366
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() << ",";
371
372 // Group events by location
373 json << "\"by_location\":{";
374 std::map<std::string, int> location_counts;
375 for (const auto& e : events_) {
376 location_counts[e.location]++;
377 }
378 bool first = true;
379 for (const auto& [loc, count] : location_counts) {
380 if (!first) json << ",";
381 json << "\"" << loc << "\":" << count;
382 first = false;
383 }
384 json << "},";
385
386 // Count by level
387 int info_count = 0, warn_count = 0, error_count = 0;
388 for (const auto& e : events_) {
389 if (e.level == PaletteDebugLevel::INFO)
390 info_count++;
391 else if (e.level == PaletteDebugLevel::WARNING)
392 warn_count++;
393 else
394 error_count++;
395 }
396 json << "\"info_count\":" << info_count << ",";
397 json << "\"warning_count\":" << warn_count << ",";
398 json << "\"error_count\":" << error_count;
399
400 json << "}";
401 return json.str();
402}
403
404std::string PaletteDebugger::GetDiagnosticSummary() const {
405 std::ostringstream summary;
406
407 // Count warnings and errors
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;
412
413 for (const auto& e : events_) {
414 if (e.level == PaletteDebugLevel::WARNING) warnings++;
415 if (e.level == PaletteDebugLevel::ERROR) errors++;
416
417 // Detect specific issues
418 if (e.message.find("WITHOUT palette") != std::string::npos) {
419 has_missing_palette = true;
420 }
421 if (e.message.find("only") != std::string::npos &&
422 e.message.find("colors") != std::string::npos) {
423 has_palette_timing_issue = true;
424 }
425 }
426
427 // Check color comparisons for mismatches
428 for (const auto& c : comparisons_) {
429 if (!c.matches) {
430 has_color_mismatch = true;
431 break;
432 }
433 }
434
435 summary << "Events: " << events_.size() << ", Warnings: " << warnings
436 << ", Errors: " << errors << ". ";
437
438 if (has_missing_palette) {
439 summary << "ISSUE: Texture created without palette. ";
440 }
441 if (has_palette_timing_issue) {
442 summary << "ISSUE: Palette may not be fully loaded. ";
443 }
444 if (has_color_mismatch) {
445 summary << "ISSUE: Color mismatch detected between expected and actual. ";
446 }
447 if (!has_missing_palette && !has_palette_timing_issue && !has_color_mismatch) {
448 summary << "No obvious issues detected. ";
449 }
450
451 return summary.str();
452}
453
454std::string PaletteDebugger::GetHypothesisAnalysis() const {
455 std::ostringstream analysis;
456
457 // Analyze events for the timing hypothesis (75% confidence from plan)
458 bool texture_before_palette = false;
459 int last_palette_load_seq = -1;
460 int first_texture_create_seq = INT_MAX;
461
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);
466 }
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);
470 }
471 }
472
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 << "). ";
484 }
485
486 // Check for palette group selection hypothesis (60% confidence)
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. ";
493 break;
494 }
495 }
496
497 // Check for surface format mismatch (40% confidence)
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. ";
501 break;
502 }
503 }
504
505 if (analysis.str().empty()) {
506 analysis << "No hypothesis conditions detected. Render pipeline may be "
507 "correct. Check actual colors vs expected in comparisons. ";
508 }
509
510 return analysis.str();
511}
512#endif
513
514} // 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