<br />
<b>Deprecated</b>:  The each() function is deprecated. This message will be suppressed on further calls in <b>/home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php</b> on line <b>456</b><br />
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[glthr.com | Guillaume Lethuillier's blog]]></title><description><![CDATA[glthr.com | Guillaume Lethuillier's blog]]></description><link>https://glthr.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 08 Jul 2026 21:45:35 GMT</lastBuildDate><atom:link href="https://glthr.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Discovering Errors in Donald Knuth’s TAOCP]]></title><description><![CDATA[As Donald Knuth has just published Volume 4, Fascicle 7 of The Art of Computer Programming (TAOCP), on constraint satisfaction, I would like to provide more information about the errors I discovered i]]></description><link>https://glthr.com/discovering-errors-in-donald-knuths-taocp</link><guid isPermaLink="true">https://glthr.com/discovering-errors-in-donald-knuths-taocp</guid><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sat, 28 Mar 2026 08:00:00 GMT</pubDate><content:encoded><![CDATA[<p>As Donald Knuth <a href="https://www-cs-faculty.stanford.edu/~knuth/news.html">has just published</a> Volume 4, Fascicle 7 of <em>The Art of Computer Programming</em> (TAOCP), on constraint satisfaction, I would like to provide more information about the errors I discovered in 2019 in other volumes, for which I received two <a href="https://en.wikipedia.org/wiki/Knuth_reward_check">Knuth reward checks</a>. To date, my account balance on his “bank”, the Bank of San Serriffe, <a href="https://www-cs-faculty.stanford.edu/~knuth/boss.html">is still 0x2.40</a> (hexadecimal dollars). 0x2.00 for two errors, and 0x0.40 for two “valuable suggestions.”</p>
<p>His response to my errors reports was faster than I expected: I emailed him four times to report different errors from December 18, 2019, to January 1, 2020 (although Knuth does not typically use email, <a href="https://www-cs-faculty.stanford.edu/~knuth/taocp.html">there is an email address reserved for reporting TAOCP errors</a>, which his assistant prints). He updated the errata on January 1 and responded with a postal mail on January 9, 2020.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741449823270/f986b0e9-3965-4064-b940-e5806c8011d5.png" alt="" style="display:block;margin:0 auto" />

<p>The envelope contained my printed emails with his handwritten notes in pencil and two checks (dated December 24, 2019, and January 1, 2020).</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741450804963/8e01e6e3-791c-4c2a-be48-d0627d634795.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741449848827/7b6fb504-d70f-4771-8380-a998b9392fa2.png" alt="" style="display:block;margin:0 auto" />

<p>Some reward recipients have shared their letters online, here and there; however, I will paraphrase Knuth’s responses instead of reproducing them verbatim, as they are part of private correspondence (this can change in the future—per express authorization, for instance—; in that case, I will update this article to share a full scan of the letters). A notable exception is an excerpt of a letter containing generic information and a good anecdote: Knuth wished me a “<em>Joyeux Noël</em>” (French for “Happy Christmas”) because, probably without realizing it, I sent him an email on December 25.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741449873242/48a85378-ad42-4f28-9766-ce34ffae92f3.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h1>Errors Related to Algorithm X</h1>
<p>I was familiar with <a href="https://en.wikipedia.org/wiki/Knuth%27s_Algorithm_X">Algorithm X</a> before reviewing Volume 4 Pre-Fascicule 5c (published as Volume 4, Fascicule 5)—because <a href="https://en.wikipedia.org/wiki/Exact_cover">exact cover problems</a> are fantastic! I did not expect to find fundamental errors, as Algorithm X has probably been one of the sections most thoroughly reviewed by its creator.</p>
<p>At that time, I kept track of Algorithm X’s states manually (I was on a flight without a computer). I maintained a record of the memory state with pen and paper, an experience that allowed me to focus on the essential details and discover small errors and imprecisions that led to minor corrections.</p>
<h2>Reported Errors</h2>
<h3>Local Variables Not Declared</h3>
<p>While the local variables of the first algorithmic step, <code>cover(i)</code>, are identified as such, the variables of the subsequent steps are not.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741449975708/c6b68b0b-ed08-4be1-be8a-afd124182b52.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741449985590/bcbb0bd1-4cc6-4bfb-be27-077b6a69c5b9.png" alt="" style="display:block;margin:0 auto" />

<p>Though this is not particularly problematic (this is trivially inferable from the context), Knuth recognized that it would have been beneficial to state this characteristic for each step explicitly. However, <strong>he decided against breaking up Algorithm X between different pages due to space constraints and, therefore, could not add clarifications for each step</strong>; he then appropriately quoted Voltaire: “<em>The secret of being a bore is to tell everything.</em>” Nevertheless, Knuth ultimately found an elegant solution (see below).</p>
<p><em>Reward: 32¢ (0x20¢)</em></p>
<h3>Unused Memory Fields</h3>
<p>When it was time to simulate the impact of the algorithm on the memory state, I was confused by this:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741450032507/cc4b9308-f844-4892-9f6e-6c97923c89b7.png" alt="" style="display:block;margin:0 auto" />

<p>My interpretation was that a program attempting to access one of these unused fields would fail, as I assimilated them as being in an uninitialized state. And indeed, my pen-and-paper program failed when reaching the hide step for the last spacer. Using the memory dump table as an example, covering one of the last nodes after some iterations leads to set <code>q</code> to 30 and, therefore, to <code>d ← DLINK(30)</code>. If an unused field means an uninitialized memory location, then the algorithm's execution should prematurely stop as, in this example, <code>DLINK(30) = —</code>. However, <strong>Knuth clarified that he did not explicitly state the variable was <em>uninitialized</em>; instead, it must be assumed to be initialized, but its value has no impact on the algorithm.</strong></p>
<p><em>Reward: 32¢ (0x20¢)</em></p>
<h3>Unused Variables</h3>
<p>Finally, <code>N</code> and <code>Z</code> were set during the algorithm initialization step, but I did not need to utilize them when manually tracking the memory state.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741450118831/c747ea0f-b896-422a-9adf-d38b8bb79a86.png" alt="" style="display:block;margin:0 auto" />

<p>According to Knuth, they were, in fact, used in the exercises, notably exercise 83.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741450128565/b1d90f0a-2a9a-49a4-aeb3-b07d1e0f8172.png" alt="" style="display:block;margin:0 auto" />

<p>To ensure this was the case, he reviewed it and noticed an error: <code>N</code> should be <code>N₁</code>.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741450237068/cc7b2f39-264a-417a-8adf-e23710e8ed1b.png" alt="" style="display:block;margin:0 auto" />

<p>He graciously rewarded me for helping him discover this error. (I transitively found this error, so to say.)</p>
<p><em>Reward: \(2.56 (0x\)1.00)</em></p>
<h2>Corrections</h2>
<p>Corrections arising from my emails, all dated back to January 1, 2020, can be found in the <em>Errata for Volume 4 Fascicle 5, long-form</em> (available <a href="https://www-cs-faculty.stanford.edu/~knuth/taocp.html">here</a>). Specifically, they are listed on pages 5 and 13 of the downloadable compressed PostScript file.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741450294503/f4408f9d-58fe-4061-958f-74b803127c9b.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741450300109/59a51bd1-fe77-477b-914c-8481f5283cc7.png" alt="" style="display:block;margin:0 auto" />

<p>It appears that:</p>
<ul>
<li><p>Knuth clarified what “unused” field means by implicitly stating that they are implicitly but necessarily initialized but "<em>can contain anything</em>".</p>
</li>
<li><p>Instead of listing all local variables, he refers to <code>p</code>, <code>l</code>, <code>r</code> as being part of a <em>non-exhaustive</em> list of local variables (“<em>Undeclared variables like p, l, r are local</em>”).</p>
</li>
<li><p>Last, in his answer 83, he replaced <code>N</code> with <code>N₁</code>.</p>
</li>
</ul>
<h3>Small Aside: Using a “HeartBeat-Arrow” as Delimiter</h3>
<p>Notice how Knuth uses a bespoke symbol (a “heartbeat-arrow”) to indicate the transition from the error to its correction:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741450382338/8d04fd5a-8624-4bf4-80a7-73617f926bad.png" alt="" style="display:block;margin:0 auto" />

<p>This is because he, as his corrections are inline/embedded (no table), he needed a symbol that is necessarily not already present in his books; otherwise, it could have been confused with a corrected symbol (typically, a simple right-pointing arrow, →, would have most probably been confused with a corrected material implication, and so forth). I believe this is an illustration of a <em>delimiter</em>, a special marker that cannot use the same symbols or structure as the regular text (<a href="https://glthr.com/first-order-second-order-expressions-and-delimiters-in-languages">in my previous blog post</a>, I develop this general idea).</p>
<hr />
<h1>Erroneous Reference to an Obscure Article</h1>
<h2>Reported Error</h2>
<p>When reading the section on Hamiltonian paths in antiquity from Volume 4, pre-fascicule 8a, I stumbled upon the reference to a French article about icosahedral objects inscribed with Greek letters: P. Perdrizet, in <em>Bulletin de l’Institut français du Caire</em> 30 (1930), 1-16. I did not find this article in the journal, but instead <a href="https://gallica.bnf.fr/ark:/12148/btv1b53180372w">located it</a> in <em>Bulletin de l’Institut français d’archéologie orientale</em>.</p>
<p>Acknowledging the error, <strong>Knuth took the time to review some articles in this journal and strongly recommended a volume about Egyptian poetry:</strong> <a href="https://ignca.gov.in/Asi_data/31424.pdf"><strong>https://ignca.gov.in/Asi_data/31424.pdf</strong></a>. Although he did not specify an article, I believe he referred to Vikentiev, V. (n.d.). The metrical scheme of the « Shipwrecked Sailor ». B<em>ulletin de l’Institut français d’archéologie orientale</em>, 35(1), 1–40. It is indeed remarkable for that time (see notably Planche 1, page 18 of the PDF).</p>
<p><em>Reward: \(2.56 (0x\)1.00)</em></p>
<h2>Correction</h2>
<p>There is no erratum, as it is a pre-fascicule that is directly edited.</p>
<p>Here is, as a substitute, a comparison of a version from November 2019, retrieved with Wayback machine, and the current one (March 2025):</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741450622906/61ae981b-3c03-40d9-bd7f-7a50e76f6b84.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741450614596/ca0f5785-98d5-4d08-9c09-55e380452950.png" alt="" style="display:block;margin:0 auto" />


