Reference4 min read3 sections

Adaptive resolution and quality presets

docs/ADAPTIVE_QUALITY.mdSource updated 2026-10-25
Markdown
On this page 3 sections

Open Menu → Settings → Visual quality. High remains the default. Automatic adjusts resolution during active play when completed frames show sustained pressure, then restores detail after sustained headroom. Balanced and Performance fix a lower resolution. The setting is saved per browser origin and survives reload; unavailable browser storage leaves it usable for the current visit.

These modes change scene target dimensions only. The shadow map, authored geometry, lights, materials, animation, simulation, input and multiplayer authority remain on their existing paths. DOM controls stay at browser resolution. This is a resolution policy, not a promise that lowering resolution fixes CPU-bound gameplay or meets a particular frame rate on unmeasured hardware.

#Presets and aspect ratio

Tier Device pixel ratio cap Resolution scale
High 1.5 1.0
Balanced 1.0 0.8
Performance 1.0 0.6

Both axes share a further scale to fit the 2560×1600 target envelope and the device's maximum texture dimension. The previous independent width/height clamps could change the projection's aspect ratio on very wide or tall displays. The new shared scale preserves it to integer-pixel rounding: a 3840×2160 viewport at DPR 2 renders High at 2560×1440, and a 3200×900 viewport at DPR 1 renders at 2560×720.

Target replacement still waits for the renderer's single in-flight frame to settle. The existing graph allocates the complete replacement, retains the previous targets on synchronous failure and releases old targets after activation. Quality changes do not create an additional queue or canvas owner.

#Automatic policy

packages/render/quality.ts supplies the browser-independent policy and target sizing. The player uses the same policy for worker rendering and the main-thread fallback. It samples only new completed-frame counters. Its work signal is render CPU time plus queue-notification wait; the latter includes scheduling delay and is not GPU timestamp duration or compositor presentation time.

The rolling window covers 2.5 seconds, retains at most 180 samples, and takes samples at least 16 ms apart. It requires four completed observations spanning at least 1.5 seconds. This keeps high-refresh observations from crowding out the necessary time span while still admitting sustained two-frame-per-second rendering.

The window's 75th percentile must exceed 28 ms for another 2.5 seconds before lowering one tier. Restoring one tier requires a percentile below 10 ms for fifteen seconds. Each change has a five-second settling period. Intermediate measurements clear the pending pressure/headroom condition. A brief spike or valley cannot jump two tiers. The resolution floor is Performance; the ceiling is High.

Sampling is inactive during menus, hidden/unfocused pages, travel, revision barriers and recovery. Stale completions, gaps, restarted counters, nonfinite values and observations above one second reset the measurement window. Very long stalls therefore do not become evidence for a permanent resolution decision; manual Performance remains available. This policy does not stop the independent render worker or change simulation cadence.

Choosing a fixed tier takes effect on the next idle rendered frame and remains fixed under pressure. Choosing Automatic starts from the current tier; a fresh page with saved Automatic starts at High. Device replacement clears measurement history while retaining the target tier, then resumes sampling after recovery and return to play. No game save or content schema migration is needed.

#Diagnostics and testing

The observatory reports the requested mode, target tier and policy status. Exported playtest reports include adaptiveQuality with the bounded observation count, current percentile and last eight tier changes. These describe the requested target; render.width and render.height confirm the actual allocated target after the idle-frame handoff.

npm run test:browser:quality checks both renderer placements. It delays real queue completion notifications by 80 ms, verifies a sustained reduction, keeps the tier fixed while paused, verifies manual overrides and repeated target replacement, reloads the preference and destroys the actual GPU device to exercise recovery. The worker journey retains a second connected traveler. The main-thread journey verifies the actual 2560×720 target at a wide viewport.

npm test includes policy regressions for brief spikes, slow and fast frame rates, restoration hysteresis, bounded history, stale/invalid observations and aspect-ratio caps. The existing renderer lifecycle probe and GPU-backpressure/independent-worker tests cover shared paths. Acceptance evidence records exact results, source hashes and limitations.

This qualifies the measured native test paths and policy logic. Physical GPU/device-tier calibration, mobile thermal behavior, long sessions, display/compositor timing, dynamic shadow/effect quality, Docker/Coolify execution and remote CI remain open. Artificial completion delay proves policy response and resource ownership; it is not a measured production FPS improvement. Character and final visual art polish remain separate gates in the full engine plan.

Authored particle effects now follow the same tier. High uses the authored count, Performance uses each emitter’s low-quality fraction, and Balanced uses the midpoint. Resolution and particle density change together; this does not change gameplay state.

Static imported models now also use distance-based geometry detail, with quality-tier error thresholds, near-object protection and hysteresis. Original collision and shadow geometry remain unchanged.

End of Adaptive quality