<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Krrish Ranjan]]></title><description><![CDATA[Krrish Ranjan]]></description><link>https://krrish-ranjan.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Krrish Ranjan</title><link>https://krrish-ranjan.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 09 Sep 2026 16:17:49 GMT</lastBuildDate><atom:link href="https://krrish-ranjan.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Architecting a Real-Time Web3 Arcade Engine: Anti-Cheat, 60 FPS Canvas Bridging, and Sub-100ms Leaderboards]]></title><description><![CDATA[Subtitle: How I engineered Space Cargo Runner with Phaser 3, Zustand, Node.js, Prisma, and Redis without dropping a single frame.
Tags: Web Development, TypeScript, Game Development, System Design, No]]></description><link>https://krrish-ranjan.hashnode.dev/architecting-a-real-time-web3-arcade-engine-anti-cheat-60-fps-canvas-bridging-and-sub-100ms-leaderboards</link><guid isPermaLink="true">https://krrish-ranjan.hashnode.dev/architecting-a-real-time-web3-arcade-engine-anti-cheat-60-fps-canvas-bridging-and-sub-100ms-leaderboards</guid><category><![CDATA[Web Development]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[System Design]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Redis]]></category><dc:creator><![CDATA[Krrish]]></dc:creator><pubDate>Wed, 02 Sep 2026 17:08:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a985403184bc8e1019c49dc/ce736313-9a67-4494-9b7d-3dfaf64f0385.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Subtitle:</strong> How I engineered Space Cargo Runner with Phaser 3, Zustand, Node.js, Prisma, and Redis without dropping a single frame.</p>
<p><strong>Tags:</strong> <code>Web Development</code>, <code>TypeScript</code>, <code>Game Development</code>, <code>System Design</code>, <code>Node.js</code>, <code>Redis</code></p>
<hr />
<h2>Introduction</h2>
<p>If your game client tells your backend what score the player achieved, your game is already hacked.</p>
<p>When building <a href="https://krrish41.github.io/space-cargo-runner/"><strong>Space Cargo Runner</strong></a> — a cyberpunk-themed, real-time arcade runner web game with optional Web3 token withdrawals on SecureChain — the hardest problem wasn't making sprites move on screen at 60 FPS.</p>
<p>It was creating a <strong>tamper-proof, server-authoritative backend architecture</strong> that could synchronize multiplayer leaderboards, prevent memory sniffing, and bridge high-frequency game physics with React UI overlays.</p>
<p>In this deep dive, I'll walk through the architectural patterns and solutions that powered the game.</p>
<hr />
<h2>1. Bridging the Phaser 3 Canvas Loop with React using Zustand</h2>
<p>One of the most frequent mistakes in HTML5 web game development is coupling your game engine's tick directly to UI framework state.</p>
<h3>The Challenge</h3>
<ul>
<li><p><strong>Phaser 3</strong> runs inside an HTML5 <code>&lt;canvas&gt;</code> on its own internal <code>requestAnimationFrame</code> loop (firing 60 to 144 times/sec).</p>
</li>
<li><p><strong>React</strong> is designed for declarative, component-based rendering.</p>
</li>
<li><p>Forcing React components to re-render on every game loop tick causes garbage collection spikes, UI stuttering, and severe frame drops.</p>
</li>
</ul>
<h3>The Solution: Decoupled State Streaming</h3>
<p>By establishing <strong>Zustand</strong> as a decoupled state mediator outside React's render tree:</p>
<ul>
<li><p>Phaser writes lightweight telemetry updates into the Zustand store on frame updates.</p>
</li>
<li><p>React components (HUD, fuel gauge, hull integrity bar) subscribe to specific selector slices.</p>
</li>
</ul>
<pre><code class="language-typescript">// Client-side Zustand State Mediator
import create from 'zustand';

interface ShipTelemetry {
  fuel: number;
  health: number;
  cargo: number;
  score: number;
  setTelemetry: (data: Partial&lt;ShipTelemetry&gt;) =&gt; void;
}

export const useShipStore = create&lt;ShipTelemetry&gt;((set) =&gt; ({
  fuel: 100,
  health: 100,
  cargo: 0,
  score: 0,
  setTelemetry: (data) =&gt; set((prev) =&gt; ({ ...prev, ...data })),
}));
</code></pre>
<p>React UI components only re-render when specific thresholds or integer values change, keeping the Canvas loop locked at 60 FPS.</p>
<hr />
<h2>2. Server-Authoritative Anti-Cheat &amp; Deterministic Validation</h2>
<p>Client-side memory manipulation (e.g., using Cheat Engine or altering JavaScript memory pointers) is trivial in web browsers. If your game submits <code>{ score: 100000 }</code> on death, malicious users will exploit your economy.</p>
<pre><code class="language-plaintext">Client Input Stream           Backend Deterministic Validator
[ (t: 0ms, MoveRight),    --&gt;   [ Replay Physics Simulation ]
  (t: 120ms, Boost),      --&gt;   [ Check Collision Bounds    ]
  (t: 450ms, LootCargo) ] --&gt;   [ Compute Derived Score     ]
                                           |
                                           v
                               { Pass: Commit / Fail: Reject }
