Skip to content

Commit 388c4be

Browse files
author
DavidQ
committed
Add gameplay metrics and telemetry overlay to validate runtime behavior and performance.
PR: BUILD_PR_LEVEL_17_42_SAMPLE_1712_GAMEPLAY_METRICS_AND_TELEMETRY
1 parent abf8737 commit 388c4be

11 files changed

Lines changed: 342 additions & 7 deletions

docs/dev/CODEX_COMMANDS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
MODEL: GPT-5.3-codex
22
REASONING: high
3-
COMMAND: Polish Sample 1708 into a complete mini-game with feedback, UI, and full game loop using existing systems only, then package to <project folder>/tmp/BUILD_PR_LEVEL_17_40_SAMPLE_1710_REAL_GAMEPLAY_POLISH.zip
3+
COMMAND: Add gameplay metrics and telemetry overlay using existing debug systems and package to <project folder>/tmp/BUILD_PR_LEVEL_17_42_SAMPLE_1712_GAMEPLAY_METRICS_AND_TELEMETRY.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
Polish real gameplay sample into a complete mini-game with UI, feedback, and full game loop.
1+
Add gameplay metrics and telemetry overlay to validate runtime behavior and performance.
22

3-
PR: BUILD_PR_LEVEL_17_40_SAMPLE_1710_REAL_GAMEPLAY_POLISH
3+
PR: BUILD_PR_LEVEL_17_42_SAMPLE_1712_GAMEPLAY_METRICS_AND_TELEMETRY
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
- [ ] game loop complete
2-
- [ ] score visible
3-
- [ ] restart works
4-
- [ ] debug panels intact
1+
- [ ] metrics visible
2+
- [ ] values update live
3+
- [ ] no performance issues
54
- [ ] no console errors
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# BUILD_PR_LEVEL_17_42_SAMPLE_1712_GAMEPLAY_METRICS_AND_TELEMETRY
2+
3+
## Implementation
4+
5+
Add metrics overlay:
6+
7+
### Metrics
8+
- player speed
9+
- object count
10+
- FPS (if available)
11+
- event counters (collisions, actions)
12+
13+
### Visualization
14+
- simple overlay or debug panel reuse
15+
16+
### Constraints
17+
- use existing systems
18+
- no new engine features
19+
20+
## Validation
21+
- metrics update live
22+
- no performance issues
23+
- no console errors
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# PLAN_PR_LEVEL_17_42_SAMPLE_1712_GAMEPLAY_METRICS_AND_TELEMETRY
2+
3+
## Purpose
4+
Add gameplay metrics and telemetry visibility to validate behavior and performance.
5+
6+
## Scope
7+
Extend gameplay sample with runtime metrics.
8+
9+
## Focus
10+
- player metrics
11+
- system metrics
12+
- simple telemetry visualization
13+
14+
## Out of Scope
15+
- external telemetry systems

samples/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ <h2>Phase 17 - Rendering Technique Expansion</h2>
510510
<a class="live" href="./phase-17/1707/index.html" title="Minecraft-style voxel chunk-window streaming around a movable camera anchor." data-tags="camera3d, minecraft-style, voxel, chunk-streaming, filled-render, scene, themetokens" data-primary="minecraft-chunk-streaming">Sample 1707 - Minecraft Chunk Streaming</a>
511511
<a class="live" href="./phase-17/1708/index.html" title="Real 3D mini-game loop with movement, hazard interaction, objective scoring, camera follow, and standard 3D debug panels." data-tags="camera3d, gameplay, objective, hazards, debug-panels, scene, themetokens" data-primary="real-gameplay-mini-game">Sample 1708 - Real Gameplay Mini-Game</a>
512512
<a class="live" href="./phase-17/1709/index.html" title="Compares direct axis movement, tank rotation controls, and weighted movement with follow-camera framing." data-tags="camera3d, movement-models, direct-controls, tank-controls, weighted-motion, scene, themetokens" data-primary="movement-models-lab">Sample 1709 - Movement Models Lab</a>
513+
<a class="live" href="./phase-17/1712/index.html" title="Gameplay telemetry overlay for mini-game state, speed, collisions, actions, and live FPS trend." data-tags="camera3d, gameplay, telemetry, metrics, debug-overlay, scene, themetokens" data-primary="gameplay-metrics-telemetry">Sample 1712 - Gameplay Metrics & Telemetry</a>
513514
</div>
514515
</section>
515516
<section>

samples/phase-17/1707-1712.zip

18.1 KB
Binary file not shown.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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+
}

samples/phase-17/1712/index.html

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!--
2+
Toolbox Aid
3+
David Quesenberry
4+
04/16/2026
5+
index.html
6+
-->
7+
<!DOCTYPE html>
8+
<html lang="en">
9+
<head>
10+
<meta charset="UTF-8" />
11+
<title>Sample 1712 - Gameplay Metrics & Telemetry</title>
12+
<link rel="stylesheet" href="../../../src/engine/ui/baseLayout.css" />
13+
</head>
14+
<body>
15+
<main>
16+
<h1>Sample 1712 - Gameplay Metrics & Telemetry</h1>
17+
<p>Adds live gameplay metrics and telemetry overlay to a real mini-game using existing debug systems only.</p>
18+
<canvas id="game" width="960" height="540"></canvas>
19+
20+
<section>
21+
<h3>Engine Classes Used</h3>
22+
<ul>
23+
<li>Engine</li>
24+
<li>Scene</li>
25+
</ul>
26+
</section>
27+
</main>
28+
29+
<script type="module" src="/samples/shared/sampleDetailPageEnhancement.js"></script>
30+
<script type="module" src="./main.js"></script>
31+
</body>
32+
</html>

samples/phase-17/1712/main.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Toolbox Aid
3+
David Quesenberry
4+
04/16/2026
5+
main.js
6+
*/
7+
import Engine from '/src/engine/core/Engine.js';
8+
import { InputService } from '/src/engine/input/index.js';
9+
import { Theme, ThemeTokens } from '/src/engine/theme/index.js';
10+
import GameplayMetricsTelemetryScene from './GameplayMetricsTelemetryScene.js';
11+
12+
const theme = new Theme(ThemeTokens);
13+
theme.applyDocumentTheme();
14+
15+
const canvas = document.getElementById('game');
16+
const input = new InputService();
17+
18+
const engine = new Engine({
19+
canvas,
20+
width: 960,
21+
height: 540,
22+
fixedStepMs: 1000 / 60,
23+
input,
24+
});
25+
26+
engine.setScene(new GameplayMetricsTelemetryScene());
27+
engine.start();

0 commit comments

Comments
 (0)