<?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: IdleCultivation</title>
    <description>The latest articles on DEV Community by IdleCultivation (@idlecultivation).</description>
    <link>https://dev.to/idlecultivation</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%2F4111642%2F0e8ad04e-ec8e-4c51-adff-f897bf8ec2ba.jpeg</url>
      <title>DEV Community: IdleCultivation</title>
      <link>https://dev.to/idlecultivation</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/idlecultivation"/>
    <language>en</language>
    <item>
      <title>Tests that pass for the wrong reason, collected from one project</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Mon, 28 Sep 2026 04:30:07 +0000</pubDate>
      <link>https://dev.to/idlecultivation/tests-that-pass-for-the-wrong-reason-collected-from-one-project-2pj3</link>
      <guid>https://dev.to/idlecultivation/tests-that-pass-for-the-wrong-reason-collected-from-one-project-2pj3</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;2 Aug 2026 to 4 Aug 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;A green check is a claim, and this week I went looking for the ones making a claim they cannot support. Five distinct shapes turned up in a single project, which suggests they are common rather than unlucky, so here they are with what each one cost.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The assertion that matched the wrong line
&lt;/h2&gt;

&lt;p&gt;A test asserted that a particular value appeared in a file. It did appear, at the top, inside the line that pulls the value in from somewhere else. The actual code below could do anything at all and the assertion held.&lt;/p&gt;

&lt;p&gt;Searching text for a name is the easiest way to write a structural check and it is almost always wrong, because a name occurs in a file for many reasons and only one of them is the reason you care about. If the check must be textual, it has to exclude the places a name appears incidentally. Better, it should parse.&lt;/p&gt;

&lt;p&gt;Cost of this one: an entire category of rule was unenforced for weeks while its badge said otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parser that stopped reading too early
&lt;/h2&gt;

&lt;p&gt;Related and worse. Another check scanned code for a forbidden pattern, and its scanner stopped consuming a function at the return type, so anything after that point was invisible to it.&lt;/p&gt;

&lt;p&gt;The check was not broken in a way that produced errors. It examined a smaller region than it claimed, reported clean, and let through exactly the case it was written to catch. Somebody had even fixed a real instance using it, which is what made everyone trust it.&lt;/p&gt;

&lt;p&gt;The general rule I now apply: a check written to catch a class of problem should be tested by feeding it a known instance of that problem. Not once when it is written. Kept, as a permanent test of the test, so a later refactor cannot quietly narrow its reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test one unlucky roll from red
&lt;/h2&gt;

&lt;p&gt;A test asserted a property of something random by actually drawing random numbers. It passed almost always. Almost.&lt;/p&gt;

&lt;p&gt;Two failure modes here and they are opposite. The obvious one is intermittent failure, which is annoying and at least visible. The dangerous one is intermittent success: an assertion that would fail on genuinely broken code most of the time and happened to pass on the run that mattered.&lt;/p&gt;

&lt;p&gt;Everything random in a test gets a fixed seed, and the property being asserted has to be true for that seed by construction rather than by luck. If you want to assert something over the whole space, sweep the space; if you want an example, fix the example. There is no useful middle.&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%2Flhlxlc71anyam4vmxyqr.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%2Flhlxlc71anyam4vmxyqr.jpg" alt="Mystery Box offering three sealed boxes with the best possible rarity listed below" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The timeout that calls a slow machine a broken one
&lt;/h2&gt;

&lt;p&gt;Automated play sessions had a five second budget for an operation. On a loaded machine that operation takes longer, so the run reported the game as broken when the only thing wrong was that the box was busy.&lt;/p&gt;

&lt;p&gt;This one is insidious because it produces confident false reports, and false reports train you to ignore the tool. A timeout is a statement about your infrastructure wearing the costume of a statement about your code. Where a limit is really needed it should be generous and it should say what it measured, so the report reads as a timeout rather than as a failure.&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%2Fgzqqk26yksy92rq8v508.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%2Fgzqqk26yksy92rq8v508.jpg" alt="Cultivator on the world map, the scene automated play sessions drive through" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The refusal that vanished
&lt;/h2&gt;

&lt;p&gt;Last one, and the least exotic. A request came back refused, and the code around it caught the error and did nothing with it. No log, no message, no state change. From the outside, the action simply did not happen.&lt;/p&gt;

&lt;p&gt;Every empty catch is a decision to discard information, and it is nearly always made in the moment you least want to think about failure. My rule now is that a catch must do one of three things: handle it, report it, or rethrow. Doing none of the three is not error handling, it is error deletion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one habit that finds all five
&lt;/h2&gt;

&lt;p&gt;Before trusting a new test, make it fail.&lt;/p&gt;

&lt;p&gt;Break the thing it is supposed to be watching, run it, and confirm it goes red. It takes thirty seconds and it is the only method I know that catches every shape above, because all five of them share the property of never having been observed to fail. A test that has only ever passed has not yet demonstrated it can do anything else.&lt;/p&gt;

&lt;p&gt;Playable today: &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Xianxia idle incremental game&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>indiedev</category>
      <category>gamedev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Reach as a stat, and other ways to make a map behave like a place</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Sun, 27 Sep 2026 04:30:05 +0000</pubDate>
      <link>https://dev.to/idlecultivation/reach-as-a-stat-and-other-ways-to-make-a-map-behave-like-a-place-555i</link>
      <guid>https://dev.to/idlecultivation/reach-as-a-stat-and-other-ways-to-make-a-map-behave-like-a-place-555i</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;2 Aug 2026 to 6 Aug 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;The overworld had been finished for a while in the sense that it worked. You could walk it, things were on it, quests sent you across it. It still read as a backdrop with a character sliding over the top, and this stretch was four changes aimed at that feeling rather than at any specific fault.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Interaction range should be a statistic, not a constant
&lt;/h2&gt;

&lt;p&gt;Everything you can touch in the world has a range: how close you must be to gather a herb, speak to somebody, notice a thing. That range had always been a constant, the same for a beginner and for someone two hundred years into their cultivation.&lt;/p&gt;

&lt;p&gt;Making it derive from a cultivation attribute changed the map without changing anything on it. A stronger cultivator interacts from further away, so the world becomes a little more compliant as you advance. That is a small mechanical difference and a large tonal one, because it is the map itself acknowledging what you have become. Genre readers already expect this: a powerful cultivator's awareness extends past their arms.&lt;/p&gt;

&lt;p&gt;The question I went back and forth on for a while was whether to draw the range as a ring. Two arguments, opposite and both right. A circle around your character shows a real quantity and answers a question players genuinely have. A circle around your character is also a large piece of interface permanently stapled to the middle of a world you spent months making look good.&lt;/p&gt;

&lt;p&gt;What settled it was noticing they are not the same question. The value is worth knowing occasionally, not continuously. So it appears when it is relevant, and the rest of the time the world is the world.&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%2Fdbsxvf8mci79w820jhah.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%2Fdbsxvf8mci79w820jhah.jpg" alt="Strike-range ring drawn around the player among levelled beasts by a river" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A creature that stands still is scenery
&lt;/h2&gt;

&lt;p&gt;Every beast on the map used to be planted, in the literal sense. It sat at its spawn point and waited to be walked into.&lt;/p&gt;

&lt;p&gt;Making them wander within their territory is one of those changes whose payoff is completely out of proportion to its cost. A creature that moves is doing something whether you are there or not, and once four of them are moving independently, the ground between the sects stops feeling laid out and starts feeling occupied. Nothing else about their behaviour changed.&lt;/p&gt;

&lt;p&gt;They also became actual encounters this stretch rather than obstacles: an overworld enemy now has a level and visible health, so meeting one is a small decision about whether you are ready rather than a collision.&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%2Fdb16paqmvsa3n3p8x2d7.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%2Fdb16paqmvsa3n3p8x2d7.jpg" alt="Beasts showing their levels above them while the player is locked in a fight" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Point at it, do not draw a line to it
&lt;/h2&gt;