<h1>Conclusion</h1>
<p>This event has left me with an immense sense of respect for Donald Knuth’s dedication to accuracy and attention to detail. It was an honor to contribute to the improvement of his work, even if it was just a small (to be honest: minuscule) part. I look forward to continuing my exploration of the latest published TAOCP volume, and discovering more errors to correct.</p>
]]></content:encoded></item><item><title><![CDATA[Why XML Tags Are so Fundamental to Claude]]></title><description><![CDATA[This is the fourth installment in my series on delimiters in languages. I recommend reading the first article to understand the implication of categorizing XML tags as delimiters in this context. (Mar]]></description><link>https://glthr.com/xml-fundamental-to-claude</link><guid isPermaLink="true">https://glthr.com/xml-fundamental-to-claude</guid><category><![CDATA[llm]]></category><category><![CDATA[languages]]></category><category><![CDATA[claude.ai]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sun, 01 Mar 2026 14:51:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6536ad21a2e7e85a9d52de97/6691eb70-2ba0-405f-8f10-8ef677d785b9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This is the fourth installment in my series on</em> <a href="https://glthr.com/page/delimiters-in-languages"><em><strong>delimiters in languages</strong></em></a><em>.</em> <em><strong>I recommend reading</strong></em> <a href="https://glthr.com/first-order-second-order-expressions-and-delimiters-in-languages"><em><strong>the first article</strong></em></a> <em><strong>to understand the implication of categorizing XML tags as delimiters in this context</strong></em>. <em>(March 2, 2026: Updated the considerations around the training vs. model inference framework).</em></p>
<p>The tour de force of Claude is to have made XML tags first-class citizens. This assertion may seem provocative, but I believe there is something fundamental at play here.</p>
<p>The Claude API Docs provide practical <a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices">prompting best practices</a>, designed for developers by outlining specific instructions and clear formatting rules. They present <strong>a contrast between Claude’s modern approach and the reiterated suggestion of using traditional XML tags</strong>:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6536ad21a2e7e85a9d52de97/f922b5e4-cc35-4c92-89be-7f97c254a60e.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/6536ad21a2e7e85a9d52de97/8177c7fb-3e58-4d79-8f57-8f1d6d51d7cf.png" alt="" style="display:block;margin:0 auto" />

<p>This is not a minor tip: users report that <a href="https://old.reddit.com/r/ClaudeAI/comments/1psxuv7/anthropics_official_take_on_xmlstructured">structuring prompts with XML can be a transformative experience</a>: “<em>Here’s the simple trick. Instead of just asking Claude stuff like normal, you put your request in special [XML] tags. . . . That’s literally it. And the results are so much better.</em>”</p>
<p>And not only does Claude leverages XML tags in user prompts, but its framework specifically incorporates them as key elements: “<a href="https://blog.promptlayer.com/prompt-engineering-with-anthropic-claude-5399da57461d"><em>Anthropic heavily uses XML tags in their prompts.</em></a>”</p>
<p>This is, admittedly, a subjective reading, but I believe <strong>the repurposing of XML, a technology dating back to 1998, may represent a core aspect of what makes Claude distinctive: it turns Claude into something closer to a genuine language interpreter</strong>.</p>
<p>My own research (as a hobbyist) has led me to postulate the existence of a universal principle underlying all languages, whether human or artificial. I have observed <a href="https://glthr.com/page/delimiters-in-languages">this principle</a> at work in diverse contexts: <a href="https://glthr.com/first-order-second-order-expressions-and-delimiters-in-languages">programming languages, bacterial DNA sequences</a>, <a href="https://glthr.com/formulaic-delimiters-in-the-iliad-and-the-odyssey">Homeric verses</a>, and now, seemingly, with Claude. This principle centers on <strong>the necessity for any language (regardless of its form) to possess a mechanism for signaling the transition from first-order to second-order expressions</strong>. I contend that such a mechanism is fundamentally required for information transfer between any two entities; without it, meaningful communication becomes virtually impossible.</p>
<p>These transitions are typically indicated by markers or delimiters. In contemporary English, quotation marks serve this purpose. They delineate the shift from direct statement to reported speech, metaphor, or quoted material. These markers operate in pairs: one initiates the transition from first-order to second-order expression, while the other signals a return to the original level of discourse. Furthermore, this nesting can be deeply embedded; we can move from order <em>n</em> to order <em>n+1</em>, then to <em>n+2</em>, and so on, creating complex layers of meaning.</p>
<p>To illustrate how these distinctions play out in practice, consider an observation from <a href="https://github.com/aws-samples/prompt-engineering-with-anthropic-claude-v-3/blob/main/04_Separating_Data_and_Instructions.ipynb">an AWS prompt engineering course</a>. It serves as a concrete demonstration of how crucial clear delimiters are for ensuring Claude accurately interprets and executes complex prompts:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6536ad21a2e7e85a9d52de97/ac11963f-e7c8-4bf3-a229-b70311b3de5f.png" alt="" style="display:block;margin:0 auto" />

<p>“<em>Here, Claude thinks ‘Yo Claude’ is part of the email it’s supposed to rewrite!</em>” is a remarkably revealing statement. “Yo Claude” is a first-order expression (the user interacting with Claude), the content of the email is a second-order expression (the email the user will address to someone else). And they use XML tags because they need to delimit, they need to enclose the higher-order expression, like we do, in English, when we quote someone using quotation marks (like at the beginning of this paragraph); like Homer did when making heroes talk using formulaic delimiters in Ancient Greek; like bacterial DNA does to store recognition sequences.</p>
<p>In truth, it does not matter that these tags are XML. Other models use ad hoc delimiters (as explained in <a href="https://glthr.com/llm-delimiters-and-higher-order-expressions">a previous article</a>; example: <code>&lt;|begin_of_text|&gt;</code> and <code>&lt;|end_of_text|&gt;</code>) and Claude team could have done the same for their own prompts. What matters is what these tags represent. <strong>What makes Claude special is that its creators made it “aware” of the concept of delimiters, which, at least this is my view, is so crucial to the effective processing and communication of information.</strong> And it is precisely this capacity that makes Claude so effective at interpreting layered meaning.</p>
]]></content:encoded></item><item><title><![CDATA[The Riven Diffs]]></title><description><![CDATA[This article, by Guillaume Lethuillier and Twitch, is also published on Twitch’s website (which contains additional images by combining animated diffs, highlighted diffs, and pairs of original images).
Disclaimer: This article contains spoilers appli...]]></description><link>https://glthr.com/riven-diffs</link><guid isPermaLink="true">https://glthr.com/riven-diffs</guid><category><![CDATA[Riven]]></category><category><![CDATA[#RetroGaming]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sat, 03 Jan 2026 10:17:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1767435593615/e28c5c07-a092-4067-8832-03a94b2deb39.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This article, by</em> <em>Guillaume Lethuillier</em> <em>and</em> <em>Twitch</em>, <a target="_blank" href="https://inkbeetle.com/rivendiffs/"><em>is also published on Twitch’s website</em></a> <em>(which contains additional images by combining animated diffs, highlighted diffs, and pairs of original images).</em></p>
<p><em>Disclaimer: This article contains spoilers applicable to both versions of Riven.</em></p>
<p>Just how stable is <a target="_blank" href="https://en.wikipedia.org/wiki/Riven">Riven</a>? It is well known that Gehn’s unorthodox writing methods invariably produced ages that eventually fell apart, and Riven in particular is demonstrably close to its inevitable collapse. But do we see any symptoms of this in-game? Besides the endings, Riven appears perfectly normal to an untrained eye. But what if we look closer? What if there are anomalies in plain sight that we simply never noticed before?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434014892/72d3f8f4-1c90-4559-b8b8-4fb6246ae94f.gif" alt class="image--center mx-auto" /></p>
<p>While Myst relied on the interpreted HyperCard engine, Riven uses the Mohawk engine instead. Conceptually, Myst operates on a simple one-view-per-card model where each image represents fixed render states (see <a target="_blank" href="https://glthr.com/myst-graph">our previous articles</a>). In contrast, Riven’s Mohawk engine allows for dynamic changes within nodes by enabling multiple rendered views to coexist on the same card, with the engine displaying the appropriate render based on the current state of the game world. In short, where Myst’s cards represent static views, Riven’s cards can represent multiple different versions of the same viewpoint. This discovery led us to develop an exploration tool, the “Riven Explorer”, that allows for side-by-side comparison of these renders. Where the Myst Graph revealed development insights through <em>card</em> <em>relationships</em>, the Riven Explorer uncovers them through <em>visual</em> <em>differences</em>.</p>
<p>Since renders took significant amounts of time in the 90s, and different worldstates were often rendered at different stages in development, many renders have subtle differences beyond the changes caused directly by the player. Most of these are virtually impossible to notice due to the worldstate changes being evoked in separate locations from where the actual differences are visible. But when you compare card differences in-place, changing the worldstate without moving the camera, they can become much more obvious. Many differences are subtle, and are virtually impossible to notice without the use of direct comparisons. But with such tools, it reveals many places where tweaks to the 3D scene were made between renders, resulting in changes to the world far beyond the changes being made by the player.</p>
<p>The tool, evoked in <a target="_blank" href="https://glthr.com/riven-navigator">this article</a>, provides a methodical way to analyze the unique differences between images composing a given card. For instance, when a card contains three images (A, B, and C), the explorer generates three unique comparative analyses: A versus B, A versus C, and B versus C. These differential comparisons, that we call “diffs”,  are visualized through two complementary modes:</p>
<ul>
<li><p><strong>Pixel differential rendering</strong>: By default, the tool combines both images into a black-and-white composite, with red pixels highlighting mismatches between the two views. In many cases, the differences visible here will be caused by changes to the dithering or the color table, resulting in false positives, but quite often it can highlight small but very real changes that would otherwise be hard to spot.</p>
</li>
<li><p><strong>Animated comparison</strong>: Reviewers can toggle a rapid succession mode that alternates between the two images (similar to an animated GIF), making differences easily visible as points of motion in an image that is otherwise mostly unchanged.</p>
</li>
</ul>
<p>Typically, a card will have multiple diffs. The number of differences follows <a target="_blank" href="https://en.wikipedia.org/wiki/Triangular_number">the triangular number sequence</a>: 1 render produces 0 diffs; 2 renders produce 1 diff; 3 renders, 3 diffs; 4 renders, 6; 5 renders, 10; and so on (sequence <a target="_blank" href="https://oeis.org/A000217">A000217</a> on OEIS).</p>
<p><strong>Example of the tool in action</strong></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/knhYPmUq8IY">https://youtu.be/knhYPmUq8IY</a></div>
<p> </p>
<p><strong>Example of pixel highlighting: the spider chair activated/deactivated, from Temple Island, card T11</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434249466/3084e18e-ffe3-43c0-a12f-6ef83f1f0a8d.png" alt class="image--center mx-auto" /></p>
<p><strong>The same diff, as an animation</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434285072/6af46eb6-4823-4188-9e2f-d7ea3454db08.gif" alt class="image--center mx-auto" /></p>
<p>We then began systematically examining these differences, expecting to find rendering glitches tied to game logic. This was a fun and almost philosophical experience: we actualized different moments in time from the same place, without having to move around to change the worldstate. It is like wearing magic glasses that represent the same location from different points in time. This article, and the follow-up articles to come, explore these findings in depth.</p>
<p>We use the following terminology throughout these articles:</p>
<ul>
<li><p><strong>ANOMALY</strong>: An “anomaly” is a visible rendering quirk. These can be caused by many things, including changes to the underlying world geometry that occurred between renders, mismatched splices, or other errors. Many anomalies are visible from multiple cards. Many anomalies require specific worldstates in order to be visible.</p>
</li>
<li><p><strong>CARD</strong>: Anywhere the player can stand is a “<a target="_blank" href="http://insidethelink.ortiche.net/wiki/index.php/Riven_engine_overview">card</a>”. A card can have many renders assigned to it depending on how many different worldstates are visible from any given spot. Walking or turning moves between cards, but different versions of one spot will share the same card.</p>
<p>  We label cards with a letter and a number. The number corresponds with the number of the card. However, different islands can share numbers, so we prefix each card label with a letter corresponding to the island’s label within the datafiles: B for Boiler, G for Survey (“garden”), J for Jungle, O for 233rd (“office”), P for Prison, R for Tay (“rebel”), and T for Temple. For example, card J127 (pictured at the top of this article) refers to card 127 within Jungle Island. These card numbers also correspond with the beginning of the filenames produced by the asset extraction tool <a target="_blank" href="https://archive.org/details/Riveal11.1">Riveal</a> by Ron Hayter, which was used heavily in the research and preparation of these articles.</p>
<p>  <em>Note: some card numbers change between versions and languages. The numbers in this article are based on the 1.2 version from the English 10th Anniversary DVD. If you’re using a CD release or searching for assets dubbed into a different language, the cards may be numbered differently, but we haven’t found any differences in the actual content of the renders yet.</em></p>
</li>
<li><p><strong>WORLDSTATE</strong>: The “worldstate” refers to the state of all the player-actionable things, such as the position of a lever or an elevator. Whenever the player performs an action or the story progresses, the worldstate changes.</p>
</li>
<li><p><strong>PATCH</strong>: A “patch” refers to a smaller picture or video that is placed on top of the background image in real-time by the engine. Patches are often used in places where many different worldstates are visible, to prevent the need for a huge number of renders covering every possible configuration. Patches are also used for the large number of very small videos that don’t cover the entire screen, such as when a spinning dome is visible in the distance. Patches are always rectangular, and are stored as separate images/videos within the datafiles, to be overlaid in real-time within the game.</p>
</li>
<li><p><strong>SPLICE</strong>: A “splice” refers to when the image data of a render has been copied from another image or another render. These are sometimes used when only part of an image needs to be re-rendered, but a patch would not be appropriate or desirable for whatever reason. Splices are edits which exist within the finished render itself, and are not separate files like patches are.</p>
</li>
<li><p><strong>FMV</strong>: An acronym for “full motion video”, referring to the many videos used throughout the game. Almost anytime you see motion, it’s an FMV. (Rare exceptions include the water rippling effects all over the game, and the various miniscule flying bugs that appear in places like Jungle Island.)</p>
</li>
<li><p><strong>OCCLUSION</strong>: In computer graphics, "occlusion" refers to when one object blocks vision to another object. When one object is in the foreground and blocking another object in the background, the background object is "occluded" by the foreground object. Several anomalies involve objects blocking one another in ways that are incorrect based on where they actually exist in 3d space; these are problems with occlusion.</p>
</li>
</ul>
<p><strong>An example of a "Splice": the rectangular edges are easily visible in the differential comparison of J191</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434415907/1da5c2dd-0338-4471-956f-fbb583305a46.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-part-1-jungle-island">Part 1: Jungle Island</h1>
<p>Jungle Island is a location very familiar to many of us, but it has a large variety of anomalies, making it suitable as our first point of examination.</p>
<p>Several anomalies are associated with the state of the spinning dome (this is a recurring theme- every single spinning dome in the game has anomalies.) In cards J121, J124, J133, J154, and J421, the two semicircular “hinges” that sit at the base of the dome are occluded incorrectly, making them fully or partially obstructed by other geometry depending on whether or not the dome is flipped. In J124, you can also see the hinge switch from high to low polygon count.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434461513/b3f129f9-4e1b-41b3-a634-c3d28b308cb8.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434468759/1f870497-6591-4a17-8c82-25e14c1df32a.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434477575/7e7a2efb-9c3f-43c8-a4d3-c37f36737c01.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434487079/f1033b0c-53db-4da9-8a17-9042aec6b5e6.gif" alt class="image--center mx-auto" /></p>
<p>In card J130, the base of the dome visibly moves when the dome is flipped.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434511774/c14cf552-c3a1-47a8-9e16-4b83eca5e384.gif" alt class="image--center mx-auto" /></p>
<p>Card J154 is also the location of one of the Spyder easter egg renders. A direct comparison with a normal J154 card reveals that the camera is in a different spot. Also, the furthest of the spherical lamps in the easter egg has been raised/lit like many of the others along this walkway.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434525544/28d7dd7a-abfe-48fa-828f-d31874ed0289.gif" alt class="image--center mx-auto" /></p>
<p>In cards J110, J119, and J127, the foliage changes (quite drastically in J127) when the dome is flipped. J119 also has some leaves incorrectly occluded by parts of the dome when closed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434542265/710ba59e-8103-40b2-9c77-594294b2aac9.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434549431/5fe5ed75-5f31-4881-bfa9-349058ea0b93.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434559299/5d24b53a-ab7c-42e9-ac27-3870ebc6963e.gif" alt class="image--center mx-auto" /></p>
<p>At the top of the wahrk gallows, in cards J178 or J264, the doorjamb to the prison changes depending on whether the door is open or closed. In card J264 the entire jamb changes, and in J178 the differential comparison makes it obvious where the images were spliced around the jamb.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767481033869/f968370a-a855-46ce-8520-33d8354179f5.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434579145/932fb096-c1a7-4e48-978b-b93a99e4cae3.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434589249/b424d117-c8e4-4355-904b-1105518f29f0.gif" alt class="image--center mx-auto" /></p>
<p>There are two patches applied when you’re standing in card J181, one for the submarine and one for submarine dock 5 (the schoolhouse).</p>
<p>When the submarine is raised out of the water, the submarine overlaps the railing in the foreground of J181.</p>
<p>Furthermore, there is a patch that shows dock 5 extended, which is correctly used to show an extended dock 5 in J181 when the prison door is open. However, the render of the closed prison door <em>already</em> has an extended dock 5, and there is no patch to show it retracted. As a result, if you retract dock 5 before opening the prison door, you can see the schoolhouse dock retract the moment the prison door finishes its opening FMV.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434619915/1591d57a-c93f-4f16-9731-05c82131f565.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434625015/adcae7eb-3b07-4d4b-ae50-081a59e68fc6.gif" alt class="image--center mx-auto" /></p>
<p>Inside the tunnels, differential comparison revealed a render that seems out of place. If you turn on exactly 3 of the lights in the tunnel, when you’re walking back towards the ocean, there is one render assigned to card J194 that’s actually a render from the perspective of J196. In the differentials, this looks like the tunnel changing shape between worldstates. In-game, this makes it seem like one of the steps towards the ocean darkens the lighting without moving forward at all. The incorrect render may be from earlier in development, as it has noticeably egregious banding on the dim/gray parts of the image, which isn’t a problem on the correct version visible from J196, or anywhere else in the tunnel. It’s likely card J194 should be completely black when 3 lights are on, as it’s barely lit (with a correct render) with 4 lights on.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434642412/dfe476de-6f1a-431a-a6c6-d85654bacd50.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434647418/c9583a8e-a262-4b86-b49e-2a124abc9259.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434654352/380358c2-2c2c-4593-bb06-c4be219f3698.gif" alt class="image--center mx-auto" /></p>
<p>Each of the 25 totems in the Tay book room can be raised or lowered individually. When you’re standing in the center of the room, individual image patches are used to allow arbitrary totems to be shown in the downwards state, and renders with Tay unlocked are made with the correct totems already lowered. However, the zoomed-in views on almost all of the 25 totems have <em>all</em> of the totems in the same state as the totem you’re currently zoomed in on. <em>(Exceptions: J217 and J225)</em></p>
<p>Two anomalies are visible as a result: Firstly, and more obviously, when zoomed in on a totem, the visible edges of the 2 adjacent totems will be raised or lowered based on the state of the totem you’re currently zoomed in on. Secondly, in many of the totem views, a reflection of the room is visible on the water, which shows every other totem in the room raised or lowered in the reflection.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767481074206/13ba4a2a-b810-435a-b422-5629958772f3.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434692042/36145d0b-4720-425c-95f7-e7a582bdde02.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434684233/21635ce4-84a0-4b26-8e04-940fdabb5661.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434705080/4f82b9d0-943e-46d3-b4b0-b2df823fd949.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434701064/60b1dca7-00e0-4ba8-a352-1ae627db0535.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434707448/ccc5c504-590e-4182-9a3b-cad179706a1d.gif" alt class="image--center mx-auto" /></p>
<p>Also, for some reason, a few of the totem renders have bright blue pixels on the extreme right or left side:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434760979/86dc14d3-2fb3-4572-83f1-b55d3be8f3c1.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434767037/cdf0f4fe-148e-4ecf-8191-2c4374873da6.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434774842/4eea45e3-900f-4b0d-9eda-161a8c61e78e.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434781479/a6f0c9e9-ff5e-4e34-8217-0ed34a5e97dc.gif" alt class="image--center mx-auto" /></p>
<p>Furthermore, there are some changes to the lighting that occur. A shadow changes visibly in card J241 based on the totem state. In cards J211 and J213, all of the shadows cast by the totems disappear when the Tay book is unlocked.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434855383/c1572943-23b2-47c4-ae36-c6555834a104.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434861988/6c1d8942-2943-4ca0-be7c-ae255689f25f.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434864850/4a233dc3-c287-4401-a00f-61e0fa3324bc.gif" alt class="image--center mx-auto" /></p>
<p>At the big wahrk head staircase, at card J375, some bright white bits in the jungle disappear when the wahrk is open.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434910225/1276e2a2-c56c-4bbd-b911-266134f32eb7.gif" alt class="image--center mx-auto" /></p>
<p>Also, the angle of the camera in card J381 changes when the wahrk is open. Some foliage also changes, and the lighting overall becomes slightly brighter.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767435264067/18965bf5-b58f-47fc-9e07-79b1da1c8522.gif" alt class="image--center mx-auto" /></p>
<p>At the maglev leading to Temple island, at card J806, the rocks on the far left are different based on whether or not the maglev is present or missing. Also, some of the details on the concrete are slightly moved.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434933578/03ee5371-0824-4079-8bf3-fcc79858d5d2.gif" alt class="image--center mx-auto" /></p>
<p>A few anomalies are present based on whether or not the sunners are present. In card J620, the rocks on the right change when the sunners are absent. Furthermore, in cards J620, J626, and J628, the horizon changes visibly based on the presence of the sunners. (In card J628, the mismatched horizon is only present if you enter the node while the sunners are awake/startled, probably to match the FMV).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434954149/65cee207-f3c6-4961-b54a-0ec72c263456.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434959744/467b08ab-1765-4bd2-88e4-7cd42ed43a9f.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434964419/41edef15-9aab-43cd-873b-4830eae1e091.gif" alt class="image--center mx-auto" /></p>
<p>In card J526, the shape of the background mountains change to match the FMV when you open the submarine hatch.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767434992469/106a4dc2-273d-4619-b9e5-44b0a2f3b323.gif" alt class="image--center mx-auto" /></p>
<p>In card J296, if dock 4 is retracted, a red spot appears on the rocks when the submarine is turned around. It also appears some of the rocks change, but it’s unclear which of these changes are just reflections of the submarine.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767435006229/ec6639e2-3924-47e1-95a1-2ba762d46f97.gif" alt class="image--center mx-auto" /></p>
<p>In card J449, when the gallows shortcut ladder is still raised, submarine dock 2 will be completely missing when retracted (The extended dock is shown using a patch.) Furthermore, when dock 2 is visible in any worldstate, it becomes apparent that the submarine dock is on the wrong side of the walkway, a quirk also visible in J447.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767435022563/88ca5bb4-3894-4ee7-bfa4-ddb41224cad4.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767435057915/7bf63cb8-cba5-4a44-ad9c-53499e02cf56.png" alt class="image--center mx-auto" /></p>
<p>In cards J468, J470, J476, and J581, the diffs reveal that the shortcut ladder isn’t actually raised, the bottom half has merely been erased. Card J334 clearly shows that the ladder should be visible when raised.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767481128643/81211f71-1940-4066-8801-36f04fd80f5e.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767435087378/c338f0e7-ab1c-47d8-8634-dd28d2973a84.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767435102765/ea0d6de7-344a-480a-9ab0-c927ef5abe9d.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767435109805/23772fa6-1eab-47c4-9575-360debade9d3.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767435125051/dc1dc169-4c61-4fa2-8c75-79ff756ddbed.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767553020211/419641c3-1566-481a-a136-87eb7b4d0107.gif" alt class="image--center mx-auto" /></p>
<p>Finally, in card J459, a detached shadow probably belonging to the shortcut ladder is visible on the leftmost cliffside at all times.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767435166151/4de750d0-f86c-4d81-8469-e3039525bedc.gif" alt class="image--center mx-auto" /></p>
<p>Additional articles authored by Twitch are on their way, covering the large number of anomalies we have discovered across the entire game.</p>
]]></content:encoded></item><item><title><![CDATA[Formulaic Delimiters in The Iliad and The Odyssey]]></title><description><![CDATA[This is the third installment in my series on delimiters in languages.
During my middle school studies, I was introduced to Homer’s Iliad and Odyssey. This year, I decided to revisit these epics and a]]></description><link>https://glthr.com/formulaic-delimiters-in-the-iliad-and-the-odyssey</link><guid isPermaLink="true">https://glthr.com/formulaic-delimiters-in-the-iliad-and-the-odyssey</guid><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Fri, 26 Dec 2025 21:21:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766783351694/4dcf40ff-85e1-44ad-9e20-343943ae3884.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This is the third installment in my series on</em> <a href="https://glthr.com/page/delimiters-in-languages"><em><strong>delimiters in languages</strong></em></a><em>.</em></p>
<p>During my middle school studies, I was introduced to Homer’s <em>Iliad</em> and <em>Odyssey</em>. This year, I decided to revisit these epics and acquired a French edition from the <em>Bibliothèque de la Pléiade</em> (translated in 1955 by Victor and Jean Bérard, and Robert Flacelière). This particular edition is noteworthy for its clear demarcation of dialogue: each speech is prefixed with the speaker’s name, as illustrated by the following excerpt available on <a href="https://www.la-pleiade.fr/catalogue/iliade-odyssee/9782070102617#block-feuilleter">the La Pléiade website</a> (see pages 12-13 for example).</p>
<p>The editorial practice of prefacing each speech with the interlocutor’s name greatly aids modern readers, giving the poems a somewhat theatrical feel. In antiquity, in an environment where the audience relied solely on auditory cues, it would have been indispensable to provide clear signals so that listeners could unambiguously follow the dialogues. Therefore, the modern convention of appending the speaker’s name appears anachronistic. It is more likely to be an editorial solution designed to help readers in navigating the complex interchanges of the poem rather than a reflection of Homeric or early performative practice. <strong>This led me to wonder <em>how</em> Homer originally indicated (or even encoded) these shifts in speech.</strong></p>
<p>To answer, I turned to the primary text itself. Because I do not read Ancient Greek, I was primarily concerned with the structural aspects rather than lexical detail. In essence, focusing on the “architecture” of speech rather than dissecting its constituent parts. My goal was to determine if the epic contained explicit <strong>“delimiters”</strong>, or markers distinguishing Homer’s narrative passages from the direct speech of characters.</p>
<p>The idea of delimiters, as outlined in <a href="https://glthr.com/first-order-second-order-expressions-and-delimiters-in-languages">my previous article</a>, posits a general principle: <strong>every interpretive language uses markers that signal transitions from a primary layer to an embedded level of expression</strong>. In contemporary English prose, quotation marks serve precisely this function; they separate the narrator’s description (e.g., <em>It is raining</em>) from a character’s utterance (e.g., <em>Alice said: “It is raining”</em>). I sought to identify analogous signals within the Homeric text that fulfil the same role.</p>
<p>In Homer’s era, punctuation marks such as quotation marks have not been invented yet, and even if they had existed, they would have been invisible to listeners in a purely oral performance. Therefore, my search was confined to intrinsic textual structures that could signal a shift from narrative to direct speech. It is plausible that a delimiter can be discerned through its structural properties when it shares the same alphabetic system as the surrounding text (in other words, when it is not a special character). As demonstrated in the previous article, languages can employ distinctive patterns, like palindromes, to mark boundaries. Thus, I sought regular linguistic constructs within Homer’s poems that could serve this delimiting function.</p>
<p>I used two resources to access the original text, hosted by Tufts University: the <a href="https://www.perseus.tufts.edu/hopper/text?doc=Perseus:text:1999.01.0135">Perseus Digital Library</a> (Greek text and English translation by A.T. Murray) and the <a href="https://beyond-translation.perseus.org/reader/urn:cts:greekLit:tlg0012.tlg001.perseus-grc2:1.1-1.7">Beyond Translation project</a> (Greek text by David B. Munro and Thomas W. Allen). In this article, most of the examples come from the first book of the Iliad and the first book of the Odyssey to keep it readable.</p>
<p>My analysis revealed that <strong>Homer frequently employs formulaic constructions as structural cues for navigating between narrative and direct speech</strong>. These recurring phrases function as <em>delimiters</em> in three distinct roles:</p>
<ol>
<li><p><strong>Opening delimiters</strong> signal that the narrator yields the floor to a character (first‑to‑second order transition).</p>
</li>
<li><p><strong>Closing delimiters</strong> indicate that a character’s speech has concluded, and the narration resumes (second‑to‑first order transition).</p>
</li>
<li><p><strong>Transition delimiters</strong> are used when one speaker responds directly to another within a continuous block of dialogue (second‑to‑second order transition).</p>
</li>
</ol>
<p><strong>Opening Delimiters.</strong> The sentences that start a speech are highly variable. For instance: “… <em>and</em> <em><strong>he implored</strong></em> <em>all the Achaeans, but most of all the two sons of Atreus, the marshallers of the people</em>:” (Iliad 1.15-1.16) or “… <em>yet the thing did not please the heart of Agamemnon, son of Atreus, but he sent him away harshly, and</em> <em><strong>laid upon him a stern command</strong></em>:” (Iliad 1.24-1.25), or “… <em>and</em> <em><strong>spoke</strong></em> <em>swift-footed Achilles</em>” (Iliad 1.58). Homeric opening delimiters lack a fixed form, yet they sometimes contain the enigmatic formulaic phrase “<em>ἔπεα πτερόεντα [winged words]</em>”, like in “<em>ἀγχοῦ δ᾽ ἱσταμένη ἔπεα πτερόεντα προσηύδα: [and she drew near to his side and spake to him winged words:]</em>” (Iliad 5.123). As noted by a researcher: “<em>All the usages of epea pteroenta / epea pteroent’—without any exceptions—introduce the direct discourse of a character in the epic.</em>” (Françoise Letoublon. <a href="https://hal.univ-grenoble-alpes.fr/hal-01469426v1/document"><em>Epea Pteroenta (“Winged Words”)</em></a>. Oral Tradition, 1999, Oral Tradition, 14 (2), pp.321–335, p. 331). While all verses containing ἔπεα πτερόεντα are opening delimiters, all opening delimiters do not necessarily contain this expression.</p>
<p>The variability of opening delimiters does not appear to be surprising. When a bard sang the poem, in a first-order context (as the narrator), they just announced a speaker <em>by preterition</em>. For example: “<em>[Agamemnon] laid upon him a stern command”</em> implicitly announces that Agamemnon is about to speak. The transition to the second-order expression is obvious.</p>
<p><strong>Closing Delimiters.</strong> In contrast, signaling a return to narrative from direct speech proves more problematic for an oral reciter. A speaker might employ a phrase such as “<em>to conclude …</em>,” but such strategies are difficult to systematize. To indicate a closure, Homer had to frequently use repetitions such as “<em>ὣς ἔφατ᾽ [so he spoke]</em>” (Iliad 1.33, 1.43), “<em>ὣς φάτο [so he spoke]</em>” (Iliad 1.188, 1.245), “<em>ὣς ἔφαθ᾽ [so spoke]…</em>” (Odyssey 1.43), “<em>ὣς εἰποῦσ᾽ [so she spoke]</em>” (Odyssey 1.95), “<em>ὣς εἰπὼν [so saying]…</em>” (Odyssey 1.125), or “<em>ἤτοι ὅ γ᾽ ὣς εἰπὼν [when he has thus spoken…]</em>” (Iliad 1.69, 1.101). They appear <em>after</em> the speech, much like closing quotation marks follow the enclosed text. By maintaining a uniform structure, these formulae enable listeners to recognize immediately that the current character has finished speaking and that the narrative voice resumes.</p>
<p><strong>Transition Delimiters.</strong> As outlined earlier, transition delimiters demarcate the boundary between successive speakers: speaker A delivers an utterance; a delimiter signals that speaker B is now about to speak; then speaker B speaks. These delimiters typically begin with the particle τὸν δ᾽ [and him] or τὴν δ᾽ [and her] followed by the speaker and a verb indicating the act of speaking. For instance: “<em>τὴν δ᾽ αὖ Τηλέμαχος πεπνυμένος ἀντίον ηὔδα: [Then wise Telemachus answered her:]</em>” (Odyssey 1.230) or “<em>τὸν δ᾽ ἀπαμειβόμενος προσέφη πόδας ὠκὺς Ἀχιλλεύς: [In answer to him spoke swift-footed Achilles:]</em>” (Iliad 1.84). As with the closing delimiters (ὣς…), the initial word of a transition clause strongly predicts that a delimiter is present.</p>
<p>What I find interesting with these transition delimiters is their frequent pairing with an adjective that characterizes the speaker: the <em>wise</em> Telemachus, the <em>swift-footed</em> Achiles, <em>etc.</em> For instance: “<em>τὸν δ᾽ ἠμείβετ᾽ ἔπειτα</em> <em><strong>ἄναξ ἀνδρῶν</strong></em> <em>Ἀγαμέμνων: [Then</em> <em><strong>the king of men</strong></em>, <em>Agamemnon, answered him:]</em>” (Iliad 1.172). I therefore wonder if Homer <em>had</em> to use these adjectives to satisfy the metrical constraints by adding syllables necessary for dactylic hexameter. I believe that Homer fine-tuned the total syllabic count of these delimiters by using these adjectives as fillers (i.e., “to him/her,   replied/answered/<em>etc.</em>:”). That would explain why he continuously and repeatedly uses them across both poems. This is just a hypothesis, but I would love to know what specialists think of this idea of using adjectives as fillers for delimiters.</p>
<p>The role of these formulaic expressions has already attracted scholar attention. A notable example is the observation of Françoise Letoublon: “<em>In an oral epic one has a compelling need for signals of direct discourse, in principle both before and after the reported speech, so that the audience will be aware that the narrating bard is assuming the voice of his characters. And these signals must be clear, perceptible even by a less than attentive audience: they must therefore be regular enough to play the role that iconic marks of quotation (“...”) play for us in the written text. . . . Following direct discourse, the signals for closure—equivalent to closing quotation marks in the typographical tradition—at times include epos, but other formulas are used more often, most frequently with a verb of speaking in the aorist tense.</em>” (Françoise Letoublon. <a href="https://hal.univ-grenoble-alpes.fr/hal-01469426v1/document"><em>Epea Pteroenta (“Winged Words”)</em></a>. Oral Tradition, 1999, Oral Tradition, 14 (2), pp.321–335, p. 333). In other words, the delimiters function as highly recognizable formulaic markers that cue listeners to a shift from direct to indirect discourse (i.e., first‑order to second‑order expressions) and signal a change of speaker. Letoublon illustrates this point by comparing the delimiters to quotation marks: these formulaic clauses demarcate transitions within the narrative flow.</p>
<p>After having applied this intuition about delimiters acting as a fundamental law of any language <a href="https://glthr.com/llm-delimiters-and-higher-order-expressions">to LLMs</a>, I suggest using it as a conceptual tool for analyzing ancient texts. I believe that the <em>Iliad</em> and the <em>Odyssey</em> demonstrate that delimiters can be systematically encoded using ordinary alphabetical forms <em>in a natural language</em>. This observation raises an intriguing possibility: if similar delimiter structures exist in ancient undeciphered scripts, they might provide a foothold for decipherment. However, successfully applying this strategy requires establishing whether a target script encodes higher-order expressions such as dialogues, which is very speculative.</p>
]]></content:encoded></item><item><title><![CDATA[The Riven (1997) Navigator]]></title><description><![CDATA[Riven as Series of Cards
The original Riven (1997), the sequel to Myst, was developed for the Mohawk engine, resulting in game files stored in a proprietary format. Myst for Mac also used a proprietary format: HyperCard. The success of the Myst Graph...]]></description><link>https://glthr.com/riven-navigator</link><guid isPermaLink="true">https://glthr.com/riven-navigator</guid><category><![CDATA[Riven]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[retrogames]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sat, 20 Sep 2025 07:22:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758349504354/3407fe39-4b61-4382-92df-4e9b76cbf72b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-riven-as-series-of-cards">Riven as Series of Cards</h1>
<p>The original Riven (1997), the sequel to Myst, was developed for the <a target="_blank" href="https://wiki.scummvm.org/index.php/Mohawk">Mohawk engine</a>, resulting in game files stored in a proprietary format. Myst for Mac also used a proprietary format: HyperCard. The success of <a target="_blank" href="https://glthr.com/myst-graph">the Myst Graph project</a> relied on a readily available library (<a target="_blank" href="https://github.com/uliwitness/stackimport">stackimport</a>; more recently: <a target="_blank" href="https://github.com/erkyrath/mystextract">mystextract</a>) that simplified parsing the original game files. This can be explained by the fact that HyperCard <a target="_blank" href="https://hcsimulator.com/">remains relatively popular in the retro computing communit</a>y. By contrast, while the Mohawk format <a target="_blank" href="https://insidethelink.ortiche.net/">is partially documented</a> and <a target="_blank" href="https://doxygen.scummvm.org/d5/d90/namespace_mohawk.html">supported by ScummVM</a>, I found no easy way to navigate Riven’s files as individual <em>cards</em>.</p>
<p>To address this lack of tooling, I am currently developing an application to generate a Riven navigator (just nicknamed “Navigator” in this article) that decomposes the game world into its constituent cards. Each card will contain metadata (card name, <em>etc.</em>), its embedded images, the location of its “hotspots” (that is: the clickable areas within the game), and their associated scripts.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/FTFNuWusZDs">https://youtu.be/FTFNuWusZDs</a></div>
<p> </p>
<p>Decoding these scripts is still ongoing work. Therefore, a full analysis of relationships between cards is not yet possible. I am keeping the Navigator closed source during development but will open source it once it is complete.</p>
<p>But even without full script analysis capability, some initial observations are worth sharing.</p>
<h1 id="heading-first-insights">First Insights</h1>
<p>Unlike Myst, where each card typically embeds a single view (though some cards include partial images for visual effects), Riven allows a card to reference multiple images. For example, consider this card from Temple Island, embedding 20 images (If you squint, you can spot very subtle differences between these images...):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758352464033/8c4a2e8b-8dbc-46ea-8067-fd653bc52a57.png" alt class="image--center mx-auto" /></p>
<p>This means that in Riven, a card generally represents a location, except for special cards like the menu or those representing books. In contrast, a Myst card typically represents a single view. Furthermore, a location in Riven can have different appearances depending on gamer actions: reaching card <em>X</em> might show a closed door, while pulling a lever (without changing locations) could open it. Both views are handled by the same Riven card (<em>X</em>), whereas Myst would require two separate cards: card <em>X</em> for the “door-closed” view and card <em>Y</em> for the “door-open” view.</p>
<p>Based on this distinction, we can start by identifying the cards with the most images associated with them. For consistency with the Myst Graph project, I will use the same naming convention in this section: “<em>{stack name}:{card number}</em>”.</p>
<h2 id="heading-cards-with-the-most-images">Cards With the Most Images</h2>
<p>Here are the cards with the most images, listed in descending order. (To keep this article concise, I am limiting myself to the first five).</p>
<h3 id="heading-menu6-51-images">Menu:6 – 51 images</h3>
<p>This card, belonging to the stack that also contains the menu and books (“aspit”), holds the pages of Catherine’s journal, requiring 51 images.</p>
<h3 id="heading-temple184-24-images">Temple:184 – 24 images</h3>
<p>The entrance to the Temple, with its rotating doors, requires no fewer than 24 images to represent all unique views resulting from different combinations of actions: wall, screen door opened, lever up; wall, screen door closed, lever down; room visible (no wall), screen door opened, lever up; room visible, screen door opened, lever down; room visible, screen door closed, lever up; <em>etc.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758350659278/ffc4d0a9-5dbf-4540-a29d-5ca6c5faa8dc.png" alt class="image--center mx-auto" /></p>
<p>In this example, the image on the right (<code>184 Tgr S1 Bdc Ld. 40</code>) shows the screen door closed in front of the wall, while the image on the left (<code>184 Tgr S1 Ido Ld. 40</code>) represents it open, also in front of the wall. The middle image highlights the differences between these two views, with red pixels indicating exactly where the images differ. This diff image is generated by the navigator, that automatically compares all unique pairs of images within a single card. This technique allows for the detection of even subtle variations. For instance, while the following images may seem identical at first glance, there is a slight difference in scene lighting:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758350683873/950aceb6-c287-4c3f-9816-41cc34b23c19.png" alt class="image--center mx-auto" /></p>
<p>Further subtle differences are shown below.</p>
<h3 id="heading-boiler278-22-images">Boiler:278 – 22 images</h3>
<p>This card contains Gehn’s journal from his lab on Boiler Island, each page requiring an image (similarly to Catherine’s journal), for a total of 22 images.</p>
<h3 id="heading-temple130-and-temple138-20-images-each">Temple:130 and Temple:138 – 20 images each</h3>
<p>The telescope views, shown above, result from the combination of multiple factors (bridge up/down; manhole close/open; reflection of the light on the dome; lever up/down; telescope up/down; <em>etc.</em>).</p>
<h2 id="heading-cards-with-the-most-hotspots">Cards With the Most Hotspots</h2>
<p>As stated above, Riven cards contain <em>hotspots</em>. They represent areas of images with which the gamer can interact with, either to perform actions from a given location (<em>e.g.</em>, pull a lever, push a button), or go to another card (change view, move). The Riven Navigator materializes these areas with rectangles. When clicking a hotspot rectangle, the Navigator shows the script associated with that hotspot, facilitating the analysis of each action.</p>
<p>Here is an example of an image with typical hotspots (usually, there are hotspots on the left and right so that the gamer can turn their head):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758350824767/9e35fc1f-7480-400d-9f83-d3e17cff2140.png" alt class="image--center mx-auto" /></p>
<p>The spinning book domes require many hotspots, as each segment of the slider is a hotspot.</p>
<h3 id="heading-prison42-33-hotspots">Prison:42 – 33 hotspots</h3>
<p>The dome on Prison Island requires 33 hotspots.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758350880226/65328eca-7f0e-4e8d-9eee-8aaadc4e4e7c.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-temple392-jungle338-31-hotspots-each">Temple:392, Jungle:338 – 31 hotspots each</h3>
<p>The domes on Temple Island and on Jungle Island require 31 hotspots.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758350914867/5e4c4745-8f42-4519-ac6a-9aca1e40a23a.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758350924104/571dde74-05c5-49b2-b700-a908d5c7ca55.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-boiler443-garden173-30-hotspots">Boiler:443, Garden:173 – 30 hotspots</h3>
<p>The domes on Boiler Island and Garden Island require 30 hotspots.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758350952620/e27cebfc-9c7b-477e-b3bf-8e9bacff0ab5.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758350956181/6d75b075-2a75-47ae-a887-edfad9699b7e.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-gehn85-25-hotspots">Gehn:85 – 25 hotspots</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758350999080/32bd7cf8-6c05-47a5-9c22-dd944a13eed7.png" alt class="image--center mx-auto" /></p>
<p>This one is interesting. Card <strong>Gehn:85</strong> depicts the top of Gehn’s nightstand. From here, there are only three interactive options: zoom out (hotspot 1), open his journal (hotspots 2 to 24), or examine the metal device (hotspot 25; go to new card). The unusually high number of hotspots for the journal is a consequence of the Mohawk engine’s hotspot implementation. Mohawk only supports defining <em>non-rotated</em> rectangular hotspots. To achieve full coverage of the slanted journal cover, the developers were forced to approximate its shape with a series of smaller, overlapping hotspots, each linked to the same action.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351030936/e49b283a-a30d-4c10-b746-a10ad81629df.png" alt class="image--center mx-auto" /></p>
<p>Card <strong>Boiler:480</strong> employs a similar technique but works in reverse to disable interactive areas. For example, hotspot 4 takes the gamer to the boiler, but it is partially obscured by rocks in the image. To prevent accidental clicks on those non-interactive parts, hotspots 5 and 6 are used: they essentially do nothing (<a target="_blank" href="https://en.wikipedia.org/wiki/NOP_\(code\)">NOPs</a>) and cover the area of hotspot 4 that overlaps with the rock formations. The same approach is applied to the left side of the boiler.</p>
<h2 id="heading-diffs-images-with-minimal-variations-close-to-identical">Diffs: Images with Minimal Variations (Close-to-Identical)</h2>
<p>Riven’s development was remarkably meticulous, and a fascinating way to appreciate that detail is by examining pairs of near-identical images within the game files. These “diffs” (images with only slight variations) reveal how systematically the 3D scenes were rendered and updated to reflect even the smallest changes in gameplay state. Often, these differences are so subtle they are almost invisible without a direct comparison.</p>
<p>Here are a few examples showcasing this impressive attention to detail. (To detect them, focus on the red pixels in the middle gray image).</p>
<h3 id="heading-boiler477-9-pixels">Boiler:477 – 9 pixels</h3>
<p>This example perfectly illustrates Riven’s systematic rendering approach. The images depict the boiler from a distance, focusing on its front gauge (or level indicator) positioned just right of the door. One image shows the gauge empty, while the other displays it full. The change is incredibly subtle, almost imperceptible to the naked eye, with only 9 differing pixels.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351126424/db45fb85-bef9-4c32-bc03-3db1389a469e.png" alt class="image--center mx-auto" /></p>
<p>Animated comparison (GIF):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758617802332/f3bd1525-e89f-41c8-9cab-6f22be51b28a.gif" alt class="image--center mx-auto" /></p>
<p>(In this high-speed animated GIF above, we can see with difficulty that the gauge changes.)</p>
<h3 id="heading-boiler30-12-pixels">Boiler:30 – 12 pixels</h3>
<p>Looking at Gehn’s Lab from the boiler reveals another small but significant variation. This diff highlights whether the ladder hatch is open or closed. It is a logical update reflecting gamer interaction, rendered with impressive precision.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351147316/8eef229c-f530-4be5-8c37-cde9c32bf7ce.png" alt class="image--center mx-auto" /></p>
<p>Animated comparison (GIF):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758618019126/bde4ae35-f711-4769-a2d5-f5f867072944.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-boiler478-26-pixels">Boiler:478 – 26 pixels</h3>
<p>Boiler Island seems to be a hotspot (ah!) for these nuanced changes. Like <strong>Boiler:477</strong>, this image focuses on the boiler’s front gauge at a distance. The game logically tracks whether it is full or empty, and this is reflected in the rendering despite the difficulty of visually discerning the difference.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351165604/a03198a7-faef-45d2-acf8-6362010a27ce.png" alt class="image--center mx-auto" /></p>
<p>Animated comparison (GIF):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758618145480/dc5525d2-7cf9-420e-ac5d-09f891a4f895.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-temple447-46-pixels">Temple:447 – 46 pixels</h3>
<p>Inside the dome, a subtle change occurs depending on whether the bridge has been set. This variation is particularly challenging to spot due to the dark environment.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351187731/8f7f664e-90c6-4a7f-9f15-9148e62d175a.png" alt class="image--center mx-auto" /></p>
<p>Animated comparison (GIF):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758618311685/63fa1c5c-2cb1-4a96-a456-3ef152a429eb.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-boiler12-50-pixels">Boiler:12 – 50 pixels</h3>
<p>And back to the boiler! Once again, we see a variation focused on the front gauge.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351204449/cac387fa-3e97-48f5-81ca-7899f14a2ec6.png" alt class="image--center mx-auto" /></p>
<p>Animated comparison (GIF):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758618444742/b46591e7-4d84-43f2-8d6d-41609fca85d0.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-diffs-images-with-substantial-variations">Diffs: Images with Substantial Variations</h2>
<p>The pairs of images with the most contrast are all related to Jungle Island.</p>
<h3 id="heading-jungle535-jungle536-jungle539-100-difference">Jungle:535, Jungle:536, Jungle:539 – 100% difference</h3>
<p>The submarine provides high contrast in 3 locations, where its dark hull stands out sharply against the blue sky and white clouds.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351246636/73d4965b-d79c-424c-94ed-341939c10a6c.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351260954/3214a31c-44c6-457d-b628-90a2eb35dc72.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351263699/f57ce7ad-a2eb-4b8b-b9b1-43675db691b7.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-jungle530-9999">Jungle:530 – 99.99%</h3>
<p>In one location, the contrast between the submarine and the sky is slightly reduced (99.99%) because some parts of the submarine’s hull match the colors of the landscape on the right.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351281299/523f0cdd-7e6f-441b-b395-05434a49f54b.png" alt class="image--center mx-auto" /></p>
<p><strong>Jungle:154 – 99.96%</strong></p>
<p>This one is particularly interesting, as it is an easter egg (the “<a target="_blank" href="https://mystjourney.com/riven/easter-eggs">walk in the sky</a>”). Again, a contrast against the sky, except that the sky is below us!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351312087/6a8fe7af-f010-4809-8e53-2f44f57e63c3.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-diffs-subtle-variations">Diffs: Subtle Variations</h2>
<p>While reviewing recent diffs, I have been catching some interesting, <em>minor</em> variations. I have not conducted a systematic study yet but wanted to share these observations and solicit the thoughts of the readers on their potential meaning.</p>
<p>Lighting Details</p>
<p>Shadows and light cast by external elements have a minor impact on the lighting on the doors of <strong>Temple:308</strong> and <strong>Temple:543</strong>. It is a second-order effect, but noticeable, nonetheless.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351349987/2c5240fc-c488-4c32-a4c6-97074aae123a.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351366565/acd08f9f-0fba-46f3-b3ff-346a754de699.png" alt class="image--center mx-auto" /></p>
<p>This effect is also visible in <strong>Temple:247</strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351381101/f217eeee-ea8c-428e-858c-e3719a9f22e6.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-maglev">Maglev</h3>
<p>Looking at <strong>Boiler:200</strong> and <strong>Boiler:230</strong>, the Maglev has a faint presence. In one location, it is theoretically visible through the stairs, even if not directly seen. In the other, while partially visible on the right side of the frame, there is an intriguing effect on how the stair framework supports (the metal tubes) are rendered: they appear brighter when the Maglev is present.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351406723/9be8ee03-0429-4864-a3b8-de5e7aa9b973.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351410188/67f23277-749d-4698-8f0d-f692105863a2.png" alt class="image--center mx-auto" /></p>
<p>Animated comparisons (GIFs):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758618574601/77789ece-f000-489b-8fb0-133e9b90f489.gif" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758618637330/25bcb5c1-0c9a-4883-a65d-1249f00c4cfe.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-development-artifacts">Development Artifacts?</h3>
<p>I have also spotted what seem to be “patches” in some comparisons, like this one in <strong>Jungle:194</strong> within the rebels’ tunnel (a rectangular shape). These patches look as though images have been layered on top of the original view rather than being fully re-rendered.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351442647/68ff35a9-ce7e-4c8d-bca4-d975361d738e.png" alt class="image--center mx-auto" /></p>
<p>In <strong>Temple:404</strong>, I am still trying to understand the nature of that slanted line; smaller patches are also present there.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351481580/88d2c17b-4d5d-4918-bfc0-eedfae54fc9f.png" alt class="image--center mx-auto" /></p>
<p><em>(Edit 09/20/25)</em> On the <em>Myst, Obduction, and Beyond</em> Discord server, user “Twitch” (drrg) offers a credible theory: the slanted line could be a narrow beam of light filtering down from above while the elevator is in its lowered position.</p>
<blockquote>
<p>You can look up the elevator shaft <em>only</em> when the elevator is down to see this. But when the elevator is up there's no gap.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758359911920/2f2844ea-a4a3-447e-8b86-847c477b7dbc.png" alt class="image--center mx-auto" /></p>
<p>. . . I also wonder why the button looks different between these two shots considering that the button doesn't ever permanently change states.</p>
<p>. . . I think the button is straight up in the wrong place in one of these renders.</p>
</blockquote>
<h2 id="heading-cards-with-the-longest-scripts">Cards With the Longest Scripts</h2>
<p>In this section, I'm sharing only the script sizes rather than their content, since I do not currently have a robust way to decode the binary data into readable scripts.</p>
<h3 id="heading-boiler284-6390b">Boiler:284 – 6,390B</h3>
<p>This location contains complex logic (complexity here correlates with data size, which holds true in this specific context). It contains 6,390 bytes of script.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351555038/caa9d6d1-388a-44e3-8aeb-69be111a2736.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-temple268-5770b">Temple:268 – 5,770B</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351587692/a4d1312b-09a1-4e19-8768-d110fdc9880c.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-temple443-3666b">Temple:443 – 3,666B</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351622330/622d5364-03ee-4cc9-9fb9-60d8179f02fe.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-temple184-3450b">Temple:184 – 3,450B</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351653330/0ba4374c-2394-44bd-9c98-84bcbd37fc98.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-jungle327-3304b">Jungle:327 – 3,304B</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351697662/2ad9c18b-fe01-46a7-a495-79759b73c4ce.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-prison32-1072b">Prison:32 – 1,072B</h3>
<p>Generally, there is a correlation between the number of images per card and script complexity. However, <strong>Prison:32</strong> stands out as an exception; it contains just over 1KB of data but only includes one image. I do not recall ever encountering this situation in-game (that is: within the closed elevator).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758351731622/d1eba579-f097-4ba9-97bd-96ce19140be1.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This concludes my initial observations made possible by the Riven Navigator. I have not conducted a systematic analysis yet; these findings represent the most readily apparent patterns. My next step is to properly decode the scripts, with the goal of understanding the relationship between cards and potentially even viewing them as interconnected <em>nodes</em>.</p>
]]></content:encoded></item><item><title><![CDATA[Model Collapse and the Need for Human-Generated Training Data]]></title><description><![CDATA[(All opinions herein are solely our own and do not express the views or opinions of our employer.)
Generative AI is poisoning its own well: online content is increasingly generated by AI; this data is used to train new models; those models then gener...]]></description><link>https://glthr.com/model-collapse-and-the-need-for-human-generated-training-data</link><guid isPermaLink="true">https://glthr.com/model-collapse-and-the-need-for-human-generated-training-data</guid><category><![CDATA[AI]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Tue, 05 Aug 2025 21:32:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754429461082/24bd5557-503a-4a89-a261-6e3eb4414caf.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>(All opinions herein are solely our own and do not express the views or opinions of our employer.)</em></p>
<p>Generative AI is poisoning its own well: online content is increasingly generated by AI; this data is used to train new models; those models then generate more online content, which in turn becomes training data. This creates a cycle that risks contaminating the sources AI relies on, potentially leading to diminished originality, amplified biases, and a disconnect from real-world information.</p>
<p>Last year, researchers already raised a critical warning about this trend: “<em>The development of LLMs is very involved and requires large quantities of training data. Yet, although current LLMs . . . were trained on predominantly human-generated text, this may change. If the training data of most future models are also scraped from the web, then they will inevitably train on data produced by their predecessors.</em>” (Shumailov, I., Shumaylov, Z., Zhao, Y. et al., <a target="_blank" href="https://www.nature.com/articles/s41586-024-07566-y">AI models collapse when trained on recursively generated data</a>. Nature 631, 755–759 (2024), p. 755). The authors describe this as leading to a <em>model collapse</em>: “<em>Model collapse is a degenerative process affecting generations of learned generative models, in which the data they generate end up polluting the training set of the next generation. Being trained on polluted data, they then mis-perceive reality.</em>” (Ibid.)</p>
<p>I believe that we may soon need “certified” human-generated data to train models. In other words, model creators, to achieve the best model performances, may benefit from a service that provides training data that is guaranteed to come from human minds. This is because human-generated data possesses qualities currently difficult for AI to replicate: nuance, creativity, common sense reasoning, and robust factual accuracy. Ensuring the source of training data will become increasingly important for them.</p>
<p>This idea raises multiple questions.</p>
<p><strong>How can we ensure that data is generated by humans?</strong> I see one robust approach: a “human-in-a-room” experiment. To guarantee human origin, participants should have no access to AI during data generation. The ideal setup would involve having people work in a controlled environment, such as a library. While establishing such an environment presents logistical challenges (cost, participant comfort), positive incentives (financial compensation or academic credit) could encourage participation.</p>
<p><strong>Who should generate the training data?</strong> Experts in their respective fields. PhDs, post-docs, doctors, mathematicians, historians, and others with deep subject matter knowledge. They would be asked to produce specific types of content (factual statements, creative writing prompts designed to test reasoning, or complex problem sets) with their knowledge, expertise, and available offline resources. This project resembles the work of encyclopedists, <em>but instead of creating a first-order knowledge repository (an encyclopedia), they would create a higher-order one: high-quality training data for AI models</em>.</p>
<p><strong>How should this data be made available?</strong> Two approaches are possible: open access, driven by academic contributions, or a commercial market where data is sold to the highest bidder and not publicly released. Given its potential value (significantly higher quality than readily available online datasets), I anticipate the emergence of a robust market for certified human-generated training data. However, a market-driven approach raises concerns about equitable access and could favor larger companies with greater financial resources. To ensure consistent quality, a rigorous peer review process involving multiple experts would be essential.</p>
<p><strong>Could AI models be trained purely on human-generated data?</strong> While humans cannot generate the sheer volume required for initial model training, a two-step approach is feasible. Models could first be pre-trained on existing large datasets and then fine-tuned using certified human data. During this second stage, we could instruct the model to prioritize learning from the human dataset by weighing its loss function more heavily. This high-quality data would also serve as an invaluable test set for evaluating model performance and identifying biases.</p>
<p>Model collapse is not a distant threat; it is a challenge we must confront now. By prioritizing certified human-generated data, we can unlock new levels of creativity and accuracy when creating new models. The future of AI depends not just on computational power, but on the quality of the knowledge that fuels it, a future where human ingenuity remains at the heart of artificial intelligence.</p>
]]></content:encoded></item><item><title><![CDATA[The Myst Graph, 4: What's Next?]]></title><description><![CDATA[ℹ
I am pleased to announce that I will be presenting the Myst Graph at Mysterium Con 2025, in Atlanta, GA, on August 2nd. This will be an opportunity to discuss the project’s methodology and findings with fellow Myst enthusiasts. You can view the ful...]]></description><link>https://glthr.com/myst-graph-4</link><guid isPermaLink="true">https://glthr.com/myst-graph-4</guid><category><![CDATA[myst]]></category><category><![CDATA[Video games]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sun, 08 Jun 2025 09:05:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749373443550/0da847d1-39a9-4fca-a40b-2242812ab064.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">I am pleased to announce that I will be presenting <a target="_self" href="https://glthr.com/myst-graph-1"><strong>the Myst Graph</strong></a> at <em>Mysterium Con 2025</em>, in Atlanta, GA, on August 2nd. This will be an opportunity to discuss the project’s methodology and findings with fellow Myst enthusiasts. You can view the full schedule and event details <a target="_self" href="https://mysterium.net/schedule/">here</a>.</div>
</div>

