You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Read live button and axis state (sticks, triggers, all buttons, D-pad) on the sokol backend, so Android-TV games — which are sokol-only (verified) — get full-gameplay controller input. Today sokol's gamepad methods are hard stubs (backends/sokol/src/input.zig:91).
The core problem
sokol_app owns the AInputQueue and exposes no hook to intercept raw native input events (verified). Button presses (KeyEvent) and sticks (MotionEventSOURCE_JOYSTICK) flow into sokol and are dropped — gamepad button keycodes almost certainly aren't in sokol's Android keymap. So unlike detection, state genuinely needs the event stream sokol consumes.
Approach — DECIDED (see prior-art research below)
sokol here is a labelle-maintained fork (commit 887b30f), which makes patching viable:
Patch the sokol fork to forward unhandled AInputEvents (passthrough callback for events sokol doesn't consume). At the NDK level these arrive as Android semantic constants (AKEYCODE_BUTTON_A, AMOTION_EVENT_AXIS_LTRIGGER, …) — not raw HID. This is exactly the input Unreal's FAndroidInputInterface consumes.
Mapping = Android semantic constants + a small quirk table (the Unreal/Unity model), not Paddleboat and not a from-scratch SDL DB. Android's HID/KeyLayout layer already normalizes most pads to semantic codes; we only need a handful of per-device axis-routing overrides (UE ships ~5, Unity ships 2). Optionally seed the quirk table from SDL gamecontrollerdb.txt later (Godot model) if coverage demands — not required to ship.
Wire results into the existing poll methods (isGamepadButtonDown/Pressed, getGamepadAxisValue), replacing the stubs.
Back-button trap: controller B often maps to KEYCODE_BACK on Android TV — intercept so it doesn't exit the app.
sokol+wasm: HTML5 Gamepad API buttons/axes are pollable — comparatively cheap.
Flow surface (currently missing)
flow-codegen has no gamepad reporter nodes — add IsGamepadButtonDown/Pressed/Released, GetGamepadAxisValue so flows can poll state (parallels the existing keyboard/mouse reporters).
Prior-art research — Paddleboat vs. hand-rolled (RESOLVED → hand-rolled)
Checked how Godot, Unreal, and Unity handle Android gamepads (primary sources):
Engine
Native source
Mapping
Identity
Paddleboat
Godot
Java onKeyDown/onGenericMotionEvent → JNI
semantic codes + SDL gamecontrollerdb.txt + own DB
None use Paddleboat. It postdates their input stacks and would mean a second mapping system; it targets projects with no input layer. Not adopting it.
"Hand-rolled" is cheap here, because Android pre-normalizes to semantic constants — the feared "maintain a gamecontrollerdb.txt" tarpit does not apply; a small quirk table suffices (UE/Unity proof).
Epic (Phase 3): Full analog gamepad state on sokol — Android-TV gameplay
Parent: labelle-toolkit/labelle-engine#609. The expensive part. Builds on Phase 1 sokol detection (#248, #249), which delivered presence/identity only.
Goal
Read live button and axis state (sticks, triggers, all buttons, D-pad) on the sokol backend, so Android-TV games — which are sokol-only (verified) — get full-gameplay controller input. Today sokol's gamepad methods are hard stubs (
backends/sokol/src/input.zig:91).The core problem
sokol_app owns the AInputQueue and exposes no hook to intercept raw native input events (verified). Button presses (
KeyEvent) and sticks (MotionEventSOURCE_JOYSTICK) flow into sokol and are dropped — gamepad button keycodes almost certainly aren't in sokol's Android keymap. So unlike detection, state genuinely needs the event stream sokol consumes.Approach — DECIDED (see prior-art research below)
sokol here is a labelle-maintained fork (commit
887b30f), which makes patching viable:AInputEvents (passthrough callback for events sokol doesn't consume). At the NDK level these arrive as Android semantic constants (AKEYCODE_BUTTON_A,AMOTION_EVENT_AXIS_LTRIGGER, …) — not raw HID. This is exactly the input Unreal'sFAndroidInputInterfaceconsumes.gamecontrollerdb.txtlater (Godot model) if coverage demands — not required to ship.isGamepadButtonDown/Pressed,getGamepadAxisValue), replacing the stubs.KEYCODE_BACKon Android TV — intercept so it doesn't exit the app.Also in scope (other sokol platforms)
Flow surface (currently missing)
IsGamepadButtonDown/Pressed/Released,GetGamepadAxisValueso flows can poll state (parallels the existing keyboard/mouse reporters).Prior-art research — Paddleboat vs. hand-rolled (RESOLVED → hand-rolled)
Checked how Godot, Unreal, and Unity handle Android gamepads (primary sources):
onKeyDown/onGenericMotionEvent→ JNIgamecontrollerdb.txt+ own DBGameActivity extends NativeActivity+ native AInputEventAKEYCODE_BUTTON_*+ small per-device quirk tablegetDescriptor()UnityPlayerActivity.injectEventdeviceDescriptorConclusions:
gamecontrollerdb.txt" tarpit does not apply; a small quirk table suffices (UE/Unity proof).getDescriptor+InputManagerhotplug). We mirror it: state via native AInputEvent (this issue), hotplug/identity via JNIInputManager/getDescriptor(feat(sokol/android): JNI InputManager gamepad detection source — centerpiece (Phase 1) #248).getDescriptor()for identity (beats Godot's name-based scheme for free).Acceptance criteria
Non-goals