&lt;p&gt;Navigation aids in this genre are a design trap. A big arrow on screen works perfectly and turns a world into a corridor, because the player stops looking at the landscape and follows the arrow.&lt;/p&gt;

&lt;p&gt;The aid that went in is a lamp that points. It is an object you own, it indicates a direction rather than a route, and it does not plot a path. You still have to look at the terrain and decide how to get around the ridge. Direction without a route keeps the player navigating instead of being conducted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everyone flying the same sword flattens the world
&lt;/h2&gt;

&lt;p&gt;Last one, and it is pure flavour with a real argument behind it.&lt;/p&gt;

&lt;p&gt;Flight unlocks partway through the game, and the first version had every player rise on a sword, because that is the image the genre is most known for. It works and it makes all six sects the same at exactly the moment when players are most visible to each other.&lt;/p&gt;

&lt;p&gt;Now each sect has its own form. The alchemy valley does not travel the way the sword sect does. It costs almost nothing to implement and it means the way somebody crosses the sky tells you who they trained under, which is a piece of world-building that only exists in motion.&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%2Fgool1yf9qlafftr23n9d.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%2Fgool1yf9qlafftr23n9d.jpg" alt="Medicine King Valley disciple flying on a glowing cauldron instead of a sword" width="800" height="896"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two other things landed alongside it. A sect's great shield blocks walking absolutely and can be flown over, so a barrier becomes a height problem rather than a wall. And staying airborne burns your own energy, so an empty reserve halfway across leaves you on the ground below. Both exist so the sky is a place with rules rather than a way to skip the map.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design note underneath all four
&lt;/h2&gt;

&lt;p&gt;They all come from the same sentence, which is now in the design document: the projection is felt, not drawn.&lt;/p&gt;

&lt;p&gt;There is no isometric grid on this map, no drawn depth, no camera trickery. The sense that it is a place comes from what responds to you and what moves while you are not looking. Once that is the goal, the useful changes stop being visual and start being about behaviour, which is a much cheaper place to work.&lt;/p&gt;

&lt;p&gt;More devlogs and the game: &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Idle Cultivation RPG&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>gamedesign</category>
      <category>worldbuilding</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>Launching a browser game Beta with no save wipes and 2,000 free copies</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Sat, 26 Sep 2026 08:57:59 +0000</pubDate>
      <link>https://dev.to/idlecultivation/launching-a-browser-game-beta-with-no-save-wipes-and-2000-free-copies-27oc</link>
      <guid>https://dev.to/idlecultivation/launching-a-browser-game-beta-with-no-save-wipes-and-2000-free-copies-27oc</guid>
      <description>&lt;p&gt;Myriad Immortal Sect opens its Beta today. It is a free idle RPG about cultivation, the xianxia kind, where you start as a mortal and climb through ten great realms, and it is a browser game through and through: a Phaser 4 canvas for the world map, React for everything around it. This Beta doesn't wipe anything. People who signed up before it keep every realm and every item, and so does everyone who starts today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That one decision changed more of the engineering than any feature did, so this post is about the constraints a no-wipe Beta puts on a small web game, and, at the end, about a launch giveaway that needed the same care.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser game saves that only move forward
&lt;/h2&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%2F5boyz4gjps8ecwdgri9c.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%2F5boyz4gjps8ecwdgri9c.jpg" alt="Cultivate screen hero card: realm, cultivation bar, stats grid and the cultivation-rate breakdown of every multiplier" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before real players, a change to the save shape could be handled by throwing old saves away. After real players, every change to what the game persists carries a version bump and a forward-only upgrade step that turns yesterday's save into today's. There are no shims that keep two shapes alive side by side; there is one current shape and a path to it from every older one.&lt;/p&gt;

&lt;p&gt;The rule that matters most is about loss. An upgrade step may merge, rename or reshape, but it never destroys a player's items, even when a new rule says a stack should be smaller. An over-sized stack left behind by a rule change becomes read-only, never deleted. An upgrade that looks tidy can still drop an item, and nobody notices until a player writes in.&lt;/p&gt;

&lt;p&gt;The screen above is a good test case. The cultivation rate is a product of a dozen multipliers, and each one is shown by name. When the formula for one changes, an old save has to land on the new number without a jump the player can see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guest play first, an account later
&lt;/h2&gt;

&lt;p&gt;Most players never want to register before they know whether they like a game, so the Beta starts as a guest. That creates a second problem the day the guest decides to sign in: the progress they made anonymously has to fold into the account without duplicating or losing anything, including things they bought or earned while anonymous. The fold moves everything the guest owns onto the account in one step, and the account is the only identity from then on.&lt;/p&gt;

&lt;h2&gt;
  
  
  A client-side idle loop with a backend that decides
&lt;/h2&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%2F067nynn1c5x86ddfkee7.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%2F067nynn1c5x86ddfkee7.jpg" alt="The Market Isles General Store in Japanese: counter tabs, pill cards with descriptions, prices and the sect discount" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An idle game runs its loop on the client, because players expect the numbers to move instantly and to keep moving offline. The trade-off is that anything touching the shared economy, buying, selling, trading, is done online and recomputed on the backend, which stays the source of truth. The client predicts and the backend decides. Keeping the two in agreement means the formulas exist in both places and are tested for parity, and big numbers are carried as strings end to end so neither side rounds them differently.&lt;/p&gt;

&lt;p&gt;The same split applies to rapid actions. A player tapping Buy ten times is normal behaviour, so the shop applies purchases locally at once and sends them as one batch after a short pause, instead of ten requests.&lt;/p&gt;

&lt;p&gt;The screenshot above is the Japanese build, one of eleven languages. The English edition is written as an original for English readers, with English character names and no leftover transliteration, while Simplified Chinese is the source the other nine languages translate from. Translations drift whenever the English changes, so a check flags every line whose English moved after its translation was written.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Phaser 4 world map with a load budget
&lt;/h2&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%2F5byjbqld0wboed4tuxxl.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%2F5byjbqld0wboed4tuxxl.jpg" alt="The player standing behind the waterfall cliff and drawn as a pale silhouette through it, level and bars still showing" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a browser game the map shares the tab with everything else, so it has a hard budget: ready within three seconds, never freezing the page, and never slowing other tabs. Two things hold it. Props are culled per object, since Phaser does not cull a plain Image and a dense chunk would otherwise submit every sprite every frame. And chunk assembly is spread across frames with a per-frame time budget, nearest chunks first, because building a whole screenful at once on arrival is exactly the kind of long main-thread task the budget forbids. Heavy terrain work is kept off the main thread.&lt;/p&gt;

&lt;p&gt;Small visual touches have to fit inside the same budget, like the silhouette in the screenshot, which draws the player through a tall prop standing in front of them so you never lose track of your character behind a cliff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving away exactly 2,000 copies without a race
&lt;/h2&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%2Flw0l4sgk3netn05slpmx.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%2Flw0l4sgk3netn05slpmx.jpg" alt="The profile screen with text size, music, language and account settings" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Beta comes with a giveaway: from September 27 to October 10, the first 2,000 players who verify an email get the Complete Edition free and keep it. That is the $9.99 unlock for the second half of the game, the realms from Void Refinement to Ascension, plus the full companion novel as an EPUB and a bigger bag.&lt;/p&gt;

&lt;p&gt;A capped giveaway looks like a counter and an if statement. The catch is that the thing that triggers it, a player verifying an email, can happen for many people in the same second, and a race there means copy 2,001 and copy 2,002 go out.&lt;/p&gt;

&lt;p&gt;So the gift is minted inside the same database transaction as the verification, and before counting, that transaction takes an advisory lock keyed on the giveaway's id. Two verifications arriving together queue on that lock, the second one sees the first one's row, and the count can never pass the cap. The per-account rule needs no extra table: a code row for this giveaway and this player already means the player has one.&lt;/p&gt;

