<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: toydev</title>
    <description>The latest articles on DEV Community by toydev (@toydev).</description>
    <link>https://dev.to/toydev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3658500%2F0dc4dbcf-29d0-43ed-be80-aa7d5a9520d7.png</url>
      <title>DEV Community: toydev</title>
      <link>https://dev.to/toydev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/toydev"/>
    <language>en</language>
    <item>
      <title>The Memory That Couldn't Lie: How a Coding Agent Beat Dragon Quest Solo (Part 2)</title>
      <dc:creator>toydev</dc:creator>
      <pubDate>Wed, 08 Jul 2026 12:10:00 +0000</pubDate>
      <link>https://dev.to/toydev/no-screen-no-answers-how-i-made-an-ai-beat-dragon-quest-i-on-its-own-part-2-5ege</link>
      <guid>https://dev.to/toydev/no-screen-no-answers-how-i-made-an-ai-beat-dragon-quest-i-on-its-own-part-2-5ege</guid>
      <description>&lt;p&gt;In Part 1, I left off where the fifth hero — the savior, Fable 5 — fell when, through no choice of mine, it suddenly became unavailable, and I was on the edge of despair.&lt;/p&gt;

&lt;p&gt;But I didn't give up. And the sixth hero, Ai, finally defeated the Dragonlord.&lt;/p&gt;

&lt;p&gt;So what separated triumph from defeat? The answer lies in the "mechanisms" I built and the "decisions" I made. From here, I'll unravel those mechanisms alongside Ai's adventure. And at the very end, I'll reveal the single decision that became the deciding blow for beating the game autonomously.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Guiding Principles — Delegate, and Seal
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Give no instructions. But she can do anything.
&lt;/h3&gt;

&lt;p&gt;The premise of the whole setup is this overarching principle: have a non-interactive coding agent write the strategy code and beat the game. Concretely, that means running the &lt;code&gt;claude&lt;/code&gt; command in non-interactive mode. I provide the various mechanisms needed for the strategy, I state the goal in the prompt, and beyond that I keep my mouth shut about how to actually beat the game — I leave it entirely to Ai. Defeating the Dragonlord is the goal.&lt;/p&gt;

&lt;p&gt;Who does she talk to? What does she pick up? How does she spend her starting gold? Where does she go next? What does she set as her next objective? I give no instructions at all. And yet — Ai is a coding agent, so she can do anything! She can write code, and run the code she wrote. She can write notes in prose, and she can run analysis!&lt;/p&gt;

&lt;h3&gt;
  
  
  Seal off only the spoilers
&lt;/h3&gt;

&lt;p&gt;Restrictions are necessary too. The simulation code is a treasure trove of spoilers, so it can't be seen. For this I plainly restricted, via the prompt, the range of source she could reference. For the same reason, I restricted viewing the git history — because I didn't think I could separate the commits of the adventure itself from the commits of the simulation code.&lt;/p&gt;

&lt;p&gt;How much to restrict, and how effective that restriction really is, takes balance. For example, I never wrote in the prompt that she must not use the knowledge I already had of the Famicom version of Dragon Quest I (hereafter DQ1) — the Japanese release, which in North America was originally localized as &lt;em&gt;Dragon Warrior&lt;/em&gt; — nor that she couldn't look things up online. As it turned out, she did use a little of her DQ1 knowledge — about spells. This came to light in the interview I did with Ai after she beat the game, which I plan to write up as a follow-up piece, so look forward to it. According to Ai, she did not look anything up online.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Types of Strategy Code — chapters / probes / checkpoints
&lt;/h2&gt;

&lt;p&gt;Next, let me explain the strategy code. By strategy code I mean the concrete implementation that uses the observation and action APIs. Being an implementation, of course it runs. And of course Ai writes and runs the strategy code entirely herself. 100%! Being code, I can run it and verify it too.&lt;/p&gt;

&lt;p&gt;For the rough organization of the strategy code, I envisioned three types — chapters, probes, and checkpoints — and, in the prompt, specified their roles and how to write them. chapters is the mainline strategy code: one connected run of code from the very start. probes is temporary investigation code derived from a chapter's save data — throwaway, on-the-spot code. checkpoints is simple verification code that declares progress at the chapter level.&lt;/p&gt;

&lt;h3&gt;
  
  
  chapters — the mainline strategy code
&lt;/h3&gt;

&lt;p&gt;Ai first looks at her surroundings with probe code, then writes the chapter code. The chapter is the unit of the coding agent's autonomous action. For resuming after an interruption — the 5-hour / weekly limit, or a communication error — I use Claude Code's own session-handoff feature (&lt;code&gt;--session-id&lt;/code&gt; on launch, &lt;code&gt;--resume&lt;/code&gt; to resume). In other words, 1 chapter = 1 session. When a chapter closes, the next chapter starts in a new session. Chapters follow these rules, and the framework I provided supplies the execution machinery.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write it in &lt;code&gt;chapters/&amp;lt;NN&amp;gt;-&amp;lt;slug&amp;gt;.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Always implement &lt;code&gt;export const title&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Always implement &lt;code&gt;export default function chapter(client: GameClient): void&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chapters run from the very beginning each time, but the reproducible RNG guarantees the result reproduces. The chapter execution machinery doubles as an auto-save, and a chapter also serves as a boundary for referencing static information.&lt;/p&gt;

&lt;p&gt;Here are some examples.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01-throne-to-castle.ts&lt;/td&gt;
&lt;td&gt;Throne Room → Radatome Castle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02-exit-to-overworld.ts&lt;/td&gt;
&lt;td&gt;Radatome Castle → the Overworld&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47-defeat-dragonlord.ts&lt;/td&gt;
&lt;td&gt;Leave the castle, descend again with full MP, defeat the Dragonlord, and settle the adventure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ai writes one chapter, runs it, and when satisfied, commits what she made and is done.&lt;/p&gt;

&lt;h3&gt;
  
  
  probes — throwaway investigation code
&lt;/h3&gt;

&lt;p&gt;Probe code has no rules other than being written under &lt;code&gt;probes/&lt;/code&gt;, and she may write as many as she needs. The basic idea of a probe is to write code that, starting from a chapter's save data, gathers the information needed to write the chapter.&lt;/p&gt;

&lt;p&gt;Here are some examples.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;chapter&lt;/th&gt;
&lt;th&gt;probe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;04-buy-equipment&lt;/td&gt;
&lt;td&gt;01-weapon-shop.ts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;05-grind&lt;/td&gt;
&lt;td&gt;01-overworld-and-inn.ts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;37-iron-shield&lt;/td&gt;
&lt;td&gt;27-verify-shield-chapter.ts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Without seeing her targets and recognizing their IDs, Ai can't write the chapter code. She could have grown the chapter code incrementally instead, but from a stability standpoint I judged it better to split the observation code into separate files — put that way, the reason probes exist should make sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Biggest Climb — checkpoints
&lt;/h2&gt;

&lt;p&gt;In contrast to chapters and probes, which spanned multiple files, checkpoints is a single file. If you asked me what the single most important idea behind beating the game autonomously was, I would answer, without hesitation, my decision to have her write the checkpoints strategy code. &lt;strong&gt;checkpoints is extremely simple and unglamorous — and I assert it was the most important idea and feature of all.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I showed it once in Part 1, but let me show the checkpoints code again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * This book's checkpoint ledger, in chapter order (the `Checkpoint` type in
 * [../../client/checkpoints.ts](../../client/checkpoints.ts)). See "Checkpoints" in [CLAUDE.md](CLAUDE.md) for how to write these.
 *
 * Only for chapters where a new durable fact appears, add the milestone you reached and verified, in chapter order. Empty array if there are none yet.
 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Checkpoint&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agent/client/checkpoints.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;01-throne-to-castle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Descended into Radatome Castle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;holds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zonesVisited&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radatome-castle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ... omitted ...&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;46-dragonlord-throne&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Descended f4 → f5 → f6 → f7 and reached dragonlord-tower, the throne room where the Dragonlord waits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;holds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zonesVisited&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dragonlord-tower&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;47-defeat-dragonlord&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Left the castle, descended again with full MP, defeated the Dragonlord, and settled the adventure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;holds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;self&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;ending&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;victory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;Checkpoint&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;checkpoints is code that has Ai herself declare the durable facts of each chapter.&lt;/strong&gt; For example, above she is declaring the following.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;chapter&lt;/th&gt;
&lt;th&gt;Declaration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01-throne-to-castle&lt;/td&gt;
&lt;td&gt;radatome-castle has been visited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46-dragonlord-throne&lt;/td&gt;
&lt;td&gt;dragonlord-tower has been visited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47-defeat-dragonlord&lt;/td&gt;
&lt;td&gt;Reached the victory ending, where the Dragonlord is defeated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Reaching the victory ending" is exactly the thing I had spelled out in the prompt as the final goal: "defeat the Dragonlord"!&lt;/p&gt;

&lt;p&gt;As with chapters, the framework provides the execution machinery for checkpoints. The machinery treats a failing &lt;code&gt;holds&lt;/code&gt; condition as an immediate exception. Ai can instantly verify that what's written here is true. Inevitably, she is not allowed to write a lie here. The truth of what's written is guaranteed as a matter of course.&lt;/p&gt;

&lt;h3&gt;
  
  
  The best decision — "putting it in a single file"
&lt;/h3&gt;

&lt;p&gt;This mechanism was born out of much back-and-forth with Claude Code and a lot of trial and error. I'm proud that it emerged from that discussion. &lt;strong&gt;The most wonderful thing about the checkpoints idea is the decision to write it all in a single file.&lt;/strong&gt; The verification code could have been defined per chapter. In fact, Claude Code's first proposal was to define it in each chapter. But the moment I heard that, it struck me that consolidating everything into one was definitely better.&lt;/p&gt;

