<?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: Jacob Miller</title>
    <description>The latest articles on DEV Community by Jacob Miller (@jacob_miller_6a433a011a83).</description>
    <link>https://dev.to/jacob_miller_6a433a011a83</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%2F4085531%2Fe565a10c-5ed0-4e94-8d2f-9b9d4d131bc9.png</url>
      <title>DEV Community: Jacob Miller</title>
      <link>https://dev.to/jacob_miller_6a433a011a83</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jacob_miller_6a433a011a83"/>
    <language>en</language>
    <item>
      <title>I Built a Live Multiplayer Game Where a Shared Multiplier Grows — Until It Collapses. Here's the Real-Time Architecture.</title>
      <dc:creator>Jacob Miller</dc:creator>
      <pubDate>Wed, 19 Aug 2026 18:53:42 +0000</pubDate>
      <link>https://dev.to/jacob_miller_6a433a011a83/i-built-a-live-multiplayer-game-where-a-shared-multiplier-grows-until-it-collapses-heres-the-1a78</link>
      <guid>https://dev.to/jacob_miller_6a433a011a83/i-built-a-live-multiplayer-game-where-a-shared-multiplier-grows-until-it-collapses-heres-the-1a78</guid>
      <description>&lt;p&gt;Following up on my last post about iCourt — this time I want to walk through Pyramid, the flagship game on the same platform, and the real-time synchronization problem that made it the hardest of the four to get right.&lt;/p&gt;

&lt;p&gt;The mechanic&lt;/p&gt;

&lt;p&gt;Players join a live round with a stake in a free, non-purchasable in-game currency. As more players join within a short recording window, a shared multiplier grows — every join pushes it up. Anyone currently in the round can exit at any time and lock in whatever the multiplier is at that moment. If the window closes and not enough new players joined, the round collapses — everyone still in loses their stake for that round.&lt;/p&gt;

&lt;p&gt;No deposits, no real money anywhere in this loop. It's a pure tension mechanic: timing, trust that others will keep joining, and the temptation to hold out for a bigger multiplier versus the risk of the whole thing collapsing under you.&lt;/p&gt;

&lt;p&gt;Why this was the hardest sync problem on the platform&lt;/p&gt;

&lt;p&gt;Unlike iCourt (which has a clear phase-by-phase flow — submit, moderate, vote, verdict), Pyramid has to handle an unbounded number of concurrent actors all racing against a shared, ticking clock. At any moment, N players could be joining, M players could be exiting, and the server has to:&lt;/p&gt;

&lt;p&gt;Accept or reject each JOIN/EXIT with a fresh, deduplicated nonce (prevents double-submission from a flaky connection retry)&lt;br&gt;
Recalculate every remaining player's live multiplier as new joins land&lt;br&gt;
Broadcast the authoritative state to every connected client — never let the client calculate its own multiplier locally, since even a few hundred milliseconds of drift between two players' views of "what the multiplier currently is" breaks trust instantly&lt;br&gt;
Handle the recording-window countdown server-side only, with the client purely rendering whatever TICK event it receives — no client-side timers pretending to be authoritative&lt;/p&gt;

&lt;p&gt;That last point took a real redesign partway through. My first instinct was to let the client run its own countdown and just sync occasionally — it felt performant. But any client-side timer drift, even small, means two players in the same round can see different time-remaining values, and if one of them exits believing they have 2 seconds left while the server has already closed the window, that's a real, trust-breaking bug. Server-authoritative TICK events every 100ms, client purely renders — no exceptions — was the fix.&lt;/p&gt;

&lt;p&gt;Stack&lt;br&gt;
Socket.IO, self-hosted (not the CDN build — ran into an issue early on where privacy-focused browsers like Brave were blocking the third-party CDN script entirely, which silently broke the game for a chunk of users before I traced it)&lt;br&gt;
Firebase / Firestore for wallet balance, player identity, persistent stats&lt;br&gt;
A dedicated REVEAL_ENTRY sequencing system — entries are revealed one at a time with a short stagger (~280ms) even though they all technically joined within the same window, purely for the visual/dramatic effect of watching the pyramid build in real time. This is entirely a presentation-layer choice on top of already-committed server state, not the actual game logic.&lt;br&gt;
The collapse mechanic specifically&lt;/p&gt;

&lt;p&gt;When a round collapses, every client gets a COLLAPSE event with the real numbers — how many joined, how many exited, the final multiplier. I made a deliberate choice here: show the actual collapse reason plainly ("3 people joined but 5 exited" vs. a vague "you lost"). Transparency about why a round collapsed, rather than hiding the mechanics behind a generic loss screen, turned out to matter a lot for how a loss feels — players seem to accept a collapse better when they can see exactly what happened numerically, rather than it feeling arbitrary.&lt;/p&gt;

&lt;p&gt;What real users have already taught me&lt;/p&gt;

&lt;p&gt;With only a handful of genuine outside players so far, I've already seen two distinct behaviors worth designing around: some players play one round and leave regardless of outcome, and at least one forfeited mid-round rather than riding it out or exiting cleanly. Both are useful signals I wouldn't have gotten from testing solo — the loss/collapse-screen experience is probably the next thing I refine, since a first-time collapse might currently feel more punishing than it needs to for someone still learning the mechanic.&lt;/p&gt;