&lt;p&gt;Two more details keep it from slowing anyone else down. The mint runs in a savepoint, so if it fails for any reason the email verification still goes through and the rule is simply tried again on the account's next read. And once a process has seen the cap reached, it remembers that, so after the 2,000th copy a verified player does not wait on the lock just to be told no.&lt;/p&gt;

&lt;p&gt;The code is not applied silently. It shows up under Profile, Complete Edition with a Claim button and has a week to be claimed. While the window is open, the website checkout offers that Claim button to a verified player instead of a payment form, so nobody pays $9.99 for something a verified email would have given them. No paid listing goes on sale anywhere until the gift window closes. After that, the edition is $4.99 until October 31 and $9.99 from November 1.&lt;/p&gt;

&lt;p&gt;If you want to see how it all holds up, the Beta of this &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;browser game&lt;/a&gt; is free to play, and there may still be a free Complete Edition waiting for your verified email. Our &lt;a href="https://discord.gg/BSFbbyHw5" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; is open for questions about any of this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>indiedev</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>A social layer for a game nobody plays at the same time</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Sat, 26 Sep 2026 05:30:06 +0000</pubDate>
      <link>https://dev.to/idlecultivation/a-social-layer-for-a-game-nobody-plays-at-the-same-time-4poh</link>
      <guid>https://dev.to/idlecultivation/a-social-layer-for-a-game-nobody-plays-at-the-same-time-4poh</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;2 Aug 2026 to 4 Aug 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;This game has other people in it and almost never at the same moment. Sessions are short, they happen whenever, and two players being online together is a coincidence rather than a design assumption. Everything social therefore has to work from traces: what you did, left where somebody else will find it later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This stretch went through that whole layer, and most of what I learned is about what asynchronous company is actually for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a ranking is for
&lt;/h2&gt;

&lt;p&gt;The obvious answer is competition and that is mostly wrong here. Very few players are seriously racing anybody. What a board does in a game like this is give a number a context.&lt;/p&gt;

&lt;p&gt;Ninety million qi means nothing on its own. Ninety million qi, when the people around you on the list are at seventy and a hundred and ten, means you are doing fine. That is the whole function, and it explains why a board should show your neighbours rather than only the top: the top is a different game and tells you nothing about yours.&lt;/p&gt;

&lt;p&gt;Which makes the fault I found genuinely bad rather than cosmetic. One board ranked every player at the lowest possible value, because the figure it ranks on was never included in what the game sends up. It sorted correctly, it displayed correctly, it had been live for weeks, and every player looking at it learned something false about where they stood. A board that is wrong is worse than a board that is missing, because it is confidently answering the exact question the player brought to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A card that offers a fight it will not give you
&lt;/h2&gt;

&lt;p&gt;The duel panel had a related problem in a different direction. It presented other cultivators as cards with a challenge button, and a note underneath explaining that challenges are not available.&lt;/p&gt;

&lt;p&gt;Every part of that is honest and the combination is not. A player reads the button, not the note. This is the same family as an inert control, with an extra twist: the thing is not merely dead, it is actively advertising a feature the game has decided not to have yet. The right shape is to show the roster and no button at all.&lt;/p&gt;

&lt;p&gt;There was a matching detail I liked less about my own work. The panel's only way out was labelled in a way that made it sound like a page refresh, so players avoided it and got stuck in a modal with no obvious exit. Exit affordances get the least attention of anything in an interface and they are the one control a confused person is looking for.&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%2F0jcntn4rrmuji7b15ngr.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%2F0jcntn4rrmuji7b15ngr.jpg" alt="Clash with a rival cultivator on the map, the player card tallying soul integrity lost" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Seasons, and the word the player should never see
&lt;/h2&gt;

&lt;p&gt;Seasons are the other lever for asynchronous company: a shared clock everybody is under, so a conversation about what happened this month has a boundary.&lt;/p&gt;

&lt;p&gt;The banner announcing the current season was printing the season's internal name. Not a title, not a phrase, the identifier the system files it under. That is a small thing that does a lot of damage, because it tells the player they are looking at plumbing rather than at a world. The rule I keep restating on this project is that no internal name may ever reach a screen, and I keep restating it because it keeps happening in a new place.&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%2F3ixq7yaewyviycj7s464.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%2F3ixq7yaewyviycj7s464.jpg" alt="Guild list with a group open to newcomers and membership by approval" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The boss everybody can hit and nobody was paid for
&lt;/h2&gt;

&lt;p&gt;The most enjoyable piece of asynchronous design here is the world boss: a very large creature that everybody chips at over days, from wherever they are, and which is a shared event without ever requiring two people to be present.&lt;/p&gt;

&lt;p&gt;It also had the most complete failure of the stretch. Everyone chipped in, the creature fell, and nobody received anything, because the settlement that pays contributors was never reached. Weeks of a shared event with no payout at the end of it.&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%2F656cpr46i2820pgqo1qd.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%2F656cpr46i2820pgqo1qd.jpg" alt="Quest Hall where a shared world boss sits beside the story quests" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I do not have a clever lesson about that beyond the obvious. The completion path of a rare event is the least tested code in any game, because triggering it takes days by design. If a thing happens once a week, nothing about your normal testing will ever see its ending, and it needs to be exercised deliberately or it does not work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check that skipped itself
&lt;/h2&gt;

&lt;p&gt;One last one, for the collection of tools that quietly stop working.&lt;/p&gt;

&lt;p&gt;There is a check that refuses oversized files. Measuring the size of a directory produces an error rather than a number, the error left the size empty, and the empty value took the comparison down a branch that passed. So the guard let anything through as long as the path was a directory, which is to say exactly when it would have mattered.&lt;/p&gt;

&lt;p&gt;The pattern is the same as an audit with an empty word list. When a check cannot compute its input, the safe answer is to stop, and the tempting one is to carry on with nothing.&lt;/p&gt;

&lt;p&gt;Free to start, no install: &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Idle Cultivation Game&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>gamedesign</category>
      <category>idlegames</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>The cheapest fix for a wrong number in fiction is to delete it</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Fri, 25 Sep 2026 05:30:02 +0000</pubDate>
      <link>https://dev.to/idlecultivation/the-cheapest-fix-for-a-wrong-number-in-fiction-is-to-delete-it-nh0</link>
      <guid>https://dev.to/idlecultivation/the-cheapest-fix-for-a-wrong-number-in-fiction-is-to-delete-it-nh0</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;5 Aug 2026 to 8 Aug 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;Numbers are where a long book comes apart. Not plot, not character, numbers: a person's age, how many years since a thing happened, what an account book says, how many words are carved on a stone. Each one is written once in a chapter nobody will reread and contradicted three hundred pages later by an author who is thinking about something else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This stretch built mechanical checks for that across the tie-in novel, and the interesting result was not the checks. It was what to do with what they found.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven checks that pay for themselves
&lt;/h2&gt;

&lt;p&gt;Each of these came from finding the same defect by hand at least twice, which is the only reason I trust the list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An age that goes backwards.&lt;/strong&gt; A character is sixty-eight in one chapter and sixty-one forty chapters later. Trivial to check once every mention is extracted, impossible to hold in a head.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A dead character who turns up.&lt;/strong&gt; One chapter says the shopkeeper passed away two years ago; a later one has her behind the counter at seventy-one. This one is embarrassing in a specific way, because it is the kind of mistake a reader remembers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dialogue arithmetic.&lt;/strong&gt; Characters trade in numbers constantly, and if two of them agree that three lots of nine hundred is two thousand eight hundred, some reader will do the multiplication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The calendar running backwards.&lt;/strong&gt; Both the long form, where the world's own year count regresses between chapters, and the short, where a date inside a single chapter goes the wrong way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An age used as a name.&lt;/strong&gt; The book often refers to an unnamed person by their age, and that number then has to stay put across every reference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Counting characters and counting punctuation.&lt;/strong&gt; When the text says an inscription is fourteen characters long, somebody will count. Punctuation is not a character in that sense and the naive count includes it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A lifespan that does not increase with rank.&lt;/strong&gt; The world's own rules say it must; two places said otherwise.&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%2Fnh2cr2j112lpmiy7pzuo.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%2Fnh2cr2j112lpmiy7pzuo.jpg" alt="Realm Codex ladder, the ranks whose lifespans the novel has to respect" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Then the part that made it usable
&lt;/h2&gt;