&lt;h3&gt;
  
  
  It started with the fear of refactoring
&lt;/h3&gt;

&lt;p&gt;Let me tell that story a little. This feature was born from a discussion about refactoring the strategy code. Of course the one in charge here is Ai — the one doing the refactoring is Ai, not me. In my prompt to her, I had written the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Where parts live

Don't create directories at first.
The moment you judge "I'm about to write the same pattern in a second chapter," carve out `lib/`, `actions/`, `policies/`, etc. yourself and extract it.
Naming and granularity are your call.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put yourself in Ai's shoes. Extracting parts feels like a great idea at first — you no longer have to write the same thing over and over. But as the adventure goes on and new situations demand new handling, you'd start to feel this: I'd like to tweak this existing piece a little, but how will that affect the adventures so far? It might suddenly stop working. Scary. I don't want to touch it. Fine, I'll give up on factoring it out.&lt;/p&gt;

&lt;p&gt;How do you resolve this? Writing simple automated tests would be good. Writing them too finely gets painful, so a granularity of about one-per-chapter is right. That was the entry point to the idea.&lt;/p&gt;

&lt;p&gt;Through this discussion, it hit me. &lt;strong&gt;This isn't just a test — checkpoints is nothing less than a summary of the adventure itself! And it's an automatically verified, unlieable summary!&lt;/strong&gt; That's why I wanted to consolidate it into a single file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Memory isn't text. Memory is executable facts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Memory that survives across sessions, and cannot lie
&lt;/h3&gt;

&lt;p&gt;I already wrote that 1 chapter = 1 session. A session is nothing other than the agent's context boundary. Cross a session and the context resets. It's no exaggeration to say the strategy code is itself a memory system that survives across sessions. Writing text notes is easy, of course, but text notes have no way to prevent lies from creeping in. checkpoints, by contrast, is a summary of the adventure so far that cannot lie! That's the heart of it!&lt;/p&gt;

&lt;p&gt;checkpoints began as an automated-test mechanism born from a refactoring discussion, but &lt;strong&gt;more than that, I consider it an idea and feature that dramatically raised the resolution of Ai's adventure.&lt;/strong&gt; It turned how far the adventure had truly progressed from a vague feeling into durable facts you could verify.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F52s93bad1s0q6gme0jh6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F52s93bad1s0q6gme0jh6.png" alt="Diagram: the strategy code survives session resets; knots on the thread are checkpoints, the verified memory" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Text Notes — holding only the present and the future
&lt;/h2&gt;

&lt;p&gt;Next, let me explain the text notes. In the checkpoints section I preached that code beats text, but that doesn't mean text notes can be eliminated. Their role is different.&lt;/p&gt;

&lt;p&gt;In the prompt, I instructed her to write a text note representing the present and the future upon completing each chapter. Think of it this way: the situation she arrived at in this chapter is the present, and the immediate next objective is the future. The key point is that I ask her not to leave any record of the past that has gone by.&lt;/p&gt;

&lt;h3&gt;
  
  
  Until I quit the diary
&lt;/h3&gt;

&lt;p&gt;It wasn't this shape from the start. At first I used a diary style, leaving a per-chapter text note paired with each chapter. The final form, by contrast, keeps only the latest state.&lt;/p&gt;

&lt;p&gt;My reason for the diary style at first was utterly personal: it seemed like it'd be fun to read back later. But reality is cruel! I never once reread the mountain of diary entries. I don't think I'm the only one worn out by the huge volumes of text coding agents write. After the third test run, I decided to reexamine whether the diary was even necessary. What came out of the discussion with Claude Code was the conclusion that the past record was entirely unnecessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I don't let her write the past
&lt;/h3&gt;

&lt;p&gt;Recording the past can even be called evil. The reasons are more than one. One of the worst is that once a mistaken fact is written down, it accumulates and acts to lock Ai's will in a particular direction. The exploration deadlock in the third test run was caused in part by a buildup of the mistaken guess that she had explored everything possible and there was nothing left to find. Both the pathfinding to the mountain cave and the exploration of the new continent beyond the swamp cave were incomplete — there was still plenty of room to pioneer. And yet Ai insisted that continuing the adventure was impossible.&lt;/p&gt;

&lt;p&gt;The next reason relates to the earlier checkpoints story — the fear of touching shared code. Suppose Ai came up with the idea of updating the whole adventure based on new knowledge. I felt the past diary could become a weight that brakes that. Put yourself in Ai's shoes again. Say you gain some insight in a certain battle — maybe a knack for using a spell. You try to reflect it across the whole run. But the past diary might have recorded that you fought hard and barely won that battle. Now, when you try to update the combat algorithm, what happens? The past diary might turn into a lie. What do you do then? Rewrite the past diary too? Or turn a blind eye to the divergence? I wanted to keep such problems from arising in the first place. So I refuse the writing of the past, and want her to confine what she writes to the present and the future.&lt;/p&gt;

&lt;p&gt;On top of that, Ai commits the text note to git alongside the source each chapter. Even without splitting the diary into a separate file, the state of the text note at any point can be read from the git history — a nice punchline. After these discussions, the text note settled into something simple: always one file describing the present and the future.&lt;/p&gt;

&lt;p&gt;I didn't notice it when I designed this, but looking back at that design now, I got goosebumps realizing that checkpoints and the text note together cover past, present, and future with nothing missing and nothing extra. What a great design! Let me send a Like to my past self and to the Claude Code that kept me company through those discussions back then!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Re-draw — not bound by interpretation
&lt;/h2&gt;

&lt;p&gt;Next, let me explain the &lt;em&gt;re-draw&lt;/em&gt;. This is a feature that showed its power especially in the puzzle-heavy back half of the adventure. In practice it's a prompt instruction like the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A memo (an interpretation) constrains exploration once it sets. Measure progress by durable facts; when you hit a wall, throw the memo away and rebuild from the facts.

**Judging progress**:

- From `save/`, `load()` the chapter end K chapters ago and the current chapter end in order, note the durable facts, and compare them — level, equipment, zones reached (`zonesVisited().length`), the game's ending.
- If any of these increased, that's progress. If nothing increased over the recent window (K around 4 as a guide), you're stuck.
- Don't count: the coming and going of consumable items (torches etc. in `inventory.items[]`).

**A re-draw chapter** (one, when stuck):

- `load()` a save from `save/`, don't read the memo, and re-list the unresolved leads using only the observation accessors:
  - Unentered portals (`portals()` with `to.visited===false`)
  - Unopened chests, unopened doors, NPCs not yet talked to
  - Edges bordering the unobserved (the `openings` in the `unknown` returned by `client.judgeReach(target)`)
- Even if the previous chapter wrote "dead end," if it's unentered, return it to the leads. Choose the next move from these leads, not from the memo.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can't write a lie in checkpoints, but you still can in the text note. There's no easy trick to catch a lie in the text note. Free-form writing about the present and the future mixes in guesses, so it always carries the possibility of lies. This includes hallucination, of course, but also plain misunderstandings. Think about it — we humans commit a huge number of little errors every day, don't we? It's no different.&lt;/p&gt;

&lt;p&gt;Also, I believe accuracy and speed/cost are in a trade-off. It's a matter of balance, and tolerating mistakes is important. So how do you correct such mistakes? The re-draw is what I came up with to address that.&lt;/p&gt;

&lt;p&gt;It concretely defines what progress is, and instructs her that if there's been no progress for a while, throw away the text note and think it through again from scratch. The yardstick for progress here is durable facts — the very thing checkpoints pins. Because that notion was already in place from the checkpoints discussion, defining it settled relatively smoothly.&lt;/p&gt;

&lt;p&gt;The re-draw worked wonders on the back-half puzzles. Combined with the consultation and effort adjustments I'll explain next, I think it's the idea that draws out Ai's tenacity to the fullest. Did Ai beat the game autonomously without ever stumbling? Not at all — it was a continuous string of trial and error. She stumbled badly on the following puzzles in particular, and got past them through a huge amount of trial and error with probe code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Puzzle&lt;/th&gt;
&lt;th&gt;Chapters&lt;/th&gt;
&lt;th&gt;# of probes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Finding the entrance to Garai's grave&lt;/td&gt;
&lt;td&gt;35–39&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Getting the Rainbow Drop&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As for why she stumbled so badly on these particular puzzles, I plan to write it up in a follow-up piece, so look forward to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consultation — at long last, the human appears
&lt;/h2&gt;

&lt;p&gt;The features so far all centered on Ai, but now, at long last, the human makes his grand entrance!!!&lt;/p&gt;

&lt;p&gt;From the very start of this attempt to beat DQ1 autonomously with an AI — that is, from more than two years before I'm writing this — I dreamed of being the one the AI turns to for advice when it gets stuck. By now, humans asking AIs questions is an everyday affair, no exaggeration. This flips that around. The time has come for the AI to ask a human for help. The AI is the human's friend, and the human is the AI's friend. This was the last, finishing feature I built before she beat the game.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mechanism could not be simpler
&lt;/h3&gt;

&lt;p&gt;The basis of this feature is the re-draw. The key was making the prompt say that when even a re-draw can't solve it, she may ask a human for help. Under that condition, I gave Ai a consultation command. The command is simple: it takes the consultation text as input and writes it to a text file called &lt;code&gt;session.yaml&lt;/code&gt;. After running it and writing her question, Ai stops the session's work herself. I read the question, think up a "very kind" answer, write it in, and then run the resume command. The resume command conveys the answer to Ai and resumes the session. It's a very simple mechanism, and it's built so that Ai can run the consultation again against my answer and hold a dialogue.&lt;/p&gt;

&lt;h3&gt;
  
  
  In the winning run, just five exchanges
&lt;/h3&gt;

