Engineering deep-dives, performance notes, and product decisions from the MetaWorldOS team.
Every entry is a code-level post-mortem.
Pokémon of the Forest ships no GLB files, no PNG textures, no baked normal maps. Every creature is marching-cubes-meshed from Wyvill metaballs at boot; every leaf is a 2D-canvas Bézier fill baked into a CanvasTexture; every terrain patch is an analytic heightfield sampled onto a PlaneGeometry; every skin material is a MeshPhysicalMaterial with a custom subsurface-wrap term injected via onBeforeCompile. This post walks the seven techniques that let a browser game with ~24k lines of TypeScript render Pallet Town without downloading a single texture.
Pokémon of the Forest's opening chapter — pick a starter, catch three research bugs, upgrade your net, cross the meadow, find rare bait, defeat the guardian — runs on a single ChapterDirector class that accepts a discriminated-union event and either advances a step, mutates a side condition, or rejects the event with a typed error code. No flags scattered across the codebase, no "did we already do this" audits, no invalid save states.
The infinite-scrolling town in our Infinitown gamecenter port isn't procedurally generated — it's a Möbius carpet. A 9×9 pool of pre-built chunks maps onto a 9×9 grid of fixed container slots through modulo arithmetic, and camera drags rebind slots to different pool entries instead of spawning new geometry. This post walks the four moving parts (pool, containers, mapping, drag event) and explains why the whole system holds together with zero allocations at runtime.
A road-surface flicker in our Infinitown gamecenter port survived every shadow-parameter tweak we threw at it — bias, normalBias, PCF radius, a 4096² shadow map, snap-tracked light. It turned out to be four separate depth-buffer bugs stacked on top of each other, only the last of which was a shadow problem. This is the debugging log — what we tried, what the symptom kept telling us, and the diagnostic toggles that finally isolated each layer.
Tank Battle's chassis rides on four downward raycasts, each of which contributes a spring impulse, a track-drive impulse, and a lateral friction impulse. It's the standard raycast-vehicle pattern from Unity's Wheel Collider or BeamNG, written straight against @dimforge/rapier3d-compat with no vehicle plugin. Here's the whole loop, the constants, the front/rear drive split, and the lateral-friction cliff that makes it feel like a tank.
Tank Battle's desert look isn't a shader trick, it's four passes and one PMREM step. RenderPass → UnrealBloomPass (0.28 strength, 0.8 radius, 0.85 threshold) → FXAA → OutputPass, plus a scene.environment sourced from PMREM(RoomEnvironment) at 0.04 sigma. Here's why each pass is doing what it does, why the stack falls back to plain renderer.render on touch devices, and the one FXAA-on-resize footgun that's caught me twice.
A tank's throttle in Tank Battle is not "one number that becomes engine force". It's a chain — key or joystick input → gear index into GEAR_KMH → target left/right track speeds with a steer differential → slew-limited actual track speeds → per-anchor drive impulses. Auto-shift decides gear from commanded track speed, not from the physics body's velocity, which produces one deliberate quirk. Here's every step.
Sound Race spawns pickups and hazards from cloned Hunyuan-generated GLB templates. Any single template can be null — because OSS was slow, or that specific asset failed to parse, or we hit a network timeout. The spawn code checks each slot and falls back to a shared procedural polytope when the GLB isn't there. Same collision box, same behavior, just a different-looking mesh. Here's the exact shape and why it's worth building even for a small feature.
Rapier surfaces contact detection through three interfaces on top of the same underlying check — the raw event queue, R3F's `onCollisionEnter` prop, and sensor colliders with `onIntersectionEnter`. Same feature, three ergonomics, different tradeoffs. This post walks through each shape with real call sites from Tank Battle and 3D Race, and where each one belongs.
Rapier's wasm loader returns a Promise, but a three.js render loop starts on `new`. If you naively `await RAPIER.init()` in the constructor, you get a black screen while wasm downloads. If you fire off `.then(...)` and forget about it, your first ticks crash on `world.step` because the world doesn't exist yet. Tank Battle threads this needle with a `physReady` flag, a purely-cosmetic pre-physics render loop, and a check-and-wait "deploy" gate.
Sound Race's 13 GLBs are compressed with Draco on upload and inflated with a WASM decoder in the browser. This post walks the whole pipeline — the @gltf-transform + draco3dgltf server side, the "if compressed is bigger, keep original" safety valve, why the decoder files live in /public/draco/gltf/ instead of on a CDN, and the exact three lines of GLTFLoader wiring that connect them.
Signed OSS URLs rotate every request, so the disk cache never matches. Sound Race redownloaded 13 GLBs on every visit until we sat an IndexedDB layer in front of GLTFLoader. Here's the code, why Cache Storage doesn't help, and one invalidation edge case we're leaving as tech debt.
How we ship a rhythm-driven WebGL racer with no backend. A Web Audio decode + a Web Worker that runs FFT off the main thread, a beatmap synthesizer that turns spectral flux into a curved elevated road, an audio-clock game loop that never drifts against the music, and a Jamendo-only music source that keeps CORS out of the failure list. All from the production code of /gamecenter/sound-race.
Four things that separate a marble runner that feels good from one that feels off — kinematic bodies driven by setNextKinematicTranslation (not setTranslation), a ray-cast grounded jump that can't be spammed mid-air, an "infinite" level that recycles blocks in place instead of growing an array, and per-frame camera lerp with reused scratch vectors. Straight from the /gamecenter 3D Race source.
The three things that decide whether a browser pool game feels good — a predictive aim line that respects the *first* ball you'd hit, a Rapier world with CCD turned on so 3 m/s balls don't tunnel through cushions, and 512×512 canvas-generated ball textures so you never ship a PNG. All from the production code of our /gamecenter Snooker.
How the Shouxing polynomial gives you every Chinese solar term from 1900–2100 to ±1 day, with two small constant arrays and a documented list of exception years — instead of either a 200-year lookup table or a 50 KB VSOP87 ephemeris.
Why LineSegments don't work for meteors, why naive Points look like a grid of squares, and how a 64×64 canvas-generated radial-gradient sprite + a dormant-spawn lifecycle gives you a beautiful 18-meteor shower in 175 lines and one draw call.
How we put the ISS, Hubble, and the entire Starlink constellation onto a 60 FPS WebGL globe — TLE pipelines, ECI→ECEF math, what to cache where, and what NOT to do.
How a generic Newton–Raphson solver and React Three Fiber gave us 20+ moons, 9 planets, and a "see the sky on the day you were born" feature for free — plus the refactor that took us from 6,470 → 1,754 lines.
A new dispatch lands in your inbox roughly once a month.
Get an email when we publish a new post — engineering deep-dives, ~once a month, no marketing.