&lt;p&gt;The first full run produced around three hundred findings. That is not a report, it is a second job.&lt;/p&gt;

&lt;p&gt;Checking a sample of them against the actual text gave a ratio I now expect: roughly a third are genuine errors, a third are false alarms from the checker misreading context, and a third are real inconsistencies the author put there deliberately and would be wrong to remove.&lt;/p&gt;

&lt;p&gt;That last third is what separates prose checking from code checking. A character can be wrong about a number. Two accounts of the same event can differ because two people remember it differently. A check that flags those is not detecting a defect, it is detecting fiction.&lt;/p&gt;

&lt;p&gt;Adding three triage rules that discarded the most common false shapes cut a three hundred item run to fifty eight, with no genuine errors lost. Getting the noise down was worth far more than adding an eighth check would have been. A report nobody reads finds nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The editorial answer is usually fewer digits
&lt;/h2&gt;

&lt;p&gt;Here is the part I did not expect to be a rule and now is.&lt;/p&gt;

&lt;p&gt;A wrong figure in the text invites you to work out the true one and set it down. Precision like that belongs in an account book, an inscription, an official notice, a formal record, because in those places precision is part of the texture and a reader feels it.&lt;/p&gt;

&lt;p&gt;Everywhere else the better move is to blur it, and the move after that is to remove it entirely. This is an adventure novel, not an arithmetic problem. A reader wants the scale of a thing, not its audit trail, and a sentence saying a fortune was several tens of thousands is both more readable and impossible to contradict later.&lt;/p&gt;

&lt;p&gt;So the fix has three settings in order: blur, delete, and only then compute. I had written that down as delete first and immediately lost the sense of scale in a few passages, which is how the middle setting arrived.&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%2F8kpo0lscr3zvxrxdruz8.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%2F8kpo0lscr3zvxrxdruz8.jpg" alt="Reading a chapter of the novel from inside the game" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A related rule fell out of the same pass. Precise numbers past four digits do not belong in narration at all. A hundred thousand is a quantity a reader can feel; a hundred and seventy four thousand two hundred and six is a number they skip, and it is one more thing that can be wrong in chapter three hundred.&lt;/p&gt;

&lt;h2&gt;
  
  
  The method behind all of this
&lt;/h2&gt;

&lt;p&gt;None of the checks was designed in the abstract. Every one started as a defect I fixed by hand, followed by one question: is this an instance of a class.&lt;/p&gt;

&lt;p&gt;That question is the whole method. Fixing the instance costs ten minutes. Asking whether it is a class costs another ten and sometimes turns into a check that finds thirty more. It also, usefully, sometimes returns no, and knowing which of your fixes were one-offs is worth having.&lt;/p&gt;

&lt;p&gt;I also keep a short list of the checks I tried that did not pay: shapes I suspected would find errors and that returned nothing but intentional repetition. Writing down what did not work is the only thing that stops the next pass from building it again.&lt;/p&gt;

&lt;p&gt;Everything here ships in &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Incremental RPG&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>writing</category>
      <category>testing</category>
      <category>indiedev</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Keeping score honestly: zero improved, four made worse</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Thu, 24 Sep 2026 05:30:06 +0000</pubDate>
      <link>https://dev.to/idlecultivation/keeping-score-honestly-zero-improved-four-made-worse-58h5</link>
      <guid>https://dev.to/idlecultivation/keeping-score-honestly-zero-improved-four-made-worse-58h5</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;5 Aug 2026 to 7 Aug 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;This stretch was mostly me auditing my own work, six rounds of it, on the tooling that edits the tie-in novel and the tooling that plays the game. It produced the single most useful measurement I have taken on this project, and I want to write it down while the sting is fresh.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Round three stopped asking whether it was correct
&lt;/h2&gt;

&lt;p&gt;The first two rounds were the ordinary kind. Does the documentation match the scripts. Does a cold start work. Do the paths resolve. Both rounds found real problems and both left me feeling productive.&lt;/p&gt;

&lt;p&gt;The third round asked a different question: has this pipeline ever made a single chapter better?&lt;/p&gt;

&lt;p&gt;I went and counted. Chapters improved: zero. Chapters damaged: four.&lt;/p&gt;

&lt;p&gt;Everything was working. Each stage ran, each check passed, the output was well formed, and the net effect on the actual artifact was negative. Two rounds of review had been auditing the machinery against its own description, which is a closed loop that can be perfectly consistent and still describe something useless.&lt;/p&gt;

&lt;p&gt;The measurement changed what I built next far more than the previous forty findings combined. It is a lot harder to arrange than a consistency check, because you have to define the outcome and then be willing to read the answer. That, I think, is exactly why it does not get done.&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%2F8kpo0lscr3zvxrxdruz8.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%2F8kpo0lscr3zvxrxdruz8.jpg" alt="In-game reader displaying a chapter of the tie-in novel" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The three shapes that kept recurring
&lt;/h2&gt;

&lt;p&gt;Across the rounds, most findings were one of three things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A guard that is always false is not a guard.&lt;/strong&gt; Several safety conditions could not fire, because the value they tested was never in the state that would trip them. Nothing errors. The guard sits there being reassuring, and reads to a later maintainer as protection already handled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A finding that is found and then un-found.&lt;/strong&gt; A previous round had identified a real defect, and the fix for it had been reverted or bypassed in later work, and no record survived that this had ever been discovered. Without a written trail, the same defect gets found again as a new one every few weeks, and each of those finds costs full price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change A, forget B.&lt;/strong&gt; The overwhelming majority of the genuine findings, and the whole content of one outside review. Somebody adjusted a rule and left the place that depended on it. Every one is trivial in isolation and there is no upper bound on how many of them a system can contain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The refuter
&lt;/h2&gt;

&lt;p&gt;The habit that came out of this and that I have kept: before shipping a fix, have something argue against it.&lt;/p&gt;

&lt;p&gt;Concretely, four findings were written up as ready to ship, and passing them to a critic whose only job was to attack them killed three. Not because they were poorly implemented, but because the thing they described was not actually wrong, or was already handled somewhere else, or would have broken a rule the fix's author had not read.&lt;/p&gt;

&lt;p&gt;A seventy-five percent kill rate on my own confident conclusions is a humbling number and a very cheap one to buy. It also inverts the usual worry about review: I had assumed the risk was missing problems, and the measured risk was inventing them.&lt;/p&gt;

&lt;p&gt;Two more habits fell out of the same rounds. Say each rule once, in the place that owns it, because a rule stated four times is four things to keep in step and I had several of those. And when the review is of documentation, simulate a cold reader following it step by step rather than reading it for correctness, because those two exercises fail in completely different places.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nothing a run starts may outlive it
&lt;/h2&gt;

&lt;p&gt;One operational note, since it cost several nights.&lt;/p&gt;

&lt;p&gt;Long unattended runs spawn things: browsers, servers, helpers. A run that finishes cleanly, writes its report and exits can still leave a browser driving away in the background, and the next run inherits a machine with several of those on it. Two of my nights were lost to memory pressure caused entirely by the previous night's leftovers.&lt;/p&gt;

