Skip to content

Commit ea0dc15

Browse files
author
DavidQ
committed
BUILD_PR_DEBUG_COMBO_KEYS_PATCH
- Replaced F-key bindings with browser-safe combo keys - Added Shift+` and Ctrl+Shift+` toggles - Removed conflicting browser shortcuts
1 parent a5138a5 commit ea0dc15

7 files changed

Lines changed: 181 additions & 59 deletions

docs/dev/CODEX_COMMANDS.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ MODEL: GPT-5.4-codex
77
REASONING: high
88

99
COMMAND:
10-
Implement dev console integration into one sample using tools/dev/devConsoleIntegration.js
10+
Update dev console integration to replace F-key bindings with combo-key bindings.
1111

12-
- follow BUILD PR
13-
- create file
14-
- wire sample
15-
- do not modify engine core
12+
- Shift + ` => toggleConsole
13+
- Ctrl + Shift + ` => toggleOverlay
14+
- Ctrl + Shift + R => reload
15+
- Ctrl + Shift + ] => next panel
16+
- Ctrl + Shift + [ => previous panel
17+
18+
Remove all F-key bindings.
19+
20+
Keep changes isolated to devConsoleIntegration.js
21+
Do not modify engine core

docs/dev/COMMIT_COMMENT.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
BUILD_PR_SAMPLE_GAME_DEV_CONSOLE_TOGGLE_INTEGRATION
1+
BUILD_PR_DEBUG_COMBO_KEYS_PATCH
22

3-
- Added dev console integration wrapper
4-
- Wired toggle keys (` and Shift+`)
5-
- Integrated into sample loop
3+
- Replaced F-key bindings with browser-safe combo keys
4+
- Added Shift+` and Ctrl+Shift+` toggles
5+
- Removed conflicting browser shortcuts

docs/dev/NEXT_COMMAND.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
APPLY_PR_SAMPLE_GAME_DEV_CONSOLE_TOGGLE_INTEGRATION
1+
APPLY_PR_DEBUG_COMBO_KEYS_PATCH

docs/dev/change_summary.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Added dev console integration plan and execution instructions
1+
Replaced F-key debug bindings with combo keys for browser compatibility

docs/dev/validation_checklist.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Toggle keys work, overlay renders, sample runs
1+
Combo keys work, no F-keys remain, no browser conflicts
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Toolbox Aid
2+
David Quesenberry
3+
04/05/2026
4+
BUILD_PR_DEBUG_COMBO_KEYS_PATCH.md
5+
6+
# BUILD PR
7+
Replace F-key debug bindings with browser-safe combo keys
8+
9+
## Changes
10+
- Remove all F-key bindings (F3, F6, F7, F8, F9)
11+
- Add combo keys:
12+
Shift + ` => toggle console
13+
Ctrl + Shift + ` => toggle overlay
14+
Ctrl + Shift + R => reload scene
15+
Ctrl + Shift + ] => next panel
16+
Ctrl + Shift + [ => previous panel
17+
18+
## Constraints
19+
- No engine core changes
20+
- Only modify dev console integration file
21+
- Use preventDefault only for handled combos
22+
- Keep behavior isolated
23+
24+
## Validation
25+
- F keys no longer trigger debug
26+
- Combo keys work without browser conflict
27+
- Console and overlay toggle correctly

tools/dev/devConsoleIntegration.js

Lines changed: 136 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ const DEFAULT_WORLD_STAGES = Object.freeze([
1818
"vector-overlay"
1919
]);
2020

21-
const DEFAULT_COMMAND_BINDINGS = Object.freeze([
22-
{ key: "F6", command: "help", label: "help" },
23-
{ key: "F7", command: "status", label: "status" },
24-
{ key: "F8", command: "scene.info", label: "scene.info" },
25-
{ key: "F9", command: "unknown.command", label: "invalid" }
26-
]);
21+
const DEFAULT_COMBO_BINDINGS = Object.freeze({
22+
toggleConsole: Object.freeze({ key: "Backquote", shift: true, ctrl: false, alt: false, meta: false }),
23+
toggleOverlay: Object.freeze({ key: "Backquote", shift: true, ctrl: true, alt: false, meta: false }),
24+
reload: Object.freeze({ key: "KeyR", shift: true, ctrl: true, alt: false, meta: false }),
25+
nextPanel: Object.freeze({ key: "BracketRight", shift: true, ctrl: true, alt: false, meta: false }),
26+
previousPanel: Object.freeze({ key: "BracketLeft", shift: true, ctrl: true, alt: false, meta: false })
27+
});
2728

2829
function sanitizeText(value) {
2930
return typeof value === "string" ? value.trim() : "";
@@ -33,6 +34,13 @@ function isObject(value) {
3334
return value !== null && typeof value === "object" && !Array.isArray(value);
3435
}
3536

37+
function cloneJson(value) {
38+
if (typeof structuredClone === "function") {
39+
return structuredClone(value);
40+
}
41+
return JSON.parse(JSON.stringify(value));
42+
}
43+
3644
function toContextSection(context, field) {
3745
return isObject(context?.[field]) ? context[field] : {};
3846
}
@@ -60,6 +68,51 @@ function getInputEdgePress(input, keyCode, edgeState) {
6068
return isDown && !wasDown;
6169
}
6270

71+
function isModifierDown(input, keyCode) {
72+
if (typeof input?.isDown !== "function") {
73+
return false;
74+
}
75+
return input.isDown(keyCode) === true;
76+
}
77+
78+
function getComboEdgePress(input, combo, edgeState) {
79+
const normalizedCombo = isObject(combo) ? combo : {};
80+
const key = sanitizeText(normalizedCombo.key);
81+
if (!key) {
82+
return false;
83+
}
84+
85+
const keyEdge = getInputEdgePress(input, key, edgeState);
86+
if (!keyEdge) {
87+
return false;
88+
}
89+
90+
const requiresShift = normalizedCombo.shift === true;
91+
const requiresCtrl = normalizedCombo.ctrl === true;
92+
const requiresAlt = normalizedCombo.alt === true;
93+
const requiresMeta = normalizedCombo.meta === true;
94+
95+
const shiftDown = isModifierDown(input, "ShiftLeft") || isModifierDown(input, "ShiftRight");
96+
const ctrlDown = isModifierDown(input, "ControlLeft") || isModifierDown(input, "ControlRight");
97+
const altDown = isModifierDown(input, "AltLeft") || isModifierDown(input, "AltRight");
98+
const metaDown = isModifierDown(input, "MetaLeft") || isModifierDown(input, "MetaRight");
99+
100+
if (requiresShift !== shiftDown) {
101+
return false;
102+
}
103+
if (requiresCtrl !== ctrlDown) {
104+
return false;
105+
}
106+
if (requiresAlt !== altDown) {
107+
return false;
108+
}
109+
if (requiresMeta !== metaDown) {
110+
return false;
111+
}
112+
113+
return true;
114+
}
115+
63116
function buildCommandContext(diagnostics) {
64117
return {
65118
runtime: toContextSection(diagnostics, "runtime"),
@@ -131,17 +184,16 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
131184
const runtime = buildRuntimeFromOptions(options);
132185
const keyEdgeState = new Map();
133186

134-
const toggleConsoleKey = sanitizeText(options?.toggleConsoleKey) || "Backquote";
135-
const toggleOverlayKey = sanitizeText(options?.toggleOverlayKey) || "F3";
136-
const commandBindings = Array.isArray(options?.commandBindings)
137-
? options.commandBindings
138-
: DEFAULT_COMMAND_BINDINGS;
187+
const comboBindings = isObject(options?.comboBindings)
188+
? options.comboBindings
189+
: DEFAULT_COMBO_BINDINGS;
139190

140191
let diagnosticsSnapshot = null;
141192
let diagnosticsReports = [];
142193
let overlayRender = { status: "hidden", sections: [], reports: [] };
143194
let lastCommandExecution = null;
144195
let lastCommandBinding = "";
196+
let panelCursorIndex = -1;
145197

146198
function executeCommand(commandText, context = null) {
147199
const command = sanitizeText(commandText);
@@ -159,47 +211,84 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
159211
return execution;
160212
}
161213

214+
function cyclePanel(direction) {
215+
if (!runtime?.panelRegistry || typeof runtime.panelRegistry.getOrderedPanels !== "function") {
216+
return null;
217+
}
218+
219+
const orderedPanels = runtime.panelRegistry.getOrderedPanels(true);
220+
if (!Array.isArray(orderedPanels) || orderedPanels.length === 0) {
221+
return null;
222+
}
223+
224+
if (panelCursorIndex < 0 || panelCursorIndex >= orderedPanels.length) {
225+
const currentlyEnabledIndex = orderedPanels.findIndex((panel) => panel.enabled === true);
226+
panelCursorIndex = currentlyEnabledIndex >= 0 ? currentlyEnabledIndex : 0;
227+
} else {
228+
panelCursorIndex = (panelCursorIndex + direction + orderedPanels.length) % orderedPanels.length;
229+
}
230+
231+
const targetPanel = orderedPanels[panelCursorIndex];
232+
orderedPanels.forEach((panel) => {
233+
runtime.panelRegistry.setPanelEnabled(panel.id, panel.id === targetPanel.id);
234+
});
235+
runtime.showOverlay();
236+
237+
const label = direction > 0 ? "panel.next" : "panel.previous";
238+
lastCommandBinding = `${label} -> ${targetPanel.id}`;
239+
lastCommandExecution = {
240+
status: "ready",
241+
output: {
242+
lines: [`panel=${targetPanel.id}`, `title=${targetPanel.title}`]
243+
},
244+
reports: []
245+
};
246+
return targetPanel;
247+
}
248+
162249
function update(frame = {}) {
163250
const engine = frame.engine || {};
164251
const input = engine.input || null;
165252

166-
if (getInputEdgePress(input, toggleConsoleKey, keyEdgeState)) {
253+
const diagnosticsContext = isObject(frame.diagnosticsContext) ? frame.diagnosticsContext : {};
254+
const diagnosticsResult = runtime.collectDiagnostics(diagnosticsContext);
255+
diagnosticsSnapshot = diagnosticsResult?.diagnostics || null;
256+
diagnosticsReports = Array.isArray(diagnosticsResult?.reports) ? diagnosticsResult.reports.slice() : [];
257+
258+
if (getComboEdgePress(input, comboBindings.toggleOverlay, keyEdgeState)) {
167259
const state = runtime.getState();
168-
if (state.consoleVisible) {
169-
runtime.hideConsole();
260+
if (state.overlayVisible) {
261+
runtime.hideOverlay();
170262
} else {
171-
runtime.showConsole();
263+
runtime.showOverlay();
172264
}
173265
}
174266

175-
if (getInputEdgePress(input, toggleOverlayKey, keyEdgeState)) {
267+
if (getComboEdgePress(input, comboBindings.toggleConsole, keyEdgeState)) {
176268
const state = runtime.getState();
177-
if (state.overlayVisible) {
178-
runtime.hideOverlay();
269+
if (state.consoleVisible) {
270+
runtime.hideConsole();
179271
} else {
180-
runtime.showOverlay();
272+
runtime.showConsole();
181273
}
182274
}
183275

184-
const diagnosticsContext = isObject(frame.diagnosticsContext) ? frame.diagnosticsContext : {};
185-
const diagnosticsResult = runtime.collectDiagnostics(diagnosticsContext);
186-
diagnosticsSnapshot = diagnosticsResult?.diagnostics || null;
187-
diagnosticsReports = Array.isArray(diagnosticsResult?.reports) ? diagnosticsResult.reports.slice() : [];
276+
if (getComboEdgePress(input, comboBindings.reload, keyEdgeState)) {
277+
const runtimeContext = buildCommandContext(diagnosticsSnapshot || {});
278+
executeCommand("scene.reload", runtimeContext);
279+
runtime.applyHotReload({
280+
status: "ready",
281+
mode: "targeted",
282+
runtimeState: toContextSection(diagnosticsSnapshot, "runtime")
283+
});
284+
}
188285

189-
const runtimeState = runtime.getState();
190-
if (runtimeState.consoleVisible) {
191-
for (let index = 0; index < commandBindings.length; index += 1) {
192-
const binding = commandBindings[index];
193-
const key = sanitizeText(binding?.key);
194-
const command = sanitizeText(binding?.command);
195-
if (!key || !command) {
196-
continue;
197-
}
198-
if (!getInputEdgePress(input, key, keyEdgeState)) {
199-
continue;
200-
}
201-
executeCommand(command);
202-
}
286+
if (getComboEdgePress(input, comboBindings.nextPanel, keyEdgeState)) {
287+
cyclePanel(1);
288+
}
289+
290+
if (getComboEdgePress(input, comboBindings.previousPanel, keyEdgeState)) {
291+
cyclePanel(-1);
203292
}
204293

205294
return {
@@ -232,7 +321,7 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
232321
if (overlayRender.status === "ready") {
233322
const overlayLines = flattenOverlaySections(overlayRender.sections, 9);
234323
overlayLines.push(`order: ${renderOrder.slice(-2).join(" -> ")}`);
235-
drawPanel(renderer, overlayX, overlayY, overlayWidth, overlayHeight, "Debug Overlay (F3)", overlayLines.slice(0, 10));
324+
drawPanel(renderer, overlayX, overlayY, overlayWidth, overlayHeight, "Debug Overlay (Ctrl+Shift+`)", overlayLines.slice(0, 10));
236325
}
237326

238327
const state = runtime.getState();
@@ -246,10 +335,13 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
246335
? lastCommandExecution.output.lines.map((line) => sanitizeText(line)).filter(Boolean)
247336
: ["No command output yet."];
248337

249-
const commandHint = commandBindings
250-
.map((binding) => `${sanitizeText(binding?.key)}=${sanitizeText(binding?.label) || sanitizeText(binding?.command)}`)
251-
.filter(Boolean)
252-
.join(" | ");
338+
const commandHint = [
339+
"Shift+`=console",
340+
"Ctrl+Shift+`=overlay",
341+
"Ctrl+Shift+R=reload",
342+
"Ctrl+Shift+]=next panel",
343+
"Ctrl+Shift+[=prev panel"
344+
].join(" | ");
253345

254346
const lines = [
255347
`last: ${lastCommandBinding || "none"}`,
@@ -259,7 +351,7 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
259351
...outputLines.slice(0, 5)
260352
];
261353

262-
drawPanel(renderer, consoleX, consoleY, consoleWidth, consoleHeight, "Dev Console (`)", lines.slice(0, 9));
354+
drawPanel(renderer, consoleX, consoleY, consoleWidth, consoleHeight, "Dev Console (Shift+`)", lines.slice(0, 9));
263355
}
264356

265357
return {
@@ -287,10 +379,7 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
287379
overlayRender,
288380
lastCommandExecution,
289381
lastCommandBinding,
290-
toggleKeys: {
291-
console: toggleConsoleKey,
292-
overlay: toggleOverlayKey
293-
}
382+
toggleCombos: cloneJson(comboBindings)
294383
};
295384
}
296385
};

0 commit comments

Comments
 (0)