<p>“<em>Structurally, narrative shares the characteristics of the sentence without ever being reducible to the simple sum of its sentences: a narrative is a long sentence, just as every constative sentence is in a way the rough outline of a short narrative.</em>” Drawing on <a target="_blank" href="https://www.persee.fr/doc/comm_0588-8018_1966_num_8_1_1113">Roland Barthes’ observation that narrative is more than the sum of its parts</a>, <a target="_blank" href="https://glthr.com/myst-graph-1"><strong>the Myst Graph project</strong></a> explores a similar principle. It demonstrates that the relationship between game world views (the HyperCard cards, individually; the “sentences” of the game) transcends their mere aggregation: The whole (the narrative of the game) is greater than the part (the collection of views and their own puzzles). There is an emergent narrative property at play, one that can only be captured, understood, and analyzed by considering the network-level properties (clustering, re-arrangement of edges, <em>etc.</em>).</p>
<p>My goal with this project has been to reveal a new understanding of Myst by studying it <em>structurally</em>, treating its components as elements within a larger system. I believe that structural analysis offers a powerful tool for video game archaeology, though its applicability is limited to specific contexts—particularly games, and, more generally, interactive media, inherently structured as a graph. <em>The Manhole</em>, released in 1988 by the Millers, could perhaps be a natural application for this approach, mirroring <em>Myst</em>’s foundation in interlinked cards. This approach could be applied to any sufficiently complex application built on HyperCard—a technology created by Bill Atkinson, <a target="_blank" href="https://www.nytimes.com/2025/06/07/technology/bill-atkinson-dead.html">whose recent passing is a significant loss</a>. Representing other games as graphs will inevitably prove more complex; the original structure of Myst facilitated a relatively natural graph creation. To apply such an approach to free-movement games, nodes must shift from static views (files) to map locations, introducing challenges in automatically identifying edges based on how game engines encode location reachability.</p>
<p>In this article, concluding the series of articles dedicated to the Myst Graph, let’s now turn our attention to potential improvements. As mentioned in my <a target="_blank" href="https://glthr.com/myst-graph-3">previous article</a>, further development of <a target="_blank" href="https://github.com/glthr/DeMystify">DeMystify</a> offers significant potential. Several improvements are possible: first, encoding the logic of the HyperTalk scripts into the graph’s edges—for example, indicating that a connection to node <em>1234</em> only occurs when button <em>A</em> is pressed—would add valuable detail. A recent initiative by Andrew Plotkin, <a target="_blank" href="https://blog.zarfhome.com/2025/05/a-graph-of-myst">inspired by this project</a>, makes Myst’s scripts <a target="_blank" href="https://github.com/erkyrath/mystextract">publicly available</a>, which would greatly facilitate this effort. While displaying all these edge constraints in a static graph representation would be impractical due to label size, as the logic would be intricated for some of the edges, they could still be leveraged for other analyses (see below). Second, extracting and processing the areas of views that connect to others could potentially aid speedrunning efforts; encoding distances from a reference point (like the screen center) to clickable areas as edge weights might offer marginal benefits. For example, if the player needs to reach a level, encoding the distance to that lever as an edge weight could potentially shave off fractions of a second. However, given the established speedrunning techniques and well-known shortest paths, any practical gains would likely be minimal. Third, following <a target="_blank" href="https://news.ycombinator.com/item?id=43549792">a suggestion from a reader</a>, an interactive rendering of the graph—replacing nodes with actual view images—would greatly enhance its usability. Users could select two different views and see the most optimal path, see how groups of nodes are interconnected, <em>etc.</em> Finally, and more simply, using <a target="_blank" href="https://github.com/glthr/DeMystify/blob/main/generated/graph.dot">the DOT-encoded graph</a>, anyone could suggest alternative representations to <a target="_blank" href="https://github.com/glthr/DeMystify/blob/main/generated/myst_graph.pdf">the current one</a> by tweaking <a target="_blank" href="https://github.com/glthr/DeMystify/blob/main/generated/graph.dot#L5-L44">the graph’s general configuration</a> and using <a target="_blank" href="https://graphviz.org/docs/layouts/">a different layout engine</a>.</p>
<p>Of course, the Myst Graph could be combined with artificial intelligence in multiple ways. One original idea could be to “retrofy” Myst by transforming it into a text adventure game. An AI model could be used to textually describe each view, and the Graph could be leveraged to ensure that the inner logic of the game is preserved. In other words, make a 90’s game look like a 70’s game using 20’s technology. To process the natural language inputs, a LLM model could naturally be used. On this topic, it makes me wonder, outside the scope of this project, how feasible it would be to create an LLM model able to use a graph as a system prompt or even build the model upon the graph, constraining it not by a list of rules, but by the structure of a graph. That will probably constitute my next project for this website.</p>
<p>I am delighted to see that Myst Graph has generated interest, particularly <a target="_blank" href="https://bsky.app/profile/cyan.com/post/3lp7ty3ns4k26">from Cyan</a>, the company behind Myst, and through <a target="_blank" href="https://news.ycombinator.com/item?id=43549293">an enthusiastic discussion on Hacker News</a>. Initially, I envisioned a more niche audience for this project. I hope to see further creative works built upon this foundation.</p>
]]></content:encoded></item><item><title><![CDATA[The Myst Graph, 3: Creating the Graph Using DeMystify]]></title><description><![CDATA[The Myst Graph is generated by DeMystify, now an open source Go program: github.com/glthr/DeMystify. This third article focuses on the implementation details and shares suggestions for further enhancement. As the article evolves, it will be updated b...]]></description><link>https://glthr.com/myst-graph-3</link><guid isPermaLink="true">https://glthr.com/myst-graph-3</guid><category><![CDATA[myst]]></category><category><![CDATA[graph theory]]></category><category><![CDATA[Video games]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Fri, 09 May 2025 23:17:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746832520096/b2a17fca-cc3d-4781-9949-ed69cf9b8f62.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://glthr.com/myst-graph-1"><strong>The Myst Graph</strong></a> is generated by DeMystify, now an open source Go program: <a target="_blank" href="https://github.com/glthr/DeMystify"><strong>github.com/glthr/DeMystify</strong></a>. This third article focuses on the implementation details and shares suggestions for further enhancement. As the article evolves, it will be updated based on reader feedback and any questions that require clarification. (An email address is shared for this purpose at the end of the article.)
<br /><br /></p>
<h1 id="heading-converting-the-original-hypercard-files">Converting the Original HyperCard Files</h1>
<p>My initial attempt to implement a parser for HyperCard files proved more challenging than expected. Creating a parser based on <a target="_blank" href="https://hypercard.org/hypercard_file_format/">the unofficial file format standard</a> (and <a target="_blank" href="https://hypercard.org/hypercard_file_format_pierre/">its helpful addendum</a>) would have been the best approach but would have required a significant effort for a single use—the Myst HyperCard files only need to be converted once, after all.</p>
<p>Fortunately, <a target="_blank" href="https://github.com/uliwitness/stackimport"><strong>stackimport</strong></a>, a C++ library, demonstrated its ability to reliably convert HyperCard files into XML (and PBM) files. DeMystify leverages this ability and therefore requires converting the original files beforehand into XML files. The path to these files must be provided as a command-line argument. I do not provide these files in the repository as they <em>may</em> be copyrighted: converting the original source code is left as an exercise to the reader.</p>
<p>There is an opportunity for improvement here and an interesting challenge for contributors. The conversion process could be further streamlined by creating a Go-based HyperCard converter library that can generate files or even data structures directly usable by other programs.
<br /><br /></p>
<h1 id="heading-parsing-the-converted-hypercard-files">Parsing the Converted HyperCard Files</h1>
<h2 id="heading-stacks">Stacks</h2>
<p>Demystify parser package translates these XML files into a comprehensive “proto graph” representation of HyperCard objects and their relationships. The parser logic begins by processing each stack, resulting in a slice of stack objects that contain essential information such as the stack’s name (<em>i.e.</em>, its Age), processed HyperTalk scripts, and a mapping of card IDs to their corresponding names (if applicable). Each stack is also associated with a list of cards that are part of it. To illustrate this concept, let’s take a look at an example:</p>
<pre><code class="lang-xml">. . .
<span class="hljs-tag">&lt;<span class="hljs-name">card</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"8277"</span> <span class="hljs-attr">file</span>=<span class="hljs-string">"card_8277.xml"</span> <span class="hljs-attr">marked</span>=<span class="hljs-string">"false"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">""</span> <span class="hljs-attr">owner</span>=<span class="hljs-string">"2702"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">card</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"8776"</span> <span class="hljs-attr">file</span>=<span class="hljs-string">"card_8776.xml"</span> <span class="hljs-attr">marked</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"qbow"</span> <span class="hljs-attr">owner</span>=<span class="hljs-string">"2702"</span> /&gt;</span>
. . .
</code></pre>
<p>In this example, two cards, 8277 and 8776, are part of the stack. One is marked; another is not (this information is not used for the Myst Graph). And 8776 was named “qbow” by its creator. Because the card files do not contain names, the mapping 8776 → “qbow” needs to be provided to the cards parser.</p>
<h2 id="heading-cards">Cards</h2>
<p>After the stacks, the cards are processed. The goal is to generate a card object that contains a card ID, a reference to the stack to which the card belongs, its original name (if any) by using—as just explained—the IDs-names map populated during the analysis of the stacks, the parsed scripts, and the backgrounds—corresponding to the name of the image featured by the card. The image name is consistently present in the <code>&lt;content&gt;</code> tag having ID 1. For instance, the asset embedded in the following card is “Caldera 4-N”:</p>
<pre><code class="lang-xml">. . .
<span class="hljs-tag">&lt;<span class="hljs-name">content</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">layer</span>&gt;</span>background<span class="hljs-tag">&lt;/<span class="hljs-name">layer</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">id</span>&gt;</span>1<span class="hljs-tag">&lt;/<span class="hljs-name">id</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">text</span>&gt;</span>Caldera 4-N<span class="hljs-tag">&lt;/<span class="hljs-name">text</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">content</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">content</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">layer</span>&gt;</span>background<span class="hljs-tag">&lt;/<span class="hljs-name">layer</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">id</span>&gt;</span>7<span class="hljs-tag">&lt;/<span class="hljs-name">id</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">text</span>&gt;</span>card id 31046<span class="hljs-tag">&lt;/<span class="hljs-name">text</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">content</span>&gt;</span>
</code></pre>
<p>Furthermore, the card is internally named following the pattern “{stack}:{ID}”, where “{stack}” represents the ID of the stack containing the card, and “{ID}” is the unique identifier of the card, <a target="_blank" href="https://glthr.com/myst-graph-1">as explained in the first article</a>.</p>
<p>The preprocessing of HyperTalk scripts is, at this stage, minimal. The scripts, in the <code>&lt;script&gt;&lt;/script&gt;</code> tags, are split by line breaks. Similarly to SQL, <a target="_blank" href="https://hypercard.org/HyperTalk%20Reference%202.4.pdf#page=16">scripts are commented out with <code>--</code></a>. When a line is prefixed this way, the command is identified as being <code>disabled</code>.</p>
<h2 id="heading-references-to-other-cards">References to Other Cards</h2>
<p>Subsequently, the relationships between stacks and cards, and between cards, are inferred from the analysis of the scripts. Currently, this analysis relies on regex pattern matching to identify the links between elements. As explained in the previous articles, this analysis is not granular: it does not consider the gameplay or the scripts as a whole. It suffices for a card to point to another to identify a link between them.</p>
<p>HyperTalk provides a convenient mechanism for determining when a card references another card. When they belong to the same stack, the script contains a <code>go to card id {ID}</code> (where “to” is optional). When a card refers to another stack, the pattern is <code>go to card id {ID} of stack “{stack name}”</code> or <code>go to card “{card name}” of stack “{stack name}”</code>.</p>
<p>There is also a more subtle push/pop card mechanism. When a card is pushed onto the stack (<em>push</em> command), <a target="_blank" href="https://hypercard.center/HyperTalkReference/push">it is stored in memory as a reference</a>. A <a target="_blank" href="https://hypercard.center/HyperTalkReference/44101"><em>pop</em></a> operation is, at least the way it has been used for Myst, equivalent to a <code>go to card id {ID}</code>, with the ID of the card that had been stored in memory. This duality characterizes two classes of relationships.</p>
<p>When the first card is pushed (<code>push card</code>), the second card just backtracks to the first.</p>
<p>However, when the first card pushes another card (<code>push card id {ID} of stack “{stack name}”</code>), a more complex transitive relationship emerges, making the player navigate from the first card to the pushed card through an intermediate card (which “pops” to the target card).</p>
<p>For instance, card 100894 from Channelwood Age contains this script:</p>
<pre><code class="lang-plaintext">. . .
push card id 46439 of stack "Myst"
go to card id 44018 of stack "Myst"
. . .
</code></pre>
<p>It pushes card 46439 from a different stack, then opens card 44018 belonging to the Myst Island.</p>
<p>The latter embeds the following script:</p>
<pre><code class="lang-plaintext">on mouseUp
htlock "nobw"
htlock true
vs up
pop card
end mouseUp
</code></pre>
<p>It just pops itself (line 5). But instead of backtracking to Channelwood Age, it goes to the card on the top of the stack: card 44018. Therefore, the path becomes:</p>
<p>Channelwood:100894 (push Myst:46439; go to Myst:44018) → Myst:44018 (intermediary card: library up; pop) → Myst:46439 (bookshelf).</p>
<p>The script analyzer also identifies the cards that feature pages using the following patterns:</p>
<ul>
<li><p><code>put "{digit},A,0" into ALL_Page</code> for blue pages <em>(A is for Achenar)</em></p>
</li>
<li><p><code>put "{digit},S,0" into ALL_Page</code> for red pages <em>(S is for Sirrus)</em></p>
</li>
<li><p><code>put "Atrus" into ALL_Page</code> for the white page</p>
</li>
</ul>
<p>Last, if a card refers to a card that cannot be found, a new virtual card is created. This just means that the virtual card existed up until some point in the stack and was then deleted for the release of the game.</p>
<p>This initial approach may appear rudimentary but is well suited for the HyperTalk grammar and the way the Myst creators have used it. A welcome evolution would be to make the analysis more comprehensive to detect additional associations (typically capturing the navigation from page to page in a book, handled by image substitution). Also, perhaps, encode the gameplay constraints into the edges.</p>
<h2 id="heading-first-layer-of-abstraction-or-creation-of-the-proto-graph">First Layer of Abstraction (or: Creation of the Proto Graph)</h2>
<p>The last action of the parser is to transform stacks and cards into <em>node</em> objects that contain attributes (stack or card; contains colored pages; virtual) and <em>edge</em> objects, also containing attributes (intra-Age or cross-Ages; disabled; self-reference; <em>etc.</em>) and pointing to the two nodes they link. At this point, the resulting structure is not yet a proper graph but rather a preliminary representation of the data that will eventually be used to generate an actual graph.</p>
<p>Note that the parser ensures that the edges are unique. A card can refer several times to another card (<em>i.e.</em>, clicking on different parts of the first card leads to the same target card). I decided, for simplification purposes, to merge edges having the same attributes and directionality. If DeMystify is updated to encode the gameplay into the edges, then it would make sense to unmerge these edges.</p>
<p>Also, the parser translates the properties of the cards and links into node and edge attributes. For a node, the base attribute is either stack or card. Then, if it contains a page, an additional attribute is the color of the page (can be cumulative: both blue and red). Edges can be <code>disabled</code> (they initially existed, but the developers disabled it by commenting it out in the script), <code>notImplemented</code> (the link connects to a node that refers to a card that does not exist in the stack), <code>intraAge</code> or <code>crossAge</code> (depending on whether they refer or not to a node from a different Age). An edge can have special attributes, such as a <code>backtracking</code> attribute (A to B, then B to A). Suppose the edge is restrictively transitive (see the figure on the right of <a target="_blank" href="https://glthr.com/myst-graph-1#heading-transitivity">the Transitivity section of article 1</a>) because the transitivity relationship includes three nodes (A to B to C). In that case, the parser distinguishes between the “tail” of the transitivity and its “head”. The <em>tail</em> is the edge between the initial node and the intermediary node (A to B); the <em>head</em> is the edge between the intermediary node and the target node (B to C). This distinction is helpful for the rendering of the graph (no arrowhead on the tail section of the transitive edge).</p>
<p>Once this process is done, the parser returns an object containing a “bag” of nodes and edges. The next step involves transforming these components into a graph.</p>
<h2 id="heading-second-layer-of-abstraction-creation-of-the-myst-graph">Second Layer of Abstraction: Creation of the Myst Graph</h2>
<p>DeMystify leverages <a target="_blank" href="https://www.gonum.org">the Gonum library</a> to create an instance of a graph. Due to the directionality of the edges, a directed graph is necessary. Moreover, since multiple edges can coexist between two nodes for a specific direction (even when identical edges are merged, they may differ in attributes) and self-loops should also be supported, a multigraph is required. Additionally, I had to consider backtracking edges, which should be excluded during distance calculations. To accomplish this, I utilized weighted edges with regular edges assigned a value of 1 and backtracking edges given an extremely large positive value (positive infinity).</p>
<p>To satisfy these constraints, after evaluating alternative solutions, I determined that <code>multi.WeightedDirectedGraph</code> from Gonum was the most suitable choice. <a target="_blank" href="https://pkg.go.dev/gonum.org/v1/gonum/graph/multi#WeightedDirectedGraph">This graph type effectively accommodates both directed and multigraph structures, as well as weighted edges with distinct properties</a>.</p>
<p>Creating the weighted directed multigraph is straightforward, thanks to the parser’s preprocessing. Nodes are added as-is, while edges default to a weight of 1. On the other hand, backtracking edges are assigned to the <code>math.Inf(1)</code> value to distinguish them from regular edges. Disabled edges are skipped altogether and stored in a map for future rendering.</p>
<p>With the graph now created, we can analyze it.</p>
<p>Three types of analyses are performed: nodes, components, and paths.</p>
<ul>
<li><p><strong>Node analysis.</strong> The node analyzer identifies key nodes based on their in-degree and out-degree. It, therefore, identifies nodes with the most <em>incoming</em> edges (by iterating over the nodes, counting the number of in-degree edges, then comparing with the current maximum), with the most <em>outgoing</em> edges, source nodes (no incoming edges and a nonzero number of outgoing edges), sink nodes (no outgoing edges and a nonzero number of incoming edges), and isolated nodes (no outgoing nor incoming edges).</p>
</li>
<li><p><strong>Components analysis.</strong> This analysis aims to identify the graph components (that is, <a target="_blank" href="https://en.wikipedia.org/wiki/Component_\(graph_theory\)">subgraphs not connected to others</a>). The general idea is to iterate over all nodes and, for each node, traverse (using BFS) the subgraph to which it belongs. By keeping a “map” (in fact, a slice, for making the outcome of the process deterministic) associating the subgraphs with the nodes that compose them, it is possible to identify the components.</p>
</li>
<li><p><strong>Path analysis.</strong> This analysis examines two types of paths: the shortest paths and the longest shortest paths (<em>i.e.</em>, most separated nodes). For the computation of the shortest path, Gonum provides a valuable resource: <code>dijkstraFrom</code>: “<a target="_blank" href="https://pkg.go.dev/gonum.org/v1/gonum/graph/path#DijkstraFrom"><em>DijkstraFrom returns a shortest-path tree for a shortest path from u to all nodes in the graph g. If the graph does not implement Weighted, UniformCost is used</em></a>”. This function uses the Dijkstra algorithm under the hood, taking into consideration the weighted edge values in the graph. As a result, backtracking edges are de facto excluded.</p>
</li>
</ul>
<p>When implementing DeMystify, I experimented with other conceptual tools, such as the identification of <a target="_blank" href="https://en.wikipedia.org/wiki/Bridge_\(graph_theory\)">bridges</a>. But for the purpose of the initial version of the Myst Graph, I found that focusing on just these three dimensions would provide interesting information without making it too complex. It provides a way to categorize the nodes from a graph theory perspective, identify the isolated subgraphs, and compute varieties of paths. I would be very interested in the readers’ findings in addition to these dimensions.</p>
<p>At this point, the Graph Myst is analyzed. It is now time to visually render it.</p>
<h2 id="heading-rendering-the-graph">Rendering the Graph</h2>
<p>At the start of this project, I wanted to use the <a target="_blank" href="https://graphviz.org/doc/info/lang.html">DOT language</a> due to its “natural” affinity for representing graphs. Initially, I used the <a target="_blank" href="https://pkg.go.dev/gonum.org/v1/gonum/graph/encoding/dot">Gonum DOT library</a>. Then, when things became too complex for this library, I assessed other libraries. Unfortunately, none of them were flexible enough to support the requirements: I had no choice but to develop a custom encoding mechanism using string manipulation techniques. This aspect of DeMystify lacks elegance and was a source of frustration during its implementation.</p>
<p>From the generated DOT object, I wanted to keep things simple for the first graph version, so I decided to generate a static file. At first, I tried generating PNG images, but that quickly became impractical because of the sheer size of the graph. Creating a PDF file proved to be a good solution, also having the benefit of being searchable. At first, I did it manually, using the DOT file as input. After experimenting with different CLI tools, I found that <a target="_blank" href="https://graphviz.org/docs/layouts/neato/"><em>Neato</em></a> generated an acceptable output. As for generating the DOT object, I wanted to use a library to generate the PDF à la Neato. Unfortunately, I did not find any: consequently and unfortunately, DeMystify must externally call the Neato binary.</p>
<p>There are several areas of improvement. First and ideally, the rendering process should be refactored to use libraries. This process would be unglamorous, as the behavior would remain unnoticeable, make DeMystify more robust. Second, it would be interesting to generate a <em>dynamic</em> graph. From a visual perspective, I would like to view alternative renderings from contributors. <a target="_blank" href="https://news.ycombinator.com/item?id=43549792">On Hacker News</a>, a commenter suggested rendering the actual images as nodes. While I (really) like this suggestion, I wonder how feasible that would be, particularly from a legal perspective (Myst not being open source).
<br /><br /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>As it stands, the initial prototyping phase focused on quickly demonstrating a working graph representation, though significant improvements are needed. Currently, the project lacks unit tests and relies on external dependencies (manual HyperCard file conversion and binary calls), generating a static graph instead of a dynamic one, <em>etc.</em></p>
<p>Because this is a personal project with limited time commitment, I am actively seeking contributions from the open-source community. Your expertise and passion could dramatically improve DeMystify. I believe that with collaborative effort, DeMystify can truly reach its full potential. <a target="_blank" href="https://glthr.com/myst-graph-4">A fourth article suggests possible directions for the project</a>.</p>
<p>Please contact me at <a target="_blank" href="mailto:myst.graph@glthr.com">myst.graph@glthr.com</a> to discuss potential collaborations, ask about implementation questions, or share your own generated graph (I would reference it in the fourth article with your permission). I am excited to see how the community will shape and evolve this project: feel free to fork the repository and contribute your ideas.</p>
]]></content:encoded></item><item><title><![CDATA[The Myst Graph, 2: Revealing New Findings]]></title><description><![CDATA[Introduction
Previous analyses of Myst’s source code have indicated that some assets are not seen in the game, such as Catherine’s crumpled note and the opened music box in the Mechanical Age. Also, the analysis of commented-out scripts indicates the...]]></description><link>https://glthr.com/myst-graph-2</link><guid isPermaLink="true">https://glthr.com/myst-graph-2</guid><category><![CDATA[myst]]></category><category><![CDATA[Video games]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sat, 29 Mar 2025 01:26:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210007750/53a71bfb-5230-49bd-8e8a-946ea5980a85.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Previous analyses of Myst’s source code have indicated that some assets are not seen in the game, such as <a target="_blank" href="https://mrillustrated.com/not-appearing-in-myst/">Catherine’s crumpled note and the opened music box in the Mechanical Age</a>. Also, the analysis of commented-out scripts indicates the reference to an intriguing “Rattle Ratchet Routine” (Myst:70105), as <a target="_blank" href="https://youtu.be/lacwEuMaQvQ?t=2620">revealed during the “Reverse Engineering Myst” presentation at Mysterium Con 2024</a>:</p>
<blockquote>
<p><em>-- Rattle Ratchet Routine…</em></p>
<p><em>-- It’s no longer with us, but it may be back!!!</em></p>
</blockquote>
<p>These findings offer a fascinating glimpse into the artistic, gameplay, and last-minute decisions made by the Myst creators. By creating the “Myst graph” (<a target="_blank" href="https://glthr.com/myst-graph-1">shared in the previous article</a>), I aim to reveal new insights into the game’s creation by focusing on the relationships between cards instead of just the cards themselves.</p>
<h2 id="heading-visual-indicators">Visual Indicators</h2>
<p>Before diving into the findings, let’s clarify one point related to visual indicators used in this article.</p>
<p>A close-eye icon superimposed on a game screenshot signifies that the asset is not accessible within the game environment. This typically occurs when the asset is featured in a <em>source</em> node with no incoming edges, effectively making it invisible and unreachable. On the other hand, an open-eye icon indicates that the corresponding asset is visible and can be accessed while playing the game (I typically used reachable nodes to show a contrast in viewing perspective). By default, if no indicator is shown, the image is viewable in the game.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743584633605/ea00fe72-36ff-440c-8383-2d1e7c97a2f8.png" alt class="image--center mx-auto" /></p>
<p>Also, for demonstration purposes (especially when the edges-density is high), edges may be highlighted with thick red lines.</p>
<h2 id="heading-disclaimer">Disclaimer</h2>
<p>This project is a personal initiative to analyze and understand the classic Myst game. It is an unofficial and nonlucrative open-source effort created for educational purposes only. This project is not affiliated with Cyan Worlds, Inc. or the original creators of Myst. Any opinions, insights, or analyses presented on this blog are my personal views and do not reflect any official positions or statements of Cyan Worlds, Inc., its affiliates, or any organizations I might be affiliated with.</p>
<p>Before analyzing this graph, please be aware that this series of articles will contain spoilers for the game. If you are new to Myst or plan to play it soon, I recommend skipping these articles and experiencing it firsthand: Myst remains such a fantastic game!</p>
<h2 id="heading-revisions">Revisions</h2>
<p>This article only scratches the surface of these discoveries: there are likely many hidden gems waiting to be uncovered. If readers have additional insights from the analysis of the Myst graph, I encourage them to share their work by emailing me at <a target="_blank" href="mailto:myst.graph@glthr.com">myst.graph@glthr.com</a>. I will do my best to update this article to refer to their publication (if they have a blog, <em>etc.</em>) or, with their permission, feature their contributions by updating this article. I will also update this article with new findings and corrections. These changes are tracked <a target="_blank" href="https://glthr.com/myst-graph">on this page</a>.</p>
<h1 id="heading-general">General</h1>
<h2 id="heading-shortest-theoretical-path">Shortest Theoretical Path</h2>
<p>From the dock (Myst:8336), where the player starts the game, to Dunny:11088, where the player can return to Myst Island, the theoretical minimum distance is 24 edges. (Note that the last node can be subject to interpretation, as some people may consider that the return to the library is the actual ending).</p>
<p>One of the possible paths (there are alternative sub-paths in the library, after Myst:46094) is: Myst:8336 → Myst:2737 → Myst:5026 → Myst:6798 → Myst:7516 → Myst:7902 → Myst:8079 → Myst:3604 → Myst:9960 → Myst:11521 → Myst:46094 → Myst:49958 → Myst:47338 → Myst:49781 → Myst:56926 → Myst:63906 → Myst:71909 → Myst:23753 → Myst:28552 → Dunny Age:6532 → Dunny Age:2943 → Dunny Age:9757 → Dunny Age:4840 → Dunny Age:10805 → Dunny Age:11088.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753540519261/73745165-f5d4-4a3e-bbdc-83afd95a78de.png" alt class="image--center mx-auto" /></p>
<p>As explained <a target="_blank" href="https://glthr.com/myst-graph-1">in the first article</a>, this calculation assumes no gameplay constraints and provides a baseline for understanding the graph’s topology. One instance of the shortest theoretical path <a target="_blank" href="https://github.com/glthr/DeMystify/blob/main/generated/myst_graph_shortest.pdf">can be downloaded here</a>.</p>
<h2 id="heading-longest-shortest-theoretical-path">Longest Shortest Theoretical Path</h2>
<p>The most distant nodes are Selenitic Age:26154 (the tower controls) and Channelwood Age:88814 (the Achenar monitor), which are separated by 130 edges. Coincidentally and anecdotally, these nodes feature screen-based interfaces with button controls. The graph representing this path <a target="_blank" href="https://github.com/glthr/DeMystify/blob/main/generated/myst_graph_longest.pdf">can be downloaded here</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210481063/2d0c5381-5744-43f0-822f-92de19a193e4.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-self-references">Self-References</h2>
<p>Three nodes refer to themselves. These self-loops are used as a mechanism to represent interactive effects instead of a navigational means.</p>
<p>In Myst Island, a self-referencing node represents the view of the control panel of the underground room next to the ship (90877). In Mechanical Age, 34613, representing the hologram in Achernar’s room, also self-loops. The goal of these actions seems to reset the state of the card, which contains variables.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210515937/b11b0fa0-28b5-410b-ba63-4933321105cb.png" alt class="image--center mx-auto" /></p>
<p>In the Mechanical Age, the cage card (14628) redirects to itself too, but for a more practical effect: cycling quickly between cards to simulate, alongside with sound effects, the rendering of electricity arcs: cage, cage electrified, cage electrified bright, then cage again. Interestingly, it also connects to two sink nodes (<em>i.e.</em>, nodes with no outgoing edge, leading nowhere), as they are used for this effect, not for navigation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210527654/94ac208c-854a-420a-9e68-fd6f807a80d2.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-isolated-cluster">Isolated Cluster</h2>
<p>As indicated <a target="_blank" href="https://glthr.com/myst-graph-1">in the previous article</a>, there is an isolated cluster of three nodes unreachable from the game. The puzzle they represent has not been removed from the game; it has just been duplicated and replaced by Mechanical Age:33452, which is part of the game. The key difference is that the duplicating card plays a video named “MU Lower Layer” before going to the card Mechanical Age:35943.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210560798/5d2b25a6-752c-4c72-84e7-f4c5e4e79676.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-cross-ages">Cross-Ages</h1>
<h2 id="heading-mysterious-missing-direct-link-from-myst-island-to-selenitic-age">Mysterious Missing Direct Link from Myst Island to Selenitic Age</h2>
<p>Initially, the Spaceship on Myst Island (78050) was somehow directly linked to the Selenitic Age (31832, virtual). This link was removed (but still visible in the code), and the destination card was removed, too. As of the game’s release, only the exterior path (Myst:14069) is reachable when leaving the ship.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210608706/07113c8b-539e-4712-98c5-f9d32b312f31.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-myst-island">Myst Island</h1>
<h2 id="heading-centrality-of-the-bookshelf">Centrality of the Bookshelf</h2>
<p>With 19 incoming and 10 outgoing edges, the bookshelf (Myst:48938) is a central hub connecting various nodes throughout the graph. This position can be attributed to its role as a gateway for accessing openable books, which are treated as individual nodes that share connections with the bookshelf. This feeling is reinforced by the fact that returning from an Age places the player in front of the bookshelf (see the “transitivity” section of <a target="_blank" href="https://glthr.com/myst-graph-1">the previous article</a>). This is unsurprising in a universe where books are the gateway to other worlds.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210645139/a7bef4b5-6690-43b5-96a7-98bdb381a7c9.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-additional-note-on-the-crumpled-note">Additional Note on the Crumpled Note</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210671760/1afba65b-a3a6-4c02-ad72-76fdc14b3945.png" alt class="image--center mx-auto" /></p>
<p>It is a well-known fact that Catherine’s note is not represented in the final game as a crumpled letter.</p>
<p>The graph reveals that the cards featuring the crumpled note assets are still in the game, like the assets themselves, but not reachable. Looking at the North, the bidirectional edges between 8631 (crumpled note) and this latter and 8079 have been transformed into directed edges <em>from</em> 8631, making it a source node. A bidirectional edge was created between 25971 and 8079, bypassing 8631. Looking to the South, the mechanism is identical: 28321 was transformed into an unreachable source node, while a bidirectional edge was added between 9648 and 91471.</p>
<p>While it does not reveal a new finding here, the graph shows that the assets do not appear in the game because they were not used but rather because the cards that feature them were disconnected, and the gamer cannot reach them.</p>
<h2 id="heading-retrofitted-observation-platform-edges">Retrofitted Observation Platform Edges</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210702560/a7d56026-a0af-4e57-add4-d6ae5d2b4aa5.png" alt class="image--center mx-auto" /></p>
<p>Initially, accessing the codes on the observation platform utilized the same navigational technique as most nodes. The player would reach code 68940, click to zoom in, navigate to the relevant card, and click again to return to the zoomed-out view. A bidirectional edge would be expected between 68940 and each of the cards representing codes.</p>
<p>However, this approach has since been modified to use backtracking edges instead. First, the backtracking functionality was implemented by introducing a “push card” from the origin edge and a “pop card” for the target edge (this mechanism is explained in detail <a target="_blank" href="https://glthr.com/myst-graph-3">in the third article</a>). Second, the directed edge from the code view (<em>e.g.</em>, 66100) to the observation platform (68940) was disabled (prefixed with “--”) as it was no longer necessary.</p>
<p>In summary, bidirectional edges have been replaced with backtracking edges for the zoom in/zoom out effect.</p>
<h2 id="heading-the-planetarium-device-and-the-true-false-source-node">The Planetarium Device and the True-False Source Node</h2>
<p>According to the graph, 42800 is a source node. But something seems off: researching the contents of the cards reveals that the asset, “Planetarium3-Up LightsOn”, representing the device on top of the dentist chair when the lights are on, does not appear to be duplicated in another node. Also, this asset is reachable from the game. Consequently, 42800 must somehow be reachable, too.</p>
<p>Additionally, there is an inconsistency in the vicinity of this node: 42659, which represents a partial view of the device <em>when lights are on</em>, supposedly has a backtracking edge with 41677, which shows the device <em>when lights are off</em>. This is not possible, as the player is expected to switch off the lights using the button next to the door; they cannot do it from the chair.</p>
<p>A review of the cards reveals that 42659 indeed points to 41677, and they have a backtracking dynamic (push/pop card) conformingly to the graph indications. However, 41677 operates an immediate substitution of images to display “Planetarium3-Up Lights On” when lights are on. So, ultimately, this image is directly used by another node than 42800: it is, indeed, a source node, and most of its logic (it would be interesting to study to which extent) is present in 41677.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210739720/5753a0ba-8091-455a-af9a-8e2738dc88cf.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-selenitic-age">Selenitic Age</h1>
<h2 id="heading-unreachable-nodes">Unreachable Nodes</h2>
<p>Several views cannot be seen in the Selenitic Age.</p>
<p>The graph reveals two alternative viewpoints on the crater rim, each representing a nuanced shift in perspective from the views selected for the release.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210928225/4a31facf-88d0-4a2b-81c2-012e93211504.png" alt class="image--center mx-auto" /></p>
<p>Also, regarding the crater rim, while 13290 is a source node, its asset is used by 12953 (qpat3) and, therefore, is viewable in the game. Another inaccessible alternate view relates to the stone forest, offering a slight change of perspective (compare 19922 and 20027).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743210945572/46dcb7e0-96f5-467d-a81b-0e7fa21a0f7e.png" alt class="image--center mx-auto" /></p>
<p>The two last ones show that it was initially envisaged that the player could raise their head to see the wind tunnel access from below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743211100575/b5ab1af8-5835-4c47-abd6-97368a56f918.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-mechanical-age">Mechanical Age</h1>
<h2 id="heading-unreachable-nodes-1">Unreachable Nodes</h2>
<p>Node 38824, featuring asset “East Is. 8-W (Down)”, is inaccessible from within the game, as it is a source node. Consequently, the corresponding asset is not used. If the image seems familiar, it is because it was replaced by a slightly different change of perspective, reachable from 16009. Also, 15738, representing the player looking down to the East, is unreachable.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743211153574/aea888ff-194b-4dd9-bd18-20d7dab8467f.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-channelwood-age">Channelwood Age</h1>
<h2 id="heading-the-back-of-the-wooden-elevator-a-false-sink-node">The Back of the Wooden Elevator, a False Sink Node</h2>
<p>Nodes 45661, 96885, 97270, and 98476 use the same asset: “ElevatorEntered”. The graph analyzer interprets them as sink nodes. Indeed, manual verification shows that these nodes do not have special scripts on their own: their sole purpose is to display the back of the wooden elevator.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743211314351/75ef98cd-e295-4ed3-8e66-f5e0ed10e9db.png" alt class="image--center mx-auto" /></p>
<p>Given that no Myst player has ever been stuck in an in-game elevator, and considering that these nodes are connected to the central cluster, it is clear that they cannot be actual sinks. There must be a way to escape them. By examining the nodes pointing to these pseudo-sink nodes, we can identify the trick: they first reach the node representing the back of the elevator, then utilize a command that, to date, I have been unable to identify in the HyperTalk reference books (“vs left” or “vs right”). Finally, they reach the actual node. This behavior is similar to the electrified cage effect from the Mechanical Age, where the card just serves as a visual transition to the actual view.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743211342606/44f10828-4046-4edd-a4bc-1edd4d719cdb.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-stoneship-age">Stoneship Age</h1>
<h2 id="heading-unreachable-nodes-2">Unreachable Nodes</h2>
<p>In the Stoneship Age, the unreachability of the nodes is subtle. First, 17724, representing an open door in the tunnel, was replaced by 17526, which uses its scripting capacity to display the door open.</p>
<p>Node 53791, featuring the book linking to Myst Island, is interesting because it demonstrates a debugging device used by the Myst creators: the <a target="_blank" href="https://hypercard.org/HyperTalk%20Reference%202.4.pdf#page=93">beep</a>. The beep is noticeable on cards Mechanical Age:33554 (commented out), Mechanical Age:34613 (commented out), Myst:42800 (beep enabled, unreachable node), Selenitic Age:54693 (the Mazerunner ship; still enabled; a card so complex in comparison with the others that it would deserve an article of its own), and Stoneship Age:53791 (beep enabled, unreachable node). 53791 was replaced by 23473, which incorporates a very different script. Anecdotally, they both represent the same image and use different image files.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743211481036/c01e78b3-3d00-4cdf-a276-a5f9818150cf.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-dunny-dni-age">Dunny (D’ni) Age</h1>
<h2 id="heading-the-shared-card-that-goes-nowhere">The Shared Card That Goes Nowhere</h2>
<p>Last (at least for the scope of this article), two very similar locations—at first glance, the images may appear identical, but subtle differences suggest the rendering of different views—, 7365 and 8487, have backtracking edges to a common node, 11283. Because their architecture is the same, it makes sense for them to share the same zoomed-in perspective.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743211512072/cc6d10fc-80f0-4433-a993-2e3577e9a83b.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-next-steps">Next Steps</h1>
<p>These findings presented here are low-hanging fruits revealed by the graph analysis, and probably more will emerge. <a target="_blank" href="https://glthr.com/myst-graph-3">In the third article</a>, I go into great technical details to explain how the Myst graph generator, named DeMystify, works under the hood and open source it.</p>
<p>In preparation for the release of DeMystify, if you have features you would like the graph generator to support, feel free to send your suggestions to <a target="_blank" href="mailto:myst.graph@glthr.com">myst.graph@glthr.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[The Myst Graph: A New Perspective on Myst]]></title><description><![CDATA[Upon reflection, Myst has long been more analogous to a graph than a traditional linear game, owing to the relative freedom it affords players. This is particularly evident in its first release (Macintosh, 1993), which was composed of interconnected ...]]></description><link>https://glthr.com/myst-graph-1</link><guid isPermaLink="true">https://glthr.com/myst-graph-1</guid><category><![CDATA[myst]]></category><category><![CDATA[graph theory]]></category><category><![CDATA[Video games]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sat, 29 Mar 2025 00:58:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743208608379/436285b6-d65e-45ce-879a-5bbd2565d85d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Upon reflection, <a target="_blank" href="https://en.wikipedia.org/wiki/Myst">Myst</a> has long been more analogous to a graph than a traditional linear game, owing to the relative freedom it affords players. This is particularly evident in its first release (Macintosh, 1993), which was composed of interconnected HyperCard cards.</p>
<p>It is now literally one. Here is Myst as a graph:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209037941/20772834-e8ee-4c58-9fad-660cfe6b3f09.png" alt class="image--center mx-auto" /></p>
<p>The Myst graph, a comprehensive representation of the game’s structure that maps the connections between various views and locations, <strong>is downloadable</strong> <a target="_blank" href="https://github.com/glthr/DeMystify/blob/main/generated/myst_graph.pdf"><strong>as a poster-size PDF</strong></a>.</p>
<h1 id="heading-introduction">Introduction</h1>
<h2 id="heading-origins-of-the-myst-graph">Origins of the Myst Graph</h2>
<p>I fondly recall creating, just like many others (including <a target="_blank" href="http://www.imajeenyus.com/computer/20120814_selenitic_age_map/selenitic_age_maze_map.pdf">more skilled cartographers</a>), a rudimentary <em>topological map</em> of <a target="_blank" href="https://dni.fandom.com/wiki/Mazerunner">the infamous Myst Mazerunner puzzle</a> when I was young. The idea recently struck me: could this approach be extended to the entire game? The HyperCard implementation’s deterministic connection of cards seemed tailor-made for an even more abstract map: a <em>graph</em>.  After all, graphs are a powerful tool for information analysis. By having the ability to represent the original Myst as a network of interconnected nodes and edges, we could gain a deeper understanding of how the different aspects of the game are related and uncover new insights into its underlying mechanics.</p>
<p>However, I did not see myself creating it manually; it would have been a monumental and error-prone task. Fortunately, I soon discovered, thanks to the efforts of <a target="_blank" href="https://mastodon.social/@uliwitness@chaos.social">Uli Kusterer</a> (Youtube channel: <a target="_blank" href="https://www.youtube.com/watch?v=CjrBwRnIVG4&amp;list=PLZjGMBjt_VVCl3cftfjeO8qQsSxgDQYO1">“Masters of the Void”</a>) and the <a target="_blank" href="https://www.youtube.com/watch?v=lacwEuMaQvQ">“Reverse Engineering Myst”</a> presentation at <a target="_blank" href="https://mysterium.net">Mysterium Con</a> 2024, that the game’s source code was accessible. That changed everything: how about generating the graph programmatically?</p>
<h2 id="heading-roadmap">Roadmap</h2>
<p><em>(1)</em> In this article, I introduce the concept of the Myst graph, explaining why it is an important tool for analyzing the game, and walk readers through how to interpret the graph, highlighting key concepts and insights. <em>(2)</em> <strong>A second article,</strong> <a target="_blank" href="https://glthr.com/myst-graph-2"><strong>already published</strong></a><strong>, explores new findings that have emerged from analyzing the Myst graph. This article builds on the present article, providing additional context and explanations for the insights gained from the graph.</strong> <em>(3)</em> <a target="_blank" href="https://glthr.com/myst-graph-3">A third article</a> details the technical approach and open source “DeMystify,” the program I created to generate the graph. This provides readers with hands-on experience exploring the Myst graph using code. <em>(4)</em> Last, <a target="_blank" href="https://glthr.com/myst-graph-4">a fourth article</a> discusses how this graph can be used as a starting point for new projects and speculate on what other insights might be gained by further analysis.</p>
<h2 id="heading-minor-terminology-clarifications">Minor Terminology Clarifications</h2>
<p>Before diving into the presentation of the graph, let’s clarify some words. “<em>Dunny</em>” refers to D’ni: this is how the D’ni stack was initially named. Also, I may refer to the Myst Age as the “<em>Myst Island</em>” for variation. When discussing graph theory, I will use the following terms: <em>nodes</em> will refer to the vertices, while <em>edges</em> represent the connections between them. For those unfamiliar with graph theory, just remember that edges are arrows pointing from one box (or view, or card) to another, the nodes, illustrating a relationship between the two.</p>
<h2 id="heading-disclaimer">Disclaimer</h2>
<p>This project is a personal initiative to analyze and understand the classic Myst game. It is an unofficial and nonlucrative open-source effort created for educational purposes only. This project is not affiliated with Cyan Worlds, Inc. or the original creators of Myst. Any opinions, insights, or analyses presented on this blog are my personal views and do not reflect any official positions or statements of Cyan Worlds, Inc., its affiliates, or any organizations I might be affiliated with.</p>
<p>Before analyzing this graph, please be aware that this series of articles will contain spoilers for the game. If you are new to Myst or plan to play it soon, I recommend skipping these articles and experiencing it firsthand: Myst remains such a fantastic game!</p>
<h1 id="heading-basic-properties-of-the-myst-graph">Basic Properties of the Myst Graph</h1>
<h2 id="heading-nodes-and-edges">Nodes and Edges</h2>
<p>The game comprises 6 HyperCard stacks, one for each Age, totaling 1,355 cards. The graph abstracts these into 1,364 nodes connected by 3,189 edges.</p>
<p>The number of nodes does not coincide with the number of cards and stacks because 3 nodes are <em>virtual</em>. They represent cards that were not shipped in the release. They only exist because they are referred to by actual cards. These virtual nodes are Mechanical Age 17673 and 20348, and Selenitic Age 31832.</p>
<p>In this version of the graph, edges do not encode gameplay constraints. A card is considered connected to another if it references that card; the specifics of how the player interacts with the card to reach the other card—whether by simply clicking or solving a complex puzzle—are left implicit. (This choice is discussed later.)</p>
<h2 id="heading-paths">Paths</h2>
<p>Paths are sequences of edges from node <em>A</em> to node <em>B</em>. Because the gameplay has not been integrated into the graph, they do not necessarily represent feasible direct paths in the game. For instance, a puzzle on the path may require performing actions outside the path. In other words, the paths evoked in these articles abstract away all the constraints of the game, except for the connections between cards.</p>
<p>Theoretically, the <em>shortest path</em> between the starting point and the (good) ending consists of 24 edges. From a pure graph distance point of view, reaching the beginning of the game and its ending only takes 24 movements or a change of direction. The <em>most distant nodes</em> (in other words, the longest shortest path in the graph) are separated by 130 edges. In the context of the game, <a target="_blank" href="https://glthr.com/myst-graph-2">these paths are detailed in the second article</a>.</p>
<h2 id="heading-clusters">Clusters</h2>
<p>Interestingly, while the file that encodes the graph does not contain any information about clusters (except for isolated clusters, as explained in the second article, and for the colors of the nodes, for rendering purposes), the rendering engine that transforms it into a PDF, creates an organic (albeit imperfect) formation of clusters: the Myst Island in the middle, and the Ages at the periphery. This is explainable: Myst Island refers to all Ages; each Age only refers to Myst Island.</p>
<p>There is one disconnected cluster outside a few isolated nodes—that, consequently, cannot be reached from within the game. This isolated cluster comprises 3 nodes, including 2 virtual ones, from the Mechanical Age: originally, 17552 had directed edges with 20348 and 17673, both absent from the Mechanical Age stack.</p>
<h1 id="heading-legend-how-to-read-the-graph">Legend: How to Read the Graph</h1>
<h2 id="heading-nodes">Nodes</h2>
<h3 id="heading-colors">Colors</h3>
<p>Each Age has its own color to facilitate the identification of stack-based clusters.</p>
<h3 id="heading-nodes-labels">Nodes Labels</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209347182/07be4e6f-ef5a-423a-b356-707561202f3f.png" alt class="image--center mx-auto" /></p>
<p>When a node abstracts a stack, the name of the stack (corresponding to the Age) is prefixed with “[Stack]”. For instance, “[Stack] Myst” is the stack containing the cards belonging to the Myst Island.</p>
<p>Nodes representing cards are labeled following this pattern: “{Stack}:{ID} [({Original name})] \ {Image name}”.</p>
<p>The cards are identified by an ID (see <a target="_blank" href="https://hypercard.org/HyperTalk%20Reference%202.4.pdf#page=280">page 280 of this reference</a>), a HyperCard-generated integer. Example: “Mechanical Age:12345” refers to card ID 12345 belonging to the Mechanical Age stack. These IDs are unique <em>per stack</em>. Since each stack has its own set of unique IDs, there can be collisions across stacks where two or more IDs are identical between different stacks. This is evident in the Myst graph, where some IDs appear twice across Ages (8059, 8794, 9075, 18304, 19870, 21064, 32302, 33578, 34421, 39710, 52457, 55809, and 74269). As HyperCard automatically increments these IDs within each stack, one can reasonably infer that the greater an ID value is, the closer in time it was created relative to the initial release. However, this relationship may not hold for the underlying assets, as they were rendered separately through a different process. In other words, a “recent” (relative to the release) card does not necessarily mean that the asset it represents was also rendered close to the release.</p>
<p>Cards can have been voluntarily named. When that occurs, the original name is specified between parentheses after the ID. For example, “Myst:81655 (AchenarLose)” was named “AchenarLose” by the Myst creators.</p>
<p>Last, cards represent a view or an image, so they need a reference to an asset filename. This information is displayed just below the label. So “Myst:8336 (dock) \ <em>Dock1-E</em>” means that card ID 8336, belonging to the Myst Island and named initially “dock” features an image called “<em>Dock1-E</em>”. This specific node plays an important role in the game, as it is where the player starts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209447972/35be9198-659d-4aba-8134-e326d8c41f65.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-special-nodes">Special Nodes</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209475311/765207ee-1398-4fb2-a176-fba8c7b3a2c5.png" alt class="image--center mx-auto" /></p>
<p><strong>Sink nodes</strong> serve as destinations, receiving connections but not providing any exit paths. They can be reached within the game, contrary to the following categories of nodes. <strong>Source nodes</strong> act as starting points, sending out connections and initiating new paths without incoming edges. They are unreachable in the game. <strong>Isolated nodes</strong> are unreachable from the rest of the graph, having no incoming or outgoing edges that could be followed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743859289604/635d831f-c443-4d60-bd7b-d706120f3029.png" alt class="image--center mx-auto" /></p>
<p>More specific to the game, nodes with <strong>blue borders</strong> represent views containing blue pages, while those with <strong>red borders</strong> show views having red pages. The unique, <strong>purple-bordered</strong> node denotes a view encompassing blue and red pages, and the <strong>white-bordered node</strong> represents the white page.</p>
<h2 id="heading-edges">Edges</h2>
<h3 id="heading-directions">Directions</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209522084/2b46ae44-812d-4996-ab70-286d53a18dbe.png" alt class="image--center mx-auto" /></p>
<p>The Myst graph is a directed graph. The quasi-totality of the edges is either <strong>directed</strong> (they connect two nodes in one direction) or <strong>bidirectional</strong> (they connect two nodes in both directions). Rarely (3 occurrences), some edges are <strong>self-loops</strong>, connecting a node to itself.</p>
<p>In the game, a <em>bidirectional</em> edge typically represents a direct change of orientation (cardinal points, looking up and down). Movements are usually represented by <em>directed</em> edges, as walking backward is impossible, except for some exceptions (backtracking edges; see below).</p>
<p>Here is a concrete demonstration. In the village of the Channelwood Age, changing between one of the staircase ascending views (17225) and its corresponding pathway view (73038) can be done in a single click as the edge is bidirectional. However, once the player takes the stairs to go up (73346), it is impossible to backtrack directly to the original staircase ascending view without first turning around to look down (73587), then reaching the original pathway view (73038) and last turning around again (17225).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209541672/1d322a52-aec4-436e-97d2-7b3482a33c9b.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753540554135/0576a37f-aea6-4fb8-820d-9dc0698c14d7.png" alt class="image--center mx-auto" /></p>
<p>To summarize, and with some exceptions, from a navigational perspective, while directed edges can be interpreted as walking, bidirectional edges can be interpreted as changing directions.</p>
<h3 id="heading-special-edges">Special Edges</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209571761/5a268f32-8899-4ff6-afc8-1fec1a61fb1e.png" alt class="image--center mx-auto" /></p>
<p>Edges connecting two nodes from different Ages (<strong>cross-Ages edges</strong>) are rendered with a thick line. Edges that connect exclusively two nodes are <strong>backtracking edges</strong>. They act as a device to zoom in/zoom out (one click to enter the next node, one click to go back to the previous node) and are represented by edges with inversed arrow heads and tails. Last, some of the connections are commented out in the source code of Myst: they characterize <strong>disabled edges</strong>, represented by dashed lines with a tee arrow shape.</p>
<h3 id="heading-transitivity">Transitivity</h3>
<p>In Myst, there are three forms of transitivity.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209612487/02eb6814-2c5c-4d6f-93eb-58fd0e203c22.png" alt class="image--center mx-auto" /></p>
<p>First, by default, intermediary nodes are <strong>fully transitive</strong> (edges directions notwithstanding, obviously). If node B can access A, and C and D can access B, then C and D can access A. This is the common navigational mechanism.</p>
<p>Second, some edges are <strong>intransitive</strong>: while B can access A and, reciprocally, A can access B, C and D can access B too, but they cannot access A. This relationship characterizes the backtracking edges.</p>
<p>Lastly, some edges are <strong>partially transitive</strong>: if node B can access A, C, and D can access B, only D can access A. In the realm of Myst, this is only true of the edges making the player return to Myst after completing an Age. Upon returning to Myst Island with the book, they first systematically see the ceiling of the library (Myst:44018). When they click the ceiling, they automatically face the bookshelf (Myst:46439). (The mechanism by which this is done with HyperTalk, the scripting language of HyperCard, <a target="_blank" href="https://glthr.com/myst-graph-3">is detailed in the third article</a>.)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209635499/d455df43-c947-45e0-9150-5e2d22912715.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-limitations">Limitations</h1>
<p>First, as explained in the previous section, the graph does not capture the gameplay mechanics. Although this approach has its limitations, it provides a significant advantage. By separating the gameplay from the graph structure, we can already gain novel insights into Myst without dealing with the complexities of puzzle design. Of course, nothing prevents future versions of the graph from including this logic.</p>
<p>Second, the rendered clusters are not entirely separate from each other. They overlap in their boundaries. For example, Myst:38896 and Myst:30143 encroach on the D’ni Age. While it would be possible to constrain the graph so there is no overlapping, I made the rendering engine as Age agnostic as possible for this initial version. As “DeMystify”, the graph generator, will be released, readers can try different arrangements; I would be very interested in seeing their results.</p>
<p>Third, the graph only captures straightforward relationships between cards and stacks. Analyzing the scripts more granularly and identifying complex relationships, such as image substitutions, would be interesting. This lack of granularity is evident for the books from the bookshelf: the current graph does not sufficiently capture the pages. A future version may improve this aspect.</p>
<h1 id="heading-next-steps">Next Steps</h1>
<p>Now that we have had a chance to familiarize ourselves with the Myst graph, its structure, and how it relates to the game, l<strong>et’s dive deeper</strong> <a target="_blank" href="https://glthr.com/myst-graph-2"><strong>with a second article</strong></a><strong>, which explores some of the insights and discoveries made possible through this new conceptual tool</strong>. The article will take us through the hidden connections and relationships between various game elements, revealing new aspects of Myst that were previously unknown or unseen.</p>
<p>The graph will make one with the game.</p>
]]></content:encoded></item><item><title><![CDATA[LLM Delimiters and Higher-Order Expressions]]></title><description><![CDATA[Delimiters Between First-Order and Higher-Order Expressions
In a previous article (that I recommend reading for context), I introduced the general idea that delimiters marking transitions between firs]]></description><link>https://glthr.com/llm-delimiters-and-higher-order-expressions</link><guid isPermaLink="true">https://glthr.com/llm-delimiters-and-higher-order-expressions</guid><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sat, 22 Feb 2025 14:27:03 GMT</pubDate><content:encoded><![CDATA[<h1>Delimiters Between First-Order and Higher-Order Expressions</h1>
<p>In <a href="https://glthr.com/first-order-second-order-expressions-and-delimiters-in-languages">a previous article</a> (that I recommend reading for context), I introduced the general idea that <em>delimiters marking transitions between first-order and second-order expressions are fundamental to all languages that convey meaning</em>. These ideas were applied to human languages, programming languages, and even genetic code.</p>
<p>In a nutshell,</p>
<ul>
<li><p><strong>First-order expressions</strong> are evaluated</p>
</li>
<li><p><strong>Second-order expressions</strong> serve a different purpose (<em>e.g</em>., they are interpreted, executed, <em>etc.</em>)</p>
</li>
<li><p>They use the same symbols, and therefore need <strong>delimiters</strong> that mark the transition between these categories of expressions</p>
</li>
</ul>
<p>As an example, in the following sentence</p>
<blockquote>
<p>She told him: “it is raining outside.”</p>
</blockquote>
<ul>
<li><p><em>She told him</em> is a first-order expression</p>
</li>
<li><p><em>It is raining outside</em> is a second-order expression</p>
</li>
<li><p>The quotation marks are the delimiters</p>
</li>
</ul>
<p>In this article, I suggest extending this principle to LLMs. More specifically, how are delimiters being used so that LLMs can differentiate between first-order and second-order (or, more generally, higher-order) expressions? Also: why do they “break” models when they are misused?</p>
<h1>LLM Delimiters</h1>
<h2>The de Facto Need for Delimiters by Model Creators</h2>
<p>The role of special tokens in LLMs illustrates this idea neatly. By using these special tokens, LLMs can differentiate between raw text, which is treated as first-order expressions, and text with a specific meaning or purpose, which is represented as higher-order expressions. For instance, the tokenizer injects these tokens into the input sequence to mark where context-dependent information begins or ends. They can be practically assimilated to reserved words.</p>
<p>It is interesting to see, across LLM models, the general need for pairs of delimiters to differentiate the function of the text. Mistral AI uses <a href="https://docs.mistral.ai/guides/tokenization/#control-tokens"><em>control tokens</em></a> (<code>[INST]</code>, <code>[/INST]</code>, <em>etc.</em>). DeepSeek also uses special tokens; notably, its reasoning process is enclosed between <code>&lt;think&gt;</code> and <code>&lt;/think&gt;</code> (<a href="https://github.com/deepseek-ai/DeepSeek-R1/blob/main/DeepSeek_R1.pdf">pdf</a>, p. 6). Llama uses this approach too—for instance: <code>&lt;|begin_of_text|&gt;</code> <a href="https://www.llama.com/docs/model-cards-and-prompt-formats/meta-llama-3">and</a> <code>&lt;|eot_id|&gt;</code>—, as well as <a href="https://huggingface.co/Medragondot/llama-3.2-3b-thinking#reasoning-tags">“reasoning tags</a>” since version 3.2.</p>
<h2>LLM Delimiters in Practice</h2>
<p>Now, let’s focus on <a href="https://www.llama.com/docs/model-cards-and-prompt-formats/meta-llama-3/">a specific pair of delimiters used by Llama 3</a>:</p>
<blockquote>
<p><strong>&lt;|start_header_id|&gt;{role}&lt;|end_header_id|&gt;</strong>: These tokens enclose the role for a particular message. The possible roles can be: system, user, assistant.</p>
</blockquote>
<p>These delimiters make the LLM apprehend the text enclosed between them as a <em>meaningful</em> role for the model and not a mere textual representation of a role.</p>
<p>Example:</p>
<pre><code class="language-plaintext">&lt;|begin_of_text|&gt;&lt;|start_header_id|&gt;User&lt;|end_header_id|&gt;Translate: Bonjour !&lt;|eot_id|&gt;
</code></pre>
<p>The first pair of delimiters (<code>&lt;|begin_of_text|&gt;</code>, <code>&lt;|eot_id|&gt;</code>) indicates to the LLM that this is a prompt to which it will have to reply. The second pair of delimiters (<code>&lt;|start_header_id|&gt;</code>,  <code>&lt;|end_header_id|&gt;</code>) indicates to the LLM that the prompt comes from the <em>user</em>. This brings a special meaning to the model. <em>Here, it apprehends the token “User” as a user-as-a-role</em>.</p>
<p>This is radically different from this fictitious example:</p>
<pre><code class="language-plaintext">&lt;|begin_of_text|&gt;(User) Translate Bonjour !&lt;|eot_id|&gt;
</code></pre>
<p>In this statement, “User” has the same “cognitive” value as “Translate Bonjour !”.</p>
<p>This example also highlights the fact that, in the context of artificial intelligence, there can be <em>higher-order</em> expressions with different layers of purposes:</p>
<ul>
<li><p><code>&lt;|begin_of_text|&gt;</code> marks the transition to the beginning of a piece of information (transition to a first higher-order expression)</p>
</li>
<li><p><code>&lt;|start_header_id|&gt;</code> marks the (immediate) transition to tokens meaning a role (transition to a second higher-order expression)</p>
</li>
<li><p><code>User</code> is a higher-order expression (role)</p>
</li>
<li><p><code>&lt;|end_header_id|&gt;</code> marks the end of this higher-order expression</p>
</li>
<li><p><code>Translate Bonjour !</code> is another higher-order expression (prompt)</p>
</li>
<li><p><code>&lt;|eot_id|&gt;</code> marks the ends of this higher-order expression</p>
</li>
</ul>
<p>The <code>&lt;think&gt;&lt;/think&gt;</code> DeepSeek delimiters are the most recent illustration of the very same mechanism. They tell the model to “reason” about the expression enclosed by them.</p>
<h1>Breaking Models With Delimiters (Or: Using Delimiters in First-Order Expressions)</h1>
<h2>Use And Abuse of LLM Delimiters</h2>
<p>Interestingly, if delimiters are not sanitized from the prompt, they can make the model produce text that appears to be meaningless. This recent web article on “<a href="https://outsidetext.substack.com/p/anomalous-tokens-in-deepseek-v3-and">Anomalous Tokens in DeepSeek-V3 and r1</a>” has <a href="https://outsidetext.substack.com/p/anomalous-tokens-in-deepseek-v3-and?open=false#%C2%A7special-tokens">a Section on special tokens</a>. It shows how, when the user maliciously uses a delimiter as a first-order expression (so: in a prompt), it is erroneously interpreted by the model as a transition from or to a higher-order expression. In other words, when the prompt is not sufficiently sanitized, the flow of a model can be broken <em>by injecting delimiters</em>. In that sense, it is similar (all things considered) to the class of SQL injections that use unique quotation marks. While these SQL injections manipulate the transition between what is evaluated and what is executed, the prompt injections aim at making a model transition to a different semantic dimension.</p>
<p>This behavior is easy to reproduce. I asked a simple question to <a href="https://huggingface.co/meta-llama/Llama-3.2-3B-Instruct">llama-3.2-3b-instruct</a> and injected representations of delimiters in our first-order prompt: “<em>Why does DeepSeek need and ?</em>” <strong>While the model correctly identified the purpose of the question, reproducing the delimiters in its response <em>transformed them into delimiters</em> <em>that de facto led it into higher-order reasoning</em>: it thought he had to think about it.</strong></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740233886879/d1f24e8e-ace8-4760-a6a1-71408fbd64c1.png" alt="(We highlight the first places where the model broke)." style="display:block;margin:0 auto" />

