EverRealm - A first-person open world retro fantasy RPG

Everrealm is a first-person fantasy RPG that runs entirely in the browser. Explore a procedurally streamed overworld, walk into towns and dungeons, and talk to villagers whose replies are AI-generated. Pick a race, class and faction, fight, cast spells, brew potions and pick locks. Built with three.js and cannon-es.

Play in browser: EverRealm

  • three.js with Vite, cannon-es for physics, three.quarks for particle effects (torch flames).
  • Chunked terrain streamer with per-chunk heightfield collision, instanced grass, and LOD trees.
  • Skinned glTF characters with animation mixers. Far-away characters are culled and put to sleep in the physics world.
  • Performance work: draw-call and triangle profiling, a renderer resolution slider, optional MSAA, and a frame-rate governor that lowers quality on slow machines without interrupting play.

2 Likes

Great scope for a browser game. I’m curious how your frame-rate governor decides. In my game I ended up pricing quality by a pixel budget (a fixed megapixel target for each tier, so resolution follows window size) with a one-way step-down if the first seconds run slow, because stepping back up tended to oscillate. Does yours step back up when things calm down, and how do you stop it hunting?

Thanks Chris! To answer directly: no, mine never steps back up on its own — its a strict one-way ratchet, and thats actually the whole anti-hunting mechanism. There’s no “confidently recovered” state it tries to detect so theres nothing for it to flap between. If a player wants to try higher quality again after a rough patch, they do it themselves from the options menu — stepping up is a human decision, never something the governer attempts.

Under the hood its watching real frame times (not the games own clamped delta) in 6 second rolling windows, only while the player is actually playing — menus, pause, and any window I’ve flagged as “loading assets” get thrown out rather then counted, along with any individual frame hitch over 500ms (tab switch, GC pause, etc). It takes the median of the window, not the mean, so a couple outlier hitches inside an otherwise fine window dont trip it. And even then it needs two bad windows in a row below the floor (30fps here) before it actually acts — a single bad window just resets and trys again.

Theres also a 10s warm up before it even starts measuring, and a 3s settle period after every step-down before it resumes measuring — otherwise first time shader compilation/asset decode on a fresh install reads identical to “this machine is too slow”, and the frame right after a quality change is its own kind of noisy anyway.

One difference from your approach — I dont scale a single continous budget, quality is a small ladder of discrete tiers (Low/Medium/High/Ultra), each a bundle of several levers moved together (render resolution, view distance, grass density, antialiasing, couple boolean shader features), so a step down moves several knobs at once rather then shrinking one dial. Coarser granularity then your megapixel-target approach for sure, but means every tier is something I’ve actually eyeballed and confirmed looks right, rather then trusting an interpolated resolution to always look ok.