Notes · Deep-dives · Build logs
Notes.
A running set of deep-dives on the things I build and break — game mechanics, the modern web platform, real-time systems and provably-fair design. Written from the actual code, not marketing. New notes land here a couple of times a week.
All notes
Stop reconnecting in onclose. Which close codes to retry, full-jitter backoff, heartbeats for half-open sockets, and resuming with sequence numbers instead of silently missing messages.
You cannot hide logic in a browser. Rate limits, tick-window clock checks against speed hacks, movement and hit validation, replay-verified leaderboards, and a suspicion score that doesn’t ban honest players.
The default data channel is reliable and ordered — a TCP shape hiding in a real-time API. The three SCTP knobs, how to split channels by delivery requirement, the 64 KB message-size default, and backpressure with bufferedAmount.
Best-effort storage is the default, and best-effort means deletable. What really evicts IndexedDB data, Safari’s 7-day cap, the real quota numbers, how to request persistence properly, and Storage Buckets.
Four CSS declarations replace a positioning library — once you know that [popover] ships with margin: auto. The minimal recipe, position-area vs anchor(), six silent failures, flip fallbacks and arrows, and 2026 support.
Ten lines of JSON make a plain multi-page site feel instant — but a prerendered page really runs. Prefetch vs prerender, the four eagerness levels, which URLs to exclude, prerender-safe analytics, and 2026 support.
Simulation rate and send rate are two different numbers. Choosing a tick rate by genre, a drift-free accumulator loop in Node, the bandwidth arithmetic for a 32-player match, sizing interpolation delay, and how far to rewind for lag compensation.
Record the seed and the button presses, not the frames: the replay file format, delta-encoded input bitfields, keyframe seeking, per-tick checksums to find the exact desync tick, and why versioning stops old replays rotting.
Every tutorial shows you (a*b)>>16 — in JavaScript that returns 0.25 for 3.5×3.5. The range and resolution budget of Q16.16, a split multiply verified against BigInt, integer sqrt, a sine LUT, and whether you need fixed point at all.
Math.random takes no seed and never will — every engine runs xorshift128+ seeded out of reach. Which small generator to paste in instead, why you must hash the seed first, one stream per subsystem, and where a PRNG is the wrong tool entirely.
Rollback is lockstep plus one extra move. A decision guide across input delay, bandwidth, state size and CPU headroom — plus exactly what breaks determinism in JavaScript and Wasm, and why a per-tick checksum is the first thing to build.
Elo keeps one number and pretends it's certain. Glicko-2 adds rating deviation and volatility: the eight steps, the worked example that doubles as a unit test, why rating periods matter, how to tune tau, and an honest checklist for whether you need it.
How synchronous C, C++ or Rust compiled to Wasm can await a Promise: WebAssembly.Suspending and WebAssembly.promising, the Emscripten -sJSPI flag, why JSPI beats Asyncify on code size but not on tiny calls, and where it ships in 2026.
Three 2026 studies looked at crawler logs and citation data: 97% of llms.txt files were never fetched, AI search bots never go looking for them, and adopters get no citation lift. What the file is still good for, and how to write one that isn't junk.
Why "SharedArrayBuffer is not defined" is a headers problem, not a code problem: cross-origin isolation with COOP/COEP, the newer Document-Isolation-Policy, how COEP differs from CORP, and the Atomics operations (including waitAsync) you need once two threads share one buffer.
Animated page transitions on a plain multi-page site with about ten lines of CSS: the @view-transition at-rule, shared elements via view-transition-name, transition types, the pageswap/pagereveal events, and why every failure mode is one of five things.
How to hide 100ms of latency in a browser multiplayer game: a fixed tick loop, an input buffer, server reconciliation with rollback re-simulation, entity interpolation for remote players, and error smoothing that hides the corrections.
The Elo formula in plain English, a dependency-free JavaScript implementation, sane K-factor tiers, and how to turn ratings into an expanding-window matchmaking queue that doesn't leave players waiting.
How to move physics simulation into a Web Worker so your browser game stays at 60 FPS — postMessage vs Transferable vs SharedArrayBuffer, practical code with Rapier, and the interpolation trick that hides physics lag.
How to build a mobile-money payment flow that never double-charges — idempotency keys, webhook deduplication, and the state-machine pattern from real M-Pesa and EcoCash integration code.
A practical guide to the Temporal API — PlainDate, ZonedDateTime, Duration and Instant explained with real code. Why Date is finally obsolete in Node.js 26 and ECMAScript 2026, and how to migrate.
WebSocket, Server-Sent Events and WebTransport compared side by side — latency, browser support, direction, and a practical decision framework for choosing the right real-time protocol now that WebTransport is Baseline.
How online slots actually decide each spin — virtual reel strips, weighted symbol mapping, PRNG algorithms, RTP calculation, and why there is no such thing as a hot or cold machine.
How provably-fair dice and mines games turn an HMAC-SHA256 hash into a roll or mine placement — the exact byte extraction, float conversion and Fisher-Yates shuffle, with Python code to verify any round yourself.
How to transfer files between phones, PCs and tablets over Wi-Fi with no internet — a practical breakdown of WebRTC DataChannels, mDNS discovery, and the signaling trick that makes it all work offline.
Why JavaScript's garbage collector stutters your browser game at 60 FPS, and how to fix it with object pooling — a practical walkthrough with code, pool sizing, and Three.js InstancedMesh patterns.
How to implement hitscan raycasting and physics-based projectile shooting in a Three.js browser FPS — code, trade-offs, and when to use each approach.
A practical walkthrough of porting a Three.js browser game from WebGLRenderer to WebGPURenderer — the real migration steps, TSL shader conversion, compute shaders for particles, and the performance wins that actually matter.
How to use Supabase Realtime's Broadcast, Presence, and Postgres Changes to build multiplayer browser games — architecture patterns, code examples, and the trade-offs nobody mentions.
Two completely different models promise fair games — provably-fair cryptography and traditional RNG lab certification. How each works, what each guarantees, and the gaps in both.
How an HMAC-SHA256 hash becomes left-or-right bounces at every peg — the most visual way to understand provably-fair game mechanics, with a Python snippet to verify any drop yourself.
The honest engineering answer to "can I predict Aviator?" — how the crash multiplier is generated from a server seed, client seed and nonce, and why it's verifiable but never predictable.