forza-trigger: remove RPM-stuck workaround + bump hysteresis to 120
Two separate causes of stationary trigger pulsing, both fixed: 1. Hysteresis too short. 30 packets (0.5s) was shorter than FH5's stationary oscillation period (~1s per state in start-grid/pause screen contexts). Bumped to 120 (2s at 60Hz). 2. RPM-stuck workaround removed entirely. Cosmii's RPM_ACCUMULATOR (legacy_Program.cs:89-109) forced is_race_on false after 3.3s of constant RPM + zero power. While stationary in-race (idle RPM constant, power near zero), this would trip, causing a false menu transition. Engine idle flutter on power could reset and re-trigger it, producing a slow oscillation with clicks on each edge. FH5 has been observed to correctly clear is_race_on between races (confirmed via live strace), so the workaround is unnecessary. Removed: RPM_ACCUMULATOR_TRIGGER_RACE_OFF constant, is_race_on's debounce logic, DaemonState.last_rpm and .rpm_accumulator fields. is_race_on is now a one-liner: return bool(is_race_on field). Tests: 57/57 pass. TestIsRaceOn simplified from 4 to 3 tests. DaemonState reset test no longer checks removed fields.
This commit is contained in:
@@ -165,45 +165,26 @@ class TestCombinedSlip(unittest.TestCase):
|
||||
self.assertAlmostEqual(rear, 0.3)
|
||||
|
||||
|
||||
# --- Race-on debounce tests -------------------------------------------------
|
||||
# --- Race-on tests ----------------------------------------------------------
|
||||
|
||||
|
||||
class TestIsRaceOn(unittest.TestCase):
|
||||
"""is_race_on returns the RAW flag (with FH5 RPM-stuck workaround)
|
||||
only — hysteresis lives in commit_in_race so the run-loop can sequence
|
||||
transition handling correctly. Tests here exercise just the raw read.
|
||||
"""
|
||||
"""is_race_on reads the raw flag from the packet. Hysteresis lives in
|
||||
commit_in_race; the old RPM-stuck workaround was removed (caused false
|
||||
menu transitions while stationary in-race)."""
|
||||
|
||||
def test_simple_yes(self):
|
||||
def test_reads_flag_true(self):
|
||||
s = ft.DaemonState()
|
||||
pkt = FakePacket(is_race_on=1.0, current_engine_rpm=4000, power=200)
|
||||
self.assertTrue(ft.is_race_on(pkt, s))
|
||||
self.assertEqual(s.rpm_accumulator, 0)
|
||||
self.assertTrue(ft.is_race_on(FakePacket(is_race_on=1.0), s))
|
||||
|
||||
def test_explicit_no(self):
|
||||
def test_reads_flag_false(self):
|
||||
s = ft.DaemonState()
|
||||
pkt = FakePacket(is_race_on=0.0)
|
||||
self.assertFalse(ft.is_race_on(pkt, s))
|
||||
self.assertFalse(ft.is_race_on(FakePacket(is_race_on=0.0), s))
|
||||
|
||||
def test_rpm_stuck_overrides_flag_after_threshold(self):
|
||||
# FH5's stuck-flag bug: is_race_on=1 but RPM and power show the car
|
||||
# is actually in a menu (power<=0, RPM unchanged). is_race_on must
|
||||
# return False once the accumulator trips, regardless of the flag.
|
||||
def test_defaults_to_flag_value(self):
|
||||
# FakePacket defaults is_race_on=1.0; is_race_on just reads it.
|
||||
s = ft.DaemonState()
|
||||
pkt = FakePacket(is_race_on=1.0, current_engine_rpm=800, power=0)
|
||||
s.last_rpm = 800
|
||||
for _ in range(ft.RPM_ACCUMULATOR_TRIGGER_RACE_OFF):
|
||||
self.assertTrue(ft.is_race_on(pkt, s))
|
||||
self.assertFalse(ft.is_race_on(pkt, s))
|
||||
|
||||
def test_rpm_change_resets_accumulator(self):
|
||||
s = ft.DaemonState()
|
||||
s.rpm_accumulator = 50
|
||||
s.last_rpm = 800
|
||||
pkt = FakePacket(is_race_on=1.0, current_engine_rpm=4000, power=100)
|
||||
ft.is_race_on(pkt, s)
|
||||
self.assertEqual(s.rpm_accumulator, 0)
|
||||
|
||||
self.assertTrue(ft.is_race_on(FakePacket(), s))
|
||||
|
||||
class TestCommitInRace(unittest.TestCase):
|
||||
"""Hysteresis on the raw is_race_on flag. FH5 emits packets where
|
||||
@@ -468,8 +449,7 @@ class TestDaemonStateReset(unittest.TestCase):
|
||||
s.last_brake_resistance = 99.0
|
||||
s.last_throttle_freq = 99.0
|
||||
s.last_brake_freq = 99.0
|
||||
s.rpm_accumulator = 50
|
||||
s.last_rpm = 4000
|
||||
s.in_race_pending_count = 50
|
||||
|
||||
s.reset()
|
||||
|
||||
@@ -477,8 +457,7 @@ class TestDaemonStateReset(unittest.TestCase):
|
||||
self.assertEqual(s.last_brake_resistance, 1.0)
|
||||
self.assertEqual(s.last_throttle_freq, 0.0)
|
||||
self.assertEqual(s.last_brake_freq, 0.0)
|
||||
self.assertEqual(s.rpm_accumulator, 0)
|
||||
self.assertEqual(s.last_rpm, 0.0)
|
||||
self.assertEqual(s.in_race_pending_count, 0)
|
||||
|
||||
def test_reset_clears_lightbar_dedup(self):
|
||||
# last_color MUST be cleared on idle reset. The `apply_lightbar`
|
||||
|
||||
Reference in New Issue
Block a user