{"id":53,"date":"2026-06-17T17:39:36","date_gmt":"2026-06-17T17:39:36","guid":{"rendered":"https:\/\/blogs.scummvm.org\/ramyak\/?p=53"},"modified":"2026-06-17T17:39:57","modified_gmt":"2026-06-17T17:39:57","slug":"read-the-error","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/ramyak\/2026\/06\/17\/read-the-error\/","title":{"rendered":"Read the Error"},"content":{"rendered":"<p><strong>Wednesday<\/strong><\/p>\n<p>This is the follow-up to the previous post.<\/p>\n<p><strong>The Blunder<\/strong><\/p>\n<p>The first task was integrating ImageDiff into the buildbot repo.<\/p>\n<p>The first PR was wrong immediately. I added the files directly without bringing in the git history from the original ImageDiff repo. Sev had asked for history. The PR wasn&#8217;t mergeable, and sev fixed the git situation himself rather than have me fight with it. First mistake.<\/p>\n<p>Then the actual blunder. When wiring ImageDiff into the buildbot&#8217;s Python pipeline, I hit an import error imagediff.py does from config import SCREENSHOTS_DIR at module level, which breaks when imported from a different directory. Instead of reading the error and fixing it, I asked an AI, got an importlib workaround, and pushed it.<\/p>\n<p>Sev said in the chat, &#8220;please don&#8217;t do that anymore, asking AI and pushing the slop I mean.&#8221;<\/p>\n<p>The correct fix was a one-line sys.path insert, something I&#8217;d have found in two minutes if I&#8217;d just <strong>read<\/strong> what <strong>the error<\/strong> was saying.<\/p>\n<p><strong>Deployment<\/strong><\/p>\n<p>After the ImageDiff PR merged, I was hesitant to deploy directly on the buildbot server as I didn&#8217;t want to break anything.<\/p>\n<p>In response I was given the green light to break it, because we already have a VM snapshot saved.<\/p>\n<p>Deployed, it worked, buildbot was up with ImageDiff integrated.<\/p>\n<p><strong>The Misunderstanding That Cost the Most Time<\/strong><\/p>\n<p>Once it was running, sev noticed the tool was timing out on target: theapartment-mac, he found it and pointed toward the cache logic.<\/p>\n<p>That was a part of it. The cache logic was kind of flawed. But we had a bigger elephant in the room.<\/p>\n<p>The real problem was something I hadn&#8217;t understood about how the tool was supposed to work at all.<\/p>\n<p>The <code>movie_diff()<\/code> function was opening every PNG frame from both builds with PIL, running <code>ImageChops.difference()<\/code> on each pair, and checking\u00a0to detect any pixel difference.<\/p>\n<p>But it was completely wrong. The Director engine already does the pixel comparison during playback, in score.cpp. It compares each new frame against the stored reference using a pixel difference threshold, and only saves the file to disk if the difference exceeds that threshold.<\/p>\n<p>So, file presence is the diff signal.<\/p>\n<p>The fix was replacing all the PIL logic with: does any file matching {movie}-*.png exist in the comparison build&#8217;s directory?<\/p>\n<p>I didn&#8217;t know the engine had this mechanism. PIL was redundant work the engine had already done.<\/p>\n<p><strong>The Punycode Crash<\/strong><\/p>\n<p>After the performance fix, a new crash appeared:<\/p>\n<p><code>ValueError: chr() arg not in range(0x110000)<\/code><\/p>\n<p>The movie name causing it was <code>xn--xn--File IO-oa82b-<\/code>. Sev explained why this exists.<\/p>\n<p>Mac HFS allowed file names with characters that would be illegal on any normal filesystem\u00a0 \/, *, newlines, etc. &#8220;The Apartment&#8221; has movies named things like <code>File I\/O and \u2022Main Menu<\/code>.<\/p>\n<p>To store these on a normal filesystem, sev designed an encoding scheme based on Punycode: files with special characters get an xn-- prefix, the special characters are removed from the name, and their positions are encoded in the tail.<\/p>\n<p><code>xn--xn--File IO-oa82b-<\/code> is doubly encoded, it went through the encoder twice, which is valid, it just needs two decode passes to get back to File I\/O.<\/p>\n<p>The bug was in decode_string(). There was a loop that stripped trailing dashes from the string before handing it to the punycode decoder:<\/p>\n<p><code>i = len(orig) - 1<br \/>\nwhile i &gt;= 0 and orig[i] == \"-\":<br \/>\ni -= 1<br \/>\norig = orig[:i+1]<\/code><\/p>\n<p>For <code>xn--xn--File IO-oa82b-<\/code>, this strips the trailing <code>-<\/code> and corrupts the input before the decoder even runs. Removing it was the entire fix. Without the loop, Python&#8217;s punycode decoder handles the string correctly.<\/p>\n<p><strong>Director Debugger<\/strong><\/p>\n<p>Three things on the dt-new branch:<\/p>\n<p>Cast Details Panel: was showing <code>...<\/code> for almost everything. Now shows : script text previews on hover with click-through. The old showScriptCasts pipeline was removed entirely everything now goes through renderScript.<\/p>\n<p>Scripts Window: decoupled from the execution context, which previously shared state with it. The scripts window now has its own handler list and browser-style back\/forward navigation.<\/p>\n<p>Also fixed a bug where the Lingo\/Bytecode toggle was a single global flag shared across all open handlers.<\/p>\n<p>Keyboard Shortcuts: Cmd+2\/3\/4 toggle Control Panel, Cast, Score. On Mac, cmd instead of ctrl must be used.<\/p>\n<p><b>PRs<\/b><\/p>\n<p>scummvm-sites <a href=\"https:\/\/github.com\/scummvm\/scummvm-sites\/pull\/39\">#39<\/a> \u00b7 <a href=\"https:\/\/github.com\/scummvm\/scummvm-sites\/pull\/40\">#40<\/a> \u00b7 <a href=\"https:\/\/github.com\/scummvm\/scummvm-sites\/pull\/41\">#41<\/a> \u00b7 scummvm <a href=\"https:\/\/github.com\/scummvm\/scummvm\/pull\/7577\">#7577<\/a><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Wednesday This is the follow-up to the previous post. The Blunder The first task was integrating ImageDiff into the buildbot repo. The first PR was wrong immediately. I added the files directly without bringing in the git history from the original ImageDiff repo. Sev had asked for history. The PR wasn&#8217;t mergeable, and sev fixed [&hellip;]<\/p>\n","protected":false},"author":32,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-53","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/ramyak\/wp-json\/wp\/v2\/posts\/53","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/ramyak\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/ramyak\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/ramyak\/wp-json\/wp\/v2\/users\/32"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/ramyak\/wp-json\/wp\/v2\/comments?post=53"}],"version-history":[{"count":13,"href":"https:\/\/blogs.scummvm.org\/ramyak\/wp-json\/wp\/v2\/posts\/53\/revisions"}],"predecessor-version":[{"id":66,"href":"https:\/\/blogs.scummvm.org\/ramyak\/wp-json\/wp\/v2\/posts\/53\/revisions\/66"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/ramyak\/wp-json\/wp\/v2\/media?parent=53"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/ramyak\/wp-json\/wp\/v2\/categories?post=53"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/ramyak\/wp-json\/wp\/v2\/tags?post=53"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}