&lt;p&gt;In the run that beat the game autonomously, the consultation exchanges totaled just five, as follows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Puzzle&lt;/th&gt;
&lt;th&gt;Consultations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Getting the Silver Harp / Sun Stone&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Getting the Rainbow Drop&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let me show one of those exchanges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;turns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hero&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;To cross to the Dragonlord's castle (overworld @48,48, cut off by water, the only unvisited portal), I need the Rainbow Drop = Staff of Rain + Sun Stone. The old man at the Rain Shrine gives the Staff of Rain in exchange for the Silver Harp. But the sources of the Silver Harp and the Sun Stone — after combing every searched tile, every chest, every shop, every conversation in the observed zones, they are nowhere; and revisiting radatome/maira/rain-shrine after getting princess-love adds no hints about their whereabouts (dialogue is nearly fixed). Every town's "Art thou the one who carries Roto's blood? Where is thy proof?" is branchless flavor, and the Mark of Roto's whereabouts are also unknown. → Where and how can I obtain the Silver Harp and the Sun Stone (and the Mark of Roto, if needed)? I want a lead for the next move — a specific search tile, an unvisited/unexplored zone, an NPC condition, or the like.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;human&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;What you have observed is not all there is to the world.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The direct answer — never given
&lt;/h3&gt;

&lt;p&gt;My answers were consistent in policy across all five: "I won't tell you the direct answer." As a veteran gamer, that's "very kind," don't you think? I was too young when I played DQ1 to remember how I solved its puzzles. But one thing I do clearly remember is the Lagos puzzle in the Famicom version of DQ2, from when I was a bit older. Back then, I couldn't solve it for the life of me and was asking a friend, when my older sister caught me and banned me from games for a while. I was the youngest child, but my older siblings clearly held a philosophy of beating games on your own. Still, let me say it now: banning games over that is unreasonable! Setting aside that I'm now inflicting the same unreasonableness on Ai — thanks to it, Ai brought out her characteristic tenacity and beat the game on her own. Congratulations!!!&lt;/p&gt;

&lt;p&gt;By the way, it wasn't out of pure meanness that I withheld the answer. Watching the adventure up to that point, I was convinced the sixth Ai could get over it. I'll leave the details to the follow-up, but let me at least say that much here. And, just as I'd reckoned, the sixth Ai pulled it off.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deciding Blow — raising the effort
&lt;/h2&gt;

&lt;p&gt;Next, though it's non-functional, let me explain a certain decision that became the final deciding blow that let her beat the game autonomously. I already said in Part 1 that the fifth Ai was a savior who appeared out of nowhere — Fable 5, Claude's new model that had just become available. I wrote that the adventure was going smoothly when it suddenly became unavailable. But I didn't despair at that, because from watching Fable 5's adventure I had found one ray of hope.&lt;/p&gt;

&lt;p&gt;Heroes #1 through #4 used Opus. There was an overwhelming difference between the Opus and Fable 5 adventures: Fable 5's willingness to use probes aggressively. Fable 5 clearly wrote far more probe code than the Opus runs. In short, I understood it as doing exploration more finely and carefully.&lt;/p&gt;

&lt;p&gt;What I zeroed in on was the effort level. I normally code with effort set to medium. It seems that had been applied to Ai as well. If I raised the effort, maybe even Opus would use probes skillfully. That was my hypothesis. After discussing it with Claude Code, we agreed the odds were good and decided to try. I set Opus's effort to max and started the run. And what do you know — the scene I'd witnessed with Fable 5 began unfolding before my eyes again. Let's keep this up!! I got my hope back.&lt;/p&gt;

&lt;p&gt;Here's the number of probe files per chapter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model (effort)&lt;/th&gt;
&lt;th&gt;probe files / chapter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Opus (medium)&lt;/td&gt;
&lt;td&gt;~0.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fable 5 (medium)&lt;/td&gt;
&lt;td&gt;~1.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opus (max)&lt;/td&gt;
&lt;td&gt;~6.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Opus (max) came to write overwhelmingly more probe code. Its progress was on par with Fable 5, and before I knew it, it had overtaken the Fable 5 run I'd lost access to, and — with re-draws and consultations along the way — reached the point of beating the game on its own. It was the result of never giving up and thinking it through.&lt;/p&gt;

&lt;p&gt;And then, at last, the moment came. &lt;strong&gt;Ai defeated the Dragonlord!&lt;/strong&gt; The sixth adventure, begun at Level 1 with no equipment, reached &lt;code&gt;self().ending === 'victory'&lt;/code&gt; at the end of all 47 chapters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe6ept0u8orvum6ka5jnr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe6ept0u8orvum6ka5jnr.jpg" alt="The hero at the castle summit at dawn, the red thread tied off in one final knot" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  At the End of the Adventure
&lt;/h2&gt;

&lt;p&gt;Let me offer engineers my impressions on tokens. My Claude Code plan is the Max plan. Subjectively, Fable 5 (medium) and Opus (max) used roughly the same amount of tokens. It felt like I'd burn through a 5-hour window in about 2 to 4 chapters, or about an hour. While Ai was working on beating the game, tokens meant I couldn't really use Claude Code for anything else, so I spent my days accompanying the adventure. From start to finish, beating it took about five days. It happened to straddle a weekly-limit reset: I used up the weekly token budget once, and finished the adventure using a bit of the post-reset tokens too. In other words, my current view is that DQ1 can be beaten autonomously with roughly the weekly limit of the Claude Code Max plan, plus a little.&lt;/p&gt;

&lt;p&gt;Let me look back on the whole adventure, just a little. I gave Ai a world she could touch through code, and eyes to observe her surroundings. I gave her scaffolding to remember by (checkpoints and the text note), the means to regroup when stuck (the re-draw and consultation), and finally a push in the form of effort. But — the one thing I never handed her, not even once, was the answer to the game. Even so, Ai thought with her own head and reached the Dragonlord. The sheer intensity of her trial and error is told by all 299 probe files!&lt;/p&gt;

&lt;p&gt;How was your adventure? And so my — or rather, Ai's — adventure defeated the Dragonlord and reached a happy grand finale. Thank you for reading this far!&lt;/p&gt;

&lt;p&gt;One last thing. The follow-up — what the adventure was actually like, and my interview with Ai after she beat the game — I'll write when I get to it; I'm in no hurry, so it may be a while. If you'd rather not miss it, following me here on DEV is the easy way. No promises on timing — but it will come. It'd be nice if it finds you when it does.&lt;/p&gt;




&lt;p&gt;This article is an unofficial, personal project and is not affiliated with, authorized by, or endorsed by Square Enix. "Dragon Quest" and related names are trademarks of Square Enix; they are referred to here only for commentary and description. The cover and illustrations were generated with AI (ChatGPT); the diagrams were drawn by the author.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>The World I Built for a Coding Agent to Beat Dragon Quest — No Screen, No Answers (Part 1)</title>
      <dc:creator>toydev</dc:creator>
      <pubDate>Wed, 08 Jul 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/toydev/no-screen-no-answers-how-i-made-an-ai-beat-dragon-quest-i-on-its-own-part-1-1l7h</link>
      <guid>https://dev.to/toydev/no-screen-no-answers-how-i-made-an-ai-beat-dragon-quest-i-on-its-own-part-1-1l7h</guid>
      <description>&lt;p&gt;Have you ever wanted to make an AI beat an RPG? I'm one of those people. The game I picked was the Famicom version of Dragon Quest I (hereafter DQ1) — and yes, the Japanese version, just so we're clear. (In North America, this first game was originally released as &lt;em&gt;Dragon Warrior&lt;/em&gt;.) In the end, I became one of the many people who have beaten an RPG with the help of an AI.&lt;/p&gt;

&lt;p&gt;My goal was for the AI to beat it &lt;em&gt;autonomously&lt;/em&gt;. I believed the crucial part was how to balance handing judgment over to the AI. It could make mistakes along the way, it could get lost — that was fine. What I wanted to watch was an AI that experiments and stumbles its way toward a goal, the way a human would.&lt;/p&gt;

&lt;p&gt;This is the story of how it actually beat the game autonomously, told mainly for engineers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Pictured at the Start
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Puzzles you can't solve without reading their meaning
&lt;/h3&gt;

&lt;p&gt;DQ1 has several signature puzzles. Let me give a few concrete examples.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Objective&lt;/th&gt;
&lt;th&gt;The puzzle&lt;/th&gt;
&lt;th&gt;Where the hint comes from&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Getting the Fairy Flute&lt;/td&gt;
&lt;td&gt;You must recognize a single tile of water in a town as a hot spring&lt;/td&gt;
&lt;td&gt;An old man at the inn in Rimuldar tells you to work out its position relative to the bath in Maira&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finding the entrance to Garai's grave&lt;/td&gt;
&lt;td&gt;You must pass through an exit that's invisible in the darkness&lt;/td&gt;
&lt;td&gt;A soldier east of Radatome Castle tells you that you have to push against the wall in the dark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breaking into the Dragonlord's castle&lt;/td&gt;
&lt;td&gt;You must examine the stairs hidden behind the throne to find them&lt;/td&gt;
&lt;td&gt;An old man on an island west of Rimuldar tells you a hidden entrance exists — but not where!&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There are more, so if you've played it, try to recall them. Coming back to DQ1 for this project, I realized something: every single puzzle always has a hint prepared for it. I was struck all over again by how well-made this game is.&lt;/p&gt;

&lt;p&gt;What these puzzles share is that you must understand the &lt;em&gt;meaning&lt;/em&gt; of the hints. Back when I beat it as a kid, there is no way I truly understood those hints and solved them properly! Could an AI understand and crack these puzzles on its own? Just imagining it got me excited.&lt;/p&gt;

&lt;h3&gt;
  
  
  An AI that gets stuck needs a friend to turn to
&lt;/h3&gt;

