2026-03-18 23:52:06 +00:00
|
|
|
import * as THREE from 'three';
|
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
|
|
|
import { getQualityTier } from './quality.js';
|
2026-03-18 23:52:06 +00:00
|
|
|
|
|
|
|
|
let rainParticles;
|
|
|
|
|
let rainPositions;
|
|
|
|
|
let rainVelocities;
|
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
|
|
|
let rainCount = 0;
|
2026-03-19 00:27:13 +00:00
|
|
|
let skipFrames = 0; // 0 = update every frame, 1 = every 2nd frame
|
|
|
|
|
let frameCounter = 0;
|
2026-03-18 23:52:06 +00:00
|
|
|
|
|
|
|
|
export function initEffects(scene) {
|
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
|
|
|
const tier = getQualityTier();
|
2026-03-19 00:27:13 +00:00
|
|
|
skipFrames = tier === 'low' ? 1 : 0; // Low tier: update rain every 2nd frame
|
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
|
|
|
initMatrixRain(scene, tier);
|
|
|
|
|
initStarfield(scene, tier);
|
2026-03-18 23:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
function initMatrixRain(scene, tier) {
|
|
|
|
|
// Scale particle count by quality tier
|
|
|
|
|
rainCount = tier === 'low' ? 500 : tier === 'medium' ? 1200 : 2000;
|
|
|
|
|
|
2026-03-18 23:52:06 +00:00
|
|
|
const geo = new THREE.BufferGeometry();
|
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
|
|
|
const positions = new Float32Array(rainCount * 3);
|
|
|
|
|
const velocities = new Float32Array(rainCount);
|
|
|
|
|
const colors = new Float32Array(rainCount * 3);
|
2026-03-18 23:52:06 +00:00
|
|
|
|
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
|
|
|
for (let i = 0; i < rainCount; i++) {
|
2026-03-18 23:52:06 +00:00
|
|
|
positions[i * 3] = (Math.random() - 0.5) * 100;
|
|
|
|
|
positions[i * 3 + 1] = Math.random() * 50 + 5;
|
|
|
|
|
positions[i * 3 + 2] = (Math.random() - 0.5) * 100;
|
|
|
|
|
velocities[i] = 0.05 + Math.random() * 0.15;
|
|
|
|
|
|
|
|
|
|
const brightness = 0.3 + Math.random() * 0.7;
|
|
|
|
|
colors[i * 3] = 0;
|
|
|
|
|
colors[i * 3 + 1] = brightness;
|
|
|
|
|
colors[i * 3 + 2] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
|
|
|
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
|
|
|
|
rainPositions = positions;
|
|
|
|
|
rainVelocities = velocities;
|
|
|
|
|
|
|
|
|
|
const mat = new THREE.PointsMaterial({
|
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
|
|
|
size: tier === 'low' ? 0.16 : 0.12,
|
2026-03-18 23:52:06 +00:00
|
|
|
vertexColors: true,
|
|
|
|
|
transparent: true,
|
|
|
|
|
opacity: 0.7,
|
|
|
|
|
sizeAttenuation: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
rainParticles = new THREE.Points(geo, mat);
|
|
|
|
|
scene.add(rainParticles);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
function initStarfield(scene, tier) {
|
|
|
|
|
const count = tier === 'low' ? 150 : tier === 'medium' ? 350 : 500;
|
2026-03-18 23:52:06 +00:00
|
|
|
const geo = new THREE.BufferGeometry();
|
|
|
|
|
const positions = new Float32Array(count * 3);
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
|
positions[i * 3] = (Math.random() - 0.5) * 300;
|
|
|
|
|
positions[i * 3 + 1] = Math.random() * 80 + 10;
|
|
|
|
|
positions[i * 3 + 2] = (Math.random() - 0.5) * 300;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
|
|
|
|
|
|
|
|
const mat = new THREE.PointsMaterial({
|
|
|
|
|
color: 0x003300,
|
|
|
|
|
size: 0.08,
|
|
|
|
|
transparent: true,
|
|
|
|
|
opacity: 0.5,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const stars = new THREE.Points(geo, mat);
|
|
|
|
|
scene.add(stars);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateEffects(_time) {
|
|
|
|
|
if (!rainParticles) return;
|
|
|
|
|
|
2026-03-19 00:27:13 +00:00
|
|
|
// On low tier, skip every other frame to halve iteration cost
|
|
|
|
|
if (skipFrames > 0) {
|
|
|
|
|
frameCounter++;
|
|
|
|
|
if (frameCounter % (skipFrames + 1) !== 0) return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When skipping frames, multiply velocity to maintain visual speed
|
|
|
|
|
const velocityMul = skipFrames > 0 ? (skipFrames + 1) : 1;
|
|
|
|
|
|
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
|
|
|
for (let i = 0; i < rainCount; i++) {
|
2026-03-19 00:27:13 +00:00
|
|
|
rainPositions[i * 3 + 1] -= rainVelocities[i] * velocityMul;
|
2026-03-18 23:52:06 +00:00
|
|
|
if (rainPositions[i * 3 + 1] < -1) {
|
|
|
|
|
rainPositions[i * 3 + 1] = 40 + Math.random() * 20;
|
|
|
|
|
rainPositions[i * 3] = (Math.random() - 0.5) * 100;
|
|
|
|
|
rainPositions[i * 3 + 2] = (Math.random() - 0.5) * 100;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rainParticles.geometry.attributes.position.needsUpdate = true;
|
|
|
|
|
}
|
2026-03-19 02:01:23 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Dispose all effect resources (used on world teardown).
|
|
|
|
|
*/
|
|
|
|
|
export function disposeEffects() {
|
|
|
|
|
if (rainParticles) {
|
|
|
|
|
rainParticles.geometry.dispose();
|
|
|
|
|
rainParticles.material.dispose();
|
|
|
|
|
rainParticles = null;
|
|
|
|
|
}
|
|
|
|
|
rainPositions = null;
|
|
|
|
|
rainVelocities = null;
|
|
|
|
|
rainCount = 0;
|
|
|
|
|
frameCounter = 0;
|
|
|
|
|
}
|