Strace post-deploy showed the daemon flipping every other packet between
in-race state (mode 0x21 FEEDBACK + LED green) and out-of-race state
(mode 0x05 OFF + LED black). 8 writes, 8 transitions in 5s — every
HID OUT report a state change.
Root cause: FH5 emits packets where the is_race_on field alternates
True/False at packet rate during menu/loading/transition states.
Cosmii's RPM_ACCUMULATOR debounce only handles the 'flag stuck True
when we're really in a menu' case (quirk #1); it does nothing for
'flag flipping at packet rate' (quirk #2).
Fix: split the read from the hysteresis. is_race_on now returns the
raw flag with quirk #1 applied. commit_in_race applies a packet-count
hysteresis (IN_RACE_HYSTERESIS_PACKETS = 30 ≈ 0.5s at 60Hz) — only
after N consecutive packets of the new value does the committed
in-race state flip. Alternation just keeps the pending counter
oscillating near 0; it never reaches threshold and the run-loop sees
a stable state.
Architecture: hysteresis in a separate function (not in is_race_on)
because the run-loop must update state.last_in_race AFTER side
effects, not before — otherwise the elif transition-detection
breaks. is_race_on stays pure read; commit_in_race is the gatekeeper;
run-loop sets state.last_in_race once at end of packet handling.
Tests grew 54→58. New TestCommitInRace covers:
- stable input (no pending growth)
- worst-case alternation (never commits)
- N consecutive packets commit
- matching raw resets pending counter
TestIsRaceOn renamed to reflect new (narrower) responsibility.
Live diagnostic on yarn revealed the daemon was receiving 324-byte FH5
packets correctly (5.7MB on the systemd socket; strace showed steady
recvfrom + write to /dev/hidraw7) but writing trigger mode 0x05
(no-resistance) on nearly every tick. Cause: `accel` and `brake` are
0 most of the time during normal play (off-throttle on straight
sections, off-brake when not braking). Both handlers had:
if accel/255 <= THROTTLE_INPUT_THRESHOLD: effect.off(); return
if brake/255 <= BRAKE_INPUT_THRESHOLD: effect.off(); return
Every off-pedal packet set the trigger to OFF. Brief pedal-on moments
set vibration. The result: rapidly oscillating off↔vibration state,
imperceptible at 60 Hz packet rate.
These early-returns were holdovers from the previous Race-Element 1:1
port (variant A), which IS designed to be silent unless slipping.
Variant D's whole point is "always feels something" — Cosmii has no
pedal-off gate, and its baseline branch produces feedback even at
brake=0/accel=0 with strength clamped to MIN.
Fix: remove both early-returns. Foot-off-pedal flows through the
baseline branch and produces feedback(strength=MIN_*_RESISTANCE). The
user feels light constant resistance instead of silence. Trigger only
returns to physical-rest when out-of-race (run-loop's reset_triggers).
Also drop the now-dead BRAKE_INPUT_THRESHOLD / THROTTLE_INPUT_THRESHOLD
constants. Two tests renamed and updated to assert MIN-strength
baseline feedback instead of effect.off() on zero pedal.
54/54 tests pass. Build clean.