Skip to content

Commit d47b80d

Browse files
committed
Scope Controls mappings by profile and use shared engine input services - PR_26161_031-controls-engine-input-and-profile-scope
1 parent 50028e2 commit d47b80d

12 files changed

Lines changed: 1259 additions & 329 deletions

assets/theme-v2/css/typography.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ dd {
131131
padding-block: var(--space-6)
132132
}
133133

134-
.text-gold {
134+
.text-gold,
135+
.btn.text-gold {
135136
color: var(--gold)
136137
}
137138

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
assets/theme-v2/css/typography.css
22
docs_build/dev/reports/codex_changed_files.txt
33
docs_build/dev/reports/codex_review.diff
4-
docs_build/dev/reports/controls-device-refresh-profile-polish-report.md
4+
docs_build/dev/reports/controls-engine-input-and-profile-scope-report.md
55
docs_build/dev/reports/coverage_changed_js_guardrail.txt
66
docs_build/dev/reports/playwright_v8_coverage_report.txt
7+
src/engine/input/GamepadInputClassifier.js
8+
src/engine/input/GamepadState.js
9+
src/engine/input/InputService.js
710
tests/playwright/tools/InputMappingV2Tool.spec.mjs
811
toolbox/controls/controls.js
912
toolbox/controls/index.html

docs_build/dev/reports/codex_review.diff

Lines changed: 922 additions & 154 deletions
Large diffs are not rendered by default.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# PR_26161_031 Controls Engine Input And Profile Scope Report
2+
3+
## Scope
4+
- Routed Controls keyboard, mouse, wheel, and gamepad detection through shared `src/engine/input` services.
5+
- Removed local gamepad polling and local gamepad input interpretation from Controls.
6+
- Removed Mapping Profile from Input Mapping rows.
7+
- Scoped mappings by `controllerProfileId`: Keyboard/Mouse mappings use the Keyboard/Mouse profile scope, and Gamepad mappings use the selected saved Controller Profile.
8+
- Relaxed Controller Profile saving so generated inputs may remain unassigned.
9+
10+
## Branch Validation
11+
- PASS: current branch verified as `main` before edits.
12+
13+
## Impacted Lane
14+
- Controls / Input Mapping.
15+
- Shared input helper lane for `src/engine/input`.
16+
17+
## Playwright Impacted
18+
- Yes.
19+
20+
## Validation Performed
21+
- PASS: `node --check src/engine/input/GamepadInputClassifier.js`
22+
- PASS: `node --check src/engine/input/InputService.js`
23+
- PASS: `node --check src/engine/input/GamepadState.js`
24+
- PASS: `node --check toolbox/controls/controls.js`
25+
- PASS: `node --check tests/playwright/tools/InputMappingV2Tool.spec.mjs`
26+
- PASS: `node tests/input/GamepadState.test.mjs`
27+
- PASS: `node tests/input/InputService.test.mjs`
28+
- PASS: `node tests/input/GamepadInputAdapter.test.mjs`
29+
- PASS: `Select-String -Path toolbox/controls/index.html -Pattern '<style',' onclick=',' onchange=',' oninput=',' onsubmit='`
30+
- PASS: `git diff --check`
31+
- PASS: `rg -n "navigator\\.getGamepads|Array\\.from\\(navigator" toolbox/controls/controls.js` returned no matches.
32+
- PASS: `npx playwright test tests/playwright/tools/InputMappingV2Tool.spec.mjs`
33+
34+
## Manual Validation Steps
35+
- Open Controls.
36+
- Confirm Input Mapping table columns are Object, Action, Input Device, Input, State, Actions.
37+
- Add a Keyboard mapping, capture a key, and confirm the selected input text turns Theme V2 gold while editing.
38+
- Add a Mouse mapping, capture a mouse button or wheel input, and confirm the selected input text turns Theme V2 gold while editing.
39+
- Refresh devices with a gamepad available and confirm diagnostics report multiple active controller inputs when multiple inputs are pressed.
40+
- Add a Controller Profile, edit it, leave generated inputs unassigned, save, and confirm the profile saves without invalid or blocking status.
41+
- Reopen the profile, assign Actions, press controller inputs, and confirm only selected Action text turns Theme V2 gold.
42+
- Add a Gamepad mapping after selecting a saved Controller Profile and confirm the saved mapping stores the selected `controllerProfileId` without displaying Mapping Profile in the row.
43+
- Reload and confirm mappings and profiles persist.
44+
45+
## Shared Input Gap / Extension
46+
- PASS: Controls needed shared generated controller profile labels and active profile input names. Added the smallest shared extension to `src/engine/input/GamepadInputClassifier.js`.
47+
- PASS: Controls needed browser gamepad button `value` support through the shared input path. Updated `InputService` and `GamepadState` to treat button value above threshold as active.
48+
- PASS: No local Controls workaround or duplicate controller polling remains.
49+
50+
## Skipped Lanes
51+
- Full samples validation skipped by request.
52+
- Auth, production DB, production game runtime, and sample JSON lanes skipped because this PR only changes Controls UI/runtime behavior plus targeted shared input helpers used by Controls.
53+
- Broad engine/game validation skipped because the shared input changes were narrow and covered by targeted input unit tests plus Controls Playwright.
54+
55+
## Runtime Engine Behavior
56+
- PASS: no production game runtime wiring or sample behavior changed.
57+
- PASS: shared input helpers were extended only to expose existing browser input state to Controls.
58+
59+
## Coverage Notes
60+
- PASS: Playwright V8 coverage report produced.
61+
- WARN: `src/engine/input/InputService.js` reports advisory low function coverage in V8 coverage, but changed lines were executed and targeted input unit tests passed.
62+
63+
## Requirement Checklist
64+
- PASS: Read `docs_build/dev/PROJECT_INSTRUCTIONS.md` before edits.
65+
- PASS: Verified branch `main` before edits.
66+
- PASS: Continued from PR_26161_030.
67+
- PASS: Controls status remains Wireframe.
68+
- PASS: Controls data remains DB-backed through the shared DB/mock adapter only.
69+
- PASS: Controller Profile can save with no Action assignments.
70+
- PASS: Generated controller inputs may remain unassigned.
71+
- PASS: Unassigned generated inputs display as unassigned, not invalid.
72+
- PASS: Controls uses `src/engine/input` classes/services for keyboard, mouse, and game controller input detection.
73+
- PASS: Controls no longer performs duplicate local controller polling or input interpretation.
74+
- PASS: Missing shared input capability was addressed with the smallest shared input extension.
75+
- PASS: Multiple simultaneous game controller input states are reported when exposed by `src/engine/input`.
76+
- PASS: Keyboard selected input text changes to Theme V2 gold during capture/edit.
77+
- PASS: Mouse button and wheel selected input text changes to Theme V2 gold during capture/edit.
78+
- PASS: Controller selected Action text remains Theme V2 gold only.
79+
- PASS: Mapping Profile no longer appears in Input Mapping rows.
80+
- PASS: Mappings are scoped by selected Controller Profile through `controllerProfileId`.
81+
- PASS: Keyboard and Mouse mappings use the Keyboard/Mouse profile scope.
82+
- PASS: Gamepad mappings use the selected saved controller profile scope.
83+
- PASS: Controller profile generation, device refresh guidance, Create Profile From Default edit mode, custom Actions, accordions, reset confirmation, DB persistence, and object-aware action filtering are preserved.
84+
- PASS: No sample JSON alignment, auth behavior, production game runtime behavior, or unrelated rewrites were added.
85+
- PASS: Theme V2 only; no inline CSS, inline JS, script/style blocks, or inline event handlers were added.

docs_build/dev/reports/coverage_changed_js_guardrail.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ Missing changed runtime JS files are WARN, not FAIL.
66
Source: Playwright/Chromium built-in V8 coverage from the active Playwright run.
77

88
Changed runtime JS files considered:
9-
(94%) toolbox/controls/controls.js - executed lines 1698/1698; executed functions 187/198
9+
(37%) src/engine/input/InputService.js - executed lines 425/425; executed functions 22/60
10+
(74%) src/engine/input/GamepadInputClassifier.js - executed lines 412/412; executed functions 31/42
11+
(80%) src/engine/input/GamepadState.js - executed lines 100/100; executed functions 16/20
12+
(95%) toolbox/controls/controls.js - executed lines 1684/1684; executed functions 181/190
1013

1114
Guardrail warnings:
12-
(100%) none - no changed runtime JS coverage warnings
15+
(37%) src/engine/input/InputService.js - WARNING: advisory low coverage below 50%; executed lines 425/425; executed functions 22/60

docs_build/dev/reports/playwright_v8_coverage_report.txt

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,42 @@ Note: entry percentages use function coverage when available, otherwise line cov
1212
Note: coverage entries are aggregated across every page/tool where coverageReporter.start(page) and coverageReporter.stop(page) ran.
1313

1414
Exercised tool entry points detected:
15-
(92%) Toolbox Index - exercised 4 runtime JS files
15+
(93%) Toolbox Index - exercised 4 runtime JS files
1616
(0%) Tool Template V2 - not exercised by this Playwright run
1717
(59%) Theme V2 Shared JS - exercised 2 runtime JS files
1818

1919
Changed runtime JS files covered:
20-
(94%) toolbox/controls/controls.js - executed lines 1698/1698; executed functions 187/198
20+
(37%) src/engine/input/InputService.js - executed lines 425/425; executed functions 22/60
21+
(74%) src/engine/input/GamepadInputClassifier.js - executed lines 412/412; executed functions 31/42
22+
(80%) src/engine/input/GamepadState.js - executed lines 100/100; executed functions 16/20
23+
(95%) toolbox/controls/controls.js - executed lines 1684/1684; executed functions 181/190
2124

2225
Files with executed line/function counts where available:
26+
(11%) src/engine/input/InputMappingManifest.js - executed lines 78/78; executed functions 1/9
27+
(23%) src/engine/input/InputComboState.js - executed lines 173/173; executed functions 3/13
28+
(33%) src/engine/input/InputMap.js - executed lines 62/62; executed functions 3/9
29+
(37%) src/engine/input/InputService.js - executed lines 425/425; executed functions 22/60
30+
(40%) src/engine/input/MouseState.js - executed lines 57/57; executed functions 4/10
31+
(43%) src/engine/input/KeyboardState.js - executed lines 34/34; executed functions 3/7
2332
(53%) src/engine/api/server-api-client.js - executed lines 159/159; executed functions 10/19
33+
(57%) src/engine/input/InputCaptureService.js - executed lines 163/163; executed functions 8/14
2434
(58%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 603/603; executed functions 30/52
35+
(59%) src/engine/input/PointerDragState.js - executed lines 225/225; executed functions 13/22
2536
(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 9/14
37+
(73%) src/engine/input/InputCapabilityDescriptors.js - executed lines 261/261; executed functions 11/15
38+
(74%) src/engine/input/GamepadInputClassifier.js - executed lines 412/412; executed functions 31/42
2639
(75%) toolbox/tool-registry-api-client.js - executed lines 152/152; executed functions 21/28
27-
(94%) toolbox/controls/controls.js - executed lines 1698/1698; executed functions 187/198
40+
(80%) src/engine/input/GamepadState.js - executed lines 100/100; executed functions 16/20
41+
(95%) toolbox/controls/controls.js - executed lines 1684/1684; executed functions 181/190
2842
(100%) toolbox/controls/controls-api-client.js - executed lines 12/12; executed functions 2/2
2943
(100%) toolbox/objects/objects-api-client.js - executed lines 10/10; executed functions 2/2
3044

3145
Uncovered or low-coverage changed JS files:
32-
(100%) none - no low-coverage changed runtime JS files
46+
(37%) src/engine/input/InputService.js - WARNING: advisory low coverage; executed lines 425/425
3347

3448
Changed JS files considered:
3549
(0%) tests/playwright/tools/InputMappingV2Tool.spec.mjs - changed JS file not collected as browser runtime coverage
36-
(94%) toolbox/controls/controls.js - changed JS file with browser V8 coverage
50+
(37%) src/engine/input/InputService.js - changed JS file with browser V8 coverage
51+
(74%) src/engine/input/GamepadInputClassifier.js - changed JS file with browser V8 coverage
52+
(80%) src/engine/input/GamepadState.js - changed JS file with browser V8 coverage
53+
(95%) toolbox/controls/controls.js - changed JS file with browser V8 coverage

src/engine/input/GamepadInputClassifier.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,36 @@ export function activeGamepadBindings(gamepad) {
140140
return [...buttonBindings, ...buttonReleaseBindings, ...axisBindings];
141141
}
142142

143+
export function gamepadProfileInputNames(gamepad) {
144+
const deviceInfo = gamepadDeviceInfo(gamepad);
145+
const buttonCount = Math.max(
146+
gamepad?.buttonsDown?.length ?? 0,
147+
gamepad?.buttons?.length ?? 0
148+
);
149+
const buttonNames = Array.from({ length: buttonCount }).map((_, buttonIndex) =>
150+
gamepadProfileButtonLabel(deviceInfo, buttonIndex)
151+
);
152+
const axisNames = (gamepad?.axes ?? []).map((_, axisIndex) => `Axis${axisIndex}`);
153+
return [...buttonNames, ...axisNames];
154+
}
155+
156+
export function activeGamepadProfileInputNames(gamepad) {
157+
return [...new Set(activeGamepadBindings(gamepad)
158+
.map((binding) => gamepadProfileInputNameFromBinding(binding, gamepad))
159+
.filter(Boolean))];
160+
}
161+
162+
export function gamepadProfileInputNameFromBinding(binding, gamepad = {}) {
163+
const match = /^Pad\d+:(Button|Axis)(\d+)/.exec(String(binding || ''));
164+
if (!match) {
165+
return '';
166+
}
167+
if (match[1] === 'Axis') {
168+
return `Axis${Number(match[2])}`;
169+
}
170+
return gamepadProfileButtonLabel(gamepadDeviceInfo(gamepad), Number(match[2]));
171+
}
172+
143173
function captureLines({ vendorProductLine }) {
144174
return ['Capture Game', vendorProductLine].filter(Boolean);
145175
}
@@ -338,6 +368,19 @@ function gamepadButtonLabel(deviceInfo, buttonIndex) {
338368
return `Button ${buttonIndex}`;
339369
}
340370

371+
function gamepadProfileButtonLabel(deviceInfo, buttonIndex) {
372+
if (buttonIndex === 6) {
373+
return 'Trigger Left';
374+
}
375+
if (buttonIndex === 7) {
376+
return 'Trigger Right';
377+
}
378+
if (buttonIndex >= 12 && buttonIndex <= 15) {
379+
return STANDARD_GAMEPAD_BUTTON_NAMES[buttonIndex];
380+
}
381+
return `Button${buttonIndex}`;
382+
}
383+
341384
function gamepadGestureDetail(gesture, defaultDetail) {
342385
const details = {
343386
GameControllerButton: 'Button',

src/engine/input/GamepadState.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export default class GamepadState {
5555

5656
const axes = Array.isArray(gamepad.axes) ? [...gamepad.axes] : [];
5757
const buttonsDown = Array.isArray(gamepad.buttons)
58-
? gamepad.buttons.map((button) => Boolean(button?.pressed))
58+
? gamepad.buttons.map((button) => Boolean(button?.pressed) || Number(button?.value ?? 0) > 0.5)
5959
: [];
6060

6161
return {

src/engine/input/InputService.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,10 @@ export default class InputService {
451451
timestamp: gamepad.timestamp ?? 0,
452452
axes: Array.isArray(gamepad.axes) ? [...gamepad.axes] : [],
453453
buttons: Array.isArray(gamepad.buttons)
454-
? gamepad.buttons.map((button) => ({ pressed: Boolean(button?.pressed) }))
454+
? gamepad.buttons.map((button) => ({
455+
pressed: Boolean(button?.pressed) || Number(button?.value ?? 0) > 0.5,
456+
value: Number(button?.value ?? 0)
457+
}))
455458
: [],
456459
}));
457460
}

0 commit comments

Comments
 (0)