&lt;p&gt;The rule is now absolute. Nothing a run starts may outlive it, and the run's own cleanup has to be tested, because cleanup code is the least exercised code in any tool and it only matters when everything else already went right.&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%2Friu3lhkg0nqvr42vvctu.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%2Friu3lhkg0nqvr42vvctu.jpg" alt="World map near a sect gate, the kind of screen long unattended play sessions walk through" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The live build is &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Cultivation Game&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>writing</category>
      <category>indiedev</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Linting a novel: what a continuity checker can honestly catch</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Wed, 23 Sep 2026 05:30:06 +0000</pubDate>
      <link>https://dev.to/idlecultivation/linting-a-novel-what-a-continuity-checker-can-honestly-catch-4j86</link>
      <guid>https://dev.to/idlecultivation/linting-a-novel-what-a-continuity-checker-can-honestly-catch-4j86</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;5 Aug 2026 to 6 Aug 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;There is a novel attached to this game. Four hundred chapters across ten volumes, set in the same world, sharing its realms and its factions and a good deal of its cast. It is downstream of the game's design: the book may not invent a realm or change a rule, only dramatise what the design already says.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keeping four hundred chapters in step with a design that moves weekly is not a thing attention can do. So the novel got a checker, the way the code has one, and working out what such a thing can honestly verify turned out to be more interesting than building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually automates
&lt;/h2&gt;

&lt;p&gt;Four categories earned their place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Facts that must be monotonic.&lt;/strong&gt; A cultivator's lifespan grows with every realm, so a later realm promising a shorter span than an earlier one is a contradiction no reader would notice and every reader would feel. The checker found one: three hundred and fifty at one realm and three hundred at the next. This is the class where a machine beats a person outright, because it is arithmetic across two files hundreds of pages apart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Names that must exist.&lt;/strong&gt; Every proper noun in the prose has to resolve to something the game's exported data actually contains. Simple, and it caught a piece of equipment that had been renamed in the game months earlier and lived on in two chapters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Point of view against presence.&lt;/strong&gt; Each chapter declares whose eyes it is told through. Eighteen chapters named a character who does not appear anywhere in their own text. That check took ten minutes to write and would have taken a full read-through to find.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typographic rules.&lt;/strong&gt; This book's Chinese uses full-width punctuation throughout, which is a house rule, absolutely mechanical, and had been sitting in the style document unenforced for months.&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%2F8kpo0lscr3zvxrxdruz8.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%2F8kpo0lscr3zvxrxdruz8.jpg" alt="The companion novel open in its reader inside the game" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What only looks automatable
&lt;/h2&gt;

&lt;p&gt;I tried two more and stopped.&lt;/p&gt;

&lt;p&gt;Chapter length distribution is measurable and turns out to mean nothing on its own. A short chapter is a problem when it is thin, not when it is short, and no counter distinguishes those. What the measurement did buy was a table printed on every run, which caught something better: the style document's stated target length had been wrong for months, and nobody had compared it to the ten volumes sitting on disk.&lt;/p&gt;

&lt;p&gt;And whether a scene is any good. Obviously. Worth saying because the temptation to add a rule that approximates it is real, and every approximation I sketched would have been an instruction to write more blandly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check that passed everything
&lt;/h2&gt;

&lt;p&gt;The best lesson came from attacking my own checker with deliberately broken input.&lt;/p&gt;

&lt;p&gt;The name check loads a word list and asks whether each name is in it. If loading that list fails, the original code caught the error and continued with an empty list, and an empty list means every name in the book passes. The check reported success on prose that violated it in twenty places, and it reported success loudly and cheerfully.&lt;/p&gt;

&lt;p&gt;I fixed it and it was still half broken: I had handled a missing file, and a file that exists and is empty took the same path to the same happy answer. It took a third pass to make both cases stop the run.&lt;/p&gt;

&lt;p&gt;The general shape is worth having a name for. A check that cannot find its own reference data must fail, never pass. This is the same instinct as failing closed on a permission question, and prose tooling has exactly the same failure mode with none of the alarm around it. The most dangerous test is one that has quietly stopped testing.&lt;/p&gt;

&lt;p&gt;There is a companion rule I now also follow: write a deliberately broken chapter for every rule and keep it, so the checker is itself tested. Every rule in there was added after finding the defect it describes in the finished book. That means each rule has, by construction, never actually been observed to fire on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meanwhile, on the map: flight
&lt;/h2&gt;

&lt;p&gt;The other change this stretch was the ability to fly, which mostly exists so a continent is not a walking simulator.&lt;/p&gt;

&lt;p&gt;Two design decisions in it are worth recording. Flight is powered by your own energy, so crossing a long distance costs something, and running out mid crossing drops you where you are rather than refusing to launch. That converts a travel option into a small judgement about range, which is more interesting than a free teleport and less punishing than a wall.&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%2F6sr7vjlgmn3dxm2j6s52.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%2F6sr7vjlgmn3dxm2j6s52.jpg" alt="Cultivator crossing the continent on a flying sword" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the great shields over rival sects, which block movement completely on foot, can be flown over. Standing outside a barrier is a boundary. Sailing over it and looking down through the canopy at somebody else's halls is trespassing, and trespassing is a much better feeling than a locked door.&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%2Fs7j9virauiu39kw0ifxe.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%2Fs7j9virauiu39kw0ifxe.jpg" alt="Protective dome over a rival sect, a barrier on foot but not from the sky" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try it in your browser: &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Myriad Immortal Sect&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>writing</category>
      <category>gamedev</category>
      <category>testing</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>Generating six hundred pages from game data without making slop</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Tue, 22 Sep 2026 05:30:06 +0000</pubDate>
      <link>https://dev.to/idlecultivation/generating-six-hundred-pages-from-game-data-without-making-slop-l04</link>
      <guid>https://dev.to/idlecultivation/generating-six-hundred-pages-from-game-data-without-making-slop-l04</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;2 Aug 2026 to 5 Aug 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;A game like this needs a reference site: what every quest asks for, what every item does, how each system works. The material for it already exists, because the game's content is exported as structured data, so producing several hundred pages is a scripting job. Producing several hundred pages anyone would want to read is not, and that gap is the whole of this write-up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generated text is not the problem, undifferentiated text is
&lt;/h2&gt;

&lt;p&gt;Take a thousand item records and a template, and you get a thousand pages that are individually accurate and collectively worthless. Every one opens the same way, every one has the same four sections, and a reader who lands on the second one already knows there is nothing here a table would not have told them better.&lt;/p&gt;

&lt;p&gt;Three rules turned it around, and none of them is about the writing itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One topic per page, with a hard word ceiling.&lt;/strong&gt; The instinct is to write the big complete guide to the whole system. A reader arriving with one question about one mechanic has to find their paragraph inside four thousand words that are mostly about something else. Splitting by question rather than by system makes each page shorter, easier to keep current, and much more likely to be the right answer to somebody.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One primary subject per page, never reused.&lt;/strong&gt; Two pages competing for the same subject means both are half a page and neither is the definitive one. Assigning subjects up front, exclusively, forces the collection to have a shape rather than a hundred overlapping restatements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A visible date on everything.&lt;/strong&gt; A game in development changes its numbers weekly, so any page quoting a number is provisional. Saying so on the page, with the date it was accurate, is the difference between a reference that ages and one that lies. The date has to come from one place, obviously, or six hundred pages become six hundred edits.&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%2F656cpr46i2820pgqo1qd.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%2F656cpr46i2820pgqo1qd.jpg" alt="Quest Hall showing story quests, a choice to settle and a world boss" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The generated draft is a first draft, not a page
&lt;/h2&gt;

&lt;p&gt;The step that mattered most was refusing to publish the generator's output.&lt;/p&gt;

&lt;p&gt;Data becomes sentences easily and the sentences are terrible. They are uniform, they open identically, and they have the specific rhythm of something assembled. So the pipeline runs a rewriting pass over every generated page whose only job is to make it read like a person wrote it: vary the openings, cut the scaffolding phrases, break the parallel structure that a template inevitably produces.&lt;/p&gt;

