|
| 1 | +/* |
| 2 | +Toolbox Aid |
| 3 | +David Quesenberry |
| 4 | +04/16/2026 |
| 5 | +GameplayMetricsTelemetryScene.js |
| 6 | +*/ |
| 7 | +import { drawPanel } from '/src/engine/debug/index.js'; |
| 8 | +import RealGameplayMiniGameScene from '/samples/phase-17/1708/RealGameplayMiniGameScene.js'; |
| 9 | + |
| 10 | +function pushSample(history, value, limit = 48) { |
| 11 | + history.push(Number.isFinite(value) ? Number(value) : 0); |
| 12 | + if (history.length > limit) { |
| 13 | + history.splice(0, history.length - limit); |
| 14 | + } |
| 15 | +} |
| 16 | + |
| 17 | +function drawTelemetrySparkline(renderer, x, y, width, height, samples, color) { |
| 18 | + renderer.drawRect(x, y, width, height, 'rgba(2, 6, 23, 0.72)'); |
| 19 | + renderer.strokeRect(x, y, width, height, '#334155', 1); |
| 20 | + |
| 21 | + if (!Array.isArray(samples) || samples.length < 2) { |
| 22 | + return; |
| 23 | + } |
| 24 | + |
| 25 | + const maxValue = Math.max(1, ...samples); |
| 26 | + const stepX = (width - 4) / Math.max(1, samples.length - 1); |
| 27 | + for (let i = 1; i < samples.length; i += 1) { |
| 28 | + const prev = samples[i - 1]; |
| 29 | + const curr = samples[i]; |
| 30 | + const x1 = x + 2 + (i - 1) * stepX; |
| 31 | + const x2 = x + 2 + i * stepX; |
| 32 | + const y1 = y + height - 2 - (Math.max(0, prev) / maxValue) * (height - 4); |
| 33 | + const y2 = y + height - 2 - (Math.max(0, curr) / maxValue) * (height - 4); |
| 34 | + renderer.drawLine(x1, y1, x2, y2, color, 1.5); |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +export default class GameplayMetricsTelemetryScene extends RealGameplayMiniGameScene { |
| 39 | + constructor() { |
| 40 | + super(); |
| 41 | + this.telemetry = { |
| 42 | + frames: 0, |
| 43 | + rawFps: 60, |
| 44 | + avgFps: 60, |
| 45 | + playerSpeed: 0, |
| 46 | + maxPlayerSpeed: 0, |
| 47 | + collisionsTotal: 0, |
| 48 | + actionEvents: 0, |
| 49 | + stateTransitions: 0, |
| 50 | + speedHistory: [], |
| 51 | + fpsHistory: [], |
| 52 | + collisionHistory: [], |
| 53 | + }; |
| 54 | + } |
| 55 | + |
| 56 | + step3DPhysics(dtSeconds, engine) { |
| 57 | + const dt = Math.max(0.001, Math.min(0.05, Number(dtSeconds) || 0.016)); |
| 58 | + const beforeScore = this.score; |
| 59 | + const beforeHealth = this.health; |
| 60 | + const beforeState = this.gameState; |
| 61 | + const beforeX = this.player.x; |
| 62 | + const beforeZ = this.player.z; |
| 63 | + |
| 64 | + super.step3DPhysics(dt, engine); |
| 65 | + |
| 66 | + const dx = this.player.x - beforeX; |
| 67 | + const dz = this.player.z - beforeZ; |
| 68 | + const speed = Math.hypot(dx, dz) / dt; |
| 69 | + const rawFps = 1 / dt; |
| 70 | + |
| 71 | + this.telemetry.frames += 1; |
| 72 | + this.telemetry.rawFps = rawFps; |
| 73 | + this.telemetry.avgFps = this.telemetry.avgFps * 0.9 + rawFps * 0.1; |
| 74 | + this.telemetry.playerSpeed = speed; |
| 75 | + this.telemetry.maxPlayerSpeed = Math.max(this.telemetry.maxPlayerSpeed, speed); |
| 76 | + this.telemetry.collisionsTotal += this.lastCollisionCount; |
| 77 | + |
| 78 | + const actionDelta = Math.max(0, this.score - beforeScore) + Math.max(0, beforeHealth - this.health); |
| 79 | + this.telemetry.actionEvents += actionDelta; |
| 80 | + if (beforeState !== this.gameState) { |
| 81 | + this.telemetry.stateTransitions += 1; |
| 82 | + } |
| 83 | + |
| 84 | + pushSample(this.telemetry.speedHistory, speed); |
| 85 | + pushSample(this.telemetry.fpsHistory, this.telemetry.avgFps); |
| 86 | + pushSample(this.telemetry.collisionHistory, this.lastCollisionCount); |
| 87 | + } |
| 88 | + |
| 89 | + render(renderer) { |
| 90 | + super.render(renderer); |
| 91 | + |
| 92 | + const remainingCores = this.cores.filter((core) => core.collected === false).length; |
| 93 | + const objectCount = this.obstacles.length + this.enemies.length + this.cores.length + 1; |
| 94 | + const panelX = 384; |
| 95 | + const panelY = 34; |
| 96 | + const panelW = 228; |
| 97 | + const panelH = 244; |
| 98 | + |
| 99 | + drawPanel(renderer, panelX, panelY, panelW, panelH, 'Telemetry Overlay', [ |
| 100 | + `state=${this.gameState}`, |
| 101 | + `fps=${this.telemetry.avgFps.toFixed(1)} raw=${this.telemetry.rawFps.toFixed(1)}`, |
| 102 | + `playerSpeed=${this.telemetry.playerSpeed.toFixed(2)}`, |
| 103 | + `maxSpeed=${this.telemetry.maxPlayerSpeed.toFixed(2)}`, |
| 104 | + `objects=${objectCount} coresRemaining=${remainingCores}`, |
| 105 | + `collisions=${this.telemetry.collisionsTotal}`, |
| 106 | + `actions=${this.telemetry.actionEvents}`, |
| 107 | + `stateTransitions=${this.telemetry.stateTransitions}`, |
| 108 | + ]); |
| 109 | + |
| 110 | + drawTelemetrySparkline( |
| 111 | + renderer, |
| 112 | + panelX + 12, |
| 113 | + panelY + panelH - 44, |
| 114 | + panelW - 24, |
| 115 | + 30, |
| 116 | + this.telemetry.speedHistory, |
| 117 | + '#38bdf8' |
| 118 | + ); |
| 119 | + } |
| 120 | +} |
0 commit comments