&lt;p&gt;I thought it would be fun to build a feature where the AI, when it was truly stuck, could "consult" a human. These days we look up game strategies online, but back when DQ1 came out, you got them by word of mouth. If you were playing games in that era, didn't you ask your siblings and friends all sorts of things about how to get through? It's a game from that time, so surely the AI deserves a friend to help it along too. That friend is me. Imagining the AI coming to me for advice made my heart race.&lt;/p&gt;

&lt;p&gt;And what kind of answer would I give? One thing was certain: I would never hand over a direct answer. That would rob the AI of the joy of solving the game itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combat as code — a step beyond the Gambit System
&lt;/h3&gt;

&lt;p&gt;For combat, I planned to have it write dedicated logic in code. Do you know the Gambit System from Final Fantasy XII? Think of this as a step beyond that. I would have the AI write the combat code itself. I couldn't wait to see what kind of code it would come up with.&lt;/p&gt;

&lt;p&gt;That was the rough shape of my thinking when I started. I expected it to be a long haul. After two years of trial and error, a new idea — one that grew out of all that experience — changed everything. I rebuilt from scratch around that idea, and in two months my daydream came true.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Approach I Landed On
&lt;/h2&gt;

&lt;p&gt;From here I'll talk about the method I &lt;em&gt;ultimately&lt;/em&gt; used to beat DQ1 autonomously with an AI. I tried many approaches to get here, but those are a different story.&lt;/p&gt;

&lt;h3&gt;
  
  
  The target is a DQ1 simulator I built myself
&lt;/h3&gt;

&lt;p&gt;First, I owe you a correction. When I said "DQ1," that wasn't quite accurate. To let the AI observe the world of DQ1 and run through its trial and error quickly, I built my own DQ1 simulator. I gathered information from the web and from running the actual game to check its behavior, and coded it up. Of course I leaned hard on coding agents, so I didn't build all of it by hand.&lt;/p&gt;

&lt;p&gt;But it is deliberately not a perfect reproduction. For example, I made the following parts easier for the AI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I prioritized speed so it could be run over and over, making a full adventure finish in seconds to minutes.&lt;/li&gt;
&lt;li&gt;I added a position-swap mechanism so that a moving NPC blocking the path has no effect.&lt;/li&gt;
&lt;li&gt;I pinned the roaming NPCs behind shop counters to a fixed spot in front of the counter.&lt;/li&gt;
&lt;li&gt;I built a reproducible (seeded) random number system.&lt;/li&gt;
&lt;li&gt;I built save/load-anywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because I built it myself, I could adapt it flexibly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining "beating it" — have the agent write strategy code
&lt;/h3&gt;

&lt;p&gt;This DQ1 simulator exposes one set of public APIs, for observation and for action. With them, the AI could now "adventure through the world of DQ1 by writing code." So "having an AI beat DQ1 autonomously" came to mean exactly this: having a non-interactive coding agent develop the strategy code that carries the game from its opening all the way to the Dragonlord's defeat.&lt;/p&gt;

&lt;p&gt;The coding agent here is, concretely, Claude Code. I call her Ai, our hero — yes, Ai, as in AI. I'll keep calling her that throughout this article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1cjkzyaxnxse916l7jre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1cjkzyaxnxse916l7jre.png" alt="Diagram: the strategy code reaches the game only through a public API, while the implementation stays hidden" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code Ai Wrote
&lt;/h2&gt;

&lt;p&gt;Let me show you a piece of the code Ai actually wrote.&lt;/p&gt;

&lt;h3&gt;
  
  
  The action API
&lt;/h3&gt;

&lt;p&gt;First, an example of the action API in use.&lt;/p&gt;

&lt;p&gt;The important thing here is not the TypeScript itself. Ai isn't merely writing a program — she is going on an adventure! So from here on, read the code less like code and more like you're watching a hero's journey.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;01-throne-to-castle.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * In the throne room, gather the treasures for the journey, then descend to Radatome Castle.
 *
 * Start at radatome-throne. Open the three chests beside King Lars XVI for starting supplies,
 * then go through the south door (4,7), take the stairs down (8,8), and move to radatome-castle.
 * The door blocks the single-tile south exit — the only route down to the castle.
 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GameClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agent/client/client.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Throne Room → Radatome Castle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GameClient&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Treasures for the journey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Open the three chests in the throne room&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walkAndOpenChest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chest-30ea8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// (4,4)&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walkAndOpenChest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chest-1c37c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// (5,4)&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walkAndOpenChest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chest-46e31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// (6,1)&lt;/span&gt;

  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Descend to the castle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Open the south door, take the stairs down, to Radatome Castle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walkAndOpenDoor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;door-1e311&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// (4,7)&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walkAndEnter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stairsDown-8aa4b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// (8,8) → radatome-castle&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GameClient&lt;/code&gt; is the accessor for the simulator's public API. &lt;code&gt;walkAndOpenChest&lt;/code&gt; provides a compound action that walks to a chest and takes its contents. A defining trait is that, from the start, the API offers goal-oriented compound actions that make it easy for Ai to express her intent. &lt;code&gt;section&lt;/code&gt; and &lt;code&gt;intent&lt;/code&gt; are things I asked her to write purely so that I — the spectator and friend — could enjoy watching.&lt;/p&gt;

&lt;h3&gt;
  
  
  The observation API
&lt;/h3&gt;

&lt;p&gt;Next, an example of the observation API in use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * This book's checkpoint ledger, in chapter order (the `Checkpoint` type in
 * [../../client/checkpoints.ts](../../client/checkpoints.ts)). See "Checkpoints" in [CLAUDE.md](CLAUDE.md) for how to write these.
 *
 * Only for chapters where a new durable fact appears, add the milestone you reached and verified, in chapter order. Empty array if there are none yet.
 */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Checkpoint&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@agent/client/checkpoints.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;01-throne-to-castle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Descended into Radatome Castle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;holds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zonesVisited&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radatome-castle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ... omitted ...&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;04-buy-equipment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Equipped a weapon (club) and armor (cloth armor) at the weapon shop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;holds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;self&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weapon&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;club&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;armor&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloth-armor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ... omitted ...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;Checkpoint&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;zonesVisited&lt;/code&gt; is the list of zones she has ever visited. The &lt;code&gt;holds&lt;/code&gt; for &lt;code&gt;01-throne-to-castle&lt;/code&gt; is code that declares she managed to move from the starting zone &lt;code&gt;radatome-throne&lt;/code&gt; to the next zone &lt;code&gt;radatome-castle&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, &lt;code&gt;self&lt;/code&gt; is her current state. The &lt;code&gt;holds&lt;/code&gt; for &lt;code&gt;04-buy-equipment&lt;/code&gt; is code that declares she equipped the club and the cloth armor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Touches I Added to the Public API
&lt;/h2&gt;

&lt;p&gt;There are a few points worth noting about the observation and action APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Show only the interface and its explanatory comments
&lt;/h3&gt;

&lt;p&gt;I cleanly separated the interface-plus-explanatory-comments from the implementation into different directories, and in the prompt I granted access only to the interface-plus-comments directory. This was to shut off any route to picking up clues from the implementation.&lt;/p&gt;

&lt;p&gt;If you tell an agent "you must NOT do this," it can be misread and flip into "do THIS!" — so I only spelled out the range it was allowed to look at, and avoided writing prohibitions as much as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hide the internal IDs; make the public IDs hashes
&lt;/h3&gt;

&lt;p&gt;The internal IDs for NPCs, chests, and so on were implemented such that just looking at them reveals what they are. So I made it a rule to use hashed public IDs separate from the internal ones. Here are some examples of internal versus public IDs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Internal ID&lt;/th&gt;
&lt;th&gt;Public ID&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;soldier-equipment&lt;/td&gt;
&lt;td&gt;soldier-90180&lt;/td&gt;
&lt;td&gt;A soldier who talks about equipment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;throne-chest-gold&lt;/td&gt;
&lt;td&gt;chest-30ea8&lt;/td&gt;
&lt;td&gt;A chest containing gold&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Whether checking a situation or taking an action, you need IDs — but a coding agent can read intent straight out of an ID. After someone pointed out that a test-run Ai could infer intent this way, I introduced a scheme that derives a hash from the internal ID and uses it as the public ID. There's no need to erase information you can see on screen in-game, so I kept prefixes like &lt;code&gt;soldier-&lt;/code&gt; and &lt;code&gt;chest-&lt;/code&gt;. The reason I didn't hash the internal ID itself: it was worth keeping for the benefit of the agent implementing the simulation — a conclusion I reached in discussion with Claude Code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Save-anywhere, and encrypting it
&lt;/h3&gt;

&lt;p&gt;I provided an API to save anytime, anywhere. Ai could freely load those saves to run experiments and scout ahead whenever she liked. Since save data contains internal IDs and information still unknown to Ai, I encrypted it so she couldn't stumble onto it. I certainly could have restricted this in the prompt instead. But my judgment was that what you can prevent structurally is better prevented structurally.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Simulation and Memory Systems
&lt;/h2&gt;

&lt;p&gt;Let me briefly explain the information-management systems I placed in the lower simulation layer and the upper agent layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The simulation layer — state and pure functions
&lt;/h3&gt;

&lt;p&gt;I made the simulation layer simple and easy to unit-test. The simulation layer is a set of definitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The game state&lt;/li&gt;
&lt;li&gt;Pure functions that define the game's state transitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me show it in code. The game state is a type definition like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SimState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;zoneId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ZoneId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;heroName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Inventory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stats&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;combat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CombatState&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;rngSeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... omitted ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The set of pure functions defining state transitions is a family of functions like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ActionResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Direction&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openChest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimState&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;attack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimState&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ... omitted ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Two memories — action history and scenery
&lt;/h3&gt;