&lt;p&gt;There is a machine-checkable part of that and a part there is not. The checkable part is a list of tells, the vocabulary and sentence shapes that mark assembled prose, run as a gate that fails a page carrying them. That gate does not make writing good. It reliably catches the writing that is obviously not human, which is a lower bar and a useful one, because the whole collection passes or fails together in a reader's mind.&lt;/p&gt;

&lt;p&gt;The unmeasurable part is whether the page is worth reading, and no gate is coming to help with that.&lt;/p&gt;

&lt;h2&gt;
  
  
  A picture of the panel, not a picture of the game
&lt;/h2&gt;

&lt;p&gt;Every page carries one to three images, and the rule that made them earn their place is about framing. A capture of the whole window shows the game. A tight crop around the single interface element under discussion shows what the sentence beside it is talking about.&lt;/p&gt;

&lt;p&gt;So the images sit inline next to the paragraph they illustrate, rather than sitting at the top as a header ornament. There is a lower size bound as well as an upper one, since a crop tight enough to be unreadable helps nobody.&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%2F5boyz4gjps8ecwdgri9c.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%2F5boyz4gjps8ecwdgri9c.jpg" alt="Hero card on the Cultivate screen breaking the cultivation rate into its multipliers" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The same price, three different ways
&lt;/h2&gt;

&lt;p&gt;The other half of this stretch was an economy audit, and one finding is worth generalising beyond games.&lt;/p&gt;

&lt;p&gt;The same item was priced differently on two screens. A discount was displayed on the shelf and not applied at the counter. Several goods advertised a price that could never be honoured. Each of those is a small bug and together they are one structural fact: a single number had several owners, and each owner was independently correct.&lt;/p&gt;

&lt;p&gt;The fix is never to correct the copies. It is to work out which layer owns the number and delete the ability of the others to hold one. Until that is done, every fix is a coin flip about which copy you found.&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%2Fe8tzvfklrbcr7kmtxu9k.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%2Fe8tzvfklrbcr7kmtxu9k.jpg" alt="Breakthrough pill shelf with its sect discount and weekly limit note" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If a value appears in the interface, in the rules and in the content data, at least two of those are lying and nothing in the system knows which.&lt;/p&gt;

&lt;p&gt;Play it at &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Xianxia idle incremental game&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>seo</category>
      <category>writing</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>The dead feature: controls that render, respond and do nothing</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Mon, 21 Sep 2026 05:30:06 +0000</pubDate>
      <link>https://dev.to/idlecultivation/the-dead-feature-controls-that-render-respond-and-do-nothing-1510</link>
      <guid>https://dev.to/idlecultivation/the-dead-feature-controls-that-render-respond-and-do-nothing-1510</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;27 Jul 2026 to 3 Aug 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;I spent this stretch doing something tedious and overdue: opening every panel in the game, pressing every control on it, and asking one question each time. What changed. Not does it error, not does it look right. What is different afterwards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The answer, dozens of times, was nothing at all. The button highlighted, the panel responded, sometimes a message appeared, and the game state was untouched. I want to write down the shapes these took, because they are more alike than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five ways to be dead
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The inert toggle.&lt;/strong&gt; A switch that turns on and off and is read nowhere. Ours was a remote-shopping option that a player could flip freely and which changed no price. Later, when the other half was built, it started applying a surcharge without ever having explained itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The promise with no implementation.&lt;/strong&gt; A skill node describing an effect, priced, purchasable, and connected to nothing. A third of one tree was in this state. The description is the feature as far as a player is concerned, so this one takes money for a lie.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The counter nothing increments.&lt;/strong&gt; Two achievements tracked a list, and no code anywhere added to that list. Perfectly implemented unlock conditions on a value fixed at zero forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The panel showing numbers the game does not use.&lt;/strong&gt; A screen displaying attributes that no mechanic reads. Everything on it is real, computed, updated, and it describes a system that does not exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The refresh that refreshes nothing.&lt;/strong&gt; A board with a refresh button whose data cannot change between presses. It does not fail. It succeeds at doing nothing, quietly, and the player concludes their luck is bad.&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%2Fazkev1mguk7ipt5iw5bp.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%2Fazkev1mguk7ipt5iw5bp.jpg" alt="Path of Legends feats paying spirit stones and Dao Fruit, later ones still locked" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why these survive every review
&lt;/h2&gt;

&lt;p&gt;None of it is carelessness, which is what makes it interesting.&lt;/p&gt;

&lt;p&gt;Every one of these was built as one half of a pair. The toggle was written while the pricing rule was still being designed. The skill node's description was authored during content work, with the effects to be wired in a later pass. The achievement was written against a tracking list that got dropped in a rewrite. In each case the missing half was on somebody's list, and the half that shipped renders perfectly, so nothing ever asks about the other one.&lt;/p&gt;

&lt;p&gt;They survive review because reviews look at changes, and a dead feature is not a change. It is an absence sitting next to a presence. They survive tests because tests assert what the code does, and this code does exactly what it says: it sets a value nothing reads. They survive play because the game does not break. The player just quietly comes away with a worse model of how any of it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule that catches most of them
&lt;/h2&gt;

&lt;p&gt;The one I am adopting: a control is not finished until something downstream reads it, and the commit that adds the control adds the read or the control does not go in.&lt;/p&gt;

&lt;p&gt;That sounds obvious and it directly contradicts how it feels to work. Building the visible half first is satisfying, demos well, and makes the remaining work look small. It is also how you accumulate a screen full of things that respond and do not act.&lt;/p&gt;

&lt;p&gt;Where the halves genuinely have to land separately, the fix is not discipline, it is visibility. An unwired control should be absent, not present and inert. Hiding it costs a line and it means a player never learns something false about the game.&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%2Fqvf73gyluhm7mfvphuyb.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%2Fqvf73gyluhm7mfvphuyb.jpg" alt="General Store pill counter with prices, sect discount and a remote access button" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooltips are a contract, and mine were lying by a factor of a hundred
&lt;/h2&gt;

&lt;p&gt;A related family, worth separating out. Several node descriptions were not merely unimplemented, they were wrong: one understated its effect a hundredfold because the number was written as a percentage in one place and a fraction in the other.&lt;/p&gt;

&lt;p&gt;A player reads that description and makes a decision with it. If the description says one thing and the mechanic does another, the mechanic may be perfectly balanced and the game still plays badly, because the player is optimising against the text. Description and effect should be derived from one value, always, and where that is impractical something should compare them automatically.&lt;/p&gt;

&lt;p&gt;The version of that I have seen bite hardest is percentages, every time. Any number that can be written as either three or nought point nought three will eventually be written as both.&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%2F86qy5690xsqbrdkj7obr.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%2F86qy5690xsqbrdkj7obr.jpg" alt="Skill tree at the start of a life, four paths radiating from the middle" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The most useful question I asked all week
&lt;/h2&gt;

&lt;p&gt;Not what is broken. What did this change.&lt;/p&gt;

&lt;p&gt;It is a slower way to go through an interface and it finds a category of problem that no error, no crash and no failing test will ever show you, because at every level below the player everything is working exactly as written.&lt;/p&gt;

&lt;p&gt;Playable today: &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Idle Cultivation RPG&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>ux</category>
      <category>indiedev</category>
      <category>testing</category>
    </item>
    <item>
      <title>How a fully translated game still shows English in fifty places</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Sun, 20 Sep 2026 05:36:36 +0000</pubDate>
      <link>https://dev.to/idlecultivation/how-a-fully-translated-game-still-shows-english-in-fifty-places-32o6</link>
      <guid>https://dev.to/idlecultivation/how-a-fully-translated-game-still-shows-english-in-fifty-places-32o6</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;27 Jul 2026 to 2 Aug 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;The game has eleven languages and every string in them was translated on purpose. Then I played it in Japanese for an hour and wrote down every English word I saw. The list ran past fifty entries: the mining screen, the skill tree, the sect exchange, duel results, map labels, the shop shelves, a modal asking for an email address. All of it translated, all of it showing English.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are only about four ways this happens, and knowing them is more useful than the fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The internal name printed as a label
