{"id":230,"date":"2026-06-30T17:10:45","date_gmt":"2026-06-30T17:10:45","guid":{"rendered":"https:\/\/blogs.scummvm.org\/mohit\/?p=230"},"modified":"2026-06-30T17:10:45","modified_gmt":"2026-06-30T17:10:45","slug":"towards-the-end-of-dm-engine","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/mohit\/2026\/06\/30\/towards-the-end-of-dm-engine\/","title":{"rendered":"Towards the end of DM engine"},"content":{"rendered":"<p>Hi everyone, this is week 5 of my GSoC project. Work on the Dungeon Master engine is almost at an end \u2014 except for one last hurdle, which I&#8217;ll discuss later.<\/p>\n<h4><span style=\"color: #993300\">The Unresponsive Wall<\/span><\/h4>\n<p class=\"font-claude-response-body break-words whitespace-normal\">At level 13, casting <em>Zo Kath Ra<\/em> spell to open the wall should free the power gem \u2014 but the wall stayed completely unresponsive.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-233\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Level-13-zo-kath-ra.png\" alt=\"\" width=\"368\" height=\"268\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Level-13-zo-kath-ra.png 978w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Level-13-zo-kath-ra-300x218.png 300w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Level-13-zo-kath-ra-768x559.png 768w\" sizes=\"auto, (max-width: 368px) 100vw, 368px\" \/><\/p>\n<p><strong>Cause:<\/strong><\/p>\n<pre>for (thingBeingProcessed = squareFirstThing; thingBeingProcessed != _vm-&gt;_thingEndOfList; thingBeingProcessed = dungeon.getNextThing(thingBeingProcessed)) {\r\n\t\tThing lastProcessedThing = thingBeingProcessed;\r\n<\/pre>\n<p>The <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">lastProcessedThing<\/code> pointer was being updated to <code>thingBeingProcessed<\/code> at the start of each iteration. Before unlinking this particular sensor, the code checks <code>if (lastProcessedThing == thingBeingProcessed)<\/code>. Since both pointed to the same thing, this check always failed and the sensor was never unlinked, failing to trigger the next sensor.<\/p>\n<p><strong>Fix:\u00a0 <\/strong>updating <code class=\"whitespace-pre-wrap\">lastProcessedThing<\/code> to <code>thingBeingProcessed<\/code> before updating <code>thingBeingProcessed<\/code> resolved the issue.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-234\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Level-13-zo-kath-ra-done.png\" alt=\"\" width=\"388\" height=\"281\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Level-13-zo-kath-ra-done.png 978w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Level-13-zo-kath-ra-done-300x217.png 300w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Level-13-zo-kath-ra-done-768x557.png 768w\" sizes=\"auto, (max-width: 388px) 100vw, 388px\" \/><\/p>\n<h4><span style=\"color: #993300\">Shade Screen Box<\/span><\/h4>\n<p>Next I implemented <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">DisplayMan::shadeScreenBox<\/code>. It draws a checkerboard pattern over a given box by coloring every other pixel based on alternating coordinates, creating a semi-transparent shading overlay on top of whatever was already drawn there, graying out menus or buttons.<\/p>\n<p><strong>Before:<\/strong><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-258\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadescreen-before-1024x654.png\" alt=\"\" width=\"520\" height=\"332\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadescreen-before-1024x654.png 1024w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadescreen-before-300x192.png 300w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadescreen-before-768x491.png 768w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadescreen-before-1200x767.png 1200w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadescreen-before.png 1416w\" sizes=\"auto, (max-width: 520px) 100vw, 520px\" \/><\/p>\n<p><strong>After:<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-259\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadrescreen-after-1024x658.png\" alt=\"\" width=\"519\" height=\"333\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadrescreen-after-1024x658.png 1024w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadrescreen-after-300x193.png 300w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadrescreen-after-768x494.png 768w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadrescreen-after-1200x771.png 1200w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Shadrescreen-after.png 1422w\" sizes=\"auto, (max-width: 519px) 100vw, 519px\" \/><\/strong><\/p>\n<p><strong>Before:<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-283\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shadescreen-1.png\" alt=\"\" width=\"349\" height=\"177\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shadescreen-1.png 444w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shadescreen-1-300x152.png 300w\" sizes=\"auto, (max-width: 349px) 100vw, 349px\" \/><\/strong><\/p>\n<p><strong>After: <img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-285\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shadescreen-3.png\" alt=\"\" width=\"355\" height=\"171\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shadescreen-3.png 414w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shadescreen-3-300x145.png 300w\" sizes=\"auto, (max-width: 355px) 100vw, 355px\" \/><\/strong><\/p>\n<p><strong>Before:<\/strong><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-284\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shadescreen-2.png\" alt=\"\" width=\"386\" height=\"284\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shadescreen-2.png 977w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shadescreen-2-300x221.png 300w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shadescreen-2-768x565.png 768w\" sizes=\"auto, (max-width: 386px) 100vw, 386px\" \/><\/p>\n<p><strong>After:<\/strong><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-282\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shade-screen-4.png\" alt=\"\" width=\"363\" height=\"268\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shade-screen-4.png 990w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shade-screen-4-300x222.png 300w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/shade-screen-4-768x568.png 768w\" sizes=\"auto, (max-width: 363px) 100vw, 363px\" \/><\/p>\n<h4><span style=\"color: #993300\">Fix blitToBitmapShrinkWithPalChange()<\/span><\/h4>\n<p>Enabled palette translation in <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">blitToBitmapShrinkWithPalChange<\/code> by mapping each source pixel through the translation table and dividing by 10 to account for the original game&#8217;s 10-multiplier scale.<\/p>\n<p>A spell stone kept on ground:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-262\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/zokathra.png\" alt=\"\" width=\"363\" height=\"264\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/zokathra.png 979w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/zokathra-300x218.png 300w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/zokathra-768x559.png 768w\" sizes=\"auto, (max-width: 363px) 100vw, 363px\" \/><\/p>\n<p>When viewed from a distance of 2 tiles, it looks:<br \/>\n<strong>Before (without Palette translation):<\/strong><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-264\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/zokathra-before.png\" alt=\"\" width=\"297\" height=\"189\" \/><\/p>\n<p><strong>After<\/strong> <strong>(with Palette translation):<\/strong><\/p>\n<h4><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-263\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/zo-kath-ra-after.png\" alt=\"\" width=\"278\" height=\"202\" \/><\/h4>\n<h4><span class=\"_animating_6ta1u_10\" data-newtext-seq=\"17\"><span style=\"color: #993300\">Segfault on restart<\/span> <\/span><\/h4>\n<p>Restarting after your party is dead would result in a crash with buffer overflow.<\/p>\n<p><strong>Cause:<\/strong>\u00a0 On restart, <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">loadDungeonFile()<\/code> was skipping reallocation to reuse existing buffers \u2014 but if the new dungeon needed more space than what was previously allocated, writes would go out of bounds.<\/p>\n<p><strong>Fix:\u00a0 <\/strong>Track each thing type&#8217;s allocated capacity in <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">_allocatedThingCounts<\/code>. On restart, if the new dungeon&#8217;s count exceeds what was previously allocated, reallocate and update the tracked limit<\/p>\n<h4><span style=\"color: #993300\">Runtime errors<\/span><\/h4>\n<p>Some places in the code used <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">&lt;&lt;<\/code> on signed integers, which is undefined behavior in C++ when the value is negative.<\/p>\n<p><strong>Fix:<\/strong> Replaced those with <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">*<\/code> to get the same result safely.<\/p>\n<p>Another instance,<\/p>\n<pre>int16 attribMask = 1;\r\nfor (uint16 stringIndex = 0; stringIndex &lt; 16; stringIndex++, attribMask &lt;&lt;= 1) {\r\n<\/pre>\n<p>On the 16th iteration the value overflowed the int type, causing signed integer overflow.<\/p>\n<p><strong>Fix:<\/strong> changed <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">attribMask<\/code> to <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">uint16<\/code><\/p>\n<h4><span style=\"color: #993300\">Fixing blitBoxFilledWithMaskedBitmap()<\/span><\/h4>\n<pre>*nextDestPixel = *mask &amp; nextSrcPixel;<\/pre>\n<p><strong>Cause:<\/strong>\u00a0 Doing a bitwise-AND directly on ScummVM&#8217;s 1 byte per pixel format corrupts the resulting color palette index. The combined indexes produced garbage color index numbers, leading to severely corrupted\/incorrect pixel colors on the screen<\/p>\n<p><strong>Fix:<\/strong>\u00a0 Rewrote the logic to pick the mask color directly if it isn&#8217;t white, or fall back to the source pixel if it isn&#8217;t transparent.<\/p>\n<h4><span style=\"color: #993300\">Spellcasting Tabs Display<\/span><\/h4>\n<p><strong>Cause:<\/strong> The highlight screen box was using an XOR mask of <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">0x0F<\/code>, but only the 3rd bit needed to be flipped, producing the wrong highlight color on the spellcasting tabs.<\/p>\n<p><strong>Fix:<\/strong>\u00a0 Changed the mask to <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">0x04<\/code>.<\/p>\n<p><strong>Before:<\/strong><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-280\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Spellcasting-tab-before.png\" alt=\"\" width=\"433\" height=\"250\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Spellcasting-tab-before.png 433w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Spellcasting-tab-before-300x173.png 300w\" sizes=\"auto, (max-width: 433px) 100vw, 433px\" \/><\/p>\n<p><strong>After:<img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-279\" src=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Spellcasting-tab-after.png\" alt=\"\" width=\"416\" height=\"219\" srcset=\"https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Spellcasting-tab-after.png 416w, https:\/\/blogs.scummvm.org\/mohit\/wp-content\/uploads\/sites\/82\/2026\/06\/Spellcasting-tab-after-300x158.png 300w\" sizes=\"auto, (max-width: 416px) 100vw, 416px\" \/><\/strong><\/p>\n<h4><span style=\"color: #993300\"><span class=\"_animating_6ta1u_10\" data-newtext-seq=\"5\">The <\/span>one last hurdle<\/span><\/h4>\n<p><strong>Issue:<\/strong> \u00a0<code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">loadDungeonFile()<\/code> reads Big-Endian file data and stores it into <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">_thingData<\/code> as native-endian <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">uint16s<\/code>. But the gameplay macros like <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">DOOR_nextThing<\/code> access this memory using <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">READ_LE_UINT16<\/code> and <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">WRITE_LE_UINT16<\/code>, which always expect Little-Endian memory layout. On Big-Endian machines this setup won&#8217;t work.<\/p>\n<p><strong>Proper Fix:<\/strong>\u00a0 As Sev suggested, I would parse the file data on load directly into arrays of structs, so the engine can just address those structs directly instead of re-parsing every time. This time it won&#8217;t take much time as I&#8217;m already familiar with the places these macros are used.<\/p>\n<div>\n<div data-test-render-count=\"1\">\n<div class=\"group\">\n<div class=\"contents\">\n<div class=\"group relative relative pb-[var(--msg-assistant-pb,0.75rem)]\" data-is-streaming=\"false\">\n<div class=\"font-claude-response relative leading-[1.65rem] [&amp;_pre&gt;div]:bg-bg-000\/50 [&amp;_pre&gt;div]:border-0.5 [&amp;_pre&gt;div]:border-border-400 [&amp;_.ignore-pre-bg&gt;div]:bg-transparent [&amp;_.standard-markdown_:is(p,blockquote,h1,h2,h3,h4,h5,h6)]:pl-2 [&amp;_.standard-markdown_:is(p,blockquote,ul,ol,h1,h2,h3,h4,h5,h6)]:pr-8 [&amp;_.progressive-markdown_:is(p,blockquote,h1,h2,h3,h4,h5,h6)]:pl-2 [&amp;_.progressive-markdown_:is(p,blockquote,ul,ol,h1,h2,h3,h4,h5,h6)]:pr-8\">\n<div>\n<div class=\"standard-markdown grid-cols-1 grid [&amp;_&gt;_*]:min-w-0 gap-3 standard-markdown\">\n<p class=\"font-claude-response-body break-words whitespace-normal\">That was it for this week. The DM engine work will be finished in a day or two \u2014 till then, goodbye \ud83d\ude42<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Hi everyone, this is week 5 of my GSoC project. Work on the Dungeon Master engine is almost at an end \u2014 except for one last hurdle, which I&#8217;ll discuss later. The Unresponsive Wall At level 13, casting Zo Kath Ra spell to open the wall should free the power gem \u2014 but the wall [&hellip;]<\/p>\n","protected":false},"author":30,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[],"class_list":["post-230","post","type-post","status-publish","format-standard","hentry","category-week-5"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/posts\/230","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/users\/30"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/comments?post=230"}],"version-history":[{"count":55,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/posts\/230\/revisions"}],"predecessor-version":[{"id":299,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/posts\/230\/revisions\/299"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/media?parent=230"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/categories?post=230"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/mohit\/wp-json\/wp\/v2\/tags?post=230"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}