<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Mahmoud Bakir</title><description>Mahmoud Bakir&apos;s portfolio</description><link>https://mahloola.com/</link><language>en-us</language><item><title>Synchronizing a Multiplayer &quot;Hard Mode&quot; Feature</title><link>https://mahloola.com/blog/jingle-multiplayer-hard-mode/</link><guid isPermaLink="true">https://mahloola.com/blog/jingle-multiplayer-hard-mode/</guid><description>My experience with adding a synchronized multiplayer &quot;hard mode&quot; feature to my music game Jingle.</description><pubDate>Wed, 21 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;A while back, I released a multiplayer mode for my music game &lt;a href=&quot;https://jingle.rs&quot;&gt;Jingle&lt;/a&gt;. Shortly after, one of my players suggested a hard mode option.&lt;/p&gt;
&lt;p&gt;Currently, singleplayer allows you to play with &apos;hard mode&apos; enabled. This means you only get an x second segment of the song to make your guess.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/HnI8AGZ.png&quot; alt=&quot;Hard Mode&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;The Problem&lt;/h3&gt;
&lt;p&gt;Behind the scenes, a network request retrieves the mp3 file, browser reads the duration, client decides on a &apos;start&apos; and &apos;end&apos; time, trims the mp3 to that segment, and renders a custom &apos;snippet player&apos; with that segment.
If we take this approach and add it to multiplayer, we notice that all players get &lt;strong&gt;different segments&lt;/strong&gt; of the song. This makes sense because start and end times are decided CLIENT SIDE. So we need to figure something out.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Storage&lt;/h2&gt;
&lt;p&gt;If we&apos;re passing snippet start + end time from server -&amp;gt; client side, then these should be stored somewhere in our data tree. There are two options:
This isn&apos;t really complicated, but I want to share my thought process here.&lt;/p&gt;
&lt;h3&gt;Option 1: Store snippet times on the currentRound object.&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;export interface MultiRound {
  id: string;
  songName: string;
  pins: Array&amp;lt;{
    userId: string;
    details: {
      clickedPosition: ClickedPosition;
      distance: number;
      confirmed: boolean;
    };
  }&amp;gt;;
  results: Array&amp;lt;{
    userId: string;
    score: number;
  }&amp;gt;;
  hardModeStartTime?: Date;
  hardModeEndTime?: Date;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OR..&lt;/p&gt;
&lt;h3&gt;Option 2: store snippet times on the gameState object.&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;export interface MultiGameState {
  status: MultiLobbyStatus;
  currentRound: MultiRound;
  rounds: MultiRound[];
  currentPhaseEndTime: Date | null;
  leaderboard: LeaderboardEntry[];
  hardModeStartTime?: Date;
  hardModeEndTime?: Date;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;Pros and Cons&lt;/h3&gt;
&lt;p&gt;Option 1: memory and storage consumption
8 bytes per Firestore timestamp * 2 = 16 bytes; if 1000 rounds are played per day that&apos;s an extra ~500KB of storage, which is like 9 cents. Not a big deal at all. In terms of memory occupation on the server, we&apos;re running Jingle on a 500MB memory virtual machine, so it&apos;s really nothing.
This will allow us to have more granular game history and gather statistics if we want in the future (e.g. looks like players struggle a lot with x segment of y song)&lt;/p&gt;
&lt;p&gt;Option 2: no memory/storage overhead, singular source of truth, but limited statistic analysis&lt;/p&gt;
&lt;p&gt;Data is cheap, so we&apos;ll go with Option 1.
Now the easy part is out of the way.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Determining Time Segments&lt;/h2&gt;
&lt;p&gt;We need hardModeStartTime + hardModeEndTime to be determined SERVER SIDE, making our server the singular source of truth so all clients share the same exact snippet. Currently, audio files are loaded into a DOM reference on the client, which gives the browser access to metadata such as song length. We DON&apos;T have that length metadata on the back end, so how can we generate snippet start + end times per song on our server?
Options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Have a separate &apos;song lengths&apos; data file that maps song titles to their durations in seconds.&lt;/li&gt;
&lt;li&gt;Make a network request on the back end to retrieve the mp3 metadata from Cloudflare (where it&apos;s stored) (cons: redundancy, latency, network bandwidth)&lt;/li&gt;
&lt;li&gt;Pass metadata from client side to back end (doesn&apos;t make sense, back end needs to be source of truth + we can&apos;t afford a network request there due to latency + we don&apos;t want all clients to send requests)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&apos;s go with Solution 1.&lt;/p&gt;
&lt;p&gt;To make this work, I went to the OSRS wiki &lt;a href=&quot;https://oldschool.runescape.wiki/w/List_of_music_release_dates&quot;&gt;music page listing&lt;/a&gt;, copied the 800 rows of song + durations, pasted that into Google Sheets, exported as a .csv, then wrote a simple JS script to convert that into a JSON (and by wrote I mean I coerced AI into doing it for me)
I&apos;ll also need to update this file every time Jagex releases a new song.&lt;/p&gt;
&lt;p&gt;Now that we have this data on the back end, we can determine start + end times on the back end. This finally allows us to use the back end as our source of truth.&lt;/p&gt;
</content:encoded><category>database</category><category>networking</category></item></channel></rss>