Implemented Behavior
- LuaJIT FFI haptics now probe
/dev/input/event*, upload
the rumble effect, and keep the first device that accepts
EVIOCSFF.
haptic_pulse() retries initialization if no active
haptic device is ready.
- The Python
haptic.py fallback uses the same dynamic
device probing model.
- MPV return still closes and re-initializes haptics.
Acceptance Criteria
- L1/R1 on the main hub rotates the dial and produces a haptic
pulse.
- Logs identify the selected haptic event node or report that no
force-feedback device is available.
- No EmulationStation restart, reboot, or app kill is required.