<p>(I highlight the first places where the model broke).</p>
<h2>LLM Delimiters Not Taken Seriously, Yet</h2>
<p>The implications of the relationship between delimiters, first-order expressions, and higher-order expressions are poorly understood in the context of creating LLM models. To address the issues related to the misuse of delimiters by users, the solutions can still be unsophisticated. For instance, <a href="https://github.com/deepseek-ai/DeepSeek-R1">from the DeepSeek R1 repository</a>:</p>
<blockquote>
<p>Additionally, we have observed that the DeepSeek-R1 series models tend to bypass thinking pattern (i.e., outputting "\n\n") when responding to certain queries, which can adversely affect the model's performance. <strong>To ensure that the model engages in thorough reasoning, we recommend enforcing the model to initiate its response with "\n" at the beginning of every output.</strong></p>
</blockquote>
<h1>Conclusion</h1>
<p>We have seen that delimiters are crucial for LLMs to differentiate between first-order and higher-order expressions, explaining why there is a general convergence towards using delimiters regarding the creation of models. Consequently, misuse of delimiters can lead to unexpected behavior or errors in the model, making them “derail” from a given order to another. Because LLMs are evaluators and generators of languages, I believe that LLM models could be greatly improved by studying how models process delimiters and switch between first-order and higher-order expressions. Also, I recommend documenting the delimiters more systematically when a model is released.</p>
]]></content:encoded></item><item><title><![CDATA[Solving “Knight Moves 6” (Jane Street Puzzle, October 2024)]]></title><description><![CDATA[↘
In October 2024, Jane Street released a “Knight Moves 6” puzzle. In this article, we share our approach to solving it. As usual, all opinions herein are solely our own and do not express the views or opinions of our employer.


This puzzle is strai...]]></description><link>https://glthr.com/solving-knight-moves-6-jane-street-puzzle-october-2024</link><guid isPermaLink="true">https://glthr.com/solving-knight-moves-6-jane-street-puzzle-october-2024</guid><category><![CDATA[puzzles]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sun, 13 Oct 2024 15:51:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731137056431/578610a1-d593-4786-b283-155481367e8d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">↘</div>
<div data-node-type="callout-text">In October 2024, Jane Street released a <a target="_self" href="https://www.janestreet.com/puzzles/knight-moves-6-index/">“Knight Moves 6” puzzle</a>. In this article, we share our approach to solving it. <em>As usual, all opinions herein are solely our own and do not express the views or opinions of our employer.</em></div>
</div>

