Skip to content

Commit 458db78

Browse files
author
DavidQ
committed
Implement Sample 1713 as the final reference gameplay sample for the repaired Level 17 sequence, with a complete playable loop, HUD, camera follow, and index integration.
PR: BUILD_PR_LEVEL_17_45_SAMPLE_1713_FINAL_REFERENCE_GAME
1 parent c95814e commit 458db78

10 files changed

Lines changed: 373 additions & 6 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: Use provided 1707-1712.zip as source. Separate and map folders into correct sample structure (1707-1712), fix numbering, update samples/index.html, and package to <project folder>/tmp/BUILD_PR_LEVEL_17_44_SAMPLE_SEQUENCE_REPAIR_1707_1712.zip
3+
COMMAND: Implement Sample 1713 as the final reference game using existing engine/sample systems only. Register it in samples/index.html after the repaired 1707-1712 sequence, preserve existing work, keep logic bounded unless truly shared, and package the repo-structured result to <project folder>/tmp/BUILD_PR_LEVEL_17_45_SAMPLE_1713_FINAL_REFERENCE_GAME.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-
Repair sample sequence 1707-1712 using provided folders as source of truth, enforcing correct structure and index alignment.
1+
Implement Sample 1713 as the final reference gameplay sample for the repaired Level 17 sequence, with a complete playable loop, HUD, camera follow, and index integration.
22

3-
PR: BUILD_PR_LEVEL_17_44_SAMPLE_SEQUENCE_REPAIR_1707_1712
3+
PR: BUILD_PR_LEVEL_17_45_SAMPLE_1713_FINAL_REFERENCE_GAME
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
- [ ] all samples present (1707–1712)
2-
- [ ] index updated
3-
- [ ] samples run independently
1+
- [ ] Sample 1713 exists and launches from samples/index.html
2+
- [ ] player movement works
3+
- [ ] camera follow works
4+
- [ ] hazard/enemy/obstacle interaction works
5+
- [ ] score or objective loop works
6+
- [ ] restart/reset path works
47
- [ ] no console errors
8+
- [ ] samples 1707-1712 remain intact
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# BUILD_PR_LEVEL_17_45_SAMPLE_1713_FINAL_REFERENCE_GAME
2+
3+
## Instructions for Codex
4+
5+
Implement Sample 1713 as the final reference game for the Level 17 gameplay sequence.
6+
7+
### Required Structure
8+
- create or normalize sample folder for 1713
9+
- ensure standalone execution
10+
- register in samples/index.html in correct order
11+
12+
### Required Gameplay
13+
- complete playable loop
14+
- player movement and response
15+
- obstacles, enemies, or hazard interaction
16+
- score and/or objective completion
17+
- restart or reset path
18+
19+
### Required Presentation
20+
- simple HUD / score / state text
21+
- camera follow
22+
- compatibility with existing debug surfaces where already available
23+
24+
### Constraints
25+
- do not add new engine systems
26+
- do not delete user work
27+
- do not break 1707–1712
28+
- keep logic bounded to the sample unless truly shared
29+
30+
### Validation
31+
- Sample 1713 loads from samples/index.html
32+
- gameplay loop is complete
33+
- no console errors
34+
- sequence remains continuous
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# PLAN_PR_LEVEL_17_45_SAMPLE_1713_FINAL_REFERENCE_GAME
2+
3+
## Purpose
4+
Create Sample 1713 as the final reference game for the repaired Level 17 gameplay sample sequence.
5+
6+
## Why This PR Exists
7+
After repairing Samples 1707–1712 into a clean sequence, the next step is a single reference game that proves the intended gameplay stack in one cohesive sample.
8+
9+
## Must Demonstrate
10+
- player movement
11+
- camera follow
12+
- enemy or obstacle interaction
13+
- scoring or objective loop
14+
- UI/HUD presence
15+
- debug compatibility
16+
- standalone launch from samples/index.html
17+
18+
## Constraints
19+
- use existing engine and sample patterns only
20+
- no new engine systems
21+
- no destructive cleanup
22+
- keep the sample educational and representative