&lt;p&gt;Try it&lt;/p&gt;

&lt;p&gt;Free to play, no deposits, no purchases — the currency exists purely to make the stakes feel real without any actual money involved: &lt;a href="https://dev.tourl"&gt;cubeengines.com/pyramid&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to go deeper on the server-authoritative timer design, the nonce-based dedup system, or anything else about the real-time architecture in the comments.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>reviews</category>
    </item>
    <item>
      <title>I Built a Game Where 3 Strangers Judge Your Photo Live — Here's the Real-Time Architecture Behind It</title>
      <dc:creator>Jacob Miller</dc:creator>
      <pubDate>Wed, 19 Aug 2026 18:45:58 +0000</pubDate>
      <link>https://dev.to/jacob_miller_6a433a011a83/i-built-a-game-where-3-strangers-judge-your-photo-live-heres-the-real-time-architecture-behind-it-k0d</link>
      <guid>https://dev.to/jacob_miller_6a433a011a83/i-built-a-game-where-3-strangers-judge-your-photo-live-heres-the-real-time-architecture-behind-it-k0d</guid>
      <description>&lt;p&gt;I've been building a small multiplayer gaming platform outside my day job, and one of the four games — iCourt — turned out to be the most interesting engineering problem of the bunch. Wanted to write up how it works, both the mechanic and the stack behind it.&lt;/p&gt;

&lt;p&gt;The idea&lt;/p&gt;

&lt;p&gt;Two players get matched into a themed photo round — best mirror selfie, best sunset shot, best family photo, whatever the prompt is. Each submits a real photo, taken in the moment, within a tight time window. Then three independent jurors — real people, not an algorithm — vote live on which photo wins. Majority wins the round.&lt;/p&gt;

&lt;p&gt;No AI scoring, no engagement-optimized ranking system deciding for you. Just three strangers looking at two photos and voting on gut feeling. It's turned out to be way more unpredictable than I expected — a technically sharper photo regularly loses to a rougher one because the jury just liked it more. You can't really game it by trying to be "correct," only interesting.&lt;/p&gt;

&lt;p&gt;Why this was a harder build than it sounds&lt;/p&gt;

&lt;p&gt;The tricky part isn't the photo upload — it's making sure everyone (both players, all three jurors) is looking at a consistent, synchronized view of the game state as it moves through phases: submission → moderation → jury viewing → live voting → verdict. If one juror's client falls half a second behind, or a player's upload confirmation doesn't reach the server the same way it reaches the other players, the whole round breaks trust immediately — "wait, why does it say the verdict already happened?"&lt;/p&gt;

&lt;p&gt;Stack&lt;br&gt;
Socket.IO for real-time state sync across all five participants (2 players + 3 jurors) in a round&lt;br&gt;
Firebase / Firestore for account state, guest sessions, and persistent profile stats&lt;br&gt;
Google Cloud Vision SafeSearch — every uploaded photo gets scanned automatically before it ever reaches a juror's screen. Adult content, graphic content, anything flagged as likely-unsafe results in immediate round termination, no juror ever sees it. This runs inline in the upload pipeline, not as a manual review step, since the whole round happens in under a minute.&lt;br&gt;
Images are deleted immediately after a session ends — the only exception is a 48-hour retention window if the opposing player files a complaint during post-match feedback, specifically so that complaint can actually be reviewed against the real image.&lt;br&gt;
The moderation pipeline was the part I underestimated&lt;/p&gt;

&lt;p&gt;Building the "three jurors vote live" mechanic was actually the easy part conceptually — Socket.IO handles broadcast-to-multiple-clients pretty cleanly once you get the room/phase logic right. The part that took real thought was making sure nothing inappropriate could reach a juror's screen in a live, timed, human-judged game — since unlike a static content feed, there's no time for a manual review queue. The SafeSearch scan has to happen synchronously in the upload path, fast enough that it doesn't blow the round's tight timing, but strict enough to catch what it needs to catch before three real people are looking at it.&lt;/p&gt;

&lt;p&gt;What I'd do differently&lt;/p&gt;

&lt;p&gt;If I rebuilt this today, I'd instrument the drop-off points more carefully from day one. Watching real early users play has already surfaced things I didn't expect — someone not realizing where the upload button was under time pressure, someone finishing a round and immediately leaving after a loss. Small UX signals that only show up once actual strangers, not just you testing your own product, touch it.&lt;/p&gt;

&lt;p&gt;Try it&lt;/p&gt;

&lt;p&gt;It's free to play — no deposits, nothing to buy, just genuine gameplay. If you're curious how the jury mechanic actually feels from the inside, or want to poke holes in the real-time sync, I'd genuinely appreciate it: &lt;a href="https://dev.tourl"&gt;cubeengines.com/icourt&lt;/a&gt;&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%2Fwdpjbliq5w43zvcqqkei.jpeg" 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%2Fwdpjbliq5w43zvcqqkei.jpeg" alt=" " width="720" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to go deeper on any part of this — the Socket.IO room/phase management, the SafeSearch integration, or the guest-auth flow — in the comments.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>beginners</category>
      <category>reviews</category>
    </item>
  </channel>
</rss>