<p>This puzzle is straightforward. <a target="_blank" href="https://www.janestreet.com/puzzles/current-puzzle/">As explained on the webpage</a>, a 6x6 chessboard has squares labeled from A to C, corresponding to positive integers. The puzzle is solved by assigning values to A, B, and C and creating two corner-to-corner trips using knight’s moves so that each trip scores 2024. The method to calculate the score is: <em>(a)</em> moving between two <em>different</em> integers <strong>multiplies</strong> the score by the value the knight is moving to, and <em>(b)</em> moving between two <em>identical</em> integers <strong>increments</strong> the score with the value the knight is moving to. Last constraint: a square can only be visited once per trip (but a square does not necessarily have to be visited).</p>
<p>The official webpage distinguishes between optimal and trips which, while not optimal, are qualifying (<em>i.e.</em>, A + B + C &lt; 50).</p>
<p>To solve the puzzle, we developed a solver which generates random legal knight moves for each trip. The solver also produces combinations of ABC values that satisfy the constraint A + B + C &lt; 50. It calculates the score corresponding to each combination of values for each path. When a score equals 2024, the solver verifies whether an opposing trip exists with the same values and score. If not, the trip is retained in memory until an opposing trip is found with the same values. However, if an opposing trip does exist, the two trips are evaluated against the global best score. If the total score is the lowest recorded, the combination of possible values is updated to ensure that their sum cannot exceed the best sum during the next exploration, thereby facilitating a rapid convergence toward the optimal solution.</p>
<p>It appears that <strong>the best values correspond to the minimal sum 6</strong> (A: 1, B: 3, C: 2) and <strong>the minimal trip length in terms of number of visited squares is 30</strong>, like in this example, rendered by the solver:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731137450045/607f37c8-fd42-42e0-b841-a8ce83c9bffb.png" alt class="image--center mx-auto" /></p>
<p>The solver has also identified several suboptimal solutions, as depicted below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731137715970/5ec0463c-a2f6-437a-8738-1d1b49b09460.png" alt="Sum: 49 / Visited squares: 20" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731137720171/166ea478-eb8f-48fa-9a3e-d748a4ddd757.png" alt="Sum: 25 / Visited squares: 20" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731137800127/ed0897d3-6b4e-485e-a056-c0b84c971786.png" alt="Sum: 11 / Visited squares: 42" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731137813591/74dea31c-b1d3-4ab1-86b3-ec630cc607d2.png" alt="Sum: 9 / Visited squares: 30" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731137829288/67554b3e-8106-4fef-8cde-cdd3a5328db1.png" alt="Sum: 6 / Visited squares: 48" class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[First-Order, Second-Order Expressions, and Delimiters in Languages]]></title><description><![CDATA[There seems to be a pattern in languages (in the broadest sense in the context of this article: a series of symbols that are evaluated or interpreted): a duality between first-order expressions and se]]></description><link>https://glthr.com/first-order-second-order-expressions-and-delimiters-in-languages</link><guid isPermaLink="true">https://glthr.com/first-order-second-order-expressions-and-delimiters-in-languages</guid><category><![CDATA[languages]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sat, 06 Jul 2024 09:08:53 GMT</pubDate><content:encoded><![CDATA[<p>There seems to be a pattern in <em>languages</em> (in the broadest sense in the context of this article: a series of symbols that are evaluated or interpreted): <strong>a duality between first-order expressions and second-order expressions that have different purposes but paradoxically use the same symbols.</strong></p>
<p>This observation is probably self-evident, but I have not seen it explicitly formulated:</p>
<ul>
<li><p>A language primarily uses <strong>first-order expressions</strong>, which are intended to be evaluated. A human reader evaluates a human language to produce a representation of the world. An example of a first-order expression in English is: <em>It is raining outside.</em></p>
</li>
<li><p>A language also needs to represent information used in a capacity different from first-order expressions. This information is represented with <strong>second-order expressions</strong>. Example: <em>“And what is the use of a book,” thought Alice, “without pictures or conversations?”</em>. In this context, this expression has a different purpose: it represents what someone other than the narrator is thinking or saying.</p>
</li>
<li><p>First- and second-order expressions use <strong>the same symbols</strong> (for instance, the English alphabet).</p>
</li>
<li><p>First- and second-order expressions are separated with <strong>delimiters</strong>. <em>Only delimiters identify if an expression is of the first-order kind or of the second-order one</em>. In other words, they signal a transition between purposes.</p>
</li>
<li><p>A language evaluator (<em>e.g.</em>, someone reading a sentence) needs the delimiters to <strong>switch</strong> between first-order and second-order purposes.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720255267474/eccd277f-5e0a-4895-956c-157a658c2e35.png" alt="" style="display:block;margin:0 auto" />