samples/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ <h2>Phase 17 - Rendering Technique Expansion</h2>
513513
<a class="live" href="./phase-17/1710/index.html" title="Polished gameplay loop variant with mission-state feedback, objective progression, and standard 3D debug panels." data-tags="camera3d, gameplay, polished-loop, objective, hazards, debug-panels, scene, themetokens" data-primary="real-gameplay-mini-game-polished">Sample 1710 - Real Gameplay Mini-Game</a>
514514
<a class="live" href="./phase-17/1711/index.html" title="Movement-models lab variant for sequence alignment with direct, tank, and weighted controls plus follow camera." data-tags="camera3d, movement-models, direct-controls, tank-controls, weighted-motion, scene, themetokens" data-primary="movement-models-lab-sequence">Sample 1711 - Movement Models Lab</a>
515515
<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>
516+
<a class="live" href="./phase-17/1713/index.html" title="Final reference game sample that combines complete mission loop, camera/collision debug output, and runtime telemetry using existing systems only." data-tags="camera3d, gameplay, reference-game, telemetry, debug-panels, mission-loop, scene, themetokens" data-primary="final-reference-game">Sample 1713 - Final Reference Game</a>
516517
</div>
517518
</section>
518519
<section>
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
Toolbox Aid
3+
David Quesenberry
4+
04/16/2026
5+
FinalReferenceGameScene.js
6+
*/
7+
import { drawPanel } from '/src/engine/debug/index.js';
8+
import GameplayMetricsTelemetryScene from '/samples/phase-17/1712/GameplayMetricsTelemetryScene.js';
9+
10+
function clamp(value, min, max) {
11+
return Math.max(min, Math.min(max, value));
12+
}
13+
14+
function computeReferenceRank(scene) {
15+
if (scene.gameState === 'lost') {
16+
return 'D';
17+
}
18+
if (scene.gameState !== 'won') {
19+
return 'N/A';
20+
}
21+
22+
const objectiveRatio = clamp(scene.score / Math.max(1, scene.targetScore), 0, 1);
23+
const healthRatio = clamp(scene.health / Math.max(1, scene.maxHealth), 0, 1);
24+
const timeRatio = clamp(scene.timeLeft / Math.max(1, scene.roundSeconds), 0, 1);
25+
const collisionPenalty = Math.min(0.35, scene.telemetry.collisionsTotal * 0.01);
26+
const weightedScore = objectiveRatio * 0.6 + healthRatio * 0.25 + timeRatio * 0.15 - collisionPenalty;
27+
28+
if (weightedScore >= 0.9) return 'S';
29+
if (weightedScore >= 0.78) return 'A';
30+
if (weightedScore >= 0.63) return 'B';
31+
return 'C';
32+
}
33+
34+
function isBetterRank(candidate, baseline) {
35+
const rankOrder = ['S', 'A', 'B', 'C', 'D', 'N/A'];
36+
return rankOrder.indexOf(candidate) < rankOrder.indexOf(baseline);
37+
}
38+
39+
export default class FinalReferenceGameScene extends GameplayMetricsTelemetryScene {
40+
constructor() {
41+
super();
42+
43+
this.maxHealth = 4;
44+
this.targetScore = 8;
45+
this.roundSeconds = 70;
46+
this.playerSpeed = 8.8;
47+
this.hitCooldownSeconds = 0.95;
48+
this.resetMatch({ toReady: true });
49+
50+
this.referenceRuntime = {
51+
runCount: 0,
52+
activeSeconds: 0,
53+
missionRank: 'N/A',
54+
bestRank: 'N/A',
55+
bestScore: 0,
56+
bestTimeRemaining: 0,
57+
completionBonus: 0,
58+
phase: 'briefing',
59+
};
60+
}
61+
62+
step3DPhysics(dtSeconds, engine) {
63+
const dt = Math.max(0, Math.min(0.05, Number(dtSeconds) || 0));
64+
const previousState = this.gameState;
65+
const input = engine?.input;
66+
67+
if (previousState === 'ready') {
68+
const wantsMovement =
69+
input?.isDown('KeyW') === true ||
70+
input?.isDown('KeyA') === true ||
71+
input?.isDown('KeyS') === true ||
72+
input?.isDown('KeyD') === true;
73+
if (wantsMovement) {
74+
this.startMatch();
75+
}
76+
}
77+
78+
super.step3DPhysics(dt, engine);
79+
80+
if (this.gameState === 'running') {
81+
if (previousState !== 'running') {
82+
this.referenceRuntime.runCount += 1;
83+
this.referenceRuntime.activeSeconds = 0;
84+
this.referenceRuntime.phase = 'active';
85+
}
86+
this.referenceRuntime.activeSeconds += dt;
87+
this.referenceRuntime.missionRank = 'N/A';
88+
this.referenceRuntime.completionBonus = 0;
89+
return;
90+
}
91+
92+
if (this.gameState === 'ready') {
93+
this.referenceRuntime.phase = 'briefing';
94+
this.referenceRuntime.activeSeconds = 0;
95+
this.referenceRuntime.missionRank = 'N/A';
96+
this.referenceRuntime.completionBonus = 0;
97+
return;
98+
}
99+
100+
if (previousState !== this.gameState) {
101+
this.referenceRuntime.phase = this.gameState === 'won' ? 'complete' : 'failed';
102+
this.referenceRuntime.missionRank = computeReferenceRank(this);
103+
this.referenceRuntime.completionBonus = this.gameState === 'won'
104+
? Math.max(0, Math.floor(this.timeLeft * 4 + this.health * 25))
105+
: 0;
106+
107+
if (this.gameState === 'won') {
108+
if (this.score > this.referenceRuntime.bestScore) {
109+
this.referenceRuntime.bestScore = this.score;
110+
this.referenceRuntime.bestTimeRemaining = this.timeLeft;
111+
} else if (this.score === this.referenceRuntime.bestScore) {
112+
this.referenceRuntime.bestTimeRemaining = Math.max(this.referenceRuntime.bestTimeRemaining, this.timeLeft);
113+
}
114+
115+
if (
116+
this.referenceRuntime.bestRank === 'N/A' ||
117+
isBetterRank(this.referenceRuntime.missionRank, this.referenceRuntime.bestRank)
118+
) {
119+
this.referenceRuntime.bestRank = this.referenceRuntime.missionRank;
120+
}
121+
}
122+
}
123+
}
124+
125+
render(renderer) {
126+
super.render(renderer);
127+
128+
drawPanel(renderer, 384, 286, 228, 248, 'Final Reference Runtime', [
129+
`profile=final-reference`,
130+
`phase=${this.referenceRuntime.phase}`,
131+
`runs=${this.referenceRuntime.runCount}`,
132+
`activeSeconds=${this.referenceRuntime.activeSeconds.toFixed(1)}`,
133+
`missionRank=${this.referenceRuntime.missionRank}`,
134+
`bestRank=${this.referenceRuntime.bestRank}`,
135+
`bestScore=${this.referenceRuntime.bestScore}/${this.targetScore}`,
136+
`bestTimeLeft=${this.referenceRuntime.bestTimeRemaining.toFixed(1)}s`,
137+
`completionBonus=${this.referenceRuntime.completionBonus}`,
138+
'restart=R after mission outcome',
139+
]);
140+
}
141+
}