&lt;/h2&gt;

&lt;p&gt;The worst one, and the most common. A screen wants the name of an object, asks for it, and the lookup misses, so the fallback shows the object's internal name. On the shop shelf, in every language including the two it was written in.&lt;/p&gt;

&lt;p&gt;This is worse than showing English, because English is at least a language. An internal name is a string the player was never meant to see, and it leaks the shape of the thing behind the game. It also passes every review, because a developer reading their own screen recognises the object and moves on.&lt;/p&gt;

&lt;p&gt;The structural cause was that item names had been keyed one way and the objects had been renamed to another scheme months earlier. Every lookup that had been updated worked, every lookup that had not fell through to the raw name, and nothing distinguished the two on screen unless you were looking for it.&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%2Fgc5k5z5licdthiohbq1q.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%2Fgc5k5z5licdthiohbq1q.jpg" alt="Quest Hall played in Japanese, with the objective shown as a target portrait" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Strings added after the translation pass
&lt;/h2&gt;

&lt;p&gt;Second mechanism, unavoidable, and worth planning for. A large translation pass takes days, and development does not stop while it runs. Every string that lands during those days is born untranslated, in the source language, and it will sit there until somebody notices, because everything around it looks right.&lt;/p&gt;

&lt;p&gt;The traditional version of this is a subtle one: the traditional Chinese locale had fallen eighty-one keys behind the simplified one, so readers of traditional Chinese got English on exactly those eighty-one strings. A gap between two close relatives is much harder to see than a gap between distant ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fallback that looks like success
&lt;/h2&gt;

&lt;p&gt;Third, and the reason the first two survive so long. When a translation is missing, the sensible behaviour is to fall back to the source language, so the player sees a real sentence instead of a blank.&lt;/p&gt;

&lt;p&gt;That is right, and it makes the failure invisible to exactly the person who could fix it. The developer reads English, the fallback is English, and the screen reads perfectly. The audit that found all fifty of these was not clever: it was playing in a language I cannot read, where a fallback is unmistakable.&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%2Flw0l4sgk3netn05slpmx.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%2Flw0l4sgk3netn05slpmx.jpg" alt="Profile screen with language, text size, music and account options" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Half a feature translated
&lt;/h2&gt;

&lt;p&gt;Fourth, and the most embarrassing. Several screens had their labels translated and their refusals not. Press the button and the interface speaks your language; press it when you are not allowed to, and it explains why in English.&lt;/p&gt;

&lt;p&gt;Refusal copy sits in the error path, so it does not get read during ordinary testing, and it is precisely the copy a confused player most needs. Any translation audit should start with every message that appears when something goes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  While I was in there: an economy that had drifted by four orders of magnitude
&lt;/h2&gt;

&lt;p&gt;A separate pass compared what the game actually pays against what the design budgets, quest by quest, realm by realm.&lt;/p&gt;

&lt;p&gt;The early realms were paying about a fifth of their budget, which reads to a player as a game that will not let them buy anything. The late realms were paying up to eleven thousand times theirs, which reads as a game with no economy at all, since everything is affordable forever. One quest at the very end handed over an amount with ten digits in it that nobody had ever chosen.&lt;/p&gt;

&lt;p&gt;None of that was one bug. It is what happens when numbers are authored per feature over months while the budget lives in a document. Every individual value looked reasonable when it was typed. The lesson I take is that a balance document is worth nothing unless something regularly compares it to the running game, and that comparison should be a script rather than an intention.&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%2F0euzglwnsslqfrjtkp7o.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%2F0euzglwnsslqfrjtkp7o.jpg" alt="Active quest listing its rewards next to a choice the player must make" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on image size, since it comes up
&lt;/h2&gt;

&lt;p&gt;Small thing worth passing on. Uploaded pictures needed to stay under a size ceiling, and the first version set the encoder quality to a value low enough to be safe.&lt;/p&gt;

&lt;p&gt;A quality setting does not bound a file size. The same setting produces a tiny file for a flat illustration and a large one for a photograph of foliage, so a fixed low quality means every simple image is needlessly ugly and some complex ones are still too big. Starting at high quality and searching downward until the file fits is barely more work and gives every image the best quality it can have inside the budget. The ceiling is the requirement; the quality is just the dial you turn to meet it.&lt;/p&gt;

&lt;p&gt;More devlogs and the game: &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Idle Cultivation Game&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>i18n</category>
      <category>gamedev</category>
      <category>gamedesign</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>Auditing a world by walking it, not by checking things exist</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Sat, 19 Sep 2026 05:39:07 +0000</pubDate>
      <link>https://dev.to/idlecultivation/auditing-a-world-by-walking-it-not-by-checking-things-exist-2hd3</link>
      <guid>https://dev.to/idlecultivation/auditing-a-world-by-walking-it-not-by-checking-things-exist-2hd3</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;27 Jul 2026 to 1 Aug 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;I set up a test rig that plays the game the way a person does: a real browser, real clicks, walk to the place, talk to the character, hunt the beast, hand the quest in. It ran overnight against the first two dozen quests. Almost everything it found belongs to one family of fault, and every one of them had passed the checks I already had.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Existence is not the property you care about
&lt;/h2&gt;

&lt;p&gt;The checks in place asked sensible questions. Does this quest name a target that exists. Does that target have somewhere to appear. Is the place it names on the map.&lt;/p&gt;

&lt;p&gt;All yes, all the time, and the quest was still impossible, because the property a player needs is not existence. It is arrival. The three that mattered:&lt;/p&gt;

&lt;p&gt;A hunt zone has to hold the number the hunt asks for. Measured across the early quests, a kill-four zone was averaging under four beasts present, a kill-eight around seven and a half. The quest was completable given enough patience and it felt broken, which for design purposes is the same as being broken. Thirty one zones went up in size.&lt;/p&gt;

&lt;p&gt;A place has to be walkable to. The reachability audit flooded outward across terrain, which is correct as far as it goes, and a great shielding dome over a rival sect is not terrain. Two quest materials sat inside a barrier the player cannot pass, and the audit reported them as fine because water and cliffs were the only obstacles it knew about.&lt;/p&gt;

&lt;p&gt;Standing room has to exist inside the zone. Scenery in this game blocks movement, so a herb can be enclosed by the grove that was scattered around it. The zone exists, the herb exists, the herb is in a sealed pocket, and nothing anywhere says so.&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%2Fdb16paqmvsa3n3p8x2d7.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%2Fdb16paqmvsa3n3p8x2d7.jpg" alt="Levelled wild beasts roaming a hunting ground while the player fights by a mountain" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deriving what blocks you from what is drawn
&lt;/h2&gt;

&lt;p&gt;Underneath most of that was a decision I am glad I made and should have made earlier.&lt;/p&gt;

&lt;p&gt;Whether a thing blocks movement is not a flag somebody sets on it. It is derived from the artwork: anything drawn taller than a certain height blocks, people and airborne things and flat decals do not, and painted water blocks by its own rule. The reason to derive it is that a flag cannot survive a rescale. Somebody adjusts a sprite's size six weeks later and the flag still says what it said, so a great tree becomes walkable and half of every building along with it. Which had happened, to a great tree and to half of every building.&lt;/p&gt;

&lt;p&gt;Deriving it fixed that class permanently and immediately created three new problems that are worth knowing about in advance. Wider footprints can disconnect part of the map, so connectivity has to be checked. Wider footprints can seal in the thing a quest wants, so anything enclosed has to be nudged out. And footprints have to be cleared when the ground under them is rebuilt, or they accumulate, which they were doing at a rate of one duplicate set per rebuild.&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%2Friu3lhkg0nqvr42vvctu.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%2Friu3lhkg0nqvr42vvctu.jpg" alt="World map outside a sect gate showing the minimap, current quest and item belt" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Every quest target now has a copy near every sect
&lt;/h2&gt;

