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 { getMaxPixelRatio, getQualityTier } from './quality.js';
|
2026-03-18 23:52:06 +00:00
|
|
|
|
|
|
|
|
let scene, camera, renderer;
|
2026-03-19 02:01:23 +00:00
|
|
|
const _worldObjects = [];
|
2026-03-18 23:52:06 +00:00
|
|
|
|
2026-03-19 02:01:23 +00:00
|
|
|
/**
|
|
|
|
|
* @param {HTMLCanvasElement|null} existingCanvas — pass the saved canvas on
|
|
|
|
|
* re-init so Three.js reuses the same DOM element instead of creating a new one
|
|
|
|
|
*/
|
|
|
|
|
export function initWorld(existingCanvas) {
|
2026-03-18 23:52:06 +00:00
|
|
|
scene = new THREE.Scene();
|
|
|
|
|
scene.background = new THREE.Color(0x000000);
|
|
|
|
|
scene.fog = new THREE.FogExp2(0x000000, 0.035);
|
|
|
|
|
|
|
|
|
|
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 500);
|
|
|
|
|
camera.position.set(0, 12, 28);
|
|
|
|
|
camera.lookAt(0, 0, 0);
|
|
|
|
|
|
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 02:01:23 +00:00
|
|
|
renderer = new THREE.WebGLRenderer({
|
|
|
|
|
antialias: tier !== 'low',
|
|
|
|
|
canvas: existingCanvas || undefined,
|
|
|
|
|
});
|
2026-03-18 23:52:06 +00:00
|
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
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
|
|
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, getMaxPixelRatio()));
|
2026-03-18 23:52:06 +00:00
|
|
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
2026-03-19 02:01:23 +00:00
|
|
|
|
|
|
|
|
if (!existingCanvas) {
|
|
|
|
|
document.body.prepend(renderer.domElement);
|
|
|
|
|
}
|
2026-03-18 23:52:06 +00:00
|
|
|
|
|
|
|
|
addLights(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
|
|
|
addGrid(scene, tier);
|
2026-03-18 23:52:06 +00:00
|
|
|
|
|
|
|
|
return { scene, camera, renderer };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addLights(scene) {
|
|
|
|
|
const ambient = new THREE.AmbientLight(0x001a00, 0.6);
|
|
|
|
|
scene.add(ambient);
|
|
|
|
|
|
|
|
|
|
const point = new THREE.PointLight(0x00ff41, 2, 80);
|
|
|
|
|
point.position.set(0, 20, 0);
|
|
|
|
|
scene.add(point);
|
|
|
|
|
|
|
|
|
|
const fill = new THREE.DirectionalLight(0x003300, 0.4);
|
|
|
|
|
fill.position.set(-10, 10, 10);
|
|
|
|
|
scene.add(fill);
|
|
|
|
|
}
|
|
|
|
|
|
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 addGrid(scene, tier) {
|
|
|
|
|
const gridDivisions = tier === 'low' ? 20 : 40;
|
|
|
|
|
const grid = new THREE.GridHelper(100, gridDivisions, 0x003300, 0x001a00);
|
2026-03-18 23:52:06 +00:00
|
|
|
grid.position.y = -0.01;
|
|
|
|
|
scene.add(grid);
|
2026-03-19 02:01:23 +00:00
|
|
|
_worldObjects.push(grid);
|
2026-03-18 23:52:06 +00:00
|
|
|
|
|
|
|
|
const planeGeo = new THREE.PlaneGeometry(100, 100);
|
|
|
|
|
const planeMat = new THREE.MeshBasicMaterial({
|
|
|
|
|
color: 0x000a00,
|
|
|
|
|
transparent: true,
|
|
|
|
|
opacity: 0.5,
|
|
|
|
|
});
|
|
|
|
|
const plane = new THREE.Mesh(planeGeo, planeMat);
|
|
|
|
|
plane.rotation.x = -Math.PI / 2;
|
|
|
|
|
plane.position.y = -0.02;
|
|
|
|
|
scene.add(plane);
|
2026-03-19 02:01:23 +00:00
|
|
|
_worldObjects.push(plane);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Dispose only world-owned geometries, materials, and the renderer.
|
|
|
|
|
* Agent and effect objects are disposed by their own modules before this runs.
|
|
|
|
|
*/
|
|
|
|
|
export function disposeWorld(disposeRenderer, _scene) {
|
|
|
|
|
for (const obj of _worldObjects) {
|
|
|
|
|
if (obj.geometry) obj.geometry.dispose();
|
|
|
|
|
if (obj.material) {
|
|
|
|
|
const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
|
|
|
|
|
mats.forEach(m => {
|
|
|
|
|
if (m.map) m.map.dispose();
|
|
|
|
|
m.dispose();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_worldObjects.length = 0;
|
|
|
|
|
disposeRenderer.dispose();
|
2026-03-18 23:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function onWindowResize(camera, renderer) {
|
|
|
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
|
|
camera.updateProjectionMatrix();
|
|
|
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
|
|
}
|