samples/phase-17/1713/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 1713 - Final Reference Game</title>
12+
<link rel="stylesheet" href="../../../src/engine/ui/baseLayout.css" />
13+
</head>
14+
<body>
15+
<main>
16+
<h1>Sample 1713 - Final Reference Game</h1>
17+
<p>Final reference mini-game that combines gameplay loop, camera debug panels, collision overlays, and telemetry in one bounded sample.</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/1713/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 FinalReferenceGameScene from './FinalReferenceGameScene.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 FinalReferenceGameScene());
27+
engine.start();
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
Toolbox Aid
3+
David Quesenberry
4+
04/16/2026
5+
Phase17Sample1713FinalReferenceGame.test.mjs
6+
*/
7+
import assert from 'node:assert/strict';
8+
import fs from 'node:fs';
9+
import path from 'node:path';
10+
import { fileURLToPath } from 'node:url';
11+
import FinalReferenceGameScene from '../../samples/phase-17/1713/FinalReferenceGameScene.js';
12+
13+
const __filename = fileURLToPath(import.meta.url);
14+
const __dirname = path.dirname(__filename);
15+
const repoRoot = path.resolve(__dirname, '..', '..');
16+
17+
function createCameraStub() {
18+
const state = {
19+
position: { x: 0, y: 0, z: 0 },
20+
rotation: { x: 0, y: 0, z: 0 },
21+
};
22+
return {
23+
setPosition(position) {
24+
state.position = { ...position };
25+
},
26+
setRotation(rotation) {
27+
state.rotation = { ...rotation };
28+
},
29+
getState() {
30+
return {
31+
position: { ...state.position },
32+
rotation: { ...state.rotation },
33+
};
34+
},
35+
};
36+
}
37+
38+
function makeInput(keys = []) {
39+
const down = new Set(keys);
40+
return {
41+
isDown(code) {
42+
return down.has(code);
43+
},
44+
};
45+
}
46+
47+
function createRendererProbe(width = 960, height = 540) {
48+
const texts = [];
49+
return {
50+
texts,
51+
getCanvasSize() {
52+
return { width, height };
53+
},
54+
clear() {},
55+
drawRect() {},
56+
strokeRect() {},
57+
drawText(text) {
58+
texts.push(String(text));
59+
},
60+
drawLine() {},
61+
drawPolygon() {},
62+
drawImageFrame() {},
63+
};
64+
}
65+
66+
function assertIndexLinkPresent() {
67+
const indexPath = path.join(repoRoot, 'samples', 'index.html');
68+
const text = fs.readFileSync(indexPath, 'utf8');
69+
assert.equal(text.includes('./phase-17/1713/index.html'), true, 'Sample 1713 link should exist in samples/index.html.');
70+
}
71+
72+
function assertFinalReferenceRuntimeFlow() {
73+
const scene = new FinalReferenceGameScene();
74+
scene.setCamera3D(createCameraStub());
75+
76+
assert.equal(scene.gameState, 'ready', 'Reference game should start in ready state.');
77+
const beforeX = scene.player.x;
78+
const beforeZ = scene.player.z;
79+
scene.step3DPhysics(0.05, { input: makeInput(['KeyW']) });
80+
assert.equal(scene.gameState, 'running', 'Movement input should start the reference game.');
81+
assert.equal(scene.player.x !== beforeX || scene.player.z !== beforeZ, true, 'Movement input should move the player.');
82+
assert.equal(scene.referenceRuntime.runCount, 1, 'Reference runtime should count mission runs.');
83+
84+
scene.step3DPhysics(0.18, { input: makeInput(['KeyD']) });
85+
assert.equal(scene.telemetry.playerSpeed > 0, true, 'Telemetry should remain active in the reference game.');
86+
87+
scene.score = scene.targetScore;
88+
scene.timeLeft = 16;
89+
scene.health = scene.maxHealth;
90+
scene.step3DPhysics(0.02, { input: makeInput([]) });
91+
92+
assert.equal(scene.gameState, 'won', 'Score completion should transition the reference game to won state.');
93+
assert.equal(scene.referenceRuntime.missionRank !== 'N/A', true, 'Reference runtime should compute mission rank.');
94+
95+
const renderer = createRendererProbe();
96+
scene.render(renderer);
97+
assert.equal(renderer.texts.some((text) => text.includes('Final Reference Runtime')), true, 'Final runtime panel should render.');
98+
assert.equal(renderer.texts.some((text) => text.includes('profile=final-reference')), true, 'Reference runtime metrics should render.');
99+
assert.equal(renderer.texts.some((text) => text.includes('activeCameraId=')), true, 'Camera debug panel should remain visible.');
100+
assert.equal(renderer.texts.some((text) => text.includes('overlayCount=')), true, 'Collision debug panel should remain visible.');
101+
}
102+
103+
export function run() {
104+
assertIndexLinkPresent();
105+
assertFinalReferenceRuntimeFlow();
106+
}

0 commit comments

Comments
 (0)