4.4 KiB
Overlay Controller Support Design
Date: 2026-03-11
Backlog: TASK-159
Goal
Add controller support to the visible overlay through the Chrome Gamepad API without replacing the existing keyboard-only workflow. Controller input should only supplement keyboard-only mode, preserve existing behavior, and expose controller selection plus raw-input debugging in overlay-local modals.
Scope
- Poll connected gamepads from the visible overlay renderer.
- Default to the first connected controller unless config specifies a preferred controller.
- Add logical controller bindings and tuning knobs to config.
- Add
Alt+Ccontroller selection modal. - Add
Alt+Shift+Ccontroller debug modal. - Map controller actions onto existing keyboard-only/Yomitan behaviors.
- Fix stale selected-token highlight cleanup when keyboard-only mode turns off or popup closes.
Out of scope for this pass:
- Raw arbitrary axis/button index remapping in config.
- Controller support outside the visible overlay renderer.
- Haptics or vibration.
Architecture
Use a renderer-local controller runtime. The overlay already owns keyboard-only token selection, Yomitan popup integration, and modal UX, and the Gamepad API is browser-native. A renderer module can poll navigator.getGamepads() on animation frames, normalize sticks/buttons into logical actions, and call the same helpers used by keyboard-only mode.
Avoid synthetic keyboard events as the primary implementation. Analog sticks need deadzones, continuous smooth scrolling, and per-action repeat behavior that do not fit cleanly into key event emulation. Direct logical actions keep tests clear and make the debug modal show the exact values the runtime uses.
Behavior
Controller actions are active only while keyboard-only mode is enabled, except the controller action that toggles keyboard-only mode can always fire so the user can enter the mode from the controller.
Default logical mappings:
- left stick vertical: smooth Yomitan popup/window scroll when popup is open
- left stick horizontal: move token selection left/right
- right stick vertical: smooth Yomitan popup/window scroll
- right stick horizontal: jump horizontally inside Yomitan popup/window
A: toggle lookupB: close lookupY: toggle keyboard-only modeX: mine cardL1/R1: previous / next Yomitan audioR2: activate current Yomitan audio buttonL2: toggle mpv play/pause
Selection-highlight cleanup:
- disabling keyboard-only mode clears the selected token class immediately
- closing the Yomitan popup also clears the selected token class if keyboard-only mode is no longer active
- helper ownership should live in the shared keyboard-only selection sync path so keyboard and controller exits stay consistent
Config
Add a top-level controller block in resolved config with:
enabledpreferredGamepadIdpreferredGamepadLabelsmoothScrollscrollPixelsPerSecondhorizontalJumpPixelsstickDeadzonetriggerDeadzonerepeatDelayMsrepeatIntervalMsbindingslogical fields for the named actions/sticks
Persist the preferred controller by stable browser-exposed id when possible, with label stored as a diagnostic/display fallback.
UI
Controller selection modal:
- overlay-hosted modal in the visible renderer
- lists currently connected controllers
- highlights current active choice
- selecting one persists config and makes it the active controller immediately if connected
Controller debug modal:
- overlay-hosted modal
- shows selected controller and all connected controllers
- live raw axis array values
- live raw button values, pressed flags, and touched flags if available
Testing
Test first:
- controller gating outside keyboard-only mode
- logical mapping to existing helpers
- continuous stick scroll and repeat behavior
- modal open shortcuts
- preferred-controller selection persistence
- highlight cleanup on keyboard-only disable and popup close
- config defaults/parse/template generation coverage
Risks
- Browser gamepad identity strings can differ across OS/browser/runtime versions. Mitigation: match by exact preferred id first; fall back to first connected controller.
- Continuous stick input can spam actions. Mitigation: deadzones plus repeat throttling and frame-time-based smooth scroll.
- Popup DOM/audio controls may vary. Mitigation: target stable Yomitan popup/document selectors and cover with focused renderer tests.