Skip to content

Commit f4c3dfb

Browse files
author
DavidQ
committed
Level 18.4 overlay performance optimization.
Improve rendering efficiency and cycle responsiveness.
1 parent 54e3bc1 commit f4c3dfb

9 files changed

Lines changed: 84 additions & 72 deletions

File tree

docs/dev/CODEX_COMMANDS.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ MODEL: GPT-5.4-codex
22
REASONING: medium
33

44
COMMAND:
5-
Implement overlay state persistence:
6-
- Store current overlay index
7-
- Restore on sample load
8-
- Keep implementation lightweight
9-
- Do not change cycle behavior
5+
Optimize overlay performance:
6+
- Minimize redundant render calls
7+
- Ensure efficient cycling transitions
8+
- Maintain current behavior
109

1110
Package ZIP to <project folder>/tmp/

docs/dev/COMMIT_COMMENT.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Level 18.3 overlay state persistence.
2-
Preserve selected overlay across reloads and navigation.
1+
Level 18.4 overlay performance optimization.
2+
Improve rendering efficiency and cycle responsiveness.
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[ ] Overlay index persists
2-
[ ] Restored on reload
3-
[ ] Works across samples
4-
[ ] No cycle regression
1+
[ ] No lag during rapid cycling
2+
[ ] Stable FPS
3+
[ ] No redundant renders observed
4+
[ ] Behavior unchanged

docs/pr/BUILD_PR.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
1-
# BUILD_PR_LEVEL_18_3_OVERLAY_STATE_PERSISTENCE
1+
# BUILD_PR_LEVEL_18_4_OVERLAY_PERFORMANCE_OPTIMIZATION
22

33
## PLAN
44

55
### Purpose
6-
Introduce overlay state persistence so selected overlay and cycle position are maintained across sample reloads and navigation.
6+
Optimize overlay rendering and cycling performance under frequent updates and rapid input.
77

88
### Goals
9-
- Persist current overlay index
10-
- Restore state on reload
11-
- Maintain consistency across samples
9+
- Reduce unnecessary re-renders
10+
- Ensure smooth cycling under load
11+
- Maintain stable FPS
1212

1313
---
1414

1515
## BUILD
1616

1717
### Scope
18-
- Store overlay state (in-memory or lightweight persistence)
19-
- Restore on sample load
20-
- No UI changes
21-
- No behavior change to cycle logic
18+
- Optimize overlay render paths
19+
- Avoid redundant DOM/canvas updates
20+
- Ensure efficient cycle transitions
21+
- No behavior changes
2222

2323
### Test Steps
24-
1. Select overlay
25-
2. Reload sample
26-
3. Confirm overlay restored
27-
4. Switch samples and return
24+
1. Rapidly cycle overlays
25+
2. Monitor frame stability
26+
3. Switch samples quickly
27+
4. Confirm no lag or stutter
2828

2929
### Expected
30-
- Overlay state preserved
31-
- No regression in cycling
30+
- Smooth overlay transitions
31+
- No visible performance degradation

