EchoQuest v0.19.11: Device Adaptation & Rendering
Bundle 4 of the performance plan targets the client rendering pipeline. Bundles 0–3 optimized the server game loop, network protocol, client rendering basics, and database layer. This bundle goes deeper into the client — hardware-adaptive configuration, object lifecycle management, animation throttling, and offloading heavy computation to a Web Worker.
Three-Tier Device Detection
The previous system was binary: desktop or mobile, detected via user-agent sniffing. An iPad Pro got the same reduced settings as a budget Android phone. The new system scores actual hardware capabilities — CPU core count (navigator.hardwareConcurrency), device memory (navigator.deviceMemory), and effective pixel count (viewport area times device pixel ratio). A mobile penalty is applied, and the total score maps to one of three tiers: high (score 7+), medium (4–6), or low (0–3).
Each tier configures pool sizes, fog raycasting precision, VFX toggles, and the new animation culling threshold. The medium tier fills the gap that didn’t exist before — capable tablets and older desktops get reasonable settings instead of being forced into the mobile floor or desktop ceiling.
Coin & Item Pickup Pools
Every enemy kill drops 1–3 coins. Each coin was created fresh via this.coins.create() and destroyed on pickup or after a 30-second despawn timer. In a group fight killing 5 enemies in 10 seconds, that’s 5–15 sprite allocations and an equal number of destructions, all generating garbage collection pressure.
The coin pool pre-allocates sprites at scene creation (20 on high, 15 on medium, 10 on low). getCoin() activates the first inactive sprite; releaseCoin() deactivates it, kills pending tweens, and resets visual state. If the pool is exhausted during a loot-heavy fight, overflow sprites are created and added to the pool permanently. The item pickup pool follows the same pattern but also manages paired icon label text objects.
Map transitions release all active coins and items back to the pool instead of destroying them, preserving pool integrity across transitions.
Physics Body Culling
Phaser’s arcade physics runs overlap checks on every active body every frame, even if the bodies are off-screen and can’t possibly overlap with the player. After the minion update loop, we now iterate active coins and items and toggle body.enable based on the same cull bounds already computed for entity culling. Off-screen coins have their physics disabled entirely; when they scroll back on-screen, physics re-enables and overlap detection resumes normally.
Animation Distance Culling
Sprite animation is one of the largest per-entity GPU costs. An enemy walking at the far edge of the viewport animates at the same frame rate as one right next to the player, even though the player can barely see it. We now compute the Manhattan distance from each entity to the camera center and pause animations for entities beyond a configurable threshold (40% of viewport diagonal on high, 30% on low).
This applies to enemies, NPCs, remote players, and minions. The pause/resume is handled externally in the update loop — no entity class changes needed. When an entity crosses back inside the threshold, its animation resumes from where it left off with no visual discontinuity.
Fog of War Web Worker
Fog of war visibility uses raycasting: 180 rays (at 2-degree steps on high tier) cast outward from the player, each stepping through tiles until hitting an occluding wall or the vision radius. On a tile crossing, this means ~2,160 Set lookups with string concatenation for tile keys. All on the main thread, every time the player steps onto a new tile.
The worker receives the map’s occluding tile data as a flat Uint8Array (no Set, no string keys). On each calculate message, it raycasts using pure typed array indexing and returns a visibility bitmap via postMessage with a transferable buffer — zero-copy transfer back to the main thread. The main thread converts the bitmap back to the Set format expected by the existing incremental fog renderer.
If the browser doesn’t support Workers, or if the worker errors out, the system falls back to the synchronous raycasting path automatically. The worker is terminated on scene shutdown and reinitialized on map transitions with the new map’s occlusion data.
What’s Next
Bundle 4 completes the client-side rendering optimizations. The remaining bundles in the performance plan address server scaling, content delivery, and monitoring infrastructure — the foundation work needed before scaling to more concurrent players.
Try the EchoQuest Demo — free in your browser. Full game coming to Steam.