{"id":57,"date":"2026-06-29T22:22:49","date_gmt":"2026-06-29T22:22:49","guid":{"rendered":"https:\/\/blogs.scummvm.org\/andy\/?p=57"},"modified":"2026-06-29T22:25:19","modified_gmt":"2026-06-29T22:25:19","slug":"week-5","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/andy\/2026\/06\/29\/week-5\/","title":{"rendered":"WEEK 5"},"content":{"rendered":"<h2 style=\"text-align: center\" data-path-to-node=\"4\">The Amiga Comes to Life!<\/h2>\n<p data-path-to-node=\"5\">This week was a massive milestone. The European Amiga version of Kult now runs almost perfectly under ScummVM \u2014 it boots, plays through, and looks exactly the way it did on real hardware. Getting there took twelve intensive commits(for now) that dragged the port all the way from <i data-path-to-node=\"5\" data-index-in-node=\"271\">&#8220;the engine knows the Amiga platform exists&#8221;<\/i> to <i data-path-to-node=\"5\" data-index-in-node=\"319\">&#8220;you can actually sit down, click around, and play it.&#8221;<\/i><\/p>\n<p data-path-to-node=\"6\">The absolute best news from the start: the Amiga build didn&#8217;t require a completely separate engine. It runs on the same chunky, one-byte-per-pixel, 16-colour pipeline as EGA \u2014 the Amiga&#8217;s planar source data just gets converted into it. Almost everything that differs comes down to the palette, the way static data is stored, and a handful of filenames. That single insight shaped my entire week.<\/p>\n<h3 data-path-to-node=\"7\">Sharing the EGA Path<\/h3>\n<p data-path-to-node=\"8\">The first major step was to stop the graphics code from asking <i data-path-to-node=\"8\" data-index-in-node=\"63\">&#8220;is this strictly EGA?&#8221;<\/i> and start asking <i data-path-to-node=\"8\" data-index-in-node=\"104\">&#8220;is this an EGA-like renderer?&#8221;<\/i><\/p>\n<p data-path-to-node=\"9\">I added an <code data-path-to-node=\"9\" data-index-in-node=\"11\">isEgaLikeRenderer()<\/code> helper and routed the sprite, portrait, and transition code through it instead of comparing directly against the EGA render mode. With that simple seam in place, the upcoming Amiga renderer could reuse all of those paths completely unchanged! There was zero behavioural change for EGA or CGA, but we now had a clean foundation to hang the new platform on.<\/p>\n<p data-path-to-node=\"10\">On top of that, I implemented the <code data-path-to-node=\"10\" data-index-in-node=\"34\">AmigaRenderer<\/code> itself: a custom 12-bit palette over the same planar graphics, selected purely by platform. It came with its own cursor decoder for the <code data-path-to-node=\"10\" data-index-in-node=\"184\">SOURI.BIN<\/code> hardware sprites, plus the title screen and its fade-in ramp wired directly into the boot path.<\/p>\n<h3 data-path-to-node=\"11\">Finding the Data<\/h3>\n<p data-path-to-node=\"12\">The Amiga release doesn&#8217;t lay its files out like the DOS versions. Instead of a compressed <code data-path-to-node=\"12\" data-index-in-node=\"91\">PXI<\/code> module, it keeps the static resources uncompressed inside the <code data-path-to-node=\"12\" data-index-in-node=\"157\">KULT<\/code> executable itself. Furthermore, it ships per-language text files \u00a0rather than the generic <code data-path-to-node=\"12\" data-index-in-node=\"303\">...I.BIN<\/code> names.<\/p>\n<p data-path-to-node=\"13\">I added a dedicated <code data-path-to-node=\"13\" data-index-in-node=\"20\">loadAmigaStaticData()<\/code> function and routed the resource loading accordingly, ensuring the engine pulls text and data from the exact right offsets for each language.<\/p>\n<h3 data-path-to-node=\"14\">Colour, At Last<\/h3>\n<p data-path-to-node=\"15\">Early on, the rooms were wearing the wrong palette entirely. The fix was to compose the room palette from the zone&#8217;s <code data-path-to-node=\"15\" data-index-in-node=\"117\">palette_index<\/code> and apply it as soon as the zone loads. This means intro and special screens (which can bypass the usual zone refresh) are no longer left stuck on the previous title palette.<\/p>\n<p data-path-to-node=\"16\">The title screen also had its own bizarre palette bug. It was reading a table at one offset in the <code data-path-to-node=\"16\" data-index-in-node=\"99\">KULT<\/code> executable that turned out to be a brightness fade-lookup ramp, not actual RGB data \u2014 which is exactly why the whole title initially rendered in spooky shades of red! Pointing it at the title&#8217;s actual 16-colour palette brought the gorgeous artwork fully back to life.<\/p>\n<p data-path-to-node=\"16\">\u00a0Before:<\/p>\n<p data-path-to-node=\"16\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-59\" src=\"https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-12-56-300x218.png\" alt=\"\" width=\"300\" height=\"218\" srcset=\"https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-12-56-300x218.png 300w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-12-56-1024x745.png 1024w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-12-56-768x559.png 768w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-12-56-1536x1117.png 1536w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-12-56-2048x1490.png 2048w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-12-56-1200x873.png 1200w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-12-56-1980x1440.png 1980w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p data-path-to-node=\"16\">After:<\/p>\n<p data-path-to-node=\"16\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-60\" src=\"https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-29-14-06-16-300x188.png\" alt=\"\" width=\"300\" height=\"188\" srcset=\"https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-29-14-06-16-300x188.png 300w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-29-14-06-16-1024x640.png 1024w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-29-14-06-16-768x480.png 768w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-29-14-06-16-1536x960.png 1536w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-29-14-06-16-2048x1280.png 2048w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-29-14-06-16-1200x750.png 1200w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-29-14-06-16-1980x1238.png 1980w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<h3 data-path-to-node=\"17\">Decoding the Sprites<\/h3>\n<p data-path-to-node=\"18\">The sprite banks were the trickiest reverse-engineering puzzle of the week. They share the EGA record layout, but with two fun twists:<\/p>\n<ul data-path-to-node=\"19\">\n<li>\n<p data-path-to-node=\"19,0,0\">The record size is stored big-endian.<\/p>\n<\/li>\n<li>\n<p data-path-to-node=\"19,1,0\">The width\/height byte pair is swapped.<\/p>\n<\/li>\n<\/ul>\n<p data-path-to-node=\"20\">The pixels themselves are word-planar \u2014 one big-endian word per four-pixel column. I added <code data-path-to-node=\"20\" data-index-in-node=\"91\">appendFromStreamAmiga()<\/code> to decode them, a shared placeholder for any missing sprites, and successfully loaded the merged Amiga banks (<code data-path-to-node=\"20\" data-index-in-node=\"225\">SPRIT<\/code>\/<code data-path-to-node=\"20\" data-index-in-node=\"231\">PUZZL<\/code>\/<code data-path-to-node=\"20\" data-index-in-node=\"237\">A<\/code>\/<code data-path-to-node=\"20\" data-index-in-node=\"239\">B<\/code>).<\/p>\n<h3 data-path-to-node=\"21\">Scripts and Threads<\/h3>\n<p data-path-to-node=\"22\">Two subtler bugs came next:<\/p>\n<ul data-path-to-node=\"23\">\n<li>\n<p data-path-to-node=\"23,0,0\"><b data-path-to-node=\"23,0,0\" data-index-in-node=\"0\">The Padding Bug:<\/b> The Amiga script blob inserts <code data-path-to-node=\"23,0,0\" data-index-in-node=\"47\">0x00<\/code>\/<code data-path-to-node=\"23,0,0\" data-index-in-node=\"52\">0xAA<\/code> padding for word alignment right in the middle of instructions. The engine didn&#8217;t expect this and was aborting scripts early (for instance, the Twins&#8217; serpent-jaw redraw would just randomly stop). Instructing the engine to skip that padding fixed it instantly.<\/p>\n<\/li>\n<li>\n<p data-path-to-node=\"23,1,0\"><b data-path-to-node=\"23,1,0\" data-index-in-node=\"0\">The Racing Thread:<\/b> I also had to explicitly skip <code data-path-to-node=\"23,1,0\" data-index-in-node=\"49\">animateGauss<\/code> on Amiga, because it runs on the timer thread and blitting from there directly races the main render thread.<\/p>\n<\/li>\n<\/ul>\n<h3 data-path-to-node=\"24\">The Ending and Final Polish<\/h3>\n<p data-path-to-node=\"25\">The endgame sequence needed several specific fixes to shine:<\/p>\n<ul data-path-to-node=\"26\">\n<li>\n<p data-path-to-node=\"26,0,0\"><b data-path-to-node=\"26,0,0\" data-index-in-node=\"0\">Saucer Animation:<\/b> The vblank flush originally only pushed the chunky screen buffer for EGA, meaning the in-place saucer take-off animation never reached the Amiga screen. Now, it correctly flushes for <i data-path-to-node=\"26,0,0\" data-index-in-node=\"201\">any<\/i> EGA-like renderer.<\/p>\n<\/li>\n<li>\n<p data-path-to-node=\"26,1,0\"><b data-path-to-node=\"26,1,0\" data-index-in-node=\"0\">End Screen Noise:<\/b> The engine was trying to decode the planar Amiga <code data-path-to-node=\"26,1,0\" data-index-in-node=\"67\">PRES.BIN<\/code> with the CGA splash loader, turning the ending into a band of static noise. Loading it properly and restoring the title palette fixed the climax.<\/p>\n<\/li>\n<li>\n<p data-path-to-node=\"26,2,0\"><b data-path-to-node=\"26,2,0\" data-index-in-node=\"0\">Infinite Loops:<\/b> The non-US end path used to loop forever on an empty busy-loop, causing the host OS to report a frozen, &#8220;not responding&#8221; application. Now it cleanly pumps the event loop, holds the end screen until the player clicks or presses a key, and then returns gracefully to the launcher.<\/p>\n<\/li>\n<li>\n<p data-path-to-node=\"26,3,0\"><b data-path-to-node=\"26,3,0\" data-index-in-node=\"0\">Portrait Pacing:<\/b> Portrait animations were pacing off a busy-wait loop that collapses to nearly zero on fast modern CPUs, making them run at lightspeed. They are now properly paced by wall-clock time.<\/p>\n<\/li>\n<li>\n<p data-path-to-node=\"26,4,0\"><b data-path-to-node=\"26,4,0\" data-index-in-node=\"0\">UI Borders:<\/b> The rectangular UI boxes finally had their borders corrected and the right color as well.<\/p>\n<\/li>\n<\/ul>\n<h3 data-path-to-node=\"27\">Next Week<\/h3>\n<p data-path-to-node=\"28\">With the European Amiga version playing through cleanly, next week I&#8217;ll turn my full attention to the US variant. I&#8217;ll be checking exactly how its assets and text differ to bring it up to the exact same standard. The hardest groundwork is done; now it&#8217;s all about making the second variant just as rock-solid as the first!<\/p>\n<p data-path-to-node=\"28\">These are some versions from before I find the right palette:<\/p>\n<p data-path-to-node=\"28\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-61\" src=\"https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-01-300x218.png\" alt=\"\" width=\"300\" height=\"218\" srcset=\"https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-01-300x218.png 300w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-01-1024x745.png 1024w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-01-768x559.png 768w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-01-1536x1117.png 1536w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-01-2048x1490.png 2048w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-01-1200x873.png 1200w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-01-1980x1440.png 1980w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-62\" src=\"https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-43-300x224.png\" alt=\"\" width=\"300\" height=\"224\" srcset=\"https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-43-300x224.png 300w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-43-1024x765.png 1024w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-43-768x573.png 768w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-43-1536x1147.png 1536w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-43-2048x1529.png 2048w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-43-1200x896.png 1200w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-30-01-17-43-1980x1478.png 1980w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The Amiga Comes to Life! This week was a massive milestone. The European Amiga version of Kult now runs almost perfectly under ScummVM \u2014 it boots, plays through, and looks exactly the way it did on real hardware. Getting there took twelve intensive commits(for now) that dragged the port all the way from &#8220;the engine [&hellip;]<\/p>\n","protected":false},"author":31,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-57","post","type-post","status-publish","format-standard","hentry","category-gsoc-2026"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/posts\/57","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/users\/31"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/comments?post=57"}],"version-history":[{"count":5,"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/posts\/57\/revisions"}],"predecessor-version":[{"id":67,"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/posts\/57\/revisions\/67"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/media?parent=57"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/categories?post=57"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/tags?post=57"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}