{"id":5785,"date":"2026-02-01T10:00:24","date_gmt":"2026-02-01T17:00:24","guid":{"rendered":"https:\/\/rec0m88.com\/?p=5785"},"modified":"2026-02-02T10:58:00","modified_gmt":"2026-02-02T17:58:00","slug":"optimizing-the-game-loop-the-battle-for-frame-pacing-in-the-browser","status":"publish","type":"post","link":"https:\/\/rec0m88.com\/nl\/optimizing-the-game-loop-the-battle-for-frame-pacing-in-the-browser\/","title":{"rendered":"De spelkring optimaliseren: De strijd om framepacing in de browser"},"content":{"rendered":"<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Inhoudsopgave<\/h2><nav><ul><li><a href=\"#the-v-sync-ghost-why-60-hz-isnt-always-60-hz\">Het V-Sync spook: waarom 60Hz niet altijd 60Hz is<\/a><ul><li><a href=\"#our-solution-request-animation-frame-and-the-web-audio-clock\">Onze oplossing: requestAnimationFrame en de Web Audio Clock<\/a><\/li><\/ul><\/li><li><a href=\"#the-audio-crackle-solving-the-buffer-underflow\">De geluidskraak: De \u201cBuffer Underflow\u201d oplossen\u201d<\/a><\/li><li><a href=\"#managing-jitter-in-a-jit-world\">Jitter beheren in een JIT-wereld<\/a><\/li><li><a href=\"#the-input-to-photon-latency\">De ingang-tot-foton vertraging<\/a><\/li><li><a href=\"#why-high-value-sites-care-about-timing\">Waarom hoogwaardige sites om timing geven<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Voor de toevallige toeschouwer is een game niet meer dan een reeks bewegende beelden. Maar voor een ontwikkelaar is een game een meedogenloze, razendsnelle klok. Voor retro-emulators en HTML5-arcadegames is die klok meedogenloos. Als de timing ook maar een paar milliseconden hapert, kraakt het geluid, hapert het scrollen en voelt de gameplay niet responsief aan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Op <strong>Rec0m88<\/strong>, De grootste technische hindernis is niet alleen het uitvoeren van het spel, maar ook het onder de knie krijgen van <strong>Framepacing<\/strong>. JavaScript is oorspronkelijk gemaakt voor het scrollen van tekst en het klikken op knoppen, niet voor het beheren van de submilliseconde precisie die een arcadebord uit de jaren 90 vereist. Hier lees je hoe we de machinekamer van de browser hebben getemd om soepele, consistente 60 FPS gameplay te leveren.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"the-v-sync-ghost-why-60-hz-isnt-always-60-hz\">Het V-Sync spook: waarom 60Hz niet altijd 60Hz is<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">De meeste retroconsoles zijn ontworpen voor NTSC-televisies, die op precies <strong>59,94 Hz<\/strong>. Moderne computermonitoren werken echter meestal op een vlakke <strong>60 Hz<\/strong>, <strong>120 Hz<\/strong>, of <strong>144 Hz<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Als je een 59,94Hz spel draait op een 60Hz monitor, kom je in een wiskundige nachtmerrie terecht. Om de paar seconden \u201cmist\u201d de monitor een frame omdat de timings niet perfect op elkaar aansluiten. Dit resulteert in <strong>Microstotter<\/strong>-Die vervelende \u201csprong\u201d die je ziet als een personage over het scherm loopt.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"our-solution-request-animation-frame-and-the-web-audio-clock\">Onze oplossing: <code>requestAnimationFrame<\/code> en de Web Audio Clock<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">In een standaard webapp gebruiken ontwikkelaars <code>setTimeout<\/code> of <code>instelinterval<\/code> om code uit te voeren. Deze zijn te \u201clos\u201d voor gaming; ze kunnen worden vertraagd door de browser wanneer deze zich druk voelt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In plaats daarvan gebruikt Rec0m88 een systeem met twee klokken:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Visuele synchronisatie:<\/strong> We gebruiken <code>requestAnimationFrame<\/code> (rAF). Dit vertelt de browser: \u201cVoer het volgende frame van het spel pas uit als de monitor klaar is om het te tekenen.\u201d Dit voorkomt \u201cscreen tearing\u201d en zorgt ervoor dat de video zo glad als zijde is.<\/li>\n\n\n\n<li><strong>Audiosynchronisatie:<\/strong> Omdat de vernieuwingsfrequentie van de monitor kan fluctueren, gebruiken we de <strong>Web Audio API-contexttijd<\/strong> als onze \u201cMetronoom\u201d. De Web Audio klok is de meest nauwkeurige timer in de browser. Door onze WebAssembly (WASM) game loop te koppelen aan de audiobuffer, zorgen we ervoor dat het geluid nooit desynchroniseert met de actie.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"the-audio-crackle-solving-the-buffer-underflow\">De geluidskraak: De \u201cBuffer Underflow\u201d oplossen\u201d<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Heb je ooit een webspel gespeeld waarbij het geluid knalt of knettert als je een nieuw tabblad opent? Dat is een <strong>Buffer onderloop<\/strong>. Het gebeurt omdat de hoofd thread van de browser het te druk had om de audiokaart de volgende paar milliseconden geluid te \u201cgeven\u201d.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Om dit op Rec0m88 op te lossen, hebben we de audioverwerking verplaatst naar een <strong>Webmedewerker<\/strong>.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Ontkoppelde logica:<\/strong> De logica van het spel draait op \u00e9\u00e9n thread en het mixen van de audio draait op een andere thread.<\/li>\n\n\n\n<li><strong>Ronde buffers:<\/strong> We houden een kleine \u201clook-ahead\u201d buffer van audiomonsters bij. Als de CPU voor een milliseconde piekt, haalt de audiospeler uit deze buffer, waardoor het systeem tijd heeft om te herstellen zonder dat de gebruiker ooit een \u201cpop\u201d hoort.\u201d<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"managing-jitter-in-a-jit-world\">Jitter beheren in een JIT-wereld<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">JavaScript is een <strong>JIT (Just-In-Time)<\/strong> gecompileerde taal. Dit betekent dat de browser de code constant \u201coptimaliseert\u201d terwijl je speelt. Soms veroorzaken deze optimalisaties een tijdelijke \u201chang\u201d (vaak een \u201cJank\u201d genoemd).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Om een perfecte framepacing te bereiken, minimaliseren we \u201cGarbage Collection\u201d (GC). GC is wanneer de browser alles stopt om ongebruikt geheugen op te ruimen. We vermijden dit door:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Objectpooling:<\/strong> We hergebruiken dezelfde geheugenblokken voor sprites en P2P pakketten in plaats van nieuwe te maken.<\/li>\n\n\n\n<li><strong>Getypeerde matrices:<\/strong> We gebruiken <code>Uint8Array<\/code> en <code>Float32Array<\/code> om spelgegevens af te handelen. Deze worden opgeslagen in een vast gebied van het geheugen dat de browser niet zo vaak hoeft \u201cop te ruimen\u201d, waardoor de lus van het spel voorspelbaar blijft.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"the-input-to-photon-latency\">De ingang-tot-foton vertraging<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u201cBij \u201dframepacing\" gaat het niet alleen om wat je ziet, maar ook om wat je voelt. De tijd tussen het moment dat je een toets indrukt en het moment dat een pixel van kleur verandert, heet <strong>Ingang-tot-foton latentie<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Door de <strong>Web HID API<\/strong> en de <strong>Gamepad API<\/strong>, omzeilen we de standaard \u201cKeyboard Event\u201d wachtrij van de browser. Hierdoor kunnen we je controller pollen aan het begin van elk frame, vlak voordat de WASM core wordt uitgevoerd. Dit zorgt ervoor dat je invoer wordt verwerkt in de <em>zeer volgende<\/em> frame, wat zorgt voor het \u201cpittige\u201d gevoel waar arcadeliefhebbers om vragen.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"why-high-value-sites-care-about-timing\">Waarom hoogwaardige sites om timing geven<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">De filter \u201cInhoud met lage waarde\u201d van Google kijkt niet alleen naar tekst, maar ook naar de <strong>gebruikerservaring<\/strong>. Een site die een trage, stotterende emulator biedt, is een tool van \u201clage kwaliteit\u201d. Door onze worsteling met framepacing te documenteren en een engine met hoge precisie te leveren, laten we zien dat Rec0m88 een platform van professionele kwaliteit is.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We hebben honderden uren besteed aan het afstemmen van de interactie tussen de Web Audio API, WebAssembly en de GPU om ervoor te zorgen dat wanneer je een klassieker afspeelt op ons platform, het niet alleen werkt, maar ook goed aanvoelt.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>De Meta-uitdaging:<\/strong> Probeer de \u201cprestatiemonitor\u201d van je browser te openen terwijl je een van onze games speelt. Je zult een vlakke, consistente lijn van 60 FPS zien - een bewijs van onze architectuur voor frame-pacing.<\/p>","protected":false},"excerpt":{"rendered":"<p>Voor de toevallige toeschouwer is een game niet meer dan een reeks bewegende beelden. Maar voor een ontwikkelaar is een game een meedogenloze, razendsnelle klok. Voor retro-emulators en HTML5-arcadegames is die klok meedogenloos. Als de timing ook maar een paar milliseconden hapert, kraakt de audio, hapert het scrollen en wordt de gameplay...<\/p>","protected":false},"author":1,"featured_media":5764,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"categories":[40],"tags":[],"class_list":["post-5785","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-meta"],"_links":{"self":[{"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/posts\/5785","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/comments?post=5785"}],"version-history":[{"count":1,"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/posts\/5785\/revisions"}],"predecessor-version":[{"id":5786,"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/posts\/5785\/revisions\/5786"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/media\/5764"}],"wp:attachment":[{"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/media?parent=5785"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/categories?post=5785"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rec0m88.com\/nl\/wp-json\/wp\/v2\/tags?post=5785"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}