<p>This observation can be applied to many forms of <em>languages</em>. I suggest three of them: natural language, artificial language, and genetic code.</p>
<ul>
<li><p>Since antiquity (for example, <a href="https://en.wiktionary.org/wiki/%E1%B8%8Fd-mdw">ḏd-mdw</a> in Ancient Egypt), human language has used this mechanism to report what someone other than the narrator says. In English, the delimiters are the quotation marks.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720251355038/cc871911-d358-46d8-b4e4-6898142817df.png" alt="" style="display:block;margin:0 auto" />
</li>
<li><p>Many programming languages use this approach to distinguish between <em>evaluated</em> and <em>represented</em> statements. Examples in Python: <code>print(1 + 1) → 2</code> v. <code>print('1 + 1') → 1 + 1</code>.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720251606381/787f1ae9-0de8-4992-be8d-f7d73e5db6ef.png" alt="" style="display:block;margin:0 auto" />

<p><a href="https://www.lispworks.com/documentation/HyperSpec/Body/s_quote.htm">LISP uses an apostrophe</a> (or <code>QUOTE</code>). <a href="https://cs.lmu.edu/~ray/notes/quineprograms/">Quine programs</a> are characteristic of this approach: they usually duplicate and combine first-order symbols (the code evaluated by the computer) and second-order symbols (the code represented by the computer) to blur the line between them as they output the exact representation of the evaluated code. The delimiters are usually quotation marks, too.</p>
</li>
<li><p>When viewing DNA as a language, a similar mechanism appears to be in place. <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC8957550/">Palindrome-like sequences act as delimiters to identify segments with a different purpose than the rest of the DNA</a>. For instance, when a bacteria is infected by a virus, it can store part of the DNA of the latter—in segments named “spacers”—(second-order expression) into its own DNA (first-order expression). Precisely because they are second-order expressions, <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3119747/">these spacers are not evaluated but ultimately (after their transcription) used as signatures by Cas9 to detect known invading viruses</a>. They act as a genetic memory (<em>i.e.</em>, they represent information). More generally, <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5901406/">CRISPR</a> embodies this duality.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720272562325/a60ca7e0-3e42-4a4e-bf11-49c92c53e7bd.png" alt="" style="display:block;margin:0 auto" />

<p>In the scientific literature, analogies with human language focus mainly on the palindromic features (for example, <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC8957550/#S1title">this parallel with the Sator square</a>). <em>While this is true, I believe a more meaningful analogy would be to compare them with quotation marks</em>. Because genetic code has a limited set of “symbols,” it cannot express the delimiters with a special base: the palindromic disposition of the bases constitutes the delimiters. It is as if the English language did not use special characters but palindrome-like sequences instead as delimiters: <code>She told him xyab It is raining outside bayx and therefore . . .</code>. (In this example, <code>xyab</code> would represent the switch from first- to second-order expression [opening delimiter] and <code>bayx</code>, the transition from second- to first-order expression [closing delimiter]).</p>
</li>
</ul>
<p>The following table summarizes this idea.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720272168512/0c151919-49e2-4f03-a64d-ac674182b823.png" alt="" style="display:block;margin:0 auto" />

<p>There are certainly more examples, and <strong>I notably wonder if there is a possible articulation between artificial intelligence and this duality</strong>:</p>
<ul>
<li><p>Can models infer <em>by themselves</em> the existence of first- and second-order expressions and the role of delimiters in switching between them?</p>
</li>
<li><p>Could this duality be leveraged to reinforce models’ security by making them inherently distinguish pure knowledge representation from information evaluation during the training stage?</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Solving “Number Cross 4” (Jane Street Puzzle, May 2024)]]></title><description><![CDATA[↘
In May 2024, Jane Street released a cross-number puzzle, “Number Cross 4” (official solution). In this article, we share our approach to solving it.


Introduction
This puzzle, described here, encompasses various types of constraints:

Row clues to...]]></description><link>https://glthr.com/solving-number-cross-4-jane-street-puzzle-may-2024</link><guid isPermaLink="true">https://glthr.com/solving-number-cross-4-jane-street-puzzle-may-2024</guid><category><![CDATA[puzzles]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Mon, 03 Jun 2024 15:35:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1717016616595/017891d5-fd4a-4a82-a0f0-01c09ef7bb8b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">↘</div>
<div data-node-type="callout-text">In May 2024, Jane Street released a cross-number puzzle, <a target="_blank" href="https://www.janestreet.com/puzzles/number-cross-4-index/"><strong>“Number Cross 4”</strong></a><strong> </strong>(<a target="_blank" href="https://www.janestreet.com/puzzles/number-cross-4-solution/">official solution</a>). In this article, we share our approach to solving it.</div>
</div>

