Files
the-matrix/js/quality.js
Perplexity Computer 916acde69c fix: QA sprint v1 — 7 issues resolved
Fixes:
- #22 OrbitControls damping: call updateControls() in animate loop
- #23 Empty catch blocks: add console.warn + error surfacing to chat panel
- #24 escapeHtml: add quote escaping (" '), use in renderAgentList
- #25 WS reconnect: check close code (1000/1001) before reconnecting,
  add exponential backoff + heartbeat zombie detection
- #26 IDLE state visibility: brighten from near-invisible to #005500
- #5 PWA: manifest.json, service worker (network-first), theme-color,
  favicon, loading screen, safe-area-inset padding, apple-mobile-web-app
- #14 Adaptive render quality: new quality.js hardware detection (low/
  medium/high tiers), tiered particle counts, grid density, antialias,
  pixel ratio caps

New files:
- js/quality.js — hardware detection + quality tier logic
- manifest.json — PWA manifest
- public/sw.js — service worker (network-first with offline cache)
- public/favicon.svg — SVG favicon
- icons/icon-192.svg, icons/icon-512.svg — PWA icons
2026-03-19 00:14:27 +00:00

91 lines
3.0 KiB
JavaScript

/**
* quality.js — Detect hardware capability and return a quality tier.
*
* Tiers:
* 'low' — older iPads, phones, low-end GPUs (reduce particles, simpler effects)
* 'medium' — mid-range (moderate particle count)
* 'high' — desktop, modern iPad Pro (full quality)
*
* Detection uses a combination of:
* - Device pixel ratio (low DPR = likely low-end)
* - Logical core count (navigator.hardwareConcurrency)
* - Device memory (navigator.deviceMemory, Chrome/Edge only)
* - Screen size (small viewport = likely mobile)
* - Touch capability (touch + small screen = phone/tablet)
* - WebGL renderer string (if available)
*/
let cachedTier = null;
export function getQualityTier() {
if (cachedTier) return cachedTier;
let score = 0;
// Core count: 1-2 = low, 4 = mid, 8+ = high
const cores = navigator.hardwareConcurrency || 2;
if (cores >= 8) score += 3;
else if (cores >= 4) score += 2;
else score += 0;
// Device memory (Chrome/Edge): < 4GB = low, 4-8 = mid, 8+ = high
const mem = navigator.deviceMemory || 4;
if (mem >= 8) score += 3;
else if (mem >= 4) score += 2;
else score += 0;
// Screen dimensions (logical pixels)
const maxDim = Math.max(window.screen.width, window.screen.height);
if (maxDim < 768) score -= 1; // phone
else if (maxDim >= 1920) score += 1; // large desktop
// DPR: high DPR on small screens = more GPU work
const dpr = window.devicePixelRatio || 1;
if (dpr > 2 && maxDim < 1024) score -= 1; // retina phone
// Touch-only device heuristic
const touchOnly = 'ontouchstart' in window && !window.matchMedia('(pointer: fine)').matches;
if (touchOnly) score -= 1;
// Try reading WebGL renderer for GPU hints
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('webgl2');
if (gl) {
const debugExt = gl.getExtension('WEBGL_debug_renderer_info');
if (debugExt) {
const renderer = gl.getParameter(debugExt.UNMASKED_RENDERER_WEBGL).toLowerCase();
// Known low-end GPU strings
if (renderer.includes('swiftshader') || renderer.includes('llvmpipe') || renderer.includes('software')) {
score -= 3; // software renderer
}
if (renderer.includes('apple gpu') || renderer.includes('apple m')) {
score += 2; // Apple Silicon is good
}
}
gl.getExtension('WEBGL_lose_context')?.loseContext();
}
} catch {
// Can't probe GPU, use other signals
}
// Map score to tier
if (score <= 1) cachedTier = 'low';
else if (score <= 4) cachedTier = 'medium';
else cachedTier = 'high';
console.info(`[Matrix Quality] Tier: ${cachedTier} (score: ${score}, cores: ${cores}, mem: ${mem}GB, dpr: ${dpr}, touch: ${touchOnly})`);
return cachedTier;
}
/**
* Get the recommended pixel ratio cap for the renderer.
*/
export function getMaxPixelRatio() {
const tier = getQualityTier();
if (tier === 'low') return 1;
if (tier === 'medium') return 1.5;
return 2;
}