Ford: keep the angle-mode shadow curvature truthful during overrides#144
Closed
jrneulight wants to merge 2 commits into
Closed
Ford: keep the angle-mode shadow curvature truthful during overrides#144jrneulight wants to merge 2 commits into
jrneulight wants to merge 2 commits into
Conversation
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>
Frame-exact port of ford.h's Lane_Assist_Data1/LateralMotionControl TX-hook semantics (value limits, steer_angle_cmd_checks, shadow-curvature check, ROC checks, and -- the piece an earlier analysis omitted -- the reset-bypass latch) replayed against real rlogs. Ground truth is independent of the sim: a sendcan frame with no TX loopback echo in the can stream was actually blocked, and pandaStates.safetyTxBlocked counter deltas cross-check the totals, so mis-attribution of block causes can't recur. Includes a --truthful-shadow counterfactual that latches the shadow from measured curvature on the frames the truthful-shadow control fix republishes, at real LKA cadence, to validate that fix against recorded routes.
…ing overrides The shadow curvature (bp_kappa_cmd, carried in Lane_Assist_Data1 bytes 5-6 and judged against angle_meas by ford.h's angle-mode deviation check) was zeroed during human-turn overrides, stall blips, and inactive frames, and kept publishing the clipped planner kappa while the driver pressed. Two problems: 1. Driver pressing mid-curve with the mode still enabled: the clipped planner kappa cannot follow the wheel, so the shadow exits the deviation band. Frame-exact replay of ~3h of road-test routes (tools/ford_lmc_safety_replay.py) attributed the ONLY in-drive lateral safety block to exactly this scenario (sustained curve, latch drained, shadow -630 vs measured [-795,-782] CAN units). 2. Zeroed shadow during mode-0/inactive frames: the panda latches the shadow from LKA (33 Hz) and enable from LMC (20 Hz), so the first re-engaged frame can compare a stale zero against real measured curvature. Today this race is masked by ford.h's reset-bypass latch (replay: 14 such frames across three routes, every one bypassed, zero actually blocked) -- but the published value was still dishonest, and any future tightening of that latch would surface the race. Fix: route every measurement read through LateralCurvExt.get_current_curvature() and publish the shadow from it whenever the planner kappa cannot honestly describe the car's steering -- while pressed, during human-turn/blip mode-0 pulses, and while inactive. The latched shadow then tracks reality continuously and the check compares measured-vs-measured at every re-engage edge. Replay validation (same tool, truthful-shadow counterfactual): shadow-check violations 2/2/14 -> 0/0/0 across the three routes; the one real block is eliminated; no new violations introduced (the value is inside the band by construction at latch time).
jrneulight
force-pushed
the
bp-7.0-shadow-race
branch
from
July 20, 2026 14:47
699c17d to
b6c45bc
Compare
Contributor
Author
|
Closing: this already landed in bp-dev as part of #145. Both commits are ancestors of bp-dev — Verified in bp-dev rather than assumed:
GitHub couldn't auto-close this because I'd force-pushed a rebased copy of the branch, so the PR head SHAs no longer matched the ones that merged. That's on me — the content is in, nothing is lost. No action needed; the follow-up work continues in #152 (restores the pinion toggle that the #145 merge dropped from settings_ui.json), #148, and #153. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ford: keep the angle-mode shadow curvature truthful during overrides
Angle mode publishes a "shadow curvature" — the kappa its
path_anglewas derived from —in
Lane_Assist_Data1's unused bits, andford.hjudges it against measured curvature(
angle_meas). That is angle mode's only commanded-vs-measured deviation check. This PRmakes the published value honest in the situations where the planner kappa cannot
describe what the car is actually doing, and ships the replay tool that found (and now
guards) the underlying behavior.
The evidence that motivated this
We built a frame-exact replay of
ford.h's LKA/LMC TX-hook semantics(
tools/ford_lmc_safety_replay.py, first commit of this PR) and ran it against ~4.5 h ofreal Explorer rlogs (routes
00000001--be********,00000002--71********,00000003--a1********,00000004--f0********; 72,782 LMC frames). Two properties makeits attributions trustworthy rather than guesswork:
sendcanframe with no TX loopbackecho in the
canstream was actually blocked by the panda, andpandaStates.safetyTxBlockedcounter deltas cross-check the totals frame-exactly.mis-attributed blocks; this tool exists so firmware semantics can never silently drift
from the analysis again.
Findings:
Exactly two real lateral safety blocks occurred in 4.5 h — same mechanism both
times. A driver correcting mid-curve with the mode still enabled: the
deviation-clipped planner kappa can't follow the wheel, so the shadow exits the error
band while a sustained curve has drained the latch.
000000030000000435 latent "re-engage race" frames, none of which ever blocked. On release of a
human-turn override or stall blip, the old code zeroed the shadow, so the first
re-enabled LMC frame (20 Hz) races LKA's 33 Hz shadow latch and can present a stale
zero against real measured curvature. Every one of these frames was bypassed by the
reset-bypass latch (all 72,782 LMC frames echoed to the bus). The race is real at the
check-semantics level and would surface under any future latch tightening — but it
has never blocked anything in current firmware.
The
safetyTxBlockedfolklore dissolved under attribution. The counter resetswhen the safety model is set, so the 5–9 counts seen at every ignition are
pre-safety-mode init TX, not drive events; the only other in-drive increments were
cancel-button frames gated at cruise-state edges (
Steering_Data_FD1passthrough,unrelated to lateral).
The latch itself does more than expected: in angle mode the wire curvature is
always 0, so any straight-driving frame recharges the 60-frame full bypass, and the
shadow check is only live during >3 s sustained curves (latch empty 23–60% of frames
depending on route). Documented here honestly; narrowing it is future work this tool
can validate.
Why the fix is shaped this way
LateralCurvExt.get_current_curvature(), so the shadow judged by the check and thecheck's own reference can never come from different measurement families.
honestly describe the car's steering: while the driver presses (fixes the two real
blocks), and during human-turn/blip mode-0 pulses and inactive frames (kills the
re-engage race at the source — the panda-latched shadow tracks reality continuously,
so the first re-engaged frame compares measured-vs-measured).
dead code under the current latch. No dead code in safety files.
(shadow ≡ measurement). We consider that correct — during an override the honest
"command" is the driver's curvature, and the driver has control — but it is a
reviewer-visible trade-off, not a hidden one.
The evidence afterwards
Re-running the same tool with
--truthful-shadow(the fix modeled at real LKA cadence,so latch-age timing skew is included):
00000001000000020000000300000004No new violations are possible by construction: the published value sits inside
[meas_min − band, meas_max + band]at latch time. On-road confirmation: a ~27 minacceptance drive on this code (demanding roads, driver corrections included) shows zero
lateral blocks and zero pre-latch shadow signatures — the latent class is empty in
live driving, not just in counterfactual replay. The only residual entries are six
engagement-transition
controls_allowedsampling artifacts, unrelated to the shadowsystem and all latch-masked. Hands-off behavior is unchanged (unit-tested: the clipped
planner kappa is still published when hands are off).
What each change is
tools/ford_lmc_safety_replay.py(first commit): the full CAN-path TX-hook port —value limits,
steer_angle_cmd_checksincluding its controls-not-allowed gate anddesired_angle_lastreset semantics, the shadow check, ROC checks, and thereset-bypass latch — plus the echo-based ground truth and the
--truthful-shadowcounterfactual.
lateral_curv_ext.py:get_current_curvature(CS)accessor (yaw-rateimplementation, identical math to the expressions it replaces).
lateral_angle_ext.py: the four publishing sites (inactive, human-turn override,stall blip, driver-pressed) switched from
0.0/clipped-kappa to the accessor.tests/test_lateral_angle_ext.py: pins the truthfulness contract at all foursites and the unchanged hands-off path, with a harness mirroring CarController's
mixin composition.
Test it yourself
The replay prints, per route: sim violations pre-latch (what would block with no latch),
post-latch (predicted real blocks), the per-check breakdown, the echo ground truth
(sent/echoed/no-echo per address), and every
safetyTxBlockedcounter increment withroute time — so you can reproduce the attribution table above on our routes or check
your own fleet's behavior before/after this change.