&lt;p&gt;The largest single change came from a question the rig could ask that I could not: what does this look like for a player who joined one of the other sects?&lt;/p&gt;

&lt;p&gt;The answer was that a couple of dozen early quests were built around the geography of one sect's neighbourhood. Its village, its elders, its hunting ground. A disciple of another sect had a very long walk to content that a local finished in a minute, and in a few cases the target sat inside somebody else's barrier and was simply unreachable.&lt;/p&gt;

&lt;p&gt;Every early quest target now has a local counterpart near each sect, and the important detail is how the copies are positioned. The obvious rule is to place a copy near the sect gate. The rule that actually works is to place it near the previous objective in the chain, because that is where the player is standing when the quest tells them to go. One sect was walking nearly twice the distance of another for identical content, and closing that gap was entirely a matter of measuring from the right starting point.&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%2Fob6gathhsokzcsgc17ma.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%2Fob6gathhsokzcsgc17ma.jpg" alt="Quest card with the portrait of the rival to defeat and an Open the map button" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things about unattended testing
&lt;/h2&gt;

&lt;p&gt;Both cost me a night, so they are going in writing.&lt;/p&gt;

&lt;p&gt;An occluded browser window stops the world. If the window is covered by anything, the animation clock stops, the game does not advance, and every input silently goes nowhere. From inside the rig this is indistinguishable from a hung game, and I spent one entire run diagnosing a freeze that was a window in front of a window.&lt;/p&gt;

&lt;p&gt;And a stalled walk is more often a panel than a wall. When the character does not move, the reflex is to assume geometry, and the truth is usually that a reward popup or a level-up screen is sitting in front of the map. Look at what is on screen before you go measuring the ground.&lt;/p&gt;

&lt;p&gt;Free to start, no install: &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Incremental RPG&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>testing</category>
      <category>gamedesign</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>A minigame in an idle game has to be allowed to stop mattering</title>
      <dc:creator>IdleCultivation</dc:creator>
      <pubDate>Fri, 18 Sep 2026 05:30:07 +0000</pubDate>
      <link>https://dev.to/idlecultivation/a-minigame-in-an-idle-game-has-to-be-allowed-to-stop-mattering-429k</link>
      <guid>https://dev.to/idlecultivation/a-minigame-in-an-idle-game-has-to-be-allowed-to-stop-mattering-429k</guid>
      <description>&lt;p&gt;Development covered &lt;strong&gt;25 Jul 2026 to 27 Jul 2026&lt;/strong&gt; (commit dates).&lt;/p&gt;

&lt;p&gt;Three small activities landed this stretch: swinging a pick at a spirit vein, holding your concentration through a sweeping meter, and sorting a tray of goods by eye. Each took a couple of days to build and considerably longer to balance, and they all ran into the same wall, which turns out to be the central problem with putting an active pastime inside a game about not being active.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The reward curve has to bend down
&lt;/h2&gt;

&lt;p&gt;The tension is easy to state. If a clicking activity pays well, the optimal way to play an idle game is to not idle, which insults the entire premise. If it pays badly, nobody touches it and you built a room nobody enters.&lt;/p&gt;

&lt;p&gt;What I landed on is a reward that is generous at the very start and fades as you advance. At the first realm, a few minutes at the vein is worth a meaningful fraction of what a quest pays. Six realms later it is worth a rounding error. The activity stays available and stays pleasant, and it stops being an answer to the question of how to make progress.&lt;/p&gt;

&lt;p&gt;That shape does two useful things at once. Early on, when a new player has nothing to spend and no quests unlocked, there is something to do with their hands that pays enough to feel worth it. Later, when the economy is real, the main loop is unambiguously where the value is. Nobody has to be told to stop mining. The numbers say it politely.&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%2Fd7jfzd963sqq56bomoc0.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%2Fd7jfzd963sqq56bomoc0.jpg" alt="Trance Meditation panel fully charged, speeding cultivation by half" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A floor for newcomers made the early half work: a guaranteed minimum for the first several swings, scaled by realm, so the first experience of the activity is never a run of nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven payouts in a row of exactly one
&lt;/h2&gt;

&lt;p&gt;Real play immediately found something the maths had hidden. A tester reported seven swings in a row paying exactly one stone.&lt;/p&gt;

&lt;p&gt;The average was correct. The distribution was flat, because the payout was drawn from a pool in a way that collapsed to the same value under common conditions. Average correct, variance nearly zero, and the result feels less like a game than a subscription. A reward with no spread does not read as a reward at all; it reads as a tax refund.&lt;/p&gt;

&lt;p&gt;The lesson I would state generally is that the average is the least interesting property of a random reward. What a player experiences is the run: the streak of nothing that makes the next hit land, the occasional result well above the others. Design that shape deliberately and the mean will follow, because the mean is a consequence and the shape is the experience.&lt;/p&gt;

&lt;p&gt;Two related legibility fixes came out of the same session. The rock's health had to be readable as a quantity, so a swing visibly takes a chunk out of it, and the reward had to be visible at the moment it is won rather than quietly added to a total somewhere. An unseen reward is not a reward.&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%2Fnhy213ngt1vbtcfmv70i.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%2Fnhy213ngt1vbtcfmv70i.jpg" alt="Spirit Stone Mining rock face with a visible health bar and the player's True Energy" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The sorting game had to teach itself
&lt;/h2&gt;

&lt;p&gt;The third activity is a sorting puzzle built from goods drawn out of your own realm, which gives it a quiet second job: it is how a player learns what the objects in their world are, without a tutorial screen.&lt;/p&gt;

&lt;p&gt;Two things came out of testing it on a phone. The first level has to be a demonstration rather than a challenge, because the rules are learned by winning once. And a board on a small screen needs its own layout, not the desktop layout scaled down, which is obvious and was still not what I built first.&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%2Fbt9up7vblbawo3q40z0d.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%2Fbt9up7vblbawo3q40z0d.jpg" alt="Discerning Eye puzzle: a pile of relics, the tray below and three helper aids" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One more thing about that puzzle, more of a warning. Three genuine faults in it were found by rereading the code rather than by playing, and none of them would have shown up in a playthrough. Some classes of bug are invisible to testing because they are about what the code can produce, not about what it did produce this time. Sitting down and reasoning about the whole space of boards is not old-fashioned; it is the only method that covers it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The frame was chosen by its name, not by what it draws
&lt;/h2&gt;

&lt;p&gt;Last one, because I made the same mistake three separate times and want it written down.&lt;/p&gt;

&lt;p&gt;Artwork in this project is looked up by name, and it is very easy to pick a piece because the name matches the concept. A place called the Frozen Stone Port should stand on snow. A forest of whispering bamboo should use the frame called forest wall.&lt;/p&gt;

&lt;p&gt;Except that particular frame draws a dark green ring, so four of them made a forest of hoops. A tile named for farmland turned out to paint almost none of its own square, so ten of them read as loose grey blocks on grass. A piece named for a mountain peak was in fact a horizon backdrop with opaque edges, which put a rectangular card on the hillside.&lt;/p&gt;

&lt;p&gt;Names describe intent. Only the picture describes the picture. The only reliable fix has been to look at every frame I choose, at the size it will be seen, before believing what it is called.&lt;/p&gt;

&lt;p&gt;Everything here ships in &lt;a href="https://idlecultivation.com/" rel="noopener noreferrer"&gt;Cultivation Game&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://idlecultivation.com/immortal-sect?utm_source=devto&amp;amp;utm_medium=devlog&amp;amp;utm_campaign=banner" rel="noopener noreferrer"&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%2F5ry5ba73ahswb860ofof.png" alt="Myriad Immortal Sect, the game this devlog is about. Play it free in the browser" width="798" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>gamedesign</category>
      <category>idlegames</category>
      <category>gamebalance</category>
    </item>
  </channel>
</rss>