samples/phase-17/1708/RealGameplayMiniGameScene.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from '/samples/phase-16/shared/threeDWireframe.js';
2323
import {
2424
createTabDebugOverlayController,
25+
getTabDebugOverlayActiveId,
2526
getTabDebugOverlayStatusLabel,
2627
isTabDebugOverlayActive,
2728
setTabDebugOverlayCycleKey,
@@ -198,6 +199,10 @@ export default class RealGameplayMiniGameScene extends Scene {
198199
return getTabDebugOverlayStatusLabel(this.tabDebugOverlays);
199200
}
200201

202+
getActiveDebugOverlayId() {
203+
return getTabDebugOverlayActiveId(this.tabDebugOverlays);
204+
}
205+
201206
pushCollisionRow(overlayId, kind, state, enabled = true) {
202207
this.debugCollisionRows.push({
203208
overlayId,
@@ -545,8 +550,8 @@ export default class RealGameplayMiniGameScene extends Scene {
545550

546551
const debugStack = createBottomRightDebugPanelStack(renderer);
547552
this.debugOverlayStack = debugStack;
548-
549-
if (this.isDebugOverlayActive(OVERLAY_UI_LAYER)) {
553+
const activeOverlayId = this.getActiveDebugOverlayId();
554+
if (activeOverlayId === OVERLAY_UI_LAYER) {
550555
drawStackedDebugPanel(renderer, debugStack, 326, 174, 'UI Layer', [
551556
`state=${this.gameState.toUpperCase()}`,
552557
`objective=${this.score}/${this.targetScore} cores`,
@@ -556,16 +561,12 @@ export default class RealGameplayMiniGameScene extends Scene {
556561
`overlayCycle=G/Shift+G`,
557562
'controls=W/A/S/D move | Q/E yaw',
558563
]);
559-
}
560-
561-
if (this.isDebugOverlayActive(OVERLAY_MISSION_FEED)) {
564+
} else if (activeOverlayId === OVERLAY_MISSION_FEED) {
562565
drawStackedDebugPanel(renderer, debugStack, 326, 160, 'Mission Feed', [
563566
...this.eventFeed,
564567
missionStatus,
565568
]);
566-
}
567-
568-
if (this.isDebugOverlayActive(OVERLAY_MISSION_READY)) {
569+
} else if (activeOverlayId === OVERLAY_MISSION_READY) {
569570
const isWin = this.gameState === WON_STATE;
570571
const outcomeLabel = this.gameState === READY_STATE
571572
? 'Press Space or Enter to deploy.'
@@ -578,9 +579,7 @@ export default class RealGameplayMiniGameScene extends Scene {
578579
isWin ? 'result=mission complete' : this.gameState === LOST_STATE ? 'result=mission failed' : 'result=pending',
579580
'restart=R after mission outcome',
580581
]);
581-
}
582-
583-
if (this.isDebugOverlayActive(OVERLAY_MINI_GAME_RUNTIME)) {
582+
} else if (activeOverlayId === OVERLAY_MINI_GAME_RUNTIME) {
584583
drawStackedDebugPanel(renderer, debugStack, 300, 120, 'Mini-Game Runtime', [
585584
`Entities: obstacles=${this.obstacles.length} sentries=${this.enemies.length}`,
586585
`Remaining cores: ${this.cores.filter((core) => !core.collected).length}`,

samples/phase-17/1709/MovementModelsLabScene.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { Theme, ThemeTokens } from '/src/engine/theme/index.js';
99
import { createBottomRightDebugPanelStack, drawFrame, drawStackedDebugPanel } from '/src/engine/debug/index.js';
1010
import {
1111
createTabDebugOverlayController,
12+
getTabDebugOverlayActiveId,
1213
getTabDebugOverlayStatusLabel,
13-
isTabDebugOverlayActive,
1414
setTabDebugOverlayCycleKey,
1515
setTabDebugOverlayPersistenceKey,
1616
stepTabDebugOverlayController,
@@ -331,7 +331,8 @@ export default class MovementModelsLabScene extends Scene {
331331
const runtimeWidth = hudWidth;
332332
const runtimeHeight = 212;
333333
const debugStack = createBottomRightDebugPanelStack(renderer);
334-
if (isTabDebugOverlayActive(this.tabDebugOverlays, OVERLAY_MOVEMENT_RUNTIME)) {
334+
const activeOverlayId = getTabDebugOverlayActiveId(this.tabDebugOverlays);
335+
if (activeOverlayId === OVERLAY_MOVEMENT_RUNTIME) {
335336
drawStackedDebugPanel(renderer, debugStack, runtimeWidth, runtimeHeight, 'Movement Runtime', [
336337
`Mode: ${formatMode(this.movementMode)}`,
337338
`Actor: x=${this.actor.x.toFixed(2)} z=${this.actor.z.toFixed(2)}`,
@@ -342,9 +343,7 @@ export default class MovementModelsLabScene extends Scene {
342343
'Tank: rotate + throttle',
343344
'Weighted: acceleration + drag',
344345
]);
345-
}
346-
347-
if (isTabDebugOverlayActive(this.tabDebugOverlays, OVERLAY_MOVEMENT_HUD)) {
346+
} else if (activeOverlayId === OVERLAY_MOVEMENT_HUD) {
348347
drawStackedDebugPanel(renderer, debugStack, hudWidth, hudHeight, 'Movement Lab HUD', [
349348
`Movement Mode: ${formatMode(this.movementMode)}`,
350349
`Input: ${this.lastInputSummary}`,

samples/phase-17/1710/RealGameplayMiniGameScene.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from '/samples/phase-16/shared/threeDWireframe.js';
2323
import {
2424
createTabDebugOverlayController,
25+
getTabDebugOverlayActiveId,
2526
getTabDebugOverlayStatusLabel,
2627
isTabDebugOverlayActive,
2728
setTabDebugOverlayCycleKey,
@@ -198,6 +199,10 @@ export default class RealGameplayMiniGameScene extends Scene {
198199
return getTabDebugOverlayStatusLabel(this.tabDebugOverlays);
199200
}
200201

202+
getActiveDebugOverlayId() {
203+
return getTabDebugOverlayActiveId(this.tabDebugOverlays);
204+
}
205+
201206
pushCollisionRow(overlayId, kind, state, enabled = true) {
202207
this.debugCollisionRows.push({
203208
overlayId,
@@ -545,8 +550,8 @@ export default class RealGameplayMiniGameScene extends Scene {
545550

546551
const debugStack = createBottomRightDebugPanelStack(renderer);
547552
this.debugOverlayStack = debugStack;
548-
549-
if (this.isDebugOverlayActive(OVERLAY_UI_LAYER)) {
553+
const activeOverlayId = this.getActiveDebugOverlayId();
554+
if (activeOverlayId === OVERLAY_UI_LAYER) {
550555
drawStackedDebugPanel(renderer, debugStack, 326, 174, 'UI Layer', [
551556
`state=${this.gameState.toUpperCase()}`,
552557
`objective=${this.score}/${this.targetScore} cores`,
@@ -556,16 +561,12 @@ export default class RealGameplayMiniGameScene extends Scene {
556561
`overlayCycle=G/Shift+G`,
557562
'controls=W/A/S/D move | Q/E yaw',
558563
]);
559-
}
560-
561-
if (this.isDebugOverlayActive(OVERLAY_MISSION_FEED)) {
564+
} else if (activeOverlayId === OVERLAY_MISSION_FEED) {
562565
drawStackedDebugPanel(renderer, debugStack, 326, 160, 'Mission Feed', [
563566
...this.eventFeed,
564567
missionStatus,
565568
]);
566-
}
567-
568-
if (this.isDebugOverlayActive(OVERLAY_MISSION_READY)) {
569+
} else if (activeOverlayId === OVERLAY_MISSION_READY) {
569570
const isWin = this.gameState === WON_STATE;
570571
const outcomeLabel = this.gameState === READY_STATE
571572
? 'Press Space or Enter to deploy.'
@@ -578,9 +579,7 @@ export default class RealGameplayMiniGameScene extends Scene {
578579
isWin ? 'result=mission complete' : this.gameState === LOST_STATE ? 'result=mission failed' : 'result=pending',
579580
'restart=R after mission outcome',
580581
]);
581-
}
582-
583-
if (this.isDebugOverlayActive(OVERLAY_MINI_GAME_RUNTIME)) {
582+
} else if (activeOverlayId === OVERLAY_MINI_GAME_RUNTIME) {
584583
drawStackedDebugPanel(renderer, debugStack, 300, 120, 'Mini-Game Runtime', [
585584
`Entities: obstacles=${this.obstacles.length} sentries=${this.enemies.length}`,
586585
`Remaining cores: ${this.cores.filter((core) => !core.collected).length}`,

samples/phase-17/1711/MovementModelsLabScene.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { Theme, ThemeTokens } from '/src/engine/theme/index.js';
99
import { createBottomRightDebugPanelStack, drawFrame, drawStackedDebugPanel } from '/src/engine/debug/index.js';
1010
import {
1111
createTabDebugOverlayController,
12+
getTabDebugOverlayActiveId,
1213
getTabDebugOverlayStatusLabel,
13-
isTabDebugOverlayActive,
1414
setTabDebugOverlayCycleKey,
1515
setTabDebugOverlayPersistenceKey,
1616
stepTabDebugOverlayController,
@@ -331,7 +331,8 @@ export default class MovementModelsLabScene extends Scene {
331331
const runtimeWidth = hudWidth;
332332
const runtimeHeight = 212;
333333
const debugStack = createBottomRightDebugPanelStack(renderer);
334-
if (isTabDebugOverlayActive(this.tabDebugOverlays, OVERLAY_MOVEMENT_RUNTIME)) {
334+
const activeOverlayId = getTabDebugOverlayActiveId(this.tabDebugOverlays);
335+
if (activeOverlayId === OVERLAY_MOVEMENT_RUNTIME) {
335336
drawStackedDebugPanel(renderer, debugStack, runtimeWidth, runtimeHeight, 'Movement Runtime', [
336337
`Mode: ${formatMode(this.movementMode)}`,
337338
`Actor: x=${this.actor.x.toFixed(2)} z=${this.actor.z.toFixed(2)}`,
@@ -342,9 +343,7 @@ export default class MovementModelsLabScene extends Scene {
342343
'Tank: rotate + throttle',
343344
'Weighted: acceleration + drag',
344345
]);
345-
}
346-
347-
if (isTabDebugOverlayActive(this.tabDebugOverlays, OVERLAY_MOVEMENT_HUD)) {
346+
} else if (activeOverlayId === OVERLAY_MOVEMENT_HUD) {
348347
drawStackedDebugPanel(renderer, debugStack, hudWidth, hudHeight, 'Movement Lab HUD', [
349348
`Movement Mode: ${formatMode(this.movementMode)}`,
350349
`Input: ${this.lastInputSummary}`,

samples/phase-17/shared/tabDebugOverlayCycle.js

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,18 @@ function persistActiveIndex(controller) {
110110
return writePersistedOverlayIndex(key, index);
111111
}
112112

113+
export function getTabDebugOverlayActiveEntry(controller) {
114+
if (!controller || !Array.isArray(controller.overlays) || controller.overlays.length === 0) {
115+
return null;
116+
}
117+
const index = normalizeActiveIndex(controller);
118+
return controller.overlays[index] || null;
119+
}
120+
121+
export function getTabDebugOverlayActiveId(controller) {
122+
return getTabDebugOverlayActiveEntry(controller)?.id || '';
123+
}
124+
113125
export function createTabDebugOverlayController({ overlays = [], initialOverlayId = '' } = {}) {
114126
const normalized = [];
115127
for (let i = 0; i < overlays.length; i += 1) {
@@ -239,31 +251,36 @@ export function stepTabDebugOverlayController(controller, input) {
239251

240252
const cycleKey = String(controller.cycleKey || 'Tab');
241253
const cyclePressed = input?.isDown(cycleKey) === true;
254+
if (!cyclePressed) {
255+
controller.cycleLatch = false;
256+
return;
257+
}
258+
if (controller.cycleLatch === true) {
259+
return;
260+
}
261+
controller.cycleLatch = true;
262+
242263
normalizeActiveIndex(controller);
243-
if (cyclePressed && controller.cycleLatch === false && controller.overlays.length > 1) {
244-
const delta = isOverlayCycleReverseModifierActive(input) ? -1 : 1;
245-
const count = controller.overlays.length;
246-
controller.activeIndex = (controller.activeIndex + delta + count) % count;
247-
persistActiveIndex(controller);
264+
if (controller.overlays.length <= 1) {
265+
return;
248266
}
249-
controller.cycleLatch = cyclePressed;
267+
268+
const delta = isOverlayCycleReverseModifierActive(input) ? -1 : 1;
269+
const count = controller.overlays.length;
270+
controller.activeIndex = (controller.activeIndex + delta + count) % count;
271+
persistActiveIndex(controller);
250272
}
251273

252274
export function isTabDebugOverlayActive(controller, overlayId) {
253-
if (!controller || !Array.isArray(controller.overlays) || controller.overlays.length === 0) {
254-
return false;
255-
}
256-
normalizeActiveIndex(controller);
257-
const active = controller.overlays[controller.activeIndex];
258-
return active?.id === overlayId;
275+
return getTabDebugOverlayActiveId(controller) === overlayId;
259276
}
260277

261278
export function getTabDebugOverlayStatusLabel(controller) {
262-
if (!controller || !Array.isArray(controller.overlays) || controller.overlays.length === 0) {
279+
const active = getTabDebugOverlayActiveEntry(controller);
280+
if (!active || !Array.isArray(controller?.overlays) || controller.overlays.length === 0) {
263281
return 'none';
264282
}
265283
const index = normalizeActiveIndex(controller);
266-
const active = controller.overlays[index];
267284
return `${active.label} (${index + 1}/${controller.overlays.length})`;
268285
}
269286

0 commit comments

Comments
 (0)