<?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: zzzu2221</title>
    <description>The latest articles on DEV Community by zzzu2221 (@zzzu2221).</description>
    <link>https://dev.to/zzzu2221</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%2F4073395%2F85be565e-bb3e-4714-b827-272a9644fcf8.png</url>
      <title>DEV Community: zzzu2221</title>
      <link>https://dev.to/zzzu2221</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zzzu2221"/>
    <language>en</language>
    <item>
      <title>Why Restart Bugs Reveal Hidden Game State</title>
      <dc:creator>zzzu2221</dc:creator>
      <pubDate>Sun, 16 Aug 2026 07:02:49 +0000</pubDate>
      <link>https://dev.to/zzzu2221/why-restart-bugs-reveal-hidden-game-state-2njc</link>
      <guid>https://dev.to/zzzu2221/why-restart-bugs-reveal-hidden-game-state-2njc</guid>
      <description>&lt;p&gt;A game can look correct until the player presses Restart. That button is one of the fastest ways to expose hidden state.&lt;/p&gt;

&lt;p&gt;In a small browser game, I treat restart as a full lifecycle test rather than a visual reset.&lt;/p&gt;

&lt;h2&gt;
  
  
  What restart should clear
&lt;/h2&gt;

&lt;p&gt;A restart should create a fresh run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;player position and health;&lt;/li&gt;
&lt;li&gt;score and collected items;&lt;/li&gt;
&lt;li&gt;enemy timers and active projectiles;&lt;/li&gt;
&lt;li&gt;win and lose flags;&lt;/li&gt;
&lt;li&gt;keyboard and pointer listeners;&lt;/li&gt;
&lt;li&gt;pending animation and timeout callbacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resetting only the score is not enough if an old enemy loop is still running.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple failure pattern
&lt;/h2&gt;

&lt;p&gt;A common implementation keeps game objects in module-level variables and starts a new animation loop on every restart. The first run works. After two restarts, movement feels faster, collisions fire twice, and the result becomes inconsistent.&lt;/p&gt;

&lt;p&gt;The bug is not in the button label. It is in ownership of the running state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the lifecycle explicit
&lt;/h2&gt;

&lt;p&gt;I prefer a small state machine: loading → playing → won/lost → restarting.&lt;/p&gt;

&lt;p&gt;The transition into restarting should stop the current loop, remove listeners, cancel timers, and create a new run context. The new context then owns its entities and callbacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the failure path first
&lt;/h2&gt;

&lt;p&gt;I run this sequence several times:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start a fresh game.&lt;/li&gt;
&lt;li&gt;Lose intentionally.&lt;/li&gt;
&lt;li&gt;Restart.&lt;/li&gt;
&lt;li&gt;Win or lose again.&lt;/li&gt;
&lt;li&gt;Refresh the page and repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the second run differs from the first without a design reason, I inspect duplicated listeners, stale references, and callbacks that still point at the previous run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical lesson
&lt;/h2&gt;

&lt;p&gt;Restart is more than a convenience button. It is a compact test of whether the game has a coherent lifecycle. When restart is reliable, win, lose, refresh, and replay are usually easier to reason about as well.&lt;/p&gt;

</description>
      <category>gamedev</category>
    </item>
    <item>
      <title>A Game Wallet Is More Than a Number: Handling Retries and Concurrency</title>
      <dc:creator>zzzu2221</dc:creator>
      <pubDate>Sun, 16 Aug 2026 06:45:33 +0000</pubDate>
      <link>https://dev.to/zzzu2221/a-game-wallet-is-more-than-a-number-handling-retries-and-concurrency-2al3</link>
      <guid>https://dev.to/zzzu2221/a-game-wallet-is-more-than-a-number-handling-retries-and-concurrency-2al3</guid>
      <description>&lt;p&gt;A game wallet often starts as a single balance field. That is fine for a prototype, but payment retries and unreliable networks quickly make the number hard to trust.&lt;/p&gt;

&lt;p&gt;A player can tap “buy,” lose the connection, and try again. A store callback can arrive more than once. Two devices can spend the same account at nearly the same time.&lt;/p&gt;

&lt;p&gt;The fix is to treat the balance as a cached view of a ledger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record every change
&lt;/h2&gt;

&lt;p&gt;Instead of silently changing a balance, record events such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a verified payment granting virtual currency;&lt;/li&gt;
&lt;li&gt;an item purchase spending currency;&lt;/li&gt;
&lt;li&gt;a refund creating a compensating entry;&lt;/li&gt;
&lt;li&gt;an administrative adjustment with an explicit reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current balance remains useful for fast reads, but the ledger explains where it came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make payment delivery idempotent
&lt;/h2&gt;

&lt;p&gt;A payment callback is a message that may be retried. I use an idempotency key derived from the provider and transaction ID, then enforce uniqueness in the database.&lt;/p&gt;

&lt;p&gt;The delivery flow is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify the external transaction.&lt;/li&gt;
&lt;li&gt;Record the payment.&lt;/li&gt;
&lt;li&gt;Grant currency with the unique key.&lt;/li&gt;
&lt;li&gt;Mark delivery complete.&lt;/li&gt;
&lt;li&gt;Return the original result for later retries.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This prevents a temporary network problem from becoming a double grant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect spending too
&lt;/h2&gt;

&lt;p&gt;Client-side balance checks are useful for interface feedback, but they cannot protect an account. The server should lock the wallet row, check the available amount, write the ledger entry, and update the cached balance in one transaction.&lt;/p&gt;

&lt;p&gt;The same request key should return the original purchase result instead of charging twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep payment, wallet, and item orders separate
&lt;/h2&gt;

&lt;p&gt;A real-money payment, a virtual-currency movement, and item delivery are connected but different events:&lt;/p&gt;

&lt;p&gt;payment order → wallet grant → item order → wallet spend&lt;/p&gt;

&lt;p&gt;That separation makes refunds and reconciliation much easier. When something goes wrong, support can identify which step is missing instead of guessing from one mutable number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests that expose the real failures
&lt;/h2&gt;

&lt;p&gt;Before building a large shop UI, test repeated callbacks, crashes between payment and delivery, concurrent spends, timeout retries, failed item delivery, and refunds after currency has been spent.&lt;/p&gt;

&lt;p&gt;The important shift is simple: a wallet is not just a number. It is a history of decisions. Making those decisions explicit and idempotent keeps payment failures explainable.&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
  </channel>
</rss>
