- 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
270 lines
7.3 KiB
JavaScript
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;
|