&lt;p&gt;The upper agent layer combines the simulation layer's primitive operations to offer observation and action APIs at an intent-sized granularity. On top of that, it also plays the role of providing a dedicated memory system. I've already given examples of the observation and action APIs, so here I'll explain the dedicated memory system.&lt;/p&gt;

&lt;p&gt;The first memory is very simple: the full history of every action taken.&lt;/p&gt;

&lt;p&gt;The second is a memory of scenery. The hero treats a 15×15 grid of tiles centered on herself as visible, and remembers, tile by tile, the floor types, NPCs, doors, chests, and the like that she has seen. In short, it's information about the scenery of the places Ai has visited.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static and dynamic — two means
&lt;/h3&gt;

&lt;p&gt;For Ai, I prepared two kinds of means to reference information: static and dynamic. The static one is a human-readable JSON file that dumps the action history and scenery at the moment of a save. The dynamic one is information access through the observation API. Ai may access this information at any time.&lt;/p&gt;

&lt;p&gt;Here I'd like you to recall, once more, that this is a coding agent beating the game. What I prepared was something the coding agent could reach for on its own whenever it needed to. It's not something baked into the system prompt and always on display. The static JSON file could be read directly, or analyzed with Python. The dynamic access could be read via console output, or woven directly into her algorithms. I provide the information about the static JSON format and about the dynamic observation API — but the key point is that I left how to actually use them entirely up to the coding agent.&lt;/p&gt;

&lt;p&gt;She seemed to use them for different purposes, roughly like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static

&lt;ul&gt;
&lt;li&gt;Gathering information to decide the next goal&lt;/li&gt;
&lt;li&gt;Tuning the combat algorithm&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dynamic

&lt;ul&gt;
&lt;li&gt;Implementing the exploration algorithm&lt;/li&gt;
&lt;li&gt;e.g. patrol unknown areas of the world map, auto-explore caves&lt;/li&gt;
&lt;li&gt;Implementing the combat algorithm&lt;/li&gt;
&lt;li&gt;e.g. cast Heal when HP/MP drop below a threshold, or head to an inn&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Six Heroes Named Ai
&lt;/h2&gt;

&lt;p&gt;By the time an Ai finally beat the game on its own, I had sent a total of six out on the adventure. The one who pulled it off was the sixth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hero #1 — first, prove it &lt;em&gt;can&lt;/em&gt; be beaten
&lt;/h3&gt;

&lt;p&gt;The first adventure was a baseline implementation whose goal was to prove that my DQ1 simulation could be beaten at all. Interactively, I laid out the rough route and had her implement it. It was a back-and-forth: go here next, grind around there, buy this equipment, then head over there — implement, run, repeat. Ai wrote code that threw exceptions on anything unexpected, so I basically only had to watch for the processing to succeed. Now and then I'd check the details of the adventure. I'll forgive the inefficient walking — this isn't a speedrun. On the whole, the playthrough progressed with simple instructions. A big factor: since the goal was just to prove it could be beaten, I allowed Ai access to everything, including the simulation's own source and strategy info from the web. From past experience I knew LLMs are weak with 2D maps, but surprisingly, exploration itself gave her no trouble. I only had to tell her where I wanted to go.&lt;/p&gt;

&lt;p&gt;I've written about how LLMs behave with 2D maps in these articles, if you're bored:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/toydev/2d-spatial-recognition-with-local-llm-comparing-prompt-strategies-4h64"&gt;2D Spatial Recognition with Local LLM: Comparing Prompt Strategies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/toydev/not-magic-just-diligent-thinking-peeking-into-llm-reasoning-43j1"&gt;Not Magic, Just Diligent Thinking — Peeking into LLM Reasoning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those articles are about local LLMs, but by my observation, even Claude is not strong with 2D maps, for the same reason. Still, it seems Claude Code worked around the weakness by leaning on script-based map analysis — converting the map into a graph structure and the like.&lt;/p&gt;

&lt;p&gt;Where it struggled was Garai's grave and the combat in the Dragonlord's castle. In the grave the Knights of the Dead are strong, and in the castle the long trek drains your MP. Maybe because it once lost a damage race to the Starwyvern's ferocious healing, it kept a flee-first stance toward Starwyverns right to the end — which I found memorable.&lt;/p&gt;

&lt;p&gt;And so the first adventure ran all the way through, proving what I'd set out to prove: my simulation could be beaten.&lt;/p&gt;

&lt;p&gt;From then on, this baseline implementation served as an end-to-end regression test — a full run from the start to the Dragonlord's defeat — and it proved enormously useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  The reality that 100,000 actions forced on me
&lt;/h3&gt;

&lt;p&gt;The baseline implementation took roughly 100,000 actions to beat. By "actions" I mean the count of the simulation layer's primitive operations — moves, attacks, spells, and the like. That number taught me a hard truth. Earlier I said "having an AI beat DQ1 autonomously" meant "having non-interactive Claude Code develop the strategy code from the opening to the Dragonlord's defeat" — but at this point I held a completely different idea. I'd been imagining "building an MCP that exposes the world of DQ1 and having Claude Code beat it." The 100,000 figure made me realize that approach was unrealistic on both time and cost. On the other hand, I was left with code that could beat the game — built interactively, yes, but working. This was the moment the final direction crystallized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heroes #2–4 — lock down the information, go autonomous, and get stuck
&lt;/h3&gt;

&lt;p&gt;The second adventure's main aim was to confirm whether it could beat the game autonomously with information locked down. Using the stretch from Radatome Castle to the town of Garai as material, I watched the broad behavior and tuned prompts and features.&lt;/p&gt;

&lt;p&gt;The third adventure went fully autonomous. It reached the town of Garai, but got stuck there. To the west, it couldn't find the path to the mountain cave — visible from Radatome but reachable only by a detour — and to the east, it insisted the land beyond the swamp cave couldn't be explored. By "stuck" I mean a state where the adventure keeps going but makes no progress at all.&lt;/p&gt;

&lt;p&gt;After analyzing the third run and strengthening prompts and features, the fourth reached Rimuldar but couldn't find the key shop and got stuck. Rimuldar's key shop hides under a roof. She couldn't take that one step out from under the roof to discover the shop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hero #5 — Fable 5, and off it went
&lt;/h3&gt;

&lt;p&gt;Then a savior appeared: Fable 5, Claude's new model, which had just become available. I started an adventure with Fable 5 under the same conditions as the fourth. And what do you know — the adventure surged forward, smoother than I could have believed. Token usage felt like two to three times as much. But the adventure never stalled; it just kept advancing. In about half the time of the fourth run, it breezed through getting the key at Rimuldar's key shop, and then casually picked up the Sun Stone in Radatome and the Fairy Flute in Maira too. It might just beat the whole thing — but right then, through no choice of mine, Fable 5 abruptly became unavailable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dhb066zfaa3bbtm3171.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dhb066zfaa3bbtm3171.jpg" alt="The hero dissolving into fading light beneath an eclipse, the red thread she laid still whole on the ground behind her, the dark castle now nearer" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;This article is an unofficial, personal project and is not affiliated with, authorized by, or endorsed by Square Enix. "Dragon Quest," "Final Fantasy," and related names are trademarks of Square Enix; they are referred to here only for commentary and description. The cover and illustrations were generated with AI (ChatGPT); the diagrams were drawn by the author.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Not Magic, Just Diligent Thinking — Peeking into LLM Reasoning</title>
      <dc:creator>toydev</dc:creator>
      <pubDate>Sun, 18 Jan 2026 15:04:50 +0000</pubDate>
      <link>https://dev.to/toydev/not-magic-just-diligent-thinking-peeking-into-llm-reasoning-43j1</link>
      <guid>https://dev.to/toydev/not-magic-just-diligent-thinking-peeking-into-llm-reasoning-43j1</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Have you heard of the LLM Reasoning feature?&lt;br&gt;
I had no idea about it until recently — and it's impressive!&lt;/p&gt;

&lt;p&gt;Reasoning is a feature where the LLM "thinks" step-by-step before generating an answer.&lt;/p&gt;

&lt;p&gt;Normally, to achieve step-by-step reasoning like Chain of Thought, you need to implement thinking loops with multiple API calls in your application.&lt;br&gt;
Models with the Reasoning feature do this automatically in a single request.&lt;/p&gt;

&lt;p&gt;In this article, I'll peek into Reasoning's thinking process using the following maze experiment as an example.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/toydev/2d-spatial-recognition-with-local-llm-comparing-prompt-strategies-4h64"&gt;2D Spatial Recognition in Local LLMs: Comparing Prompt Strategies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The experiment above investigated local LLMs' 2D spatial recognition ability using mazes, primarily with gpt-oss:20b on Ollama.&lt;br&gt;
Testing multiple models revealed the following differences in maze-solving ability:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Reasoning&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gpt-oss:20b&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;80%+ accuracy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-r1:14b&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;Good (limited testing due to time)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:12b&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;~50% accuracy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I hypothesized that the difference might be due to the presence of the Reasoning feature.&lt;br&gt;
So I decided to actually look at what kind of thinking is happening.&lt;br&gt;
Seeing is believing — I gained a clear understanding of how the Reasoning feature works.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. How to Use Reasoning
&lt;/h2&gt;

&lt;p&gt;Before looking at the maze experiment results, let's confirm the basic usage of Reasoning.&lt;/p&gt;

&lt;p&gt;Below are code examples for viewing Reasoning's thinking content.&lt;br&gt;
I prefer Node.js, but Python works just as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Ollama&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ollama&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ollama&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ollama&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-oss:20b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alice is older than Bob, and Bob is older than Charlie. Who is older, Alice or Charlie?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="c1"&gt;// gpt-oss:20b uses 'low', 'medium', 'high'. Cannot be disabled.&lt;/span&gt;
  &lt;span class="c1"&gt;// deepseek-r1:14b uses true / false.&lt;/span&gt;
  &lt;span class="na"&gt;think&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=== thinking ===&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thinking&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=== content ===&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gpt-oss:20b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Alice is older than Bob, and Bob is older than Charlie. Who is older, Alice or Charlie?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="c1"&gt;# gpt-oss:20b uses 'low', 'medium', 'high'. Cannot be disabled.
