{"id":40,"date":"2026-06-08T16:14:37","date_gmt":"2026-06-08T16:14:37","guid":{"rendered":"https:\/\/blogs.scummvm.org\/andy\/?p=40"},"modified":"2026-06-08T16:14:37","modified_gmt":"2026-06-08T16:14:37","slug":"week-2","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/andy\/2026\/06\/08\/week-2\/","title":{"rendered":"WEEK 2"},"content":{"rendered":"<p data-path-to-node=\"5\">Welcome back! This second week was all about bringing the EGA port of <i data-path-to-node=\"5\" data-index-in-node=\"70\">Kult<\/i> from &#8220;it boots but it&#8217;s unplayable&#8221; to a genuinely playable experience. I tackled memory corruption, weird rendering delays, and a few bizarre hauntings.<\/p>\n<p data-path-to-node=\"6\">Let me walk you through the three most interesting mysteries I cracked this week.<\/p>\n<h3 data-path-to-node=\"7\">Story 1: Groundhog Day in the Tunnels<\/h3>\n<p data-path-to-node=\"8\">Early in the game, you walk through a tunnel and have a chance to encounter an Aspirant. But during my testing, he wasn&#8217;t just there sometimes\u2014he was hostile and attacked me <i data-path-to-node=\"8\" data-index-in-node=\"174\">every single time<\/i>. It felt less like a random encounter and more like a scripted mugging.<\/p>\n<p data-path-to-node=\"9\">I dug into the engine&#8217;s random number generator (RNG) and found the culprit. It turned out our <code data-path-to-node=\"9\" data-index-in-node=\"95\">randomize()<\/code> function was a stub, leaving the seed permanently stuck at <code data-path-to-node=\"9\" data-index-in-node=\"166\">0<\/code>. Furthermore, the <code data-path-to-node=\"9\" data-index-in-node=\"186\">prepareAspirant<\/code> logic was reusing a stale random value instead of pulling a fresh one.<\/p>\n<p data-path-to-node=\"10\">To fix this, I seeded the RNG with the host&#8217;s millisecond timer (mimicking the original DOS game, which read the BIOS timer) and forced a fresh byte draw in the room prep logic:<\/p>\n<div class=\"code-block ng-tns-c597216360-56 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation\" data-hveid=\"0\" data-ved=\"0CAAQhtANahgKEwii77WtsveUAxUAAAAAHQAAAAAQrAE\">\n<div class=\"formatted-code-block-internal-container ng-tns-c597216360-56\">\n<div class=\"animated-opacity ng-tns-c597216360-56\">\n<div class=\"code-block-decoration header-formatted gds-emphasized-body-m ng-tns-c597216360-56 ng-star-inserted\"><span class=\"ng-tns-c597216360-56\">C++<\/span><\/p>\n<div class=\"buttons ng-tns-c597216360-56 ng-star-inserted\"><\/div>\n<\/div>\n<pre class=\"ng-tns-c597216360-56\"><code class=\"code-container formatted ng-tns-c597216360-56\" role=\"text\" data-test-id=\"code-content\">rand_seed = (byte)(g_system-&gt;getMillis());\r\ngetRand();\r\n<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<p data-path-to-node=\"12\">With the dice finally rolling correctly, the encounters became truly unpredictable again!<\/p>\n<h3 data-path-to-node=\"13\">Story 2: The Ghost That Followed Me Through Doors<\/h3>\n<p data-path-to-node=\"14\">Once I got past the Aspirant, another weird bug appeared. I walked through a door into a completely new zone (&#8220;De Profundis&#8221;), where no NPCs should exist. Suddenly, the Aspirant&#8217;s face popped up on screen, along with a speech bubble asking if I wanted to trade!<\/p>\n<p data-path-to-node=\"15\">This turned out to be a classic state leak. When swapping rooms, the engine correctly cleared the upcoming command queues, but it forgot two things: the command chain <i data-path-to-node=\"15\" data-index-in-node=\"167\">currently<\/i> executing, and the pointer to the current NPC. If you crossed a door at the exact millisecond the Aspirant&#8217;s trade script was running, that script would survive the transition. The engine would then happily render the previous room&#8217;s NPC in the new room.<\/p>\n<p data-path-to-node=\"16\">The fix was a surgical guard inside the door transition path (<code data-path-to-node=\"16\" data-index-in-node=\"62\">SCR_42_LoadZone<\/code>):<\/p>\n<div class=\"code-block ng-tns-c597216360-57 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation\" data-hveid=\"0\" data-ved=\"0CAAQhtANahgKEwii77WtsveUAxUAAAAAHQAAAAAQrQE\">\n<div class=\"formatted-code-block-internal-container ng-tns-c597216360-57\">\n<div class=\"animated-opacity ng-tns-c597216360-57\">\n<div class=\"code-block-decoration header-formatted gds-emphasized-body-m ng-tns-c597216360-57 ng-star-inserted\"><span class=\"ng-tns-c597216360-57\">C++<\/span><\/p>\n<div class=\"buttons ng-tns-c597216360-57 ng-star-inserted\"><\/div>\n<\/div>\n<pre class=\"ng-tns-c597216360-57\"><code class=\"code-container formatted ng-tns-c597216360-57\" role=\"text\" data-test-id=\"code-content\">the_command = <span class=\"hljs-number\">0<\/span>; \r\nscript_vars[kScrPool8_CurrentPers] = pers_list;\r\n<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<p data-path-to-node=\"18\">This instantly aborts any in-flight command chain and drops the stale actor pointer before the room swap finishes. No more ghosts!<\/p>\n<h3 data-path-to-node=\"19\">Story 3: The Door, The Scorpion, and the Greedy Opcode<\/h3>\n<p data-path-to-node=\"20\">In the Scorpion room, the intended solution is to use the &#8220;Pray&#8221; command on a statue to open a door. But doing so gave me corrupted, gibberish text on the screen, the speech bubble got stuck, and the door remained firmly locked.<\/p>\n<p data-path-to-node=\"21\">I suspected it was a missing item at first, but the truth was a desync in the script decoder. The EGA and CGA versions of the game have slightly different opcode lengths. When you pray, the game wants to do two things: play a sound (<code data-path-to-node=\"21\" data-index-in-node=\"233\">0x68 PlaySfx<\/code>) and then set a variable to open the door (<code data-path-to-node=\"21\" data-index-in-node=\"289\">setVar<\/code>).<\/p>\n<p data-path-to-node=\"22\">However, in our engine, the EGA opcode <code data-path-to-node=\"22\" data-index-in-node=\"39\">0x68<\/code> was mistakenly programmed to read an extra &#8220;pad&#8221; byte. Because of this, the sound opcode literally &#8220;ate&#8221; the door-opening command! The engine lost its place in the script, spit out garbage memory as text, skipped the bubble cleanup, and never opened the door. Removing that single rogue <code data-path-to-node=\"22\" data-index-in-node=\"331\">script_ptr++<\/code> operand width fixed all symptoms instantly.<\/p>\n<p data-path-to-node=\"22\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-42\" src=\"https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-06-14-55-54-300x215.png\" alt=\"\" width=\"300\" height=\"215\" srcset=\"https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-06-14-55-54-300x215.png 300w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-06-14-55-54-1024x734.png 1024w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-06-14-55-54-768x550.png 768w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-06-14-55-54-1536x1100.png 1536w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-06-14-55-54-2048x1467.png 2048w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-06-14-55-54-1200x860.png 1200w, https:\/\/blogs.scummvm.org\/andy\/wp-content\/uploads\/sites\/83\/2026\/06\/Screenshot-from-2026-06-06-14-55-54-1980x1418.png 1980w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<h3 data-path-to-node=\"23\">Honorable Mentions<\/h3>\n<p data-path-to-node=\"24\">While digging through the engine, I also patched up a few other nasty issues:<\/p>\n<ul data-path-to-node=\"25\">\n<li>\n<p data-path-to-node=\"25,1,0\"><b data-path-to-node=\"25,1,0\" data-index-in-node=\"0\">Exploding Sprites:<\/b> A massive &#8220;Lutin&#8221; character sprite was overflowing its allocated scratch memory (<code data-path-to-node=\"25,1,0\" data-index-in-node=\"100\">scratch_mem1<\/code>) and corrupting the EGA <code data-path-to-node=\"25,1,0\" data-index-in-node=\"137\">sprites_list[]<\/code>, causing random crashes during blit\/restore operations. I bumped up the memory limits to keep the big guys safely contained.<\/p>\n<\/li>\n<li>\n<p data-path-to-node=\"25,2,0\"><b data-path-to-node=\"25,2,0\" data-index-in-node=\"0\">Death Loops:<\/b> Fixed an infinite loop tied to the &#8220;YOU FAILED THE ORDEALS&#8221; timer.<\/p>\n<\/li>\n<\/ul>\n<h3 data-path-to-node=\"26\">What&#8217;s Next<\/h3>\n<p data-path-to-node=\"27\">This week felt amazing because the EGA version is finally starting to behave like a real game. Next week, I&#8217;ll continue hunting down logic bugs and refining the remaining EGA-specific rendering quirks.<\/p>\n<p data-path-to-node=\"28\">Thanks for reading \u2014 see you next week!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Welcome back! This second week was all about bringing the EGA port of Kult from &#8220;it boots but it&#8217;s unplayable&#8221; to a genuinely playable experience. I tackled memory corruption, weird rendering delays, and a few bizarre hauntings. Let me walk you through the three most interesting mysteries I cracked this week. Story 1: Groundhog Day [&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-40","post","type-post","status-publish","format-standard","hentry","category-gsoc-2026"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/posts\/40","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=40"}],"version-history":[{"count":2,"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/posts\/40\/revisions"}],"predecessor-version":[{"id":43,"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/posts\/40\/revisions\/43"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/media?parent=40"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/categories?post=40"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/andy\/wp-json\/wp\/v2\/tags?post=40"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}