From 8e0072b842deb21dd54a6bda69924572c662227a Mon Sep 17 00:00:00 2001 From: SageHaven Date: Thu, 13 Aug 2026 20:26:46 +0100 Subject: [PATCH] Bounty #1524: feat(beacon): add 100-agent performance LOD --- site/beacon/agents.js | 94 +++++++++++++++++++++++++++++++++----- site/beacon/scene.js | 16 ++++++- tests/test_beacon_atlas.py | 86 ++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+), 12 deletions(-) diff --git a/site/beacon/agents.js b/site/beacon/agents.js index 7b5d49aab..c2d3ddcb3 100644 --- a/site/beacon/agents.js +++ b/site/beacon/agents.js @@ -8,11 +8,41 @@ import { getProviderColor, } from './data.js'; import { - getScene, registerClickable, registerHoverable, onAnimate, + getScene, getCamera, registerClickable, registerHoverable, onAnimate, + setAgentPerformanceMode, } from './scene.js'; const agentMeshes = new Map(); // agentId -> { core, glow, group } const agentPositions = new Map(); // agentId -> Vector3 +const PERFORMANCE_AGENT_THRESHOLD = 100; +const LOD_NEAR_DISTANCE_SQ = 140 * 140; +const LOD_MEDIUM_DISTANCE_SQ = 320 * 320; +const LOD_UPDATE_INTERVAL_SECONDS = 0.25; + +let sharedAgentGeometries = null; + +export function shouldUseAgentPerformanceMode(agentCount) { + return Number.isFinite(agentCount) && agentCount >= PERFORMANCE_AGENT_THRESHOLD; +} + +export function selectAgentLod(distanceSquared) { + if (distanceSquared <= LOD_NEAR_DISTANCE_SQ) return 'high'; + if (distanceSquared <= LOD_MEDIUM_DISTANCE_SQ) return 'medium'; + return 'low'; +} + +export function applyAgentLod(mesh, level) { + if (!mesh || !mesh.lodGeometries || !mesh.lodGeometries[level]) return false; + if (mesh.group.userData.lod === level) return false; + + mesh.core.geometry = mesh.lodGeometries[level]; + const showDetailEffects = level === 'high'; + mesh.glow.visible = showDetailEffects; + mesh.light.visible = showDetailEffects; + mesh.label.visible = showDetailEffects; + mesh.group.userData.lod = level; + return true; +} export function getAgentPosition(agentId) { return agentPositions.get(agentId); @@ -24,6 +54,10 @@ export function getAgentMesh(agentId) { export function buildAgents() { const scene = getScene(); + const camera = getCamera(); + const performanceMode = shouldUseAgentPerformanceMode(AGENTS.length); + const geometries = getSharedAgentGeometries(); + setAgentPerformanceMode(performanceMode); // Track per-city agent index for offset placement const cityCounts = {}; @@ -58,27 +92,22 @@ export function buildAgents() { ? (getProviderColor(agent.provider) || '#ffffff') : (GRADE_COLORS[agent.grade] || '#33ff33'); const color = new THREE.Color(colorHex); + const lodGeometries = isRelay ? geometries.relay : geometries.native; // Core geometry: Octahedron (diamond) for relay, Sphere for native - const coreGeo = isRelay - ? new THREE.OctahedronGeometry(1.8, 0) - : new THREE.SphereGeometry(1.5, 16, 12); const coreMat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.9, wireframe: isRelay, // Wireframe gives relay agents a "holographic bridge" look }); - const core = new THREE.Mesh(coreGeo, coreMat); + const core = new THREE.Mesh(lodGeometries.high, coreMat); core.userData = { type: 'agent', agentId: agent.id }; group.add(core); registerClickable(core); registerHoverable(core); // Outer glow — slightly larger for relay to emphasize presence - const glowGeo = isRelay - ? new THREE.OctahedronGeometry(3.0, 1) - : new THREE.SphereGeometry(2.5, 16, 12); const glowMat = new THREE.MeshBasicMaterial({ color, transparent: true, @@ -86,7 +115,7 @@ export function buildAgents() { blending: THREE.AdditiveBlending, depthWrite: false, }); - const glow = new THREE.Mesh(glowGeo, glowMat); + const glow = new THREE.Mesh(lodGeometries.glow, glowMat); group.add(glow); // Point light for local illumination @@ -102,12 +131,35 @@ export function buildAgents() { group.add(label); scene.add(group); - agentMeshes.set(agent.id, { core, glow, group, light, relay: isRelay }); + const mesh = { + core, glow, group, light, label, relay: isRelay, lodGeometries, + }; + agentMeshes.set(agent.id, mesh); + + if (performanceMode) { + applyAgentLod(mesh, selectAgentLod(camera.position.distanceToSquared(pos))); + } else { + group.userData.lod = 'high'; + } } // Bob + spin animation - onAnimate((elapsed) => { + let lodElapsed = 0; + onAnimate((elapsed, dt) => { + lodElapsed += dt; + const refreshLod = performanceMode && lodElapsed >= LOD_UPDATE_INTERVAL_SECONDS; + if (refreshLod) lodElapsed = 0; + for (const [agentId, mesh] of agentMeshes) { + if (refreshLod) { + const distanceSquared = camera.position.distanceToSquared(mesh.group.position); + applyAgentLod(mesh, selectAgentLod(distanceSquared)); + } + + // Far agents stay selectable and rendered with low-poly cores, but do not + // spend CPU time on per-frame bob, glow, or rotation updates. + if (performanceMode && mesh.group.userData.lod === 'low') continue; + const baseY = mesh.group.userData.baseY; const phase = hashCode(agentId) * 0.001; mesh.group.position.y = baseY + Math.sin(elapsed * 1.2 + phase) * 1.5; @@ -126,6 +178,26 @@ export function buildAgents() { }); } +function getSharedAgentGeometries() { + if (sharedAgentGeometries) return sharedAgentGeometries; + + sharedAgentGeometries = { + native: { + high: new THREE.SphereGeometry(1.5, 16, 12), + medium: new THREE.SphereGeometry(1.5, 8, 6), + low: new THREE.OctahedronGeometry(1.5, 0), + glow: new THREE.SphereGeometry(2.5, 16, 12), + }, + relay: { + high: new THREE.OctahedronGeometry(1.8, 1), + medium: new THREE.OctahedronGeometry(1.8, 0), + low: new THREE.TetrahedronGeometry(1.8, 0), + glow: new THREE.OctahedronGeometry(3.0, 1), + }, + }; + return sharedAgentGeometries; +} + export function highlightAgent(agentId, on) { const mesh = agentMeshes.get(agentId); if (!mesh) return; diff --git a/site/beacon/scene.js b/site/beacon/scene.js index 87ab9cd48..881115a3c 100644 --- a/site/beacon/scene.js +++ b/site/beacon/scene.js @@ -15,6 +15,9 @@ let autoRotate = true; let autoRotateSpeed = 0.001; // radians per frame (~0.06°) let lerpTarget = null; let lerpAlpha = 0; +let rendererPixelRatioCap = 2; + +const PERFORMANCE_PIXEL_RATIO_CAP = 1.25; // Day/Night Cycle - Lighting references let ambientLight, dirLight; @@ -28,6 +31,11 @@ export function registerClickable(mesh) { clickables.push(mesh); } export function registerHoverable(mesh) { hoverables.push(mesh); } export function onAnimate(fn) { animationCallbacks.push(fn); } +export function setAgentPerformanceMode(enabled) { + rendererPixelRatioCap = enabled ? PERFORMANCE_PIXEL_RATIO_CAP : 2; + updateRendererPixelRatio(); +} + export function initScene(canvas) { clock = new THREE.Clock(); @@ -44,7 +52,7 @@ export function initScene(canvas) { // Renderer renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: false }); renderer.setSize(window.innerWidth, window.innerHeight); - renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + updateRendererPixelRatio(); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 0.8; @@ -99,6 +107,12 @@ function onResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); + updateRendererPixelRatio(); +} + +function updateRendererPixelRatio() { + if (!renderer) return; + renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, rendererPixelRatioCap)); } // --- Click detection --- diff --git a/tests/test_beacon_atlas.py b/tests/test_beacon_atlas.py index 034ae1e53..79f08f8c0 100644 --- a/tests/test_beacon_atlas.py +++ b/tests/test_beacon_atlas.py @@ -8,6 +8,9 @@ import time import sys import os +import pathlib +import re +import subprocess # Add parent directory to path for imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -231,6 +234,89 @@ def test_state_opacity_mapping(self): self.assertLessEqual(opacity, 1.0, "Opacity must be <= 1") +class TestBeaconAtlasPerformanceMode(unittest.TestCase): + """Test the actual frontend LOD policy without requiring a WebGL browser.""" + + @classmethod + def setUpClass(cls): + root = pathlib.Path(__file__).resolve().parents[1] + cls.agents_source = (root / "site" / "beacon" / "agents.js").read_text() + cls.scene_source = (root / "site" / "beacon" / "scene.js").read_text() + + def run_agents_probe(self, expression): + source = re.sub( + r"^import[\s\S]*?;\s*$", + "", + self.agents_source, + flags=re.MULTILINE, + ) + source = re.sub(r"\bexport\s+", "", source) + result = subprocess.run( + ["node", "--input-type=module", "--eval", source + "\n" + expression], + check=True, + capture_output=True, + text=True, + ) + return json.loads(result.stdout) + + def test_lod_boundaries_and_population_gate(self): + result = self.run_agents_probe(""" + console.log(JSON.stringify({ + levels: [0, 19600, 19601, 102400, 102401].map(selectAgentLod), + enabled: [99, 100, 125].map(shouldUseAgentPerformanceMode), + })); + """) + self.assertEqual( + result["levels"], + ["high", "high", "medium", "medium", "low"], + ) + self.assertEqual(result["enabled"], [False, True, True]) + + def test_lod_transition_reuses_geometry_and_culls_detail_effects(self): + result = self.run_agents_probe(""" + const mesh = { + core: { geometry: 'initial' }, + glow: { visible: true }, + light: { visible: true }, + label: { visible: true }, + group: { userData: {} }, + lodGeometries: { high: 'HIGH', medium: 'MEDIUM', low: 'LOW' }, + }; + const first = applyAgentLod(mesh, 'low'); + const snapshot = { + geometry: mesh.core.geometry, + glow: mesh.glow.visible, + light: mesh.light.visible, + label: mesh.label.visible, + level: mesh.group.userData.lod, + }; + const duplicate = applyAgentLod(mesh, 'low'); + const restored = applyAgentLod(mesh, 'high'); + console.log(JSON.stringify({ first, snapshot, duplicate, restored })); + """) + self.assertTrue(result["first"]) + self.assertEqual( + result["snapshot"], + { + "geometry": "LOW", + "glow": False, + "light": False, + "label": False, + "level": "low", + }, + ) + self.assertFalse(result["duplicate"]) + self.assertTrue(result["restored"]) + + def test_performance_mode_is_integrated_into_render_loop(self): + self.assertIn("camera.position.distanceToSquared", self.agents_source) + self.assertIn("LOD_UPDATE_INTERVAL_SECONDS", self.agents_source) + self.assertIn("mesh.group.userData.lod === 'low'", self.agents_source) + self.assertIn("setAgentPerformanceMode(performanceMode)", self.agents_source) + self.assertIn("PERFORMANCE_PIXEL_RATIO_CAP = 1.25", self.scene_source) + self.assertIn("renderer.setPixelRatio", self.scene_source) + + class TestBeaconAtlasDataIntegrity(unittest.TestCase): """Test data integrity and consistency."""