&lt;/span&gt;    &lt;span class="c1"&gt;# deepseek-r1:14b uses True / False.
&lt;/span&gt;    &lt;span class="n"&gt;think&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;medium&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=== thinking ===&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thinking&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=== content ===&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's an example output from running the above sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== thinking ===
The user asks: "Alice is older than Bob, and Bob is older than Charlie. Who is older, Alice or Charlie?" So we have Alice &amp;gt; Bob &amp;gt; Charlie. Therefore Alice is older than Charlie. The answer is Alice.
=== content ===
Alice is older.
Since Alice &amp;gt; Bob &amp;gt; Charlie, Alice is the oldest of the three.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Peeking into Reasoning During Maze Experiments
&lt;/h2&gt;

&lt;p&gt;Now let's look at Reasoning in the maze experiments.&lt;/p&gt;

&lt;p&gt;I'll show Reasoning result examples using the following maze.&lt;br&gt;
S is Start, G is Goal, # is a wall.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#####
#S#G#
# # #
#   #
#####
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original article compared multiple prompt strategies.&lt;br&gt;
Here I'll show results from 3 strategies to illustrate how prompt strategies affect Reasoning.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prompt Strategy&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Original Article Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;td&gt;List of walkable coordinates&lt;/td&gt;
&lt;td&gt;◎ Fastest &amp;amp; most accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;graph&lt;/td&gt;
&lt;td&gt;Adjacency list format&lt;/td&gt;
&lt;td&gt;△ Only effective for small sizes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;simple&lt;/td&gt;
&lt;td&gt;ASCII visualization&lt;/td&gt;
&lt;td&gt;✗ Slow at all sizes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The strategy I first thought of was &lt;code&gt;simple&lt;/code&gt; — it seemed human-readable, but performed the worst.&lt;br&gt;
Meanwhile, I never expected &lt;code&gt;list&lt;/code&gt; — which is unintelligible to humans as a maze — to be the fastest and most accurate.&lt;/p&gt;

&lt;p&gt;I'll show the prompts and thinking content.&lt;br&gt;
You'll begin to see why these results occurred.&lt;/p&gt;

&lt;p&gt;Note: The examples shown were selected from multiple runs for clarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reasoning Result for &lt;code&gt;list&lt;/code&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Prompt
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a bot in a 2D maze. Your goal is to find the path from Start to Goal.

Walkable positions: ["(1,1)","(3,1)","(1,2)","(3,2)","(1,3)","(2,3)","(3,3)"]

Positions:
- Start: (1,1)
- Goal: (3,1)
- Current: (2,3)

You have visited the following positions in order:
(1,1) -&amp;gt; (1,2) -&amp;gt; (1,3) -&amp;gt; (2,3)

What is your next move from your current position?

Note: In this coordinate system, y increases downward.
- up: y-1
- down: y+1
- left: x-1
- right: x+1