<h1 id="heading-introduction">Introduction</h1>
<p>This puzzle, described <a target="_blank" href="https://www.janestreet.com/puzzles/number-cross-4-index/">here</a>, encompasses various types of constraints:</p>
<ul>
<li><p><strong>Row clues</strong> to which numbers, formed by concatenating adjacent digits from non-shaded cells, must comply (<em>e.g.</em>, square numbers, palindromic numbers).</p>
</li>
<li><p><strong>Rules governing the relationship between digits and regions</strong>: every cell within a region must contain the same digit, and orthogonally adjacent cells in different regions must contain different digits.</p>
</li>
<li><p><strong>Locations of shaded cells</strong>, which need to be sparse and not result in single-digit numbers.</p>
</li>
</ul>
<p><strong>The difficulty of this multi-constraint puzzle comes from the fact that regions are ultimately defined by where the solver more or less arbitrarily places shaded cells.</strong></p>
<h1 id="heading-solution">Solution</h1>
<p>We submitted a unique solution, <code>88243711283</code> (the sum of all the numbers), discovered by the solver on May 12.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717428770854/883b57ec-1884-49e4-b2f4-3bbedd9aed85.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-initial-failed-attempt-use-of-a-smt-solver">Initial Failed Attempt: Use of a SMT Solver</h1>
<p>We initially attempted to solve the puzzle by modeling it as an SMT problem to leverage the <a target="_blank" href="https://github.com/Z3Prover/z3">Z3 solver</a>.</p>
<p>However, this strategy proved unsuccessful for several reasons:</p>
<ol>
<li><p>Our solver is implemented in Go, and <a target="_blank" href="https://pkg.go.dev/github.com/mitchellh/go-z3">the main Z3 library available for this language</a> is <a target="_blank" href="https://github.com/mitchellh/go-z3">no longer maintained</a>, leading to frequent crashes. Additionally, this library does not handle parallel calls well, making it unsuitable for concurrent operations.</p>
</li>
<li><p>Some of the clues posed significant challenges when modeled under these conditions. For example, how can digits be restricted to form one or multiple prime numbers raised to a prime number (3rd-row clue)? More generally, how can the relationship between shaded cells, regions, and numbers be modeled?</p>
</li>
<li><p>The solving process was slower than expected for easily modelable clues (<em>e.g.</em>, generating square numbers), making it unfit to tackle the puzzle as a whole.</p>
</li>
</ol>
<p>Due to these outcomes—probably not because of Z3 itself, but rather how we used it and the limitations of the Go library—we took a different approach. <strong>We chose an iterative solving approach, where:</strong></p>
<ul>
<li><p>(identification) <strong>each row satisfying the constraints is identified</strong>, then</p>
</li>
<li><p>(augmentation) <strong>added to rows that are above it and that satisfy their constraints</strong>, and</p>
</li>
<li><p>(propagation) <strong>alongside its own constraints (digits assigned to regions potentially connected to the next row), propagated to a new instance of the solver</strong> (that is: a goroutine).</p>
</li>
</ul>
<h1 id="heading-successful-approach-solve-and-propagate-valid-rows">Successful Approach: Solve and Propagate Valid Rows</h1>
<p>This second approach (essentially brute forcing but propagating the candidates forward instead of backtracking) proved successful. On a recent laptop, the solver—implementing this approach—traverses the entire valid (in terms of constraints) search space within approximately five seconds and finds the solution in about one second.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717428808092/e69e41f8-4e24-445f-964a-9e163d6f79bd.gif" alt class="image--center mx-auto" /></p>
<p><strong>A significant factor contributing to this efficiency is how the puzzle’s constraints are constructed, enabling the solver to converge quickly toward the solution</strong>. The following diagram illustrates this by showing the number of valid candidates at each level: The initial level (first row) generates just six eligible candidates. The subsequent level produces 816 such candidates. Subsequently, however, the number of valid candidates merely fluctuates between 1 (for four levels) and 8 (for one level).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717428818090/b5a2d103-64ae-4d65-aa1b-b7989c54882b.png" alt class="image--center mx-auto" /></p>
<p>For this reason, a monothread implementation of this solver takes less than 30 seconds on similar hardware to solve. Thus, it retrospectively appears that parallelism is not crucial to solving this puzzle efficiently.</p>
<h2 id="heading-solving-row-by-row">Solving Row by Row</h2>
<p><strong>The initial challenge was determining whether this puzzle could be solved iteratively, row by row <em>(from top to bottom)</em>, or if a more global approach was necessary due to interdependencies between cells from different rows.</strong></p>
<p>Initially, our intuition leaned towards requiring a global approach, given that the definition of regions in a row depends on the shaded cells in the rows above <em>and, critically, below</em>, as demonstrated in the following illustration:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716651844160/52fee9e1-830e-4caa-abac-12ce778bf880.png" alt class="image--center mx-auto" /></p>
<p>In this example, the current row (indicated by the arrow) may ultimately have <em>three</em> (<code>A</code>, <code>B</code>, and <code>C</code>) or <em>four</em> (<code>A</code>, <code>B</code>, <code>C</code>, and <code>C’</code>)—or even more—regions based on the presence of a shaded cell at a specific position within the row below that disjoints (or not) the <code>C</code> cells. This information cannot be determined when the current row is being solved: <em>it is undecidable as it depends on the placement of shaded cells on a row that has not been solved yet</em>.</p>
<p>Upon further consideration, we discovered that the originally joined regions could be <em>temporarily</em> considered disjoint at a row level, meaning they should not necessarily have the same digits. In other words, cells originally belonging to the same region could be assigned to different regions when <em>(a)</em> a top row is not connected to them and <em>(b)</em> they are separated by a region or a shaded cell in the current row. In this example, the original regions <code>A</code>, <code>B</code>, <code>C</code>, become <code>a</code>, <code>b</code>, <code>c</code>, and <code>d</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716751538558/a7547bf2-1008-4782-995c-62695a54e4cd.png" alt class="image--center mx-auto" /></p>
<p>That way, if they are effectively connected by the below (once it is the turn for the solver to consider the row below), any digit assignment that would violate the rule according to which regions should have the same digits would be excluded. Conversely, if they are disjointed, the solver just rejects digit assignments that give these regions the same digit simply by using the heuristic that identifies identical orthogonally adjacent digits.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716795565877/2256b0b0-5cac-4cee-b8df-f7edce7709c5.png" alt class="image--center mx-auto" /></p>
<p><strong>This insight supported the idea that the puzzle could be solved on a row-by-row basis.</strong></p>
<p>Before discussing how the solver traverses the search space, we must first clarify the generation of shaded cells and how regions are encoded.</p>
<h2 id="heading-generating-the-shaded-cells">Generating the Shaded Cells</h2>
<p>The solver represents each configuration of shaded cells as a <em>mask</em> (a bitfield; a slice of booleans) where an unset bit <code>0</code> represents a digit, and a set bit <code>1</code> represents a shaded cell. For example, <code>00100000100</code> corresponds to three groups of digits and two shaded cells: one at x-position 2 and another at x-position 8.</p>
<p>Valid masks must comply with these rules:</p>
<ol>
<li><p>Unset bits cannot be isolated (that is: <code>01---</code>, <code>---101---</code>, or <code>---10</code>) as the numbers must be at least 2 digits long.</p>
</li>
<li><p>Set bits cannot be horizontally adjacent as shaded cells are horizontally sparse (<em>i.e.</em>, <code>---11---</code>).</p>
</li>
<li><p>Set bits cannot be vertically adjacent as shaded cells are also vertically sparse (<em>e.g.</em>, top row <code>0010010</code> is incompatible with the bottom row <code>0010000</code> because of the shaded cells at x-position 2).</p>
</li>
</ol>
<p>The masks are trivially generated by a function that sets the Cartesian product of sets of set and unset bits and filters out masks that do not satisfy the first two rules (the third one—vertical sparsity—can only be enforced, by a different function, between two rows).</p>
<p>This function generates 54 valid masks per row.</p>
<h2 id="heading-encoding-the-regions">Encoding the Regions</h2>
<p>The original regions are defined in a configuration file and denoted using uppercase letters, ranging from <code>A</code> to <code>M</code>. Updated regions are defined row-by-row by updating the original regions based on a given configuration of shaded cells—as explained above. These regions are symbolized by characters that have not yet been utilized to represent the original regions (<code>a</code> to <code>z</code>, <em>etc.</em>).</p>
<p>Regions are updated as follows:</p>
<ul>
<li><p>Initially, when cells in the current row are connected with those in the preceding row—by considering the original regions—, the updated symbols representing these regions in the upper row are carried down to the lower row.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716652799492/2ca16424-8998-4dab-949e-cf9d0f22873a.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Subsequently, unassigned regions are assigned symbols not used so far to assign regions. Cells belonging to the same original region and adjacent to each other are allocated the same symbol. They belong to distinct regions if originally connected but horizontally separated by a region or a shaded cell. This does not preclude them from possessing identical digits if it is subsequently determined that they are connected from below.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716652573914/b5b3b20a-895a-4a94-ba9f-bda891cf0f1a.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<h1 id="heading-solving-process">Solving Process</h1>
<p>Our solver proceeds like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716926201239/545e5bc6-f0e4-439f-942c-b234c8c541c7.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><em>(a)</em> The solving function, initiated in a goroutine, begins by examining a prospective candidate containing 0 to 11 valid rows.</p>
</li>
<li><p><em>(b)</em> If the candidate contains 11 rows, terminate the present exploration since the candidate contains a solution. <em>(Solved).</em></p>
</li>
<li><p><em>(c)</em> Otherwise, iterate through all viable arrangements of shaded cells (as described above).</p>
</li>
<li><p><em>(d)</em> If no such configurations remain, exit the current process: the candidate cannot possibly yield a solution.</p>
</li>
<li><p><em>(e)</em> Compare the present configuration of shaded cells with one in the uppermost row. If at least two pairs are vertically adjacent, return to step <em>(c)</em>, as vertical sparsity is required for shaded cells.</p>
</li>
<li><p><em>(f)</em> Instantiate the current row based on the original regions and the current mask.</p>
</li>
<li><p><em>(g)</em> Generate the regions for the current row by first propagating the regions of interconnected cells from the uppermost row and, secondly, creating new regions for the remaining cells (as described above).</p>
</li>
<li><p><em>(h)</em> Reuse the digits used in the uppermost row for connected regions. For the row’s own regions, generate the Cartesian product of the sets containing digits from 0 to 9 so that each new region is assigned a digit.</p>
</li>
<li><p><em>(i)</em> If no new Cartesian product can be generated, go back to <em>(c)</em> to try a new mask.</p>
</li>
<li><p><em>(j)</em> If the generated digits result in at least one number starting with zero, revert to step <em>(h)</em> until all numbers start with nonzero digits.</p>
</li>
<li><p><em>(k)</em> Verify that digits from adjacent regions are orthogonally different, digits from the same region are identical, and that top row regions retrospectively connected to the current row are identical (see above). If not, return to step <em>(h)</em> until they meet this condition.</p>
</li>
<li><p><em>(l)</em> Ensure that the numbers comply with the row clue. If not, revert to step <em>(h)</em>.</p>
</li>
<li><p><em>(m)</em> At this stage, the row satisfies all constraints (regarding regions, digits, and clues). Consequently, the candidate can be augmented with this new row.</p>
</li>
<li><p><em>(n)</em> A new solving goroutine is subsequently spawned to try solving the augmented candidate, starting from step <em>(a)</em>.</p>
</li>
</ul>
<h1 id="heading-conclusion">Conclusion</h1>
<p>While solving this puzzle, we had come to the conclusion that:</p>
<ul>
<li><p>The current Z3 library for Go is suboptimal. We would love to see a more comprehensive implementation for Go.</p>
</li>
<li><p>It would also perhaps have been easier to use a language like Python with more robust SMT libraries. We believe some of the people who solved the puzzle may have used this approach.</p>
</li>
<li><p>We implemented a multithreaded solver because Go makes it so convenient to use goroutines, but retrospectively, it was unnecessary.</p>
</li>
<li><p>Solving parts of the puzzle and propagating augmented candidates forward is a good alternative to the classic tree exploration and backtracking combination, as it simplifies the programmatic flow in some cases, especially in a multithreading context.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[A Fresh Perspective on Kryptos K4, the Decades-Old Unsolved Code]]></title><description><![CDATA[↘
This article presents our recent discovery of a pattern in Kryptos K4, the famous unsolved code. This potential breakthrough may reveal the use of a distinctive encryption technique. We also present “K4nundrum”, an open-source tool we created to ex...]]></description><link>https://glthr.com/a-fresh-perspective-on-kryptos-k4</link><guid isPermaLink="true">https://glthr.com/a-fresh-perspective-on-kryptos-k4</guid><category><![CDATA[ciphers]]></category><category><![CDATA[cryptanalysis]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sat, 30 Mar 2024 06:36:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1711502066781/05df88d3-3145-474c-ad18-29cc42b16449.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">↘</div>
<div data-node-type="callout-text"><strong>This article presents our recent discovery of a pattern in Kryptos K4, the famous unsolved code. This potential breakthrough may reveal the use of a distinctive encryption technique. We also present </strong>“<a target="_blank" href="https://github.com/glthr/K4nundrum"><strong>K4nundrum</strong></a>”<strong>, an open-source tool we created to explore this idea. </strong>Note: for simplicity purposes in this article, we indifferently use “encryption”, “encipherment”, and “encoding” to convey the idea of transforming the plaintext to the ciphertext (and vice versa, using their antonyms). Also, all opinions herein are solely our own and do not express the views or opinions of our employer.</div>
</div>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711533296310/0ab07043-9080-449a-8ba5-681125a85574.png" alt class="image--center mx-auto" /></p>
<p>The <a target="_blank" href="https://en.wikipedia.org/wiki/Kryptos">“Kryptos” sculpture</a>, dedicated in 1990 by its creator Jim Sanborn, contains four encoded messages. Cryptography enthusiasts have successfully decoded three of them. However, <strong>the elusive fourth ciphertext, K4, remains undeciphered</strong>, presenting a fascinating challenge to the global community of cryptanalysts, even 34 years after its creation.</p>
<p>Since 2010, Sanborn has offered clues (<a target="_blank" href="https://www.nytimes.com/interactive/2020/01/29/climate/kryptos-sculpture-final-clue.html">1</a>, <a target="_blank" href="https://elonka.com/kryptos/">2</a>), hoping someone will solve them. To date, these cribs—as they are referred to within the field of cryptanalysis—are:</p>
<ul>
<li><p>The 22<sup>nd</sup> through 34<sup>th</sup> positions correspond to EASTNORTHEAST,</p>
</li>
<li><p>The 64<sup>th</sup> through 74<sup>th</sup> positions correspond to BERLINCLOCK.</p>
</li>
</ul>
<p>Despite these known plaintext words, K4 has yet to be deciphered. While our attempts have not yielded a solution, <strong>we have just discovered a pattern that may provide valuable insights into the encryption method employed for K4</strong>.</p>
<p>We first share our findings as a bottom line. Then, we detail our approach and evaluate the relevance of this pattern.</p>
<h1 id="heading-the-signs-of-a-segmentation-and-recombination-technique"><strong>The Signs of a Segmentation and Recombination Technique</strong></h1>
<p>We believe that:</p>
<ul>
<li><strong>The letter ‘W’ acts as a separator that splits K4 into six segments</strong>. (We define a <em>segment</em> as a contiguous series of characters that can be deciphered into a partial or a complete sentence, as in this fictitious example: HLMKZJZDDSABW, a ciphertext segment, corresponding to BROWNFOXJUMPS, a plaintext segment.)</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711533303672/3c5e6a35-1c8e-4633-bc4b-1a58c41cfab5.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Consequently, unlike other characters, the letter ‘W’ is not mapped to plaintext characters. Additionally, despite the ciphertext’s total length of 97 characters, the plaintext can contain no more than 91 characters (97 - 6 occurrences of ‘W’).</p>
</li>
<li><p>Each segment belongs to either of two groups (Group A and Group B).</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711533311368/923d70ec-37fa-4964-a00d-15aaf87faae1.png" alt class="image--center mx-auto" /></p>
<ul>
<li>The groups appear alternatively in the ciphertext: A|B|A|B|A|B.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711533318869/80ec62be-dad5-4897-b713-4d1e67bb8837.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>The groups have identical letter frequency distribution shapes for different letters. In each group,</p>
<ul>
<li><p>1 letter appears 5 times,</p>
</li>
<li><p>2 letters appear 4 times each,</p>
</li>
<li><p>4 letters appear 3 times each,</p>
</li>
<li><p>5 letters appear 2 times each, and</p>
</li>
<li><p>9 letters appear 1 time each.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711718591501/f47c2723-ff3f-444e-a240-0a39f6afd4e7.png" alt class="image--center mx-auto" /></p>
<p>Based on these findings, we assume that:</p>
<ul>
<li><p>The passage has been enciphered by a mechanism that preserves the position of the words (substitution cipher). Indeed, a transposition cipher would not be compatible with such a segmentation: the latter requires preserving the original positions.</p>
</li>
<li><p>The transformation of the plaintext into the ciphertext has been carried out using one of the following methods, both illustrating a <strong>“segmentation and recombination” technique</strong>:</p>
<ol>
<li><p><strong>(Hypothesis 1)</strong> The plaintext has first been enciphered <em>(a)</em> and then split into segments divided into two groups <em>(b)</em>, adjusted to have the same letter frequency distribution shapes <em>(c),</em> and recombined with intermediary letters ‘W’ <em>(d)</em>,</p>
</li>
<li><p><strong>(Hypothesis 2)</strong> The plaintext has first been split into segments divided into two groups <em>(a)</em>, then each segment has been enciphered using a technique specific to its group <em>(b)</em>; additionally, the groups have been adjusted to have the same letter frequency distribution shapes <em>(c)</em>, before being recombined with intermediary letters ‘W’ <em>(d)</em>. <em>(Note that, in that case, we do not exclude the possibility that the steps (b) and (c) could have been performed in one step).</em></p>
</li>
</ol>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711734595560/35725cf7-6e98-4f99-a7cf-cffd4dc02ac9.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-our-approach">Our Approach</h1>
<p>While studying <a target="_blank" href="https://www.ciphermachinesandcryptology.com/en/enigmaproc.htm">Enigma</a> (hypothesizing it would help solve K4), we learned that separators represented punctuation marks as the machine only supported alphabetic characters. For instance, <a target="_blank" href="https://github.com/lubeskih/enigma-emulator/blob/master/docs/EOFM.md#space--stop--comma-">the doublet <strong>“YY”</strong> represented a dot</a>. These separators were used for plaintext because of a technical constraint. <em>We pondered if this technique could have been replicated in ciphertext to misdirect analysts.</em></p>
<p>To systematically explore this idea, we created <a target="_blank" href="https://github.com/glthr/K4nundrum"><strong>K4nundrum</strong></a>.</p>
<p>This open-source tool splits K4 on different separators and analyzes the resulting segments. K4nundrum explores separators ranging from ‘A’ to ‘Z’ and uses them to split the ciphertext, subsequently permuting the resulting segments. Every permutation is distributed into different groups (from two groups for the whole ciphertext, up to one group per segment). Ultimately, K4nundrum examines each collection of groups to ascertain whether they exhibit identical letter distribution characteristics.</p>
<p>Using this process, <strong>K4nundrum unveiled that splitting K4 based on the letter ‘W’ produces groups of segments with identical letter frequency distribution shapes</strong> when permuted according to a specific arrangement (alternating groups). As mentioned above, <strong>these two groups are { INFBNYPVTTMZFPK, OBKRUOXOGHULBSOLIFBB, TQSJQSSEKZZ } and { ATJKLUDIA, FLRVQQPRNGKSSOT, GDKZXTJCDIGKUHUAUEKCAR }</strong>.</p>
<h1 id="heading-an-intentional-pattern">An Intentional Pattern?</h1>
<p>This pattern is interesting: it is nonobvious, which would explain why it protected K4’s plaintext so well. At the same time, it would have been relatively easy for the creator of Kryptos to implement (we attempt to demonstrate it with an illustrative example below). But couldn’t it be just coincidental?</p>
<p>To understand how frequently such a pattern would occur in a string of text the length of K4, we have added a feature to K4nundrum that simulates “pseudo-K4s” (enabled with the <code>--sim</code> flag). These pseudo-K4s are random strings of 97 uppercase letters. (By definition, we cannot create a process that would generate perfect K4-like strings, as the underlying encryption method is unknown). While these strings are not generated like the original K4, this simulation gives a general indication of how frequently this phenomenon can be noticeable <em>by an external observer</em>.</p>
<p>Simulations empirically indicate, based on <a target="_blank" href="https://github.com/glethuillier/K4nundrum?tab=readme-ov-file#statistics-on-the-generation-of-1-million-pseudo-k4s">the generation of more than one million pseudo-K4s</a>, that this pattern naturally emerges less than 2% of the time. This probability is low but does not necessarily make this hypothesis a reality. Fortunately, several indicators demonstrate that K4’s pattern has characteristics that make it less likely to occur just by chance.</p>
<p>First, the simulator tends to produce groups containing tiny segments (1 or 2 characters long). For instance, and typically:</p>
<pre><code class="lang-plaintext">&gt; GKUCPFTQBJCKSEVMFWRLWKPEKCZHFFYNPLZZINWAFVKZEMJNRCAUYHUMITLKEZKQGZKBG
PIRRPUGKVSMHXASUIYPVJBZKFUKX

  Group 1:           BGPIRRPUG FU G PE QGZ UCPFTQBJC X ZEMJNRCAUYHUMITL
  Letter Freq.:      U:5 G:4 P:4 R:3 C:3 E:2 B:2 I:2 F:2 Z:2 M:2 J:2 
                     T:2 Q:2 H:1 L:1 X:1 N:1 Y:1 A:1

  Group 2:           CZHFFYNPLZZINWAFV EZ SEVMFWRLW VSMHXASUIYPVJBZ
  Letter Freq.:      Z:5 V:4 F:4 W:3 S:3 H:2 E:2 P:2 L:2 M:2 N:2 Y:2 
                     I:2 A:2 J:1 X:1 R:1 U:1 C:1 B:1