</code></pre>
<h3>How the Anti-Cheat Pipeline Works:</h3>
<ol>
<li><p><strong>RNG Seed Synchronization</strong>: When a run starts, the backend generates and signs a cryptographically secure random seed.</p>
</li>
<li><p><strong>Action Stream Logging</strong>: During the run, the client records input actions and collision timestamps.</p>
</li>
<li><p><strong>Replay Validation</strong>: When the player crashes or lands, the client sends the action payload. The backend re-executes the obstacle layout from the seed and checks whether the claimed score is mathematically achievable.</p>
</li>
<li><p><strong>Prisma Atomic Transactions</strong>: Validated rewards are added using Prisma interactive transactions with database-level row locking:</p>
</li>
</ol>
<pre><code class="language-typescript">// apps/backend/src/services/scoreService.ts
export async function commitRunResult(userId: string, runData: RunPayload) {
  return await prisma.$transaction(async (tx) =&gt; {
    const user = await tx.user.findUnique({ where: { id: userId } });
    if (!user) throw new Error('User not found');

    const isValid = verifyReplay(runData);
    if (!isValid) throw new Error('Anti-cheat validation failed');

    await tx.gameSession.create({
      data: {
        userId,
        score: runData.calculatedScore,
        cargoCollected: runData.cargoCount,
        validated: true,
      },
    });

    return await tx.user.update({
      where: { id: userId },
      data: { credits: { increment: runData.cargoCount } },
    });
  });
}
</code></pre>
<hr />
<h2>3. Sub-100ms Leaderboard Fan-Out with Redis &amp; WebSockets</h2>
<p>High-concurrency games need instant score updates without hammering relational databases with heavy <code>ORDER BY score DESC</code> queries.</p>
<ul>
<li><p><strong>Redis Sorted Sets (</strong><code>ZADD</code> <strong>/</strong> <code>ZREVRANGE</code><strong>)</strong>: We maintain leaderboards in memory with \(O(\log(N))\) time complexity.</p>
</li>
<li><p><strong>Socket.io Event Fan-Out</strong>: When a player secures a top ranking, an event is emitted across Socket.io rooms, updating all connected clients in real time.</p>
</li>
</ul>
<pre><code class="language-typescript">// Redis leaderboard update and fanout
await redis.zadd('leaderboard:daily', score, username);
const topScores = await redis.zrevrange('leaderboard:daily', 0, 9, 'WITHSCORES');
io.to('game_lobby').emit('leaderboard_sync', { topScores });
</code></pre>
<hr />
<h2>4. Fullstack Monorepo Architecture with Shared Type Contracts</h2>
<p>To prevent frontend and backend schemas from drifting out of sync, the project is structured as an <strong>npm workspaces monorepo</strong>:</p>
<ul>
<li><p><code>apps/frontend</code>: React + Vite UI, Phaser 3 game loop, Zustand state bridge, and Web3 connection modals.</p>
</li>
<li><p><code>apps/backend</code>: Express REST API, Socket.io real-time engine, Prisma ORM with PostgreSQL.</p>
</li>
<li><p><code>packages/shared</code>: Shared TypeScript models, API request/response payloads, and socket event interfaces.</p>
</li>
<li><p><code>packages/contracts</code>: Solidity smart contracts (Hardhat, OpenZeppelin) for optional SCAI token withdrawals.</p>
</li>
</ul>
<hr />
<h2>5. Role-Gated "Mission Control" Admin Panel</h2>
<p>The backend includes a dedicated <code>/admin</code> dashboard featuring:</p>
<ul>
<li><p>Role-Based Access Control (RBAC) middleware verifying session tokens against database roles.</p>
</li>
<li><p>CLI-driven administrator provisioning: <code>npm run make-admin &lt;username&gt;</code>.</p>
</li>
<li><p>Live telemetry monitoring, active player session inspectors, ban management, and dynamic economy variable adjustments (drop rates, multiplier costs) without needing client rebuilds.</p>
</li>
</ul>
<hr />
<h2>Summary</h2>
<p>Building production-ready web games requires treating the frontend as an untrusted rendering layer while keeping state management clean and decoupled.</p>
<ul>
<li><p><strong>Play the live game</strong>: <a href="https://krrish41.github.io/space-cargo-runner/">https://krrish41.github.io/space-cargo-runner/</a></p>
</li>
<li><p><strong>View the source code on GitHub</strong>: <a href="https://github.com/Krrish41/space-cargo-runner">https://github.com/Krrish41/space-cargo-runner</a></p>
</li>
</ul>
<p><em>How do you handle anti-cheat and multiplayer synchronization in your web applications? Drop your thoughts below!</em></p>
]]></content:encoded></item></channel></rss>