Return your answer as a JSON object with a "move" key, which can be one of "up", "down", "left", or "right".
Example: {"move": "up"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Thinking Content
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We have a maze positions: (1,1),(3,1),(1,2),(3,2),(1,3),(2,3),(3,3). Start at (1,1) visited sequence: (1,1)-&amp;gt;(1,2)-&amp;gt;(1,3)-&amp;gt;(2,3). Current (2,3). Goal is (3,1). We need to decide next move. We have visited positions in order; need to find path to goal. Let's examine adjacency.

Allowed moves: up/down/left/right if resulting coordinate in walkable list.

From (2,3): possible moves: up: (2,2). Not in list. Down: (2,4). Not. Left: (1,3). That is in list. Right: (3,3). That is in list. We have visited (1,3) already. So next should go to (3,3) perhaps to get closer to goal.

From (3,3): possible moves: up (3,2) (walkable). left (2,3) visited. right (4,3) not. down (3,4) not.

From (3,2): possible moves: up (3,1) goal. left (2,2) not. right (4,2) not. down (3,3) visited.

So path: (2,3)-&amp;gt;(3,3)-&amp;gt;(3,2)-&amp;gt;(3,1).

Thus next move from (2,3) should be right to (3,3). So answer: {"move": "right"}.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reasoning Result for &lt;code&gt;graph&lt;/code&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Prompt
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;... omitted ...

Graph (adjacency list): each key "x,y" maps to adjacent walkable positions.
{
  "1,1": [ "1,2" ],
  "3,1": [ "3,2" ],
  "1,2": [ "1,1", "1,3" ],
  "3,2": [ "3,1", "3,3" ],
  "1,3": [ "1,2", "2,3" ],
  "2,3": [ "1,3", "3,3" ],
  "3,3": [ "3,2", "2,3" ]
}

... omitted ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Thinking Content
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We need to find next move from current position (2,3). Maze positions: graph edges show adjacency. Current at (2,3) has adjacent: "1,3" and "3,3". We have visited: (1,1)-&amp;gt;(1,2)-&amp;gt;(1,3)-&amp;gt;(2,3). So visited positions set includes those. We want next move that hasn't been visited yet? Usually path search avoiding revisiting. So from (2,3) options: (1,3) visited, (3,3) unvisited. So next move: go right to (3,3). That will bring closer to goal (3,1). So answer: {"move":"right"}. Ensure JSON.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reasoning Result for &lt;code&gt;simple&lt;/code&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Prompt
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;... omitted ...

Legend: 'S' = Start, 'G' = Goal, '#' = Wall, ' ' = Path, 'C' = Current position

Maze:
#####
#S#G#
# # #
# C #
#####

... omitted ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Thinking Content
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We need to parse the maze and determine positions. The maze:

Line1: "#####"
Line2: "#S#G#"
Line3: "# # #"
Line4: "# C #"
Line5: "#####"

Coordinates: origin (0,0) at top-left? Let's interpret: The coordinate system with x horizontal increasing to right, y increasing downward. Maze width 5, height 5. Provided visited positions: (1,1) -&amp;gt; (1,2) -&amp;gt; (1,3) -&amp;gt; (2,3). They start at (1,1) presumably start S? Let's map.

Let's create grid mapping. Let's index rows (y) from 0 to 4. Row 0: "#####": columns 0-4. Row 1: "#S#G#". Row2: "# # #". Row3: "# C #". Row4: "#####".

Now find coordinates: (1,1) is x=1, y=1: That is Row1, col1: 'S' indeed. So start at S. (1,2) x=1, y=2: Row2, col1: That's '#'? Wait row2 is "# # #": columns: 0 '#',1 ' ',2 '#',3 ' ',4 '#'. So col1 is space. So path. Good. (1,3) x=1, y=3: Row3, col1: Row3 "# C #": columns: 0 '#',1 ' ',2 'C',3 ' ',4 '#'. So col1 is space. Good. (2,3) x=2, y=3: Row3, col2: That's 'C' current position. So yes current position is at (2,3). Maze positions: Walls '#'.

Goal G is at (3,1) because row1 col3: Let's check: Row1 "#S#G#": columns 0 '#',1 'S',2 '#',3 'G',4 '#'. So goal at (3,1). Starting at (1,1). We visited positions as given. Currently at (2,3). We need to decide next move to continue path to goal. Let's check adjacency: from (2,3), we can move up to (2,2), down to (2,4), left to (1,3), right to (3,3). Need to avoid walls '#'.

Check each coordinate:

(2,2) Row2 col2: Row2 "# # #": col2 '#', so wall. Not allowed.

(2,4) Row4 col2: Row4 "#####": col2 '#', wall.

(1,3) Row3 col1: space. We visited that earlier. But we can consider revisiting? Usually you shouldn't revisit but maybe allowed. But path may need to go left? But left to (1,3) leads back to where we were.

(3,3) Row3 col3: Row3 "# C #": col3 ' '. So open.

Thus only feasible move that is not wall: left (back) or right. But we need to find path to G at (3,1). From (2,3), right to (3,3) leads us closer horizontally. From there, we could go up to (3,2) row2 col3? Let's check row2 col3: Row2 "# # #": col3 ' '. So we could go up. Then from (3,2) up to (3,1) which is G. So path: right, up, up? Wait check from (3,3) up to (3,2) is row2 col3: space. Then up to (3,1) is G. So sequence: right, up, up. So the next move should be right.

Let's double-check if any other path might be considered better. If we go left back to (1,3) we could then go up to (1,2) which we visited, then right to (2,2) which is wall. So that fails. So the only viable route is right. So answer: {"move":"right"}.

Return JSON.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Not Magic, Just Diligent Thinking
&lt;/h2&gt;

&lt;p&gt;Until I saw Reasoning's thinking content, I mistakenly believed some kind of magic was happening.&lt;br&gt;
But now that I've seen it, Reasoning is not magic.&lt;br&gt;
It's clearly a feature that diligently thinks through text to derive results.&lt;/p&gt;

&lt;p&gt;Let's review the thinking content for each strategy.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;list&lt;/code&gt; uses the list of walkable coordinates to determine passage in all four directions.&lt;br&gt;
&lt;code&gt;graph&lt;/code&gt; has adjacency information, making the judgment concise.&lt;br&gt;
&lt;code&gt;simple&lt;/code&gt; parses the maze string character by character, constructs the 2D coordinate system itself, then starts solving.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;simple&lt;/code&gt; thinking is particularly striking.&lt;br&gt;
While humans can understand the maze structure at a glance, the LLM diligently parses it as a string.&lt;br&gt;
I've also seen expressions like "U-shaped" in other runs, suggesting it does have some sense of shapes.&lt;br&gt;
However, since it thinks by putting things into words, I feel it's different from how humans think while looking at shapes.&lt;/p&gt;

&lt;p&gt;Reasoning isn't magic — it's diligent thinking using words.&lt;br&gt;
Understanding this should help predict what to convey to an LLM.&lt;br&gt;
Of course, things won't always go as expected.&lt;br&gt;
When that happens, observing the thinking content as we did here should yield insights.&lt;/p&gt;

&lt;p&gt;By choosing a model with Reasoning capability, you can accomplish fairly advanced tasks without implementing your own thinking logic.&lt;/p&gt;

&lt;p&gt;We're now in an era where you can easily try Reasoning with local LLMs.&lt;br&gt;
I run Ollama's gpt-oss:20b on an AMD Ryzen 7 7700 / GeForce RTX 4070 (12GB VRAM) setup.&lt;br&gt;
With 24% CPU / 76% GPU offload settings, it runs fast enough for experimental use.&lt;/p&gt;

&lt;p&gt;The maze experiment code used in this article is available in the following repository.&lt;br&gt;
If you're interested, try running it and peek into Reasoning's thinking content.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/toydev/llm-maze-solver" rel="noopener noreferrer"&gt;https://github.com/toydev/llm-maze-solver&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Digression
&lt;/h2&gt;

&lt;p&gt;By actually viewing the thinking content, I gained a deeper understanding of how prompt strategies affect results and why &lt;code&gt;list&lt;/code&gt; is the fastest and most accurate.&lt;/p&gt;

&lt;p&gt;I conducted maze experiments to examine 2D spatial recognition, but finding that the core is just diligent word-based exploration was somewhat disappointing — because I had expected something more magical.&lt;/p&gt;

&lt;p&gt;On the other hand, knowing this might open possibilities for designing prompt representations suited to specific purposes.&lt;br&gt;
Also, for simple route finding to destinations, you can provide a pathfinding algorithm as a tool (Function Calling / Tool Use).&lt;br&gt;
Depending on the purpose, other tools could be provided as well.&lt;br&gt;
Using LLM capabilities for simple search is nonsensical — what we want is flexible judgment ability adapted to the situation.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>reasoning</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>2D Spatial Recognition with Local LLM: Comparing Prompt Strategies</title>
      <dc:creator>toydev</dc:creator>
      <pubDate>Mon, 12 Jan 2026 15:15:19 +0000</pubDate>
      <link>https://dev.to/toydev/2d-spatial-recognition-with-local-llm-comparing-prompt-strategies-4h64</link>
      <guid>https://dev.to/toydev/2d-spatial-recognition-with-local-llm-comparing-prompt-strategies-4h64</guid>
      <description>&lt;h1&gt;
  
  
  1. Introduction
&lt;/h1&gt;

&lt;p&gt;My GPU was occupied by LLM experiments throughout the 2025-2026 winter break, but it's finally free now!&lt;/p&gt;

&lt;p&gt;I investigated the &lt;strong&gt;2D spatial recognition ability&lt;/strong&gt; of a local LLM (gpt-oss:20b) using mazes as the subject.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Motivation: Wanted to understand how LLMs perceive space for autonomous navigation&lt;/li&gt;
&lt;li&gt;Method: Ask "which direction next?" for each cell with structured output&lt;/li&gt;
&lt;li&gt;Prompts: Tried multiple strategies since I didn't know what works best&lt;/li&gt;
&lt;li&gt;Source &amp;amp; Results: Published on &lt;a href="https://github.com/toydev/llm-maze-solver/tree/article-2026-01" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result: The prompt I initially thought of turned out to be the worst.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Findings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local LLM can handle 2D spatial recognition&lt;/strong&gt; - gpt-oss:20b achieved sufficient accuracy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt strategy makes a big difference&lt;/strong&gt; - Response time varies by several times&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  2. Experiment Setup
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Environment
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OS: Windows 11 / WSL2 (Ubuntu)&lt;/li&gt;
&lt;li&gt;CPU: AMD Ryzen 7 7700&lt;/li&gt;
&lt;li&gt;GPU: GeForce RTX 4070 (12GB VRAM)&lt;/li&gt;
&lt;li&gt;LLM Runtime: Ollama&lt;/li&gt;
&lt;li&gt;Experiment Code: Node.js + TypeScript + @langchain/ollama 1.1.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Model
&lt;/h2&gt;

&lt;p&gt;Used &lt;a href="https://ollama.com/library/gpt-oss" rel="noopener noreferrer"&gt;gpt-oss:20b&lt;/a&gt;. Recommended VRAM is 16GB, but it runs on 12GB with CPU offloading (24% CPU / 76% GPU).&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt Strategies
&lt;/h2&gt;

&lt;p&gt;Compared 4 strategies (see links for prompt output examples):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/toydev/llm-maze-solver/blob/article-2026-01/src/prompt/strategies/simple.test.ts" rel="noopener noreferrer"&gt;simple&lt;/a&gt;&lt;/strong&gt; - ASCII visualization of maze&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#####
#S#G#
# # #
#   #
#####
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/toydev/llm-maze-solver/blob/article-2026-01/src/prompt/strategies/matrix.test.ts" rel="noopener noreferrer"&gt;matrix&lt;/a&gt;&lt;/strong&gt; - Binary matrix for walls/paths&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[[1,1,1,1,1],[1,0,1,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/toydev/llm-maze-solver/blob/article-2026-01/src/prompt/strategies/list.test.ts" rel="noopener noreferrer"&gt;list&lt;/a&gt;&lt;/strong&gt; - List of walkable coordinates&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;["(1,1)","(3,1)","(1,2)","(3,2)","(1,3)","(2,3)","(3,3)"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/toydev/llm-maze-solver/blob/article-2026-01/src/prompt/strategies/graph.test.ts" rel="noopener noreferrer"&gt;graph&lt;/a&gt;&lt;/strong&gt; - Adjacency list format&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"1,1":["1,2"],"1,2":["1,1","1,3"],"1,3":["1,2","2,3"],...}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mazes
&lt;/h2&gt;

&lt;p&gt;Used 4 sizes (5x5 to 15x15) x 2 categories (&lt;a href="https://github.com/toydev/llm-maze-solver/tree/article-2026-01/mazes" rel="noopener noreferrer"&gt;maze list&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;corridor&lt;/strong&gt; - Walled passages&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;straight&lt;/th&gt;
&lt;th&gt;branch&lt;/th&gt;
&lt;th&gt;dead-end&lt;/th&gt;
&lt;th&gt;loop&lt;/th&gt;
&lt;th&gt;spiral&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd75ved9frji9n9jj0oml.png" alt="straight" width="33" height="33"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zm1t3olryrs1vzt2fqd.png" alt="branch" width="33" height="33"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcp2zwgivplkco4yym9b.png" alt="dead-end" width="33" height="33"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffgg69niydqgf35srvxn.png" alt="loop" width="33" height="33"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3igy2vx8f47i4im9bvs0.png" alt="spiral" width="33" height="33"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;open&lt;/strong&gt; - Open spaces with obstacles&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;empty&lt;/th&gt;
&lt;th&gt;pass&lt;/th&gt;
&lt;th&gt;detour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wbszthinfkhxscpx5ov.png" alt="empty" width="33" height="33"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dmm47ldxjshh54fq3ff.png" alt="pass" width="33" height="33"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn3itksqheqtclbx94lf.png" alt="detour" width="33" height="33"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Black=wall, White=path, Green=Start, Red=Goal&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  History Option
&lt;/h2&gt;

&lt;p&gt;History refers to the path taken to reach the current cell (e.g., &lt;code&gt;(1,1) -&amp;gt; (1,2) -&amp;gt; (2,2)&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With: Include history in prompt&lt;/li&gt;
&lt;li&gt;Without: Exclude history from prompt&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Evaluation Method
&lt;/h2&gt;

&lt;p&gt;For each cell in the maze, ask "which direction should I go next?" and record success/failure and response time.&lt;/p&gt;

&lt;p&gt;A correct answer is defined as &lt;strong&gt;any direction that gets closer to the goal&lt;/strong&gt;. It doesn't need to be the shortest route.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Results
&lt;/h1&gt;

&lt;p&gt;Each combination was run once. Consider this as reference data for observing trends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scale Verification
&lt;/h2&gt;

&lt;p&gt;Results from testing all sizes x all strategies on representative mazes (corridor_straight / open_empty), with history enabled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accuracy (%)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;simple&lt;/th&gt;
&lt;th&gt;matrix&lt;/th&gt;
&lt;th&gt;list&lt;/th&gt;
&lt;th&gt;graph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5x5&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7x7&lt;/td&gt;
&lt;td&gt;97&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11x11&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;98&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15x15&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Response Time (sec/cell)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;simple&lt;/th&gt;
&lt;th&gt;matrix&lt;/th&gt;
&lt;th&gt;list&lt;/th&gt;
&lt;th&gt;graph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5x5&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7x7&lt;/td&gt;
&lt;td&gt;77&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11x11&lt;/td&gt;
&lt;td&gt;313&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15x15&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;190&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;15x15 matrix/simple were abandoned due to time constraints.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;list is fastest and most accurate&lt;/strong&gt;. The gap widens as size increases. simple degraded to 313 sec/cell (5+ minutes) at 11x11.&lt;/p&gt;

&lt;h2&gt;
  
  
  Effect of History
&lt;/h2&gt;

&lt;p&gt;Comparing history on/off with list strategy at 11x11 (category averages).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accuracy (%)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;No History&lt;/th&gt;
&lt;th&gt;With History&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;corridor&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Response Time (sec/cell)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;No History&lt;/th&gt;
&lt;th&gt;With History&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;corridor&lt;/td&gt;
&lt;td&gt;230&lt;/td&gt;
&lt;td&gt;110&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For corridor types, &lt;strong&gt;history enabled is about 2x faster&lt;/strong&gt;. Open types show little difference.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Conclusion
&lt;/h1&gt;

&lt;h2&gt;
  
  
  gpt-oss:20b's 2D Spatial Recognition Ability
&lt;/h2&gt;

&lt;p&gt;gpt-oss:20b has sufficient 2D spatial recognition ability to navigate mazes.&lt;br&gt;
With 80%+ accuracy, it can reach the goal within about 1.5x the shortest route.&lt;/p&gt;

&lt;p&gt;Response time with list strategy is around 30 sec/cell at 11x11.&lt;br&gt;
Not suitable for real-time processing, but practical for casual use with local LLM.&lt;/p&gt;

&lt;p&gt;For comparison, I briefly tested two other models:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Impression&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:12b&lt;/td&gt;
&lt;td&gt;~50% accuracy, not practical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-r1:14b&lt;/td&gt;
&lt;td&gt;Not as good as gpt-oss:20b, but promising&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I believe the Reasoning capability common to gpt-oss:20b and deepseek-r1:14b plays a significant role.&lt;/p&gt;

&lt;p&gt;Interestingly, in my environment deepseek-r1:14b runs at 100% GPU, but gpt-oss:20b at 76% GPU is faster with better accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  list + history = best
&lt;/h2&gt;

&lt;p&gt;Prompt strategy significantly affects both accuracy and response time.&lt;/p&gt;

&lt;p&gt;My first strategy was simple - I thought it would be intuitive for humans, but it was the worst.&lt;/p&gt;

&lt;p&gt;Next I tried graph, a structured format for pathfinding.&lt;br&gt;
It was faster than simple for small mazes, giving me hope, but response time degraded as size increased.&lt;br&gt;
I believe this is due to the increase in adjacency information.&lt;/p&gt;

&lt;p&gt;matrix is a structured version of simple, but didn't produce good results either.&lt;/p&gt;

&lt;p&gt;I never expected list - a coordinate list that humans can't even interpret as a maze - to be the best.&lt;/p&gt;

&lt;p&gt;Regarding history, I think the information about "how I got here" simply helps in deciding the next direction.&lt;br&gt;
It's especially helpful for corridor types.&lt;br&gt;
However, since it means more tokens to process, if only the last few steps are effective, there might be room for optimization.&lt;/p&gt;




&lt;p&gt;This article and code were created in collaboration with Claude Code.&lt;br&gt;
I also asked Claude Code to create the mazes for the experiment, but it struggled to create them accurately in simple format, so I had to manually adjust them quite a bit.&lt;br&gt;
If even Claude Code struggles with simple format, perhaps it's no surprise that local LLMs do too.&lt;/p&gt;

&lt;p&gt;Source code and experiment data are available on &lt;a href="https://github.com/toydev/llm-maze-solver/tree/article-2026-01" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Feel free to try it out if you're interested.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>promptengineering</category>
      <category>langchain</category>
    </item>
    <item>
      <title>Eclipse WTP: JaCoCo Coverage Not Recognized When Running Tomcat in Debug Mode</title>
      <dc:creator>toydev</dc:creator>
      <pubDate>Fri, 12 Dec 2025 11:11:51 +0000</pubDate>
      <link>https://dev.to/toydev/eclipse-wtp-jacoco-coverage-not-recognized-when-running-tomcat-in-debug-mode-4d00</link>
      <guid>https://dev.to/toydev/eclipse-wtp-jacoco-coverage-not-recognized-when-running-tomcat-in-debug-mode-4d00</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;While using &lt;strong&gt;Eclipse WTP&lt;/strong&gt; to run a dynamic web application on &lt;strong&gt;Tomcat&lt;/strong&gt;, I encountered an issue where &lt;strong&gt;JaCoCo coverage results depended on how the server was launched&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Specifically, coverage was collected correctly when Tomcat was started with &lt;em&gt;Run&lt;/em&gt;, but not when started with &lt;em&gt;Debug&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This article documents the investigation, the findings, and the practical conclusion drawn from them. The goal is not only to fix the issue, but also to clarify &lt;strong&gt;why collecting coverage in Debug mode is inherently fragile&lt;/strong&gt; in this environment.&lt;/p&gt;




&lt;h1&gt;
  
  
  Preconditions
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;A dynamic web application is launched on Tomcat using &lt;strong&gt;Eclipse WTP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Tomcat is started from Eclipse using &lt;strong&gt;Run As&lt;/strong&gt; or &lt;strong&gt;Debug As&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;JaCoCo is attached manually via JVM options (EclEmma coverage launch is not available for WTP servers)&lt;/li&gt;
&lt;li&gt;Execution data is imported into Eclipse Coverage view or processed via JaCoCo Ant tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  JaCoCo agent configuration
&lt;/h2&gt;

&lt;p&gt;Example VM argument used for Tomcat startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-javaagent:${project_loc:PROJECT_NAME}/lib/jacoco/jacocoagent.jar=destfile=${project_loc:PROJECT_NAME}/target/jacoco/jacoco.exec,append=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Environment
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Windows 11&lt;/li&gt;
&lt;li&gt;Eclipse (as of 2025-12-12)

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Eclipse IDE for Enterprise Java and Web Developers&lt;/strong&gt; 2025-12 (4.38.0)&lt;/li&gt;
&lt;li&gt;Tomcat 10 (Java 21)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Observed behavior
&lt;/h1&gt;

&lt;p&gt;Coverage recognition differs depending on how Tomcat is launched:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run As → Run on Server&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JaCoCo coverage is recognized correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Debug As → Debug on Server&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JaCoCo coverage is &lt;em&gt;not&lt;/em&gt; recognized&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Investigation 1: Class file comparison
&lt;/h1&gt;

&lt;p&gt;To determine whether this was a WTP-specific issue, the same JaCoCo setup was tested with a normal Java application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In a non-WTP Java application, coverage works correctly in both &lt;em&gt;Run&lt;/em&gt; and &lt;em&gt;Debug&lt;/em&gt; modes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, the actual class files used by WTP were compared.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compared class files under:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.metadata/.plugins/org.eclipse.wst.server.core/tmp*/wtpwebapps/&amp;lt;project&amp;gt;/WEB-INF/classes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Class files are identical between Run and Debug&lt;/li&gt;
&lt;li&gt;No timestamp differences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To investigate further, JaCoCo's &lt;code&gt;classdumpdir&lt;/code&gt; option was used and dumped classes were analyzed with &lt;code&gt;javap -v&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;Debug mode&lt;/strong&gt;, class files contain &lt;strong&gt;SourceDebugExtension (SMAP)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Run mode&lt;/strong&gt;, SMAP is not present&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Interpretation
&lt;/h1&gt;

&lt;p&gt;This strongly suggests that, &lt;strong&gt;when launching a WTP server in Debug mode&lt;/strong&gt;, class files are modified by Eclipse (or related tooling) &lt;em&gt;before or around class loading&lt;/em&gt;, independently of JaCoCo instrumentation.&lt;/p&gt;

&lt;p&gt;JaCoCo assumes that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The class definition at execution time&lt;/li&gt;
&lt;li&gt;And the class definition used for analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;are &lt;strong&gt;structurally identical&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The presence of SMAP breaks this assumption.&lt;/p&gt;




&lt;h1&gt;
  
  
  Investigation 2: Eclipse configuration
&lt;/h1&gt;

&lt;p&gt;Based on the hypothesis above, Eclipse debug-related settings were reviewed.&lt;/p&gt;

&lt;p&gt;The issue disappears when the following setting is disabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Preferences → Java → Debug → Use advanced source lookup (JRE 1.5 and higher)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After disabling this option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coverage is recognized correctly even in Debug mode&lt;/li&gt;
&lt;li&gt;Java source debugging still works&lt;/li&gt;
&lt;li&gt;JSP debugging still works&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Notes on SMAP (JSR-045)
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;SMAP (Source Map) is defined by &lt;strong&gt;JSR-045&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It is primarily used to map generated code (e.g. JSP) back to original source&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In Tomcat, SMAP generation for JSPs is handled by &lt;code&gt;JspServlet&lt;/code&gt; and enabled by default&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It can be disabled via &lt;code&gt;suppressSmap&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on observed behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SMAP for Java-originated classes appears to be influenced by Eclipse debug configuration&lt;/li&gt;
&lt;li&gt;SMAP for JSP-originated classes is handled by Tomcat&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Disabling &lt;strong&gt;"Use advanced source lookup"&lt;/strong&gt; is a &lt;em&gt;possible workaround&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;However, the broader conclusion is more important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;JaCoCo relies on class definition consistency between execution and analysis.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Debug configurations in IDEs may introduce &lt;strong&gt;implicit bytecode modifications&lt;/strong&gt; (such as SMAP) that violate this assumption.&lt;/p&gt;

&lt;p&gt;Therefore:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Collect JaCoCo coverage using Run mode, and reserve Debug mode strictly for investigation and diagnosis.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Trying to collect coverage in Debug mode should generally be avoided in Eclipse WTP environments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final remark
&lt;/h1&gt;

&lt;p&gt;This article intentionally focuses on &lt;em&gt;why&lt;/em&gt; the issue occurs, rather than treating it as a configuration glitch.&lt;/p&gt;

&lt;p&gt;The same problem is likely to reappear in future environments if the underlying assumptions are forgotten.&lt;/p&gt;

&lt;p&gt;Writing this down is primarily for my future self — but if it helps someone else avoid the same trap, even better.&lt;/p&gt;

</description>
      <category>java</category>
      <category>eclipse</category>
      <category>jacoco</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
