Files
the-nexus/performance-benchmark.js
Alexander Whitestone 319ea08b24
Some checks failed
Review Approval Gate / verify-review (pull_request) Successful in 10s
CI / test (pull_request) Failing after 43s
CI / validate (pull_request) Failing after 44s
perf: Three.js LOD and texture audit for local hardware (#873)
- Add performance-monitor.js: stats.js overlay with FPS, frame time,
  draw calls, and agent LOD stats. Toggle with Shift+P.
- Add lod-system-enhanced.js: THREE.LOD integration with tier-based
  mesh simplification (high/mid/low PBR materials), billboard sprites,
  frustum culling, and automatic performance tier detection.
- Add texture-optimizer.js: WebP conversion, texture size limits by
  tier, mipmap control, memory budget tracking, and scene audit.
- Add performance-benchmark.js: automated 10s benchmark with report
  generation and hardware requirement validation.
- Add docs/MINIMUM_SOVEREIGN_HARDWARE.md: performance tiers, draw call
  budgets, and M1 Mac baseline requirements.
- Update app.js: integrate PerformanceMonitor.update in game loop,
  pass renderer to LODSystem.init().
- Update index.html: include new performance scripts.

Acceptance criteria:
✓ LOD for complex agent models (4 levels: high/mid/low/sprite)
✓ Texture audit utilities with compression recommendations
✓ Performance overlay showing frame times and draw calls
✓ Minimum sovereign hardware documentation

Closes #873
2026-04-25 21:29:50 -04:00

270 lines
7.3 KiB
JavaScript

/**
* Performance Benchmark for The Nexus
*
* Runs automated performance tests and generates a report:
* - FPS stability test with 5+ agents
* - Draw call count validation
* - Frame time analysis
* - Memory usage tracking
*
* Usage:
* PerformanceBenchmark.run();
* PerformanceBenchmark.generateReport();
*/
const PerformanceBenchmark = (() => {
let _isRunning = false;
let _results = {
fps: [],
frameTime: [],
drawCalls: [],
timestamps: [],
agentCount: 0,
tier: 'unknown',
duration: 0
};
let _startTime = 0;
let _rafId = null;
function init() {
console.log('[PerformanceBenchmark] Initialized');
}
async function run(duration = 10000) {
if (_isRunning) return;
_isRunning = true;
console.log(`[PerformanceBenchmark] Starting ${duration}ms test...`);
// Show performance monitor
if (window.PerformanceMonitor) {
window.PerformanceMonitor.show();
}
// Get current tier
if (window.LODSystem) {
_results.tier = window.LODSystem.getPerformanceTier();
}
// Get agent count
if (window.agents) {
_results.agentCount = window.agents.length;
}
// Reset data
_results = {
fps: [],
frameTime: [],
drawCalls: [],
timestamps: [],
agentCount: _results.agentCount,
tier: _results.tier,
duration: duration
};
_startTime = performance.now();
return new Promise((resolve) => {
let lastTime = performance.now();
let frameCount = 0;
function measure() {
const now = performance.now();
const elapsed = now - _startTime;
if (elapsed >= duration) {
finish();
resolve(_results);
return;
}
// Calculate FPS
frameCount++;
if (now - lastTime >= 1000) {
const fps = Math.round((frameCount * 1000) / (now - lastTime));
_results.fps.push(fps);
_results.timestamps.push(Math.round(elapsed));
// Get draw calls
if (window.renderer && window.renderer.info) {
_results.drawCalls.push(window.renderer.info.render.calls);
}
frameCount = 0;
lastTime = now;
}
_rafId = requestAnimationFrame(measure);
}
measure();
});
}
function finish() {
_isRunning = false;
if (_rafId) cancelAnimationFrame(_rafId);
// Calculate statistics
const fps = _results.fps;
const avgFps = fps.reduce((a, b) => a + b, 0) / fps.length;
const minFps = Math.min(...fps);
const maxFps = Math.max(...fps);
_results.summary = {
averageFPS: Math.round(avgFps),
minFPS: minFps,
maxFPS: maxFps,
targetMet: avgFps >= 60,
stability: ((avgFps - minFps) / avgFps * 100).toFixed(1) + '%'
};
console.log('[PerformanceBenchmark] Complete:', _results.summary);
// Hide monitor
if (window.PerformanceMonitor) {
window.PerformanceMonitor.hide();
}
// Show results overlay
showResultsOverlay();
}
function showResultsOverlay() {
const div = document.createElement('div');
div.id = 'perf-benchmark-results';
div.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(10, 15, 26, 0.95);
border: 1px solid #00ffcc;
padding: 2rem;
border-radius: 8px;
color: #e0e0ff;
font-family: 'Orbitron', monospace;
z-index: 10001;
min-width: 300px;
`;
const s = _results.summary;
const targetStatus = s.targetMet
? '<span style="color: #00ffcc">✓ TARGET MET</span>'
: '<span style="color: #ff4444">✗ BELOW TARGET</span>';
div.innerHTML = `
<h3 style="margin: 0 0 1rem 0; color: #00ffcc;">Performance Benchmark</h3>
<div style="margin-bottom: 0.5rem;"><strong>Tier:</strong> ${_results.tier}</div>
<div style="margin-bottom: 0.5rem;"><strong>Agents:</strong> ${_results.agentCount}</div>
<div style="margin-bottom: 0.5rem;"><strong>Average FPS:</strong> ${s.averageFPS}</div>
<div style="margin-bottom: 0.5rem;"><strong>Min/Max FPS:</strong> ${s.minFPS} / ${s.maxFPS}</div>
<div style="margin-bottom: 0.5rem;"><strong>Stability:</strong> ${s.stability} variance</div>
<div style="margin: 1rem 0; padding: 0.5rem; background: rgba(0,0,0,0.3); border-radius: 4px;">
<strong>60 FPS Target:</strong> ${targetStatus}
</div>
<button onclick="this.parentElement.remove()" style="
background: #00ffcc;
color: #0a0f1a;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
font-weight: bold;
">Close</button>
`;
document.body.appendChild(div);
}
function generateReport() {
const r = _results;
const s = r.summary || {};
const report = `
# The Nexus Performance Benchmark Report
**Date:** ${new Date().toISOString()}
**Duration:** ${r.duration}ms
**Performance Tier:** ${r.tier}
**Agent Count:** ${r.agentCount}
## Results
| Metric | Value | Target | Status |
|--------|-------|--------|--------|
| Average FPS | ${s.averageFPS || 'N/A'} | ≥ 60 | ${s.targetMet ? '✓ PASS' : '✗ FAIL'} |
| Minimum FPS | ${s.minFPS || 'N/A'} | ≥ 45 | ${(s.minFPS >= 45) ? '✓ PASS' : '✗ FAIL'} |
| Stability | ${s.stability || 'N/A'} | < 20% | ${parseFloat(s.stability) < 20 ? '✓ PASS' : '✗ FAIL'} |
## Hardware Requirements Met
${getHardwareRequirements(r)}
## Recommendations
${getRecommendations(r)}
`;
return report;
}
function getHardwareRequirements(results) {
const tier = results.tier;
const passed = results.summary?.targetMet;
if (passed) {
return `- Current hardware (${tier} tier) meets minimum sovereign requirements
- Suitable for deployment on similar hardware
- Consider lowering settings for mobile/integrated graphics`;
} else {
return `- Current hardware (${tier} tier) BELOW minimum requirements
- Recommend: M1 Mac or equivalent for 60 FPS
- Consider: Reduce agent count, disable shadows, lower resolution`;
}
}
function getRecommendations(results) {
const recs = [];
if (!results.summary?.targetMet) {
recs.push('- Enable LOD system if not active');
recs.push('- Reduce shadow map resolution or disable shadows');
recs.push('- Lower pixel ratio to 1.0');
recs.push('- Reduce agent draw distance');
}
if (results.drawCalls.some(c => c > 1000)) {
recs.push('- High draw call count detected - consider batching geometry');
}
if (results.agentCount < 5) {
recs.push('- Test with 5+ agents for full validation');
}
return recs.length ? recs.join('\n') : '- Performance optimal - no action required';
}
function downloadReport() {
const report = generateReport();
const blob = new Blob([report], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `nexus-performance-report-${Date.now()}.md`;
a.click();
URL.revokeObjectURL(url);
}
return {
init,
run,
generateReport,
downloadReport,
get results() { return _results; }
};
})();
window.PerformanceBenchmark = PerformanceBenchmark;