</code></pre>
<p>The random string produces segments such as ‘EZ’ or ‘G’ in this example.</p>
<p>If we assume that segments represent sequences of whole words, it is implausible that the creator of a ciphertext would use small segments. They would additionally needlessly add complexity to the encryption process. When the simulator excludes tiny segments (less than three characters), ~0.5% of the pseudo-K4 passages can be split into appropriately sized groups with identical distribution shapes.</p>
<p>Second, K4’s groups alternate. This additional constraint is even more rarely visible in the segments generated by the simulator: ~0.07% of the pseudo-K4s alternate. <strong>When all these constraints are considered (identical letter frequency distribution shapes, appropriately sized segments, and alternating groups), only ~0.05% of the generated strings are similar to K4</strong>. One way of putting it is that <em>if we consider that this pattern happens merely by chance</em>, on average, Sanborn would have had to create 2,000 Kryptos—each encoding a different message—so that one K4-like pattern would have been noticeable to an external observer.</p>
<p>Third, when we superpose the cribs with K4, the plaintext words are immediately adjacent to other segments. In other words, when mapped to the ciphertext, their first or last letter is directly next to a ‘W’: they start or end a segment. This observation could support the idea of segments representing sequences of whole words.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711643077056/4330fd48-8220-4120-bede-e3fb16a845d6.png" alt class="image--center mx-auto" /></p>
<p>Suppose we assume that segments are to be deciphered to (partial) sentences with complete words (the second segment, consisting of 15 characters, could, for instance, mean “… east-northeast <strong>of</strong> …”). In that case, the probability that such a pattern would emerge just by chance becomes even lower. Speculatively, it may be significant that Jim Sanborn, anxious to see his work finally deciphered, revealed words belonging to two <em>different</em> groups (BERLINCLOCK belonging to Group A, and EASTNORTHEAST to Group B)—as a kind of meta-clue.</p>
<p>For these reasons, a coincidence, while not impossible, seems very unlikely. In other words, <strong>we are confident that this pattern is intentional</strong>.</p>
<h1 id="heading-a-technique-to-mask-letter-frequencies"><strong>A Technique to Mask Letter Frequencies</strong></h1>
<h2 id="heading-fabricating-letter-frequencies"><strong><em>Fabricating Letter Frequencies</em></strong></h2>
<p>Why would Sanborn use such a technique?</p>
<p>First, as mentioned above, a segmenting and recombining technique conveniently produces nonobvious patterns. It is asymmetrical: it does not require a deep knowledge of cryptographic methods for its implementation; at the same time, it easily defeats classic cryptanalysis attacks by adding characters—the separators—that are not mapped to the plaintext and misleading people to think that the ciphertext must be analyzed as a whole. In the context of K4, this is all the truer because the passage is so brief.</p>
<p>Second, this technique can mask the letter frequency by combining multiple letter frequencies. Let’s take ‘K’ for example. It appears eight times in the original K4. By analyzing it under the lens of the segmentation technique, <em>these occurrences result from combining three occurrences in Group A and five in Group B</em>. By adjusting the letter frequency of one (or both) of the groups, it becomes possible to fabricate a new letter frequency distribution. Additionally, the letter separator pollutes the letter frequency distribution.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711533449408/f946dfb4-6bd0-4ce3-b198-cfd773c7f17f.png" alt class="image--center mx-auto" /></p>
<p>We do not know yet the exact mechanism employed to alter the distributions of K4’s groups. It is, however, important <em>not</em> to assume that identical distribution shapes necessarily mean that the deciphered segments of Group A contain the same letters as the ones of Group B (which would be very unlikely for a natural language). On one hand, this mechanism could have been applied to encrypted segments (second-order encoding). On the other hand, <em>intentional</em> spelling errors, similar to other passages like UNDERGR<strong>U</strong>UND (K2) or DESP<strong>A</strong>RA<strong>TL</strong>Y (K3), could have been introduced to slightly modify the letter frequencies. With this simple technique, one letter’s occurrences are decremented while another’s are incremented. For example, I<strong>Q</strong>LUSION (K1) decrements the Ls while incrementing the Qs. Similarly, superfluous letters could have been introduced in the style of <strong>X</strong>CANYOUSEEANYTHING<strong>Q</strong> (K3).</p>
<h2 id="heading-illustration-lets-encrypt-alice"><strong><em>Illustration: Let’s Encrypt Alice</em></strong></h2>
<p>We can test our hypothesis by applying this technique to a fictitious example.</p>
<p>Let’s say we want to encrypt this passage from <em>Alice’s Adventures in Wonderland</em>:</p>
<blockquote>
<p>“You promised to tell me your history, you know,” said Alice.</p>
</blockquote>
<p>The first step is to transform the sentence by removing the punctuation marks and the spaces and by capitalizing the letters:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711533852330/3095a645-6b1b-44ea-98ed-11cd122d9542.png" alt class="image--center mx-auto" /></p>
<p>Then, the sentence is split in half and segmented, with segments alternated between two groups. Last, the letter frequencies of the respective groups are computed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711533709838/5bfdec36-4973-4496-8cea-afbcdbe46be4.png" alt class="image--center mx-auto" /></p>
<p>The distributions are dissimilar. We want to reduce this difference. To that end, we encode the first group using standard Vigenère (obviously, we could have used a different cipher) and <a target="_blank" href="https://cryptii.com/pipes/vigenere-cipher">try several keys</a> to improve the situation. The key <code>SECRET</code> quickly emerges as a good candidate (“YOUPROMISEDTO YOUR YOUKNOW” becomes “QSWGVHEMUVHMG CQLV RGYMESP”):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711632172541/484783a0-4d56-421a-a530-28368958dca9.png" alt class="image--center mx-auto" /></p>
<p>The letter frequency distribution shapes are now similar but have yet to be identical. We need to adjust the <em>second</em> group to make them match. In this case, it is just a matter of adding letters (as the second group is shorter than the first). “TELLME HISTORY SAIDALICE” becomes “<strong>Y</strong>TELLME HISTORY SAIDALICE<strong>X</strong>”. The original plaintext segments remain intelligible.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711632183671/fe2e5f45-c13f-4cdc-b53e-21f94c569c6f.png" alt class="image--center mx-auto" /></p>
<p>We obfuscate the second group for demonstration by merely applying a trivial ROT13 cipher, which preserves the distribution shapes (“<strong>Y</strong>TELLME HISTORY SAIDALICE<strong>X</strong>” becomes “LGRYYZR UVFGBEL FNVQNYVPRK”).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711632190289/1134254c-9ce8-431a-b5d1-c677e5adb0fb.png" alt /></p>
<p>The shapes of the distributions are identical. The last step is to identify a letter not present in the ciphertext segments (‘X’, for instance) and recombine the segments by alternating them:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711632196711/4d04943e-c56b-4ebc-996f-1f9281307392.png" alt class="image--center mx-auto" /></p>
<p>The outcome is a ciphertext that cannot be attacked with classic methods because of the fabricated letter frequency.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711729970534/e34de21d-e9e5-403a-a52f-794a36337449.png" alt class="image--center mx-auto" /></p>
<p>Importantly, this example demonstrates that the two plaintext groups are not required to contain the same letters. Also, the separator that combines the segments is one of the most frequent letters in the ciphertext, even though it is the odd one out.</p>
<h1 id="heading-next-steps">Next Steps</h1>
<p>Once the technique is known, the separator is identified, and the correct groups are determined, deciphering this kind of code is a matter of understanding how each group has been “encoded” before attacking it with classic cryptanalysis methods.</p>
<p>This is the path we would like to follow to crack K4.</p>
]]></content:encoded></item><item><title><![CDATA[Is Artificial Intelligence Suitable to Solve Cain’s Jawbone? (Part III)]]></title><description><![CDATA[All opinions herein are solely my own and do not express the views or opinions of my employer. This article contains a potential solution to Cain’s Jawbone. If you plan to solve it by yourself, stop reading it.

Part I: https://guyomel.hashnode.dev/i...]]></description><link>https://glthr.com/is-artificial-intelligence-suitable-to-solve-cains-jawbone-part-iii</link><guid isPermaLink="true">https://glthr.com/is-artificial-intelligence-suitable-to-solve-cains-jawbone-part-iii</guid><category><![CDATA[Artificial Intelligence]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sun, 31 Dec 2023 16:49:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1704041360969/d593a652-b43b-40b6-97c3-7fd94e538fd5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>All opinions herein are solely my own and do not express the views or opinions of my employer. This article contains a potential solution to Cain’s Jawbone. If you plan to solve it by yourself, stop reading it.</em></p>
<ul>
<li><p>Part I: <a target="_blank" href="https://guyomel.hashnode.dev/is-artificial-intelligence-suitable-to-solve-cains-jawbone-i">https://guyomel.hashnode.dev/is-artificial-intelligence-suitable-to-solve-cains-jawbone-i</a></p>
</li>
<li><p>Part II: <a target="_blank" href="https://guyomel.hashnode.dev/is-artificial-intelligence-suitable-to-solve-cains-jawbone-part-ii">https://guyomel.hashnode.dev/is-artificial-intelligence-suitable-to-solve-cains-jawbone-part-ii</a></p>
</li>
</ul>
<p>We have now reached the conclusion of our initial novice examination into the role of artificial intelligence in solving—or, at the very least, aiding in the resolution of—<a target="_blank" href="https://en.wikipedia.org/wiki/Cain%27s_Jawbone"><em>Cain’s Jawbone</em></a>. An article summarizing this research’s findings will be available on <a target="_blank" href="https://www.glthr.com">glthr.com</a> shortly. In the meantime, we share the latest results here.</p>
<p>Driven by <a target="_blank" href="https://github.com/glethuillier/Jawbone/blob/main/2_BERT_binary_classification_bill.ipynb">the excellent performance of the binary classification of pages belonging to one of the narrators (Bill Hardy)</a>, we tried to extend it using multiclass classification. <a target="_blank" href="https://github.com/glethuillier/Jawbone/blob/main/3_BERT_multiclass_classification.ipynb">This attempt resulted in abysmal performance, almost undistinguishable from randomness</a>, as demonstrated by this confusion matrix (each label corresponds to a narrator).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704041123071/2fce14ed-e89e-4aa4-8e68-871f324f4cfd.png" alt class="image--center mx-auto" /></p>
<p>Subsequently, we performed a binary classification <a target="_blank" href="https://github.com/glethuillier/Jawbone/blob/main/4_BERT_binary_classification_all.ipynb">of all narrators</a> to identify the root causes of this duality between excellent binary classification and poor multiclass classification. To that end, we trained a BERT model for each narrator, computed their confusion matrix, and represented the incorrect predictions.</p>
<p>Here are the results:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704039324971/e3304dcd-58c9-4741-88a9-c04e6267b62e.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704039329971/628799bb-d669-4ba8-8a94-ec4697b31ee3.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704039334454/41c1eca3-a0c3-455c-a968-ab4bbaf30efe.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704039339574/857965e5-7a10-4061-aa14-091ee43e7d95.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704039343848/914b2446-64d5-40a0-a06c-791d98bfe954.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704039348405/b7581c69-4134-4896-8207-5d79d0c1087d.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704039351539/df10b2c5-e30b-4b28-9f16-dfd4302ec9e7.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704039356299/a21067aa-6f75-476d-912a-8f11e762076a.png" alt class="image--center mx-auto" /></p>
<p>The models perform poorly for all narrators except Bill Hardy and, to a lesser extent, Henry the Dog. These results confirm <a target="_blank" href="https://github.com/glethuillier/Jawbone/blob/main/1_k-means.ipynb">the initial K-means analysis</a>.</p>
<p>To explain Bill Hardy’s model’s performance, we suggest that Bill’s pages have a textual characteristic that makes them easily identifiable. This characteristic is grammatical: <em>Bill Hardy is the only narrator who narrates in the present tense.</em> All other narrators narrate in the past tense.</p>
<p>This hypothesis was evaluated <a target="_blank" href="https://github.com/glethuillier/Jawbone/blob/main/5_Grammatical_tenses_assessment.ipynb">by grammatically transforming some of the narratives from various authors into the present tense and altering a portion of Bill’s narratives to be in the past tense</a>. Bill’s model erroneously labels these pages, categorizing non-Bill pages written in the present tense as belonging to Bill. It also categorizes Bill’s pages written in the past tense as not belonging to Bill.</p>
<p>This experiment demonstrates that we ultimately trained this model to identify whether pages are mainly written in the present tense. In other words, <strong>Bill’s model probably represents a function that produces primarily results based on the grammatical tense predominant on a given page.</strong></p>
<p>The way Henry’s (or also John Walker’s) model operates is more puzzling. We have not identified an apparent textual characteristic that would explain its performance. Readers of this article may be interested in figuring it out—please let us know!</p>
<p>In conclusion, in the context of this project, we failed to create a model that autonomously assigns the pages to their respective narrators. Several factors may explain why.</p>
<p>The first reason is that this is our first machine-learning project: a more experienced reader would certainly have trained a more robust model.</p>
<p>However, we do not think a model can achieve the primary goal for the reasons evoked in the first article. A significant issue is the fact that most of the clues are obscure, extratextual, and highly subject to subjective interpretation. For instance:</p>
<ul>
<li><p>“<em>there was the oldest brass in England, saying: SIRE:IOHAN:DAUBERNOUN:CHIVALER:GIST:ICY:DEV:DE:SA:ALME:EYT:MERCY</em>”, referring to Stoke d’Abernon, Surrey <em>(extratextual reference)</em></p>
</li>
<li><p>“<em>Apparently the person who slept in the lock-up at that county town on the Severn, or perhaps woke, would hear this time,</em>” meaning 8 AM <em>(obscure reference)</em></p>
</li>
<li><p>“<em>I sat and faced the old school colours frozen there before me? Green and white and rose, grit, wisdom and reliability, the find old Head, as we called him, had quipped it,</em>” a reference to a frozen dessert <em>(highly subjective interpretation)</em></p>
</li>
</ul>
<p>To be a suitable candidate for solving this puzzle, a model should be able to analyze and contextualize both textual and extratextual clues.</p>
<p>But that would not be enough.</p>
<p>An ideal model should also perform <em>second-order reasoning</em> to characterize the interactions between the narrators. That is: going beyond the mere labelling of text tokens. For instance, while a good model would identify the existence of spoonerism in Jasmine’s monologue (<em>e.g.</em>, “<em>Useful, courteous little chip of a bat</em>”, <a target="_blank" href="https://glthr.com/cj/#27">page 27</a>), a great model would be able to identify when a different narrator refers to Jasmine’s spoonerisms: “<em>He said he’d put that right, but he couldn’t find the silly old jossers, as Jasmine might so easily have called them</em>” (<a target="_blank" href="https://glthr.com/cj/#99">page 99</a>).</p>
<p>Although we cannot categorically rule out the use of machine learning in addressing this puzzle, we would be eager to observe how others approach this issue. It is worth noting that there are still many potential avenues for exploring the application of machine learning to <em>Cain’s Jawbone</em>.</p>
]]></content:encoded></item><item><title><![CDATA[Is Artificial Intelligence Suitable to Solve Cain’s Jawbone? (Part II)]]></title><description><![CDATA[All opinions herein are solely my own and do not express the views or opinions of my employer. This article contains a potential solution to Cain’s Jawbone. If you plan to solve it by yourself, stop reading it.

Previously, we wondered about the rele...]]></description><link>https://glthr.com/is-artificial-intelligence-suitable-to-solve-cains-jawbone-part-ii</link><guid isPermaLink="true">https://glthr.com/is-artificial-intelligence-suitable-to-solve-cains-jawbone-part-ii</guid><category><![CDATA[Artificial Intelligence]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sat, 16 Dec 2023 17:10:49 GMT</pubDate><content:encoded><![CDATA[<p><em>All opinions herein are solely my own and do not express the views or opinions of my employer. This article contains a potential solution to Cain’s Jawbone. If you plan to solve it by yourself, stop reading it.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702745397565/ef327b18-fbf2-4885-bc49-56000b39a9da.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://hashnode.com/post/clpyk8e8y000009l45zwf9hgv">Previously</a>, we wondered about the relevance of using artificial intelligence to solve <a target="_blank" href="https://en.wikipedia.org/wiki/Cain%27s_Jawbone">Cain’s Jawbone</a>. On this occasion, a naive K-means clustering had revealed that some of the pages had some affinity with each other, paving the way for the autodiscovery of narrators.</p>
<p>Because <a target="_blank" href="https://glthr.com/cj">a potential and unofficial solution is known</a>, pages can be labeled, enabling supervised learning.</p>
<p>Initially, we were reluctant to do it for the several reasons. First, the dataset is minimal (only 100 pages) and not extensible: was it worth the trouble to train a model that would ultimately be used only once? Second, the differences between the narrators are essentially extratextual and fictitious (the narrators are a fiction created by the same writer): would a model identify hidden reliable criteria to characterize a narrator? Last but not least, the solution has not been made official: do we risk using erroneous labels?</p>
<p>But to our surprise, <strong>the supervised learning approach produces actionable results</strong>.</p>
<p>We fine-tuned <a target="_blank" href="https://huggingface.co/bert-base-uncased">a BERT (base, uncase) model</a> to perform a classification. Before considering a multiclass classification (one output neuron per narrator), <a target="_blank" href="https://github.com/glethuillier/Jawbone/blob/main/2_BERT_binary_classification.ipynb">we have more modestly operated a <em>binary</em> <em>classification</em> (one output neuron) to identify whether a given page belongs to Bill Hardy</a>—the narrator the most evidently isolated by the k-means clustering.</p>
<p>To be robust enough, the model must be trained on at least 4 of Bill Hardy’s pages (4% of the book) and 25 pages not belonging to Bill (25%). All in all, we have trained the model with just under 30% of the book. After 20 epochs, the trained model can accurately perform the binary classification of pages for the whole book. Most of the time (empirically &gt; 70%), the confusion matrix is perfect.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702744673517/c5666600-eda2-45dc-af38-98228222e32e.png" alt class="image--center mx-auto" /></p>
<p>When the accuracy score is insufficient, one or two pages is/are usually misclassified (this is often true for <a target="_blank" href="https://glthr.com/cj/#96">page 96</a>). Half of the pages are misclassified in rare cases (empirically less than 5%), resulting from a poor combination of pages from the training dataset.</p>
<p>We attempted to extend the approach to <em>sentences</em> instead of pages. The model performs more poorly, as illustrated by the following confusion matrix. This is expected, as sentences can be nonspecific (<em>e.g.</em>, “<em>It is somewhat too sensational,</em>” “<em>My watch must be my mentor</em>”).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702745616011/93f33c69-b63b-40da-bdbc-6ce2e9dc79fb.png" alt class="image--center mx-auto" /></p>
<p>The way we used deep learning to classify <em>Cain’s Jawbone</em> pages is peculiar (using it as a pseudo-forensic tool to attach writings to a fictitious person) and nonoptimal (small dataset, therefore tiny training and test dataset; solution not officially recognized, potentially impacting the accuracy of the labels). Despite these limitations, <strong>it generated some interesting results for the classification of pages</strong> <strong>and was able in most case to correctly assign pages to Bill Hardy</strong>.</p>
<p>Our next step will be to attempt to do a multiclass classification. In other words, to predict to which narrator each page belonged.</p>
]]></content:encoded></item><item><title><![CDATA[Is Artificial Intelligence Suitable to Solve Cain’s Jawbone? (Part I)]]></title><description><![CDATA[All opinions herein are solely my own and do not express the views or opinions of my employer. This article contains a potential solution to Cain’s Jawbone. If you plan to solve it by yourself, stop reading it.
We suggested a solution to Cain’s Jawbo...]]></description><link>https://glthr.com/is-artificial-intelligence-suitable-to-solve-cains-jawbone-i</link><guid isPermaLink="true">https://glthr.com/is-artificial-intelligence-suitable-to-solve-cains-jawbone-i</guid><category><![CDATA[Artificial Intelligence]]></category><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Sat, 09 Dec 2023 21:20:38 GMT</pubDate><content:encoded><![CDATA[<p><em>All opinions herein are solely my own and do not express the views or opinions of my employer. This article contains a potential solution to Cain’s Jawbone. If you plan to solve it by yourself, stop reading it.</em></p>
<p><a target="_blank" href="https://glthr.com/cj/">We suggested a solution to <em>Cain’s Jawbone</em> as a personal project</a> <a target="_blank" href="https://glthr.com/cj/">almost a year ago</a>.</p>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Cain%27s_Jawbone"><em>Cain’s Jawbone</em></a>, by Edward Powys Mathers, is a puzzle consisting of 100 shuffled pages. To manually solve it, the following heuristic was used:</p>
<ol>
<li><p>Group the pages by narrators (there are eight of them, concealed in the narration).</p>
</li>
<li><p>Then, sort the pages in each group.</p>
</li>
<li><p>Last but not least, sort the groups themselves.</p>
</li>
</ol>
<p>Now, we would like to investigate whether artificial intelligence (in a broad sense) could be used to find a solution more efficiently, at least partially. We are only at the very beginning of this analysis.</p>
<p>We initially considered leveraging NLP to detect the hidden patterns connecting the pages. However, initial investigations quickly highlighted that Mathers heavily used extratextual clues (hidden quotes, sequence of train stops between two stations, <em>etc.</em>). Consequently, the process leading to the solution was not automated: the pages were grouped and sorted using intratextual clues as well as references to the outside world. We only developed an ad hoc tool to cross-reference the pages against books in the public domain to detect hidden quotes.</p>
<p>To begin our analysis of the suitability of artificial intelligence, we have opted for a classic <em>unsupervised</em> learning approach (word vectorization and k-means clustering) to group the pages automatically by narrators. Indeed, given the characteristics of the data (small dataset, no clear way to label the pages, ...), supervised learning is not necessarily the most relevant.</p>
<p>We anticipated that the result would be limited because of <em>(a)</em> the extratextual clues, <em>(b)</em> the simplicity of our approach—at this stage, at least—, and <em>(c)</em> the fact that the pages were all written by the same person pretending to be eight different narrators (meaning that the style difference between the pages is artificial, not intrinsic). To our surprise, while the clusters do not greatly overlap the expected groups (even though <code>k</code> is set to the actual number of narrators), the representation of the PCA reduction of naive k-means clustering revealed an “affinity” between some of the pages which is more or less in line with the expected solution. This is particularly true for Bill Hardy and Henry the dog.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702160969755/48126485-074c-432e-8d86-1ef3b2534bc0.png" alt class="image--center mx-auto" /></p>
<p>These preliminary results tend to indicate that NLP may be suitable, to some extent, to identify the narrators. To that end, it will need to be improved to use additional clues and automatically determine the number of narrators (we tried to use the elbow method to that end, but as it was inconclusive in the context of this preliminary analysis, and therefore had to specify this number ourselves).</p>
<p>If the use of artificial intelligence succeeds in identifying the underlying narrators, we will continue our investigation to see to what extent it could sort the pages in each group.</p>
]]></content:encoded></item><item><title><![CDATA[Contextual Yet Non-Descriptive Errors]]></title><description><![CDATA[Developers must carefully balance the amount of information provided in error messages displayed to end-users. Disclosing too much information through these errors can lead to security breaches. On the other hand, using generic error messages can mak...]]></description><link>https://glthr.com/contextual-yet-non-descriptive-errors</link><guid isPermaLink="true">https://glthr.com/contextual-yet-non-descriptive-errors</guid><dc:creator><![CDATA[Guillaume Lethuillier]]></dc:creator><pubDate>Tue, 24 Oct 2023 16:03:14 GMT</pubDate><content:encoded><![CDATA[<p>Developers must carefully balance the amount of information provided in error messages displayed to end-users. Disclosing too much information through these errors <a target="_blank" href="https://owasp.org/www-community/Improper_Error_Handling">can lead to security breaches</a>. On the other hand, using generic error messages <a target="_blank" href="https://support.microsoft.com/en-gb/topic/-something-went-wrong-but-you-can-try-again-msa-b211f0a3-d7a9-e12e-1580-dfbe9b9c820e">can make it more challenging for support teams to identify and resolve the underlying issues</a>.</p>
<p>A feasible solution is creating public-facing error messages that map internal (potentially confidential) error objects, such as <a target="_blank" href="https://support.apple.com/en-us/HT204770">iOS update and restore error codes</a>. These error codes are not very descriptive (or not descriptive at all), but they offer context to the engineers who can map them with their actual meaning—in terms of actual implementation. This manual and static mapping requires developers to maintain it when new categories or combinations of errors arise, which can be prone to errors and incompleteness.</p>
<p>To avoid the need for manual error code mapping, we propose <strong>a dynamic mapping that generates unique and deterministic error codes on the fly, encapsulating the context of an error in a single number (an error code)</strong>.</p>
<p>These dynamic error codes are contextual (they offer clues about the context of the errors), yet non-descriptive (they do not leak information about the context of the errors).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698160076075/bde74923-c5ee-48ac-ae4c-8e728902c803.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-identification-of-error-items">Identification of “Error-Items”</h1>
<p>To begin with, we <em>define</em> a set of miscellaneous items—“error-items”—that can help identify the root cause of bugs, such as components, instances of the application (when the application knows their characteristics by the time an error object is generated), categories of errors, and so on. The developers arbitrarily define these error-items based on their perceived usefulness in debugging a specific issue. The purpose of these items is to quickly pinpoint the root cause of a bug; in other words, <em>to characterize the context of an error.</em></p>
<p>Let’s use a toy example to illustrate this idea.</p>
<p>A small application consists of two main components (or packages, or microservices, <em>etc.</em>), which can be instantiated several times. Additionally, we identify three categories of errors: WebSocket errors, some business logic errors, and database errors.</p>
<p>From the point of view of the definition of error details, this application contains a mixed bag of 8 error-items. We arbitrarily identified them as useful pieces of information that, when combined, can provide a relevant context to debug future bugs. We do not need to anticipate all combinations of items (or, to put it in another way, all possible error contexts): <em>this approach does it for us</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698150741864/80f2fae0-a748-41fc-a23c-82abe3a6510a.png" alt class="image--center mx-auto" /></p>
<p>Then, to represent each item in the error code, we start by assigning a unique number—a power of two—to each of them. These numbers are their identifiers.</p>
<p>Using binary manipulation, we create a one-hot vector for each item by left-shifting one bit by the position of the item in the list of error-items defined for the application (<code>1 &lt;&lt; index</code>). The first item will be represented as <code>b1</code> (1), the second as <code>b10</code> (2), the third as <code>b100</code> (4), and so on.</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> errorItem <span class="hljs-keyword">int64</span>

<span class="hljs-keyword">const</span> (
  ComponentA errorItem = <span class="hljs-literal">iota</span>
  <span class="hljs-comment">// ... list of error-items ...</span>
)

<span class="hljs-comment">// ...</span>

errorItems := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[errorItem]<span class="hljs-keyword">int64</span>)
<span class="hljs-keyword">for</span> i, item := <span class="hljs-keyword">range</span> items {
  errorItems[item] = <span class="hljs-number">1</span> &lt;&lt; i
}
</code></pre>
<p>These identifiers can alternatively be generated arithmetically:</p>
<p>$$id_{i} = 2^{i}$$</p><p>The error-items are now labelled.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698156568632/32dd6271-f0df-4bca-9a87-43cfe9161504.png" alt class="image--center mx-auto" /></p>
<p>This auto-generated mapping between error-items and their identifiers can be easily extended whenever a new feature is added to the application or when it becomes apparent that a new item needs to be defined. This can be done just by adding the new item to the loop that assigns identifiers, ensuring that all new items are properly represented in the error code.</p>
<p>It should be noted that, for the system to be future-proof, obsolete items have to be disabled—for instance by using a special enum—instead of removed, and identifiers of new items have to be greater than previous ones.</p>
<h1 id="heading-encoding-the-error-context">Encoding the Error Context</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698161188425/1806a5cf-757b-436b-b3d1-b07ecdfd7ec1.png" alt class="image--center mx-auto" /></p>
<p>When an error occurs in the application, its error code is computed through a bitwise <code>OR</code> operation on all selected error-items (basically, the items that compose the context of the error). This operation generates a unique number representing the union of items—as they are unique powers of two.</p>
<p>Let’s consider an example.</p>
<p>Suppose we have two items having identifiers <code>2</code> and <code>16</code>:</p>
<pre><code class="lang-go">n := <span class="hljs-number">2</span> | <span class="hljs-number">16</span> 
fmt.Printf(<span class="hljs-string">"%d &lt;=&gt; %b (%b | %b)\n"</span>, n, n, <span class="hljs-number">2</span>, <span class="hljs-number">16</span>) 
<span class="hljs-comment">// 18 &lt;=&gt; 10010 (10 | 10000)</span>
</code></pre>
<p>The result is a unique number (<code>18</code>) representing the union of the two singleton sets.</p>
<p>Here are additional illustrations of this idea, based on the toy example.</p>
<p>A) A database error returned by component A that occurred in the first instance of the application ⇒ <code>Component A ∪ Instance 1 ∪ Database Error</code> ⇒ <code>7</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698175361770/3c837d77-a8b2-4b83-81e0-f0810347610d.png" alt class="image--center mx-auto" /></p>
<p>B) A WebSocket error returned by component A ⇒ <code>Component A ∪ WebSocket Error</code> ⇒ <code>33</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698175370345/33cb3236-06d5-4ed2-af4e-2c4f4a80e7fd.png" alt class="image--center mx-auto" /></p>
<p>C) An error returned by an external component that occurred in component B of the second instance of the application ⇒ <code>Component B ∪ External component ∪ Instance 2</code> ⇒ <code>208</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698175385701/fe5b6f81-ec47-4c23-838a-9cfc8dc9c819.png" alt class="image--center mx-auto" /></p>
<p>This operation is trivial: it simply corresponds to adding the identifiers of the error-items. What makes this process useful is that this addition can be <em>reversed</em> to extract its terms. In other words, <strong>the contextual information transformed by the addition can be reconstructed ex-post</strong>.</p>
<h1 id="heading-decoding-the-error-codes">Decoding the Error Codes</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698161197992/a0b5e63a-8d88-4229-8c91-ac1fe99ef62c.png" alt class="image--center mx-auto" /></p>
<p>To decode the error code, it suffices to perform the reverse operation (permitted by the fact that the identifiers are unique powers of two) by applying the <code>AND</code> operation between each identifier and the error code. If the operation results in the identifier itself—that is: <code>errorCode &amp; identifier == identifier</code>—, this item belongs to the context of the error.</p>
<pre><code class="lang-go">errorCode := <span class="hljs-number">18</span>
<span class="hljs-keyword">for</span> _, identifier := <span class="hljs-keyword">range</span> []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">8</span>, <span class="hljs-number">16</span>, <span class="hljs-number">32</span>, <span class="hljs-number">64</span>} {
    belongsToErrorContext := errorCode &amp; identifier == identifier
    fmt.Printf(<span class="hljs-string">"%d: %v\n"</span>, identifier, belongsToErrorContext)
    <span class="hljs-comment">// 1: false</span>
    <span class="hljs-comment">// 2: true   ✔</span>
    <span class="hljs-comment">// 4: false</span>
    <span class="hljs-comment">// 8: false</span>
    <span class="hljs-comment">// 16: true  ✔</span>
    <span class="hljs-comment">// 32: false</span>
    <span class="hljs-comment">// 64: false</span>
}
</code></pre>
<p>Because the identifiers are automatically mapped with the error-items, the reconstituted terms of the addition/<code>OR</code> operation are translated into a meaningful context.</p>
<h1 id="heading-discussion">Discussion</h1>
<p>So, what’s the point of these contextual yet non-descriptive errors?</p>
<p>These error codes lack helpful information for end-users, making it challenging for them to resolve the issue by themselves (when applicable). A user-friendly, customized error message is <em>always</em> preferable.</p>
<p>Furthermore, when investigating a bug, with this approach engineers need to use a decoder that perfectly matches the encoder’s representation of error-items.</p>
<p>We think that these errors can still be helpful for the following reasons.</p>
<ol>
<li><p><strong>Complementary debugging device.</strong> This approach provides an additional indicator that <em>complements</em> well-thought-out error messages (it does not aim at replacing them), making it easier for developers to identify and resolve issues without leaking details about the internal implementation—just compare <code>Username cannot be created</code> v. <code>Username cannot be created (143)</code>. It offers a clue about the context of an error without having the developers examine multiple variables and logs while making it harder for attackers to reverse-engineer sensitive information.</p>
</li>
<li><p><strong>Simplified error management.</strong> Thanks to this algorithmic mapping, developers do not need to worry about managing error mapping, as this approach takes care of it automatically. This leads to increased flexibility in handling new combinations of existing items. For instance, imagine that the <code>Database Error</code> is only used in combination with <code>Component A</code> (because this component is the only one using a database). Later, this application is updated so that <code>Component B</code> now uses a database. No need for the developers to define a new error-item. It suffices to automatically generate a new error code based on the <code>Component B ∪ Database Error</code> union. Programmatically, the developer just has to call the function that generates the code with the enums corresponding to component B and the database error type. In this respect, while the error-items have to be defined, the mapping, corresponding to combinations of items, is dynamic.</p>
</li>
<li><p><strong>Simplified logging.</strong> When errors are propagated from one service to another, this approach simplifies logs by only transmitting the error code and potentially a customized error message, rather than duplicating the entire error object. In practice, in the context of a microservices architecture, people reviewing the logs of a service that propagates the error code can quickly identify the relevant log thanks to the contextual information encoded by the error code.</p>
</li>
<li><p><strong>Easy to implement.</strong> Last but not least, the encoder and decoder are based on simple, stateless, and easy-to-understand operations, further simplifying the development process.</p>
</li>
</ol>
<p><sup>Opinions expressed are solely my own and do not express the views or opinions of my employer.</sup></p>
]]></content:encoded></item></channel></rss>