Skip to content

Ford: fire the stall-rescue pulse only on a fractional delivery deficit#148

Open
jrneulight wants to merge 1 commit into
BluePilotDev:bp-devfrom
jrneulight:bp-7.0-stall-frac
Open

Ford: fire the stall-rescue pulse only on a fractional delivery deficit#148
jrneulight wants to merge 1 commit into
BluePilotDev:bp-devfrom
jrneulight:bp-7.0-stall-frac

Conversation

@jrneulight

@jrneulight jrneulight commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

The angle-mode reactive stall detector (the 300 ms mode-0 "rescue pulse" that resets
post-override PSCM attenuation) arms on |desired| > |measured| plus an absolute gap
threshold. That condition cannot tell a stalled PSCM from an honestly-lagging one: on a
deep curve entry the car tracks at 0.7–0.85× of a large, fast-rising demand, clears
the gap threshold on magnitude alone, and the pulse releases steering mid-curve exactly
when the car is already behind.

The evidence that motivated this

  • On one windy section of a single drive (route 0000001e--be********, 25–31 mph,
    |desired curvature| 0.016–0.021), the detector fired twice mid-curve while the car was
    delivering 0.71–0.83× — each pulse followed by the driver grabbing the wheel within
    0.2 s.
  • Settled hands-off delivery on the same drive measured ~1.0 in every speed band: these
    were entry transients, not stalls.
  • True stalls measure differently: 0.28–0.59× delivered, across five diagnosed routes
    (~47 engaged minutes).

Why the fix is shaped this way

A stall is a fractional failure, not an absolute gap. The pulse now arms only when
the car is delivering under 0.65× of the demand (_STALL_DELIVERY_FRACTION) — one
constant and one condition. The measured separation is wide: every true stall in the
archive sits at ≤ 0.59×, every honest transient at ≥ 0.71×. All other guards (0.5 s
hold, 2 s cooldown, 3-pulse episode cap, press/lane-change resets) are untouched, so a
genuinely stuck PSCM is rescued exactly as before.

The evidence afterwards

Detector replay, ported verbatim and measurement-faithful (live steer ratio,
instantaneous angle offset, roll compensation, the recorded devLim flag), across five
routes:

  • Baseline reproduces every recorded pulse — including both misfires — so the sim
    is calibrated, not curve-fit.
  • With the fractional gate: every true rescue kept (all fire at 0.28–0.59×
    delivered), both misfires eliminated, zero new fires anywhere.

On-road since opening (update, two drives on a branch carrying this gate): a windy
hands-on route (00000027--97********, 26 min) crossed 38 qualifying curve entries with
zero entry-transient fires — a per-blip audit shows the gate honored on every pulse
that did fire — while a true stall still rescued cleanly; a follow-up drive
(0000002a--ce********) fired 4 pulses, each in a legitimate post-press window, zero
misfires. The gate's exact claim, confirmed on the road in its target scenario.

What each change is

  1. _STALL_DELIVERY_FRACTION = 0.65 constant with the measured justification in the
    comment.
  2. The arming condition's abs(desired) > abs(measured) becomes
    abs(measured) < 0.65 * abs(desired).
  3. New unit tests (plus the minimal test scaffolding for this module): an entry
    transient (0.72× of a deep demand, gap over threshold) must never fire; a true stall
    (0.20×) must.

Test it yourself

# unit tests (no hardware)
cd opendbc_repo && pytest opendbc/sunnypilot/car/ford/tests/test_lateral_angle_ext.py

# on any Ford angle-mode route: count mode-0 pulses and check the delivery ratio at
# each fire — with this change, every fire should sit well under 0.65x

Risks / limits

ghbarker added a commit to ghbarker/openpilot that referenced this pull request Jul 20, 2026
…luePilotDev#144 (truthful shadow)

Folds the two open upstream PRs that bear on the Mach-E angle-mode
lateral failures into this branch, adapted to compose with the drift
branch and low-speed boost already here:

- PR BluePilotDev#148: the classic stall branch now arms on a fractional delivery
  deficit (measured < 0.65x desired) instead of desired-leads-measured
  plus the absolute gap alone. Upstream's on-road evidence: the absolute
  condition misfired twice mid-curve on honest deep-curve entry
  transients (0.7-0.85x delivered), releasing steering exactly when the
  car was behind; true stalls measure 0.28-0.59x. The drift branch keeps
  its own near-straight guard and is unaffected.

- PR BluePilotDev#144: bp_kappa_cmd (the shadow curvature ford.h's angle-mode
  deviation check judges against angle_meas) is now published from
  measured curvature whenever the planner kappa cannot honestly describe
  the car's steering: inactive frames, human-turn override, stall-blip
  pulses, and while the driver presses. Kills the stale-zero re-engage
  race at the source -- which matters more on this branch, since the
  drift branch fires mode-0 pulses in situations the old code never did
  -- and fixes the driver-press shadow excursions behind the only two
  real lateral safety blocks in upstream's 4.5h replay. All measurement
  reads route through the new LateralCurvExt.get_current_curvature().

Tests: merged both PRs' unit suites into one file (shared harness),
plus two new drift-branch cases (hands-off drift fires; curve exit with
a genuine command never fires). 9/9 pass locally. Replay of the three
incident segments (4/6/8) is unchanged except one classic fire shifting
0.2s later under the stricter gate -- every fire on this route is a true
stall (0.2-0.3x delivered) or a guarded drift event.

Not folded: PR BluePilotDev#145 (pinion yaw sensor) -- changes panda safety firmware
(ford.h) and is opt-in; deferred until after this branch's road test.
PR BluePilotDev#146 (Expedition FW), BluePilotDev#147 (audio), BluePilotDev#132 (comma-four UI refactor) are
unrelated to this issue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reactive stall detector's curve condition was only |desired| > |measured|, so an
honest deep-curve ENTRY -- the car tracking at 0.7-0.85x of a large, fast-rising
demand -- armed the pulse on absolute gap alone, dropping steering for 300 ms mid-curve
exactly when the car was already behind. Observed on-road twice in one windy section
(|des| 0.016-0.021, delivery 0.71-0.83), each pulse followed by the driver grabbing the
wheel within 0.2 s; settled delivery on the same drive was ~1.0 everywhere, confirming
these were transients, not stalls.

Arm the pulse only when the car delivers under 0.65x of the demand. Detector replay
across five routes (~47 engaged minutes, measurement-faithful): every true rescue kept
(all fire at 0.28-0.59x delivered), both misfires eliminated, zero new fires; the
closest blocked transient sits at 0.71x. All other guards (hold, cooldown, episode cap,
press/lane-change resets) unchanged. Unit tests pin both directions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant