mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-13 06:33:45 +08:00
Ford: restore original selected-curvature v1 controller
Restore the exact tracked tree from 5fc16abc7, identified in the
9b and 9e strong-tracking routes, including opendbc c21a9013 and
the original 100 Hz CAN FD cadence. C0 samples model y at 7 m;
C1 is max(7 m, speed times 1 s) times selected desired curvature.
This removes the later model-orientation, forecast, damping and
cadence experiments. Model selection/bundles and Panda safety are
unchanged. Restoring the prior behavior does not establish the
physical cause of the reported wobble.
Validation: exact original tree and submodule match; 494 tests and
9146 subtests passed, 178 inapplicable safety skips; canonical
Sunnylink schema; 100 real-sender messages in 100 control cycles,
with counters/checksums, C2/C3 zero and unchanged Panda TX checks.
Assisted-by: OpenAI Codex
This commit is contained in:
@@ -1,60 +0,0 @@
|
||||
# Selected-action 20 Hz cadence experiment
|
||||
|
||||
The selected-action controller now sends LateralMotionControl2 every fifth
|
||||
100 Hz update (20 Hz, nominally 50 ms). Each send uses the latest published
|
||||
C0/C1. The v6 controller module is byte-identical to `c70a9ee84`: its geometry,
|
||||
150 ms forecast, caps, two states, and 4 m/s / 0.5 rad/s slew are unchanged.
|
||||
C2 and C3 remain zero. This tests transport cadence, not a new strength gain.
|
||||
|
||||
The existing default-off `FordModelActionController` Sunnylink setting is
|
||||
snapshotted by `card` into `CarParamsSP.flags` on the supported CAN FD Lightning.
|
||||
Both controller selection and send cadence use that snapshot. An onroad setting
|
||||
write cannot switch either one. Complete an offroad-to-onroad cycle after updating.
|
||||
The diagnostic hypothesis remains `model-action-measured-pose-v6`; the build
|
||||
commit, CarParamsSP flag, and measured send cadence distinguish this experiment.
|
||||
|
||||
With the toggle off, existing CAN FD controllers retain 100 Hz transmission.
|
||||
Legacy CAN stays at 20 Hz. Panda safety is byte-identical to `c21a9013`.
|
||||
The sender retains Panda's existing per-message C2 slew bound even on the
|
||||
20 Hz path; this controller does not use C2. No safety limit is relaxed.
|
||||
Counters advance once per transmitted request, including wrap from 15 to 0.
|
||||
Invalid paths zero the next scheduled request; disengagement sends mode 0 on
|
||||
that request, without a new ramp-out sequence. Relative to a 100 Hz sender,
|
||||
a change can wait up to four more control ticks (nominally 40 ms).
|
||||
|
||||
## Evidence and limits
|
||||
|
||||
On route `84865544361f55cb_000000a5--d0f935d323`, the camera's observed inactive
|
||||
LMC2 stream ran at 19.993 Hz (7,952 messages, median 50.051 ms). Our sender ran
|
||||
at 99.321 Hz (38,496 messages, median 9.938 ms). The camera data does not establish
|
||||
the factory's active-mode cadence. All supplied v1–v6 drives already used
|
||||
100 Hz; cadence has not been established as the cause of weak tracking.
|
||||
|
||||
The [transport replay record](ford_model_action_cadence_validation.json) covers
|
||||
38,496 recorded send cycles at each of five possible scheduling phases:
|
||||
192,480 sender updates and 38,496 transmitted requests in total. Every emitted
|
||||
C0/C1 exactly matched the corresponding recorded request; C2/C3 stayed zero;
|
||||
mode, counter and checksum checks passed. Every request passed the unchanged,
|
||||
compiled Panda TX hook with controls eligibility set from the recorded mode.
|
||||
This tests TX bounds, not a full Panda RX watchdog, vehicle response, or device boot.
|
||||
|
||||
Targeted tests additionally cover exact send intervals, latest-sample delivery,
|
||||
counter wrap, every disengagement/invalid-input phase, unchanged fallback cadence,
|
||||
100 Hz core slew, and a shared selection snapshot surviving serialization and
|
||||
subsequent stored-toggle changes. The broader offline run passed 689 tests and
|
||||
9,146 subtests; 178 inherited safety cases were skipped as inapplicable.
|
||||
Ruff and typechecking of the touched production modules passed.
|
||||
|
||||
No physical tracking improvement is claimed. The next drive must establish
|
||||
whether the lower cadence helps ordinary bends and turn exits, while checking
|
||||
for added turn-in delay. Offline replay cannot predict that closed-loop response.
|
||||
|
||||
## Reproduce
|
||||
|
||||
Initialize the pinned submodule and use the project's built Python/native environment:
|
||||
|
||||
```sh
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
export PYTHONPATH=.:opendbc_repo
|
||||
python -m pytest -q openpilot/selfdrive/controls/tests/test_ford*.py tools/ford_pscm_lab opendbc_repo/opendbc/car/ford/tests/test_ford.py openpilot/sunnypilot/sunnylink/tests openpilot/sunnypilot/mads/tests openpilot/selfdrive/car/tests/test_ford_pscm_status.py openpilot/common/tests/test_params.py opendbc_repo/opendbc/safety/tests/test_ford.py
|
||||
```
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"scope": "Frozen a5 publications through actual 20Hz CarController and unchanged compiled Panda TX hook, all five scheduling phases. No physical response simulation.",
|
||||
"route": "84865544361f55cb_000000a5--d0f935d323",
|
||||
"baseline_root": "c70a9ee84bbf6db9a687d63ef674ed4db9c13e3a",
|
||||
"baseline_opendbc": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||
"input_sha256": "815d1e248ff5c3e5e5cfc11dfbd0690d0d13ddafe1436890e239975a72dae9b8",
|
||||
"phases": [
|
||||
{
|
||||
"phase": 0,
|
||||
"input_cycles": 38496,
|
||||
"sent_and_accepted": 7700
|
||||
},
|
||||
{
|
||||
"phase": 1,
|
||||
"input_cycles": 38496,
|
||||
"sent_and_accepted": 7699
|
||||
},
|
||||
{
|
||||
"phase": 2,
|
||||
"input_cycles": 38496,
|
||||
"sent_and_accepted": 7699
|
||||
},
|
||||
{
|
||||
"phase": 3,
|
||||
"input_cycles": 38496,
|
||||
"sent_and_accepted": 7699
|
||||
},
|
||||
{
|
||||
"phase": 4,
|
||||
"input_cycles": 38496,
|
||||
"sent_and_accepted": 7699
|
||||
}
|
||||
],
|
||||
"core_byte_identical": true,
|
||||
"ford_safety_byte_identical": true,
|
||||
"source_sha256": {
|
||||
"openpilot/selfdrive/controls/lib/ford_model_action.py": "326539389b04034932db41ca2c67794779403b2c0e97f408a81a9f5734114899",
|
||||
"openpilot/selfdrive/controls/controlsd.py": "002d57a0b5b6e4e3a04789bee20b9b175a71d6893dd06074cebca2738f3986da",
|
||||
"openpilot/sunnypilot/mads/helpers.py": "24970993d37242fe8a0457bac118e2c265c4c32427dc930c0001de12e5f204dd",
|
||||
"opendbc_repo/opendbc/car/ford/carcontroller.py": "591b0d8455d256f7504cbd2a6a11fe1a54ba6ad3e12ac16a6952dbd92274f303",
|
||||
"opendbc_repo/opendbc/car/ford/values.py": "edadaacc13581642917d1fba473ef3efc90d7145dbe801915f860747a4294046",
|
||||
"opendbc_repo/opendbc/safety/modes/ford.h": "1d9d996292d6697ab4f02d55fae348d6aca1df94a07f7bdae48b68971b91afe7",
|
||||
".cache/ford_cadence/replay.py": "7227f0b4f130be0e5b28f517416ffbf281f0b33085b8391f66814aa531c3e514"
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
# Experimental Ford offset damping, v2
|
||||
|
||||
This document and its validation counts describe the archived v2 source. The
|
||||
[current v4 experiment](ford_model_action_full_prediction.md) uses full path prediction.
|
||||
|
||||
Segment 10 of the supplied route9b recording shows measured turning persisting
|
||||
as requested right curvature falls. At about 643.0 s, before strong driver
|
||||
intervention, device-gyro curvature is approximately 0.01786/m against a
|
||||
0.01172/m request. Around 643.9 s, heading demand has reversed slightly but
|
||||
C0 still requests approximately +0.12 m into the turn. Strong column input
|
||||
starts around 643.852 s; later motion cannot establish autonomous recovery.
|
||||
Earlier light driver input also exists.
|
||||
|
||||
The outgoing CAN commands match preceding publications. All 70,937 decoded
|
||||
frames have zero C2/C3 and valid checksums. Focused exit diagnostics have fresh
|
||||
model/carState inputs and targets within normal quantization of the outputs.
|
||||
This supports trying less residual C0; it does not identify PSCM dynamics or
|
||||
prove C0 alone caused the physical oversteer.
|
||||
|
||||
## Change
|
||||
|
||||
C0 starts from the clipped current model offset at 7 m. When C0 and measured
|
||||
host yaw point in the same direction, compute:
|
||||
|
||||
```
|
||||
requested_yaw = max(0, sign(C0) * speed * desiredCurvature)
|
||||
excess = max(0, sign(C0) * yaw - requested_yaw - 0.02 rad/s)
|
||||
reduction = 7 m * 0.2 s * excess
|
||||
target = sign(C0) * max(0, abs(C0) - reduction)
|
||||
```
|
||||
|
||||
Opposing centering demand is unchanged. The correction cannot increase the
|
||||
target's magnitude or reverse its sign. Opposite-direction planned curvature
|
||||
cannot amplify a small yaw bias into a correction. Existing 4 m/s C0 slew still
|
||||
applies; this target bound is not a claim that every stateful output is smaller than a
|
||||
separate v1 controller after arbitrary direction reversals. C1 construction,
|
||||
clipping and slew are unchanged; C2=C3=0. Only C0/C1 slew states persist.
|
||||
There is no integral, model-history filter, turn state machine or fitted plant.
|
||||
|
||||
Host yaw is `-carState.yawRate`, as in the existing Ford call path. The
|
||||
0.02 rad/s deadband exceeds the approximately 0.008 rad/s offset measured
|
||||
against the device gyro on quiet straights. The 0.2 s scale is an initial
|
||||
engineering choice, not an identified delay or gain. Both remain physically
|
||||
unvalidated. Large biased or noisy yaw within the existing sanity gate can
|
||||
still attenuate useful centering; fixed-input replay cannot establish stability.
|
||||
|
||||
## Offline evidence
|
||||
|
||||
The complete 12-rlog route is replayed at original controls publication times,
|
||||
with exact consumed model geometry, causal carState, and carControl matched
|
||||
within 5 ms. These times proxy computation; full SubMaster health is unavailable.
|
||||
V1 reconstruction is within one field quantum of all 64,701 paired active
|
||||
publications. V2 has identical eligibility and exactly identical C1.
|
||||
|
||||
On 381.78 seconds of driver-clean low requests above 8 m/s, only 3 of 37,937 cycles
|
||||
change C0, each by one 0.01 m quantum. Across the 642.7–643.852 s exit window,
|
||||
C0 changes on all 115 cycles, averaging 0.080 m reduction. Entry/peak C0
|
||||
maximum stays 2.80 m; some entry-window samples decrease by up to 0.05 m.
|
||||
These are command comparisons on recorded inputs, not predicted tracking.
|
||||
Low request means requested lateral acceleration below 0.15 m/s²; it is a
|
||||
proxy for straight driving and does not establish a physically straight path.
|
||||
|
||||
The original routes90/95 also run through v2. Their zero-yaw baseline pass
|
||||
checks archived v1 compatibility; the measured-yaw adapter pass checks current
|
||||
construction, eligibility, field limits and packing. It is not an exact match
|
||||
to v1 or v8. Randomized testing checks the damping against an independent
|
||||
piecewise oracle, mirror symmetry, resets and slew, with real Float32/CAN
|
||||
round trips. See `ford_model_action_damping_validation.json` for counts and hashes.
|
||||
|
||||
Final validation passes 325 tests and 26 subtests, with 100% controller
|
||||
statement/branch coverage, 204,946 original route cycles and 628,030 CAN round
|
||||
trips. The module is 166 total lines, including 107 code lines excluding
|
||||
comments, blanks and docstrings. Standards and Spec reviews have no remaining findings.
|
||||
|
||||
## Reproduce
|
||||
|
||||
Use the dependency setup and suite command in the [drive-test guide](ford_model_action_drive_test.md).
|
||||
The new route replay requires the deployment opendbc pin recorded there:
|
||||
|
||||
```sh
|
||||
python -m tools.ford_pscm_lab.damping_replay /path/to/complete/rlogs --baseline v1 --candidate v2 --window segment10_entry_peak 637 640 --window segment10_exit_before_strong_input 642.7 643.852 --output /path/to/separate/results
|
||||
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260907 --opendbc-revision c21a9013700734dd20b09e05aa68329ad8cc20f9 --output /path/to/stress.json
|
||||
```
|
||||
|
||||
At the v2 revision, the same default-off Sunnylink toggle selects v2; no additional setting is
|
||||
introduced. Updating an installation with the toggle already enabled selects
|
||||
v2 at the next controlsd startup. `calibration_approved=false` remains explicit.
|
||||
No physical fix, hardware build or device boot is established by these checks.
|
||||
@@ -1,164 +0,0 @@
|
||||
{
|
||||
"date": "2026-09-07",
|
||||
"baseline_commit": "5fc16abc7662020706e29f57d31a6d5e2bc1293a",
|
||||
"deployment_target": {
|
||||
"repository": "sunnypilot/sunnypilot",
|
||||
"branch": "hiimisaac-dev"
|
||||
},
|
||||
"hypothesis": "model-action-c0-c1-yaw-damping-v2",
|
||||
"scope": "Experimental bounded offset damping; fixed-input offline evidence only.",
|
||||
"calibration_approved": false,
|
||||
"hardware_build_and_device_boot": "not performed",
|
||||
"controller_size": {
|
||||
"total_lines": 166,
|
||||
"code_lines_excluding_blanks_comments_docstrings": 107,
|
||||
"core_persistent_values": 2,
|
||||
"adapter_timestamps": 3
|
||||
},
|
||||
"checks": {
|
||||
"combined_ford_params_sunnylink_suite": "325 passed, 26 subtests passed; no skips",
|
||||
"suite_log_sha256": "69cb8ab40e93e00a9ff7b6ea1933e4c3554336746860efb53dad3b29f94fc9d3",
|
||||
"coverage": {
|
||||
"statements": 98,
|
||||
"branches": 28,
|
||||
"percent": 100.0
|
||||
},
|
||||
"ruff": "pass",
|
||||
"ty_controller_and_lab": "pass",
|
||||
"settings_compiler_check": "pass",
|
||||
"standards_review_remaining_findings": 0,
|
||||
"spec_review_remaining_findings": 0,
|
||||
"review_resolutions": [
|
||||
"Prevent opposite-direction planned curvature amplifying small yaw bias; eight new cases failed before the fix and passed after it.",
|
||||
"Relabel requested-acceleration cohort as low request; it does not establish physically straight driving."
|
||||
],
|
||||
"mutation_probe": "Disabling damping fails all four mirrored recorded-exit cases."
|
||||
},
|
||||
"stress": {
|
||||
"random_cycles": 200000,
|
||||
"mirrored_core_updates": 200000,
|
||||
"field_boundary_cases": 18138,
|
||||
"float32_can_round_trips": 218138,
|
||||
"bounded_excess_yaw_damping_checked": true,
|
||||
"opendbc_import_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9"
|
||||
},
|
||||
"routes": {
|
||||
"route90": {
|
||||
"cycles": 78812,
|
||||
"core_exact_archived_match": true,
|
||||
"adapter_active_cycles": 73055,
|
||||
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||
"adapter_validity_differs_from_archive_cycles": 0,
|
||||
"adapter_command_differs_from_archive_cycles": 2280,
|
||||
"adapter_max_absolute_command_difference_c0_c1": [
|
||||
0.1900000000000004,
|
||||
0.0020000000000000018
|
||||
],
|
||||
"float32_can_round_trips": 157624,
|
||||
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7"
|
||||
},
|
||||
"route95": {
|
||||
"cycles": 54738,
|
||||
"core_exact_archived_match": true,
|
||||
"adapter_active_cycles": 37614,
|
||||
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||
"adapter_validity_differs_from_archive_cycles": 0,
|
||||
"adapter_command_differs_from_archive_cycles": 2051,
|
||||
"adapter_max_absolute_command_difference_c0_c1": [
|
||||
0.22999999999999998,
|
||||
0.0010000000000000009
|
||||
],
|
||||
"float32_can_round_trips": 109476,
|
||||
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7"
|
||||
},
|
||||
"route9b": {
|
||||
"cycles": 71396,
|
||||
"eligible_cycles": 64701,
|
||||
"same_validity": true,
|
||||
"c1_exactly_unchanged": true,
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 142792,
|
||||
"v1_reconstruction_vs_recorded": {
|
||||
"paired_cycles": 64701,
|
||||
"within_one_quantum_cycles": 64701,
|
||||
"maximum_absolute_error_c0_c1": [
|
||||
0.010000114440917862,
|
||||
0.0005000143051147043
|
||||
]
|
||||
},
|
||||
"timing": "Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.",
|
||||
"baseline": "Current adapter with zero yaw retains v1 targets and actual-yaw sanity gate.",
|
||||
"opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||
"cohorts": {
|
||||
"driver_clean_low_request_above_8mps": {
|
||||
"cycles": 37937,
|
||||
"seconds": 381.77672216900055,
|
||||
"changed_c0_cycles": 3,
|
||||
"mean_absolute_c0_change_m": 8.419774473786704e-07,
|
||||
"max_absolute_c0_change_m": 0.009999999999999787,
|
||||
"v1_peak_absolute_c0_m": 0.08999999999999986,
|
||||
"v2_peak_absolute_c0_m": 0.08999999999999986
|
||||
},
|
||||
"segment10_entry_peak": {
|
||||
"cycles": 298,
|
||||
"seconds": 2.9932484459999387,
|
||||
"changed_c0_cycles": 100,
|
||||
"mean_absolute_c0_change_m": 0.007564164795694466,
|
||||
"max_absolute_c0_change_m": 0.04999999999999982,
|
||||
"v1_peak_absolute_c0_m": 2.8000000000000003,
|
||||
"v2_peak_absolute_c0_m": 2.8000000000000003
|
||||
},
|
||||
"segment10_exit_before_strong_input": {
|
||||
"cycles": 115,
|
||||
"seconds": 1.156770048999988,
|
||||
"changed_c0_cycles": 115,
|
||||
"mean_absolute_c0_change_m": 0.0799313220721162,
|
||||
"max_absolute_c0_change_m": 0.1200000000000001,
|
||||
"v1_peak_absolute_c0_m": 0.75,
|
||||
"v2_peak_absolute_c0_m": 0.7000000000000002
|
||||
}
|
||||
},
|
||||
"exit_c0_strictly_lower_on_all_115_cycles": true,
|
||||
"rlog_sha256_by_segment": {
|
||||
"0": "22746f7119109b73ed7f2c26ce8c99f87136e9124fb7fc14c9554409a28a7c3f",
|
||||
"1": "4c2e1d7083c31a2b37d0f8dd3be4d330898511b7e02c26f7d40ca9bc2779397d",
|
||||
"2": "62f3e049e220cd3681fadf386f2969537bd571998ae2f6ba2d08479428b5a28f",
|
||||
"3": "83bf0131b2d36b2ba7e5ba050bbc13c0a3350feb5c9b89dc9c87d3a37abebfb3",
|
||||
"4": "430985a80dd6e10f7abeb89457a17022e6bb6978617f415c905f584b1647603e",
|
||||
"5": "8c0c5ae6323ec33b3e14f84ca834f70cb56f6b29f471a350f1e3efc06b6ba553",
|
||||
"6": "db53dfa8156b9d66792c3eff0b2ce5d31b71ad41cc580dec85f528845593c184",
|
||||
"7": "91b0b3be10cb7d7d7f7dd2024d8f9ee99d1e9fd2204203a3a9a2f2f1c6e3fa03",
|
||||
"8": "687dbbfc49837efbfe8fa6bc091e40f7fad2908832234d7884f4616d1bc9ccff",
|
||||
"9": "a88ec4d25b04cdbf5844686fc77f6b28dca920c9b164e37ebf69844a3ae398fc",
|
||||
"10": "fe6b29580a6c94e1c236d13e18db4cd9f31cc1b25d52e1e6e19a5021125c9932",
|
||||
"11": "150d31b1944d7a1b8c562f3aee20b66cefa6c4e8d02660ec889907d835142f45"
|
||||
}
|
||||
}
|
||||
},
|
||||
"total_original_route_cycles": 204946,
|
||||
"total_float32_can_round_trips": 628030,
|
||||
"source_sha256": {
|
||||
"docs/ford_model_action_damping.md": "1aca8ca8e78d953beeda5b0c9803161a1d7c58556b1966040c71f809c3960bf8",
|
||||
"docs/ford_model_action_drive_test.md": "3ce8bf6cb511a4461fa7abf194f47020af7ef5a6c90fcae7ed09571d1f750846",
|
||||
"openpilot/selfdrive/controls/lib/ford_model_action.py": "59d66297a017557f3d4f28b115be3f6220b800566c11935e2284b3814783fb7e",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_controlsd_logging.py": "318742bd707ae526d0f5181bf7f081660c2c55de4bdf28518ccdf50d60e88080",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "de6f8524347f7c4a339941bc8565ccaa131cb93aa0418c75006ce08ab7edeb99",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_damping.py": "76466997ab4fef435f44339a6cb2d06303d5f0ab8717d487f27655297bd429d1",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui.json": "dbb78c98f57eef532f0dff0cb0b38396442882876e6d115f0d3c36159f64baf5",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "b92b55e23467e74227988fb39fff13a4ebba3c960fb90e34a6b341411699fc95",
|
||||
"tools/ford_pscm_lab/damping_replay.py": "2f52fef12116ce87c0a1f465cf13d6af5e9fd76ce05d4318d72e34b70bc6a11f",
|
||||
"tools/ford_pscm_lab/model_action_replay.py": "05a658dfcaf81693bf0d92184c0edff0172802f351a61a9e866a7967e74ae46d",
|
||||
"tools/ford_pscm_lab/stress_model_action.py": "3e308733f4af0101ad0c414fbd724f6a99f56269ed8e791f0e15526d8bfd8f17"
|
||||
},
|
||||
"artifact_sha256": {
|
||||
"route9b/report.json": "1b9317850c1724f433269c6a58349d0a0ee4eb6c9a03d6ec5858fa17713796c5",
|
||||
"route9b/commands.npz": "d9f553a5384c84416a27c52b5dda0e751dda25edc5d6620fc311977f34a7a946",
|
||||
"route90/report.json": "e37f71dc032e375b1c9b0beb4d6e0c915257bf72e59b0785ac0ca49c2472d2c2",
|
||||
"route95/report.json": "d42d5a080fef8a1f0b3c7ae2cabcad20c88ce01d91be07ab33770ff5948c06b6",
|
||||
"stress.json": "06e69a23340e3f5ed174e8e0b2e5791b320686dce7df963233d50a9982dca17b",
|
||||
"coverage.json": "8f7915b9bd884abedfdbc2e0c18ef4474737e27225a714414384242535cc396f",
|
||||
"mutation.txt": "67a76549fd7bb71e7092a155d4d0c3459be7b04dc2ca36eef9b54fbb574ad83d",
|
||||
"bias_regression_red.txt": "8903c5a967e8c376050db85f7cf5f73abf71ee972f6025b25eb874479f94c62c",
|
||||
"segment10_damping.png": "7d60cf9aabdcca9000fcf49bd14bd6130418aa6ab1b57af2678b171498ee9505"
|
||||
}
|
||||
}
|
||||
@@ -1,80 +1,83 @@
|
||||
# Ford model-point drive-test branch
|
||||
# Ford selected-action drive-test branch
|
||||
|
||||
The default-off **Selected-Action Path Tracking (Experimental)** Sunnylink toggle
|
||||
now selects the v7 model-point candidate on the **Ford CAN FD F-150 Lightning**.
|
||||
The stored key remains `FordModelActionController`; an already enabled setting
|
||||
selects this revision after updating and completing an offroad-to-onroad cycle.
|
||||
|
||||
The controller reads **model lateral position and model heading at the same
|
||||
point**. Start with the model's predicted distance at one second, enforce the
|
||||
existing seven-metre minimum, and hold the available endpoint when necessary.
|
||||
C0 is that point's lateral position in metres; C1 is its unwrapped heading in
|
||||
radians. C2 and C3 stay zero. The 150 ms yaw-based forecast and the reconstruction
|
||||
of heading from selected curvature are removed.
|
||||
|
||||
This point choice is an engineering guess, not an identified Ford reference or
|
||||
PSCM calibration. `calibration_approved=false`: offline tests do not establish
|
||||
physical tracking, turn-exit behavior, or stability across different PSCMs.
|
||||
See [the model-point decision and validation](ford_model_points.md).
|
||||
The candidate is selectable on the **Ford CAN FD F-150 Lightning** behind
|
||||
its own persistent, default-off Sunnylink toggle. The command law and input
|
||||
gates from the [offline candidate](ford_model_action_candidate.md) are unchanged.
|
||||
`calibration_approved=false`: offline checks do not establish physical tracking,
|
||||
turn-exit behavior or closed-loop stability.
|
||||
|
||||
## Select and restore
|
||||
|
||||
1. Install branch `hiimisaac-dev` from `sunnypilot/sunnypilot` and allow the build
|
||||
to finish.
|
||||
2. While offroad, open Sunnylink device settings → Vehicle → Ford. Keep or enable
|
||||
**Selected-Action Path Tracking (Experimental)**.
|
||||
3. Complete a real offroad-to-onroad cycle. `card` snapshots the toggle into
|
||||
`CarParamsSP`; the sender and `controlsd` share that selection. Changing a
|
||||
stored toggle or disengaging alone cannot swap an active controller.
|
||||
1. Install branch `hiimisaac-dev` from
|
||||
`sunnypilot/sunnypilot` on the device using your normal branch-switch process.
|
||||
Allow its build to finish before changing the setting.
|
||||
2. While offroad, open Sunnylink device settings → Vehicle → Ford and enable
|
||||
**Selected-Action Path Tracking (Experimental)** (`FordModelActionController`).
|
||||
3. Complete a real offroad-to-onroad cycle. Selection occurs when `controlsd`
|
||||
starts; changing a stored toggle or disengaging alone cannot swap an active
|
||||
controller. Initial physical evaluation remains controlled testing.
|
||||
|
||||
The startup event `Ford path controller selected` reports
|
||||
`FordModelActionController`. Periodic `Ford C2-free path tracking` events report
|
||||
`hypothesis=model-pose-one-second-v7`, `pose_source=model`, `preview_time_s=1.0`
|
||||
and `minimum_station_m=7.0`, plus input ages, slew state and the command tuple.
|
||||
Selected desired curvature is still logged, but no longer constructs C0/C1.
|
||||
The startup log event `Ford path controller selected` should report
|
||||
`FordModelActionController`. Periodic `Ford C2-free path tracking` events
|
||||
identify `hypothesis=model-action-c0-c1-v1` and report the command tuple.
|
||||
|
||||
Turning the toggle off and completing another offroad-to-onroad cycle restores
|
||||
**PSCM Coefficient Observer** if selected, otherwise the original Ford path
|
||||
controller. Other vehicles keep their previous selection. The retired v8 toggle
|
||||
cannot select this candidate.
|
||||
Turning the new toggle off and completing another offroad-to-onroad cycle
|
||||
restores **PSCM Coefficient Observer** if selected, otherwise the original
|
||||
Ford path controller. The stored observer selection is preserved. The candidate
|
||||
takes priority on the supported vehicle, independently of EPS firmware query
|
||||
results. Other vehicles retain their existing selection.
|
||||
|
||||
## Wiring and limits
|
||||
The v8 implementation, its Sunnylink toggle and its dedicated tests are removed.
|
||||
A leftover `FordVirtualAngleController=1` file cannot enable the new controller.
|
||||
The shared Float32/CAN rounding helper now lives in `ford_model_action.py`;
|
||||
unused v8 PSCM-feedback plumbing is removed. Historical v8 route evidence remains
|
||||
in Git history and the archived validation documents.
|
||||
|
||||
Both model fields must have matching, finite, strictly increasing time arrays
|
||||
starting at zero. Malformed geometry, stale required services, invalid timing,
|
||||
or disengagement resets both actuator states. Freshness still requires model,
|
||||
car-state and reference publications no older than 150 ms, with at most 5 ms
|
||||
future skew. The valid control timestep remains 2–100 ms.
|
||||
## Wiring and validation
|
||||
|
||||
Only the two unquantized C0/C1 slew positions persist in the core. Field caps are
|
||||
±5.11 m / ±0.5 rad and slew rates are 4 m/s / 0.5 rad/s. Calculation stays at
|
||||
100 Hz; the existing [cadence experiment](ford_model_action_cadence.md) sends this
|
||||
candidate at 20 Hz. Float32 publication, host-to-wire negation, packing and
|
||||
Panda safety are unchanged. The opendbc pin remains
|
||||
`87ca78e6e641eefb2d654f260a6ab08df3058bd5`.
|
||||
`Controls.__init__` selects the candidate once at startup. It shares the
|
||||
existing Ford call path, selected upstream-limited curvature, service gates,
|
||||
invalid-output disengagement, Float32 publication and downstream CAN builder.
|
||||
C2 and C3 stay zero. No opendbc pointer or Panda safety change is included.
|
||||
|
||||
Normal operation uses the model's point directly. The upstream scalar curvature
|
||||
and its clipping still exist for logging/other controllers, but no longer bound
|
||||
this candidate's heading target. Its C0/C1 field caps and slew still apply;
|
||||
passing Panda TX checks does not establish an actual vehicle acceleration bound.
|
||||
Lateral maneuver test mode supplies only a scalar curvature, not a model pose.
|
||||
It explicitly invalidates/disengages this candidate as `unsupported_reference`.
|
||||
Optional measured motion is no longer a command input.
|
||||
Sunnylink publishes the toggle through its generated settings schema and
|
||||
writes the registered Boolean through the existing parameter endpoint. The
|
||||
offroad UI rule and `needs_onroad_cycle` metadata describe when it can be
|
||||
changed and when it takes effect. An onroad backend write changes storage
|
||||
only; the controller continues using its startup selection.
|
||||
|
||||
## Reproduce offline checks
|
||||
Native validation also exposed a pre-existing `params_keys_by_flag` bug:
|
||||
every returned buffer referenced the same reusable string. Sunnylink backup
|
||||
key enumeration could therefore return corrupted names. The bridge now
|
||||
returns separate strings owned by the parameter handle. Regression tests
|
||||
check distinct registered keys across flags, and toggle tests check its
|
||||
persistence and backup registration using the rebuilt native library.
|
||||
|
||||
Initialize the pinned submodule and build the project's native Python dependencies:
|
||||
The current validation record is `ford_model_action_drive_test_validation.json`.
|
||||
The final combined Ford, Params and Sunnylink suite passes **284 tests and
|
||||
26 subtests**, with no skips. The candidate has **100% statement and branch
|
||||
coverage** (87 statements, 26 branches). Ruff, Ty, settings compilation and
|
||||
both review axes pass. The fresh route/stress runs check **485,238 Float32/CAN
|
||||
round trips**, including 200,000 randomized and 200,000 mirrored core updates.
|
||||
The controller is 145 total lines, including 95 code lines excluding comments,
|
||||
blanks and docstrings; v8's 469-line module is removed.
|
||||
|
||||
The previous 133,550-cycle route reconstruction, 485,238 packing round trips
|
||||
and mutation probes remain recorded separately in
|
||||
`ford_model_action_validation.json` at the offline-stage source hashes.
|
||||
|
||||
## Reproduce deployment checks
|
||||
|
||||
Initialize the branch's exact opendbc submodule (`c21a9013700734dd20b09e05aa68329ad8cc20f9`)
|
||||
and build the native Params library from this branch before testing.
|
||||
|
||||
```sh
|
||||
git submodule update --init opendbc_repo
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
export PYTHONPATH=.:opendbc_repo
|
||||
python -m pytest -q openpilot/selfdrive/controls/tests/test_ford*.py tools/ford_pscm_lab opendbc_repo/opendbc/car/ford/tests/test_ford.py openpilot/sunnypilot/sunnylink/tests openpilot/sunnypilot/mads/tests openpilot/selfdrive/car/tests/test_ford_pscm_status.py openpilot/common/tests/test_params.py opendbc_repo/opendbc/safety/tests/test_ford.py
|
||||
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260908 --opendbc-revision 87ca78e6e641eefb2d654f260a6ab08df3058bd5 --output .cache/ford_model_points/stress.json
|
||||
python -m pytest -q -p no:cacheprovider openpilot/selfdrive/controls/tests/test_ford_*.py tools/ford_pscm_lab openpilot/selfdrive/car/tests/test_ford_pscm_status.py openpilot/sunnypilot/sunnylink/tests openpilot/common/tests/test_params.py opendbc_repo/opendbc/car/ford/tests/test_ford.py
|
||||
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260907 --opendbc-revision c21a9013700734dd20b09e05aa68329ad8cc20f9 --output .cache/ford_model_action_drive_test/stress.json
|
||||
```
|
||||
|
||||
Historical v1–v6 validation files retain their original source hashes and apply
|
||||
to those revisions. In particular, `ford_model_action_measured_pose_validation.json`
|
||||
describes v6, not the current model-point mapping. The hardware build and device
|
||||
boot are not performed by these offline checks. Pushing a branch does not update
|
||||
a device or change its stored settings.
|
||||
The full hardware build and device boot are not performed by these offline
|
||||
tests. Installing the branch and enabling the toggle are separate actions;
|
||||
pushing the branch does not change a device's selected software or settings.
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
# Experimental Ford full path prediction, v4
|
||||
|
||||
This document archives v4. The [current v5 controller](ford_model_action_no_yaw_damping.md)
|
||||
retains this prediction and removes yaw damping.
|
||||
|
||||
V4 removes the extra 15 cm / 25% limit on the geometric prediction introduced
|
||||
in [v3](ford_model_action_prediction.md). Those numbers were hand-chosen tuning
|
||||
bounds, not identified Ford response limits. The current user request is to
|
||||
remove that restriction; the existing default-off Sunnylink toggle remains.
|
||||
|
||||
The controller now uses the full predicted offset from the same model path,
|
||||
assuming 150 ms of motion along the selected, upstream-limited curvature. The
|
||||
150 ms horizon remains an engineering assumption. Available model geometry
|
||||
still limits the prediction distance, with endpoint hold for short paths and
|
||||
fallback to the valid base offset if prediction arithmetic is nonfinite.
|
||||
|
||||
The existing total command limits (C0 ±5.11 m, C1 ±0.5 rad), independent slew
|
||||
rates (4 m/s, 0.5 rad/s), quantization, yaw damping, input/service gates and
|
||||
zero C2/C3 remain unchanged. Only two control states persist. No integrator,
|
||||
model history, extra toggle or PSCM feedback loop is added.
|
||||
|
||||
Removing the adjustment cap also permits the predicted C0 to oppose the
|
||||
original offset or become nonzero from a zero original offset. For example,
|
||||
a straight path with a nonzero selected turn request can have an opposing
|
||||
future-frame offset. Tests cover that behavior, mirrored turn releases,
|
||||
return to zero, and unchanged slew; sign preservation of the original C0 is
|
||||
no longer claimed. The yaw damper still cannot reverse its input target.
|
||||
|
||||
## Evidence and interpretation
|
||||
|
||||
The PSCM reports a generic `LimitReached` state. It does not tell us whether
|
||||
an incoming target is geometrically correct or well timed. Its internal
|
||||
limits cannot establish the tracking performance of this predictor. Removal
|
||||
is an experiment supported by command comparisons, not by an assumption that
|
||||
the PSCM will correct an excessive or mistimed request.
|
||||
|
||||
Compared with capped v3 on identical recorded inputs:
|
||||
|
||||
| Interval | Effect of removing the extra cap |
|
||||
| --- | --- |
|
||||
| Latest tight-left entry, 173–175.4 s | Mean C0 magnitude +0.032 m, maximum change 0.07 m |
|
||||
| Earlier right entry, 637–640 s | Mean magnitude +0.028 m, maximum change 0.10 m |
|
||||
| Earlier right exit, 642.7–643.852 s | All 115 commands unchanged |
|
||||
| Driver-clean low requests above 8 m/s, routes9b/9e | Mean absolute change 0.0018 / 0.0014 m; maximum 0.02 m |
|
||||
| All eligible samples on either recent route | Maximum absolute command change 0.13 m |
|
||||
|
||||
C1 and command eligibility are exactly identical to v3 on both recent routes.
|
||||
Some command signs change near zero: this is an intended consequence of using
|
||||
the full transform, not proof those corrections improve driving. Entry windows
|
||||
include driver input, reported in the validation record. The magnitude changes
|
||||
above describe controller C0, not measured lateral vehicle displacement.
|
||||
|
||||
The earlier v1 archive is also reproduced exactly on routes90/95, separately
|
||||
from the current controller pass. All replay uses original timestamps;
|
||||
publication times proxy computation, exact consumed model frames and causal
|
||||
carState are retained, and complete SubMaster health is unavailable. The
|
||||
recorded model and vehicle motion remain fixed. There is no measured physical
|
||||
improvement, stability result or new desired-versus-actual steering trajectory.
|
||||
|
||||
Final validation passes 374 tests and 26 subtests with 100% controller statement
|
||||
and branch coverage, 299,604 original route cycles and 817,346 Float32/CAN
|
||||
round trips. The controller is 190 total lines / 122 code lines excluding
|
||||
blanks, comments and docstrings; two control states persist.
|
||||
|
||||
See `ford_model_action_full_prediction_validation.json` for final test counts,
|
||||
coverage, dependency pins, source hashes and route/packing results. The initial
|
||||
uncapped variant was evaluated in a separate lab file before editing production;
|
||||
final route checks execute the production v4 source.
|
||||
|
||||
## Reproduce and select
|
||||
|
||||
Use the dependencies and combined suite command in the
|
||||
[drive-test guide](ford_model_action_drive_test.md). For the recent routes:
|
||||
|
||||
```sh
|
||||
python -m tools.ford_pscm_lab.damping_replay /path/to/route9e/rlogs --baseline v3 --candidate current --window left_entry 173 175.4 --window left_peak 175.4 178.3 --window left_exit 178.3 180.5 --window reversal 728 734 --output /path/to/separate/route9e-results
|
||||
python -m tools.ford_pscm_lab.damping_replay /path/to/route9b/rlogs --baseline v3 --candidate current --window right_entry 637 640 --window right_exit 642.7 643.852 --output /path/to/separate/route9b-results
|
||||
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260907 --opendbc-revision c21a9013700734dd20b09e05aa68329ad8cc20f9 --output /path/to/stress.json
|
||||
```
|
||||
|
||||
The same **Selected-Action Path Tracking (Experimental)** toggle selects v4
|
||||
on the CAN FD F-150 Lightning. An installation with the toggle already enabled
|
||||
selects v4 after updating and restarting controlsd. Diagnostics identify
|
||||
`model-action-c0-c1-prediction-v4`; `calibration_approved=false` remains explicit.
|
||||
Deployment branch: `sunnypilot/sunnypilot`, `hiimisaac-dev`. This work does not
|
||||
install software on the device or change its settings.
|
||||
@@ -1,799 +0,0 @@
|
||||
{
|
||||
"date": "2026-09-07",
|
||||
"baseline_commit": "01f8d51c82b3e863f1012d383b5994813ef01b81",
|
||||
"hypothesis": "model-action-c0-c1-prediction-v4",
|
||||
"scope": "Removal of only the extra prediction adjustment cap; no physical tracking or stability claim.",
|
||||
"deployment_target": {
|
||||
"repository": "sunnypilot/sunnypilot",
|
||||
"branch": "hiimisaac-dev"
|
||||
},
|
||||
"calibration_approved": false,
|
||||
"hardware_build_and_device_boot": "not performed",
|
||||
"controller_size": {
|
||||
"total_lines": 190,
|
||||
"code_lines_excluding_blanks_comments_docstrings": 122,
|
||||
"core_persistent_values": 2,
|
||||
"adapter_timestamps": 3
|
||||
},
|
||||
"checks": {
|
||||
"combined_ford_params_sunnylink_suite": "374 passed, 26 subtests passed; no skips",
|
||||
"coverage": {
|
||||
"covered_lines": 113,
|
||||
"num_statements": 113,
|
||||
"percent_covered": 100.0,
|
||||
"percent_covered_display": "100",
|
||||
"missing_lines": 0,
|
||||
"excluded_lines": 0,
|
||||
"percent_statements_covered": 100.0,
|
||||
"percent_statements_covered_display": "100",
|
||||
"num_branches": 32,
|
||||
"num_partial_branches": 0,
|
||||
"covered_branches": 32,
|
||||
"missing_branches": 0,
|
||||
"percent_branches_covered": 100.0,
|
||||
"percent_branches_covered_display": "100"
|
||||
},
|
||||
"ruff": "pass",
|
||||
"ty_controller_and_lab": "pass",
|
||||
"settings_compiler_check": "pass",
|
||||
"cap_removal_red_probe": "11 tests fail with the v3 cap present; all 40 prediction tests pass after removal.",
|
||||
"standards_review_remaining_findings": 0,
|
||||
"spec_review_remaining_findings": 0,
|
||||
"independent_review_verification": "Each reviewer passed 172 focused tests and verified source/artifact hashes and route/packing totals."
|
||||
},
|
||||
"stress": {
|
||||
"seed": 20260907,
|
||||
"random_cycles": 200000,
|
||||
"mirrored_core_updates": 200000,
|
||||
"invalid_or_inactive_resets": 3537,
|
||||
"field_boundary_cases": 18138,
|
||||
"float32_can_round_trips": 218138,
|
||||
"analytic_targets_scalar_slew_and_mirror_checks_pass": true,
|
||||
"bounded_excess_yaw_damping_checked": true,
|
||||
"full_geometric_prediction_checked": true,
|
||||
"direct_raw_float32_packing_matches_host_output": true,
|
||||
"max_continuous_step_c0_c1": [
|
||||
0.400000000000329,
|
||||
0.05000000000000002
|
||||
],
|
||||
"calibration_approved": false,
|
||||
"scope": "Numerical construction only; no PSCM response or closed-loop performance claims.",
|
||||
"opendbc_import_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/stress_model_action.py": "66adef6cba120a1a3d8e9730ea987ef49f8fe283023362dbb99608dee0e3229f",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "7cddac7ce9e88cc32bc7afbec7f9df79b66bb4dfa2fa5f9b36654891fae23a19"
|
||||
}
|
||||
},
|
||||
"routes": {
|
||||
"route90": {
|
||||
"scope": "Command construction and adapter reconstruction only; no counterfactual closed-loop score.",
|
||||
"calibration_approved": false,
|
||||
"executes_live_selector": false,
|
||||
"cycles": 78812,
|
||||
"core_active_cycles": 73055,
|
||||
"core_exact_archived_match": true,
|
||||
"cohorts_reproduced": true,
|
||||
"adapter_active_cycles": 73055,
|
||||
"adapter_status_counts": {
|
||||
"inactive": 5757,
|
||||
"active": 73055
|
||||
},
|
||||
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||
"core_active_path_shorter_than_7m_cycles": 0,
|
||||
"adapter_validity_differs_from_archive_cycles": 0,
|
||||
"adapter_command_differs_from_archive_cycles": 42170,
|
||||
"adapter_max_absolute_command_difference_c0_c1": [
|
||||
0.33999999999999986,
|
||||
0.0020000000000000018
|
||||
],
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 157624,
|
||||
"timing": "Original controls publication timestamps proxy computation time; repeated frames and gaps retained. No identified delay.",
|
||||
"eligibility": "Adapter checks recorded services independently; full SubMaster health is unavailable. Core uses archived validity.",
|
||||
"reference": "Recorded controlsState.desiredCurvature, already selected/limited. These two routes have no maneuver publications.",
|
||||
"host_yaw": "Extract cs.yaw equals -carState.yawRate; current adapter uses it for bounded damping.",
|
||||
"archived_core_revision": "5fc16abc7662020706e29f57d31a6d5e2bc1293a",
|
||||
"archived_core_source_sha256": "8f3bc5d68e0051776f614a2ccffae84a88f7898dc95bdc12c23dcfe10dfe676a",
|
||||
"cohorts": {
|
||||
"small_request": {
|
||||
"seconds": 475.24161910000026,
|
||||
"core_c0_c1_rms": [
|
||||
0.06125184365338734,
|
||||
0.0057692154738982855
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.057970374224682646,
|
||||
0.007060795713496332
|
||||
],
|
||||
"adapter_eligible_seconds": 475.24161910000026,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.0666350702799088,
|
||||
0.0057692154738982855
|
||||
]
|
||||
},
|
||||
"turn": {
|
||||
"seconds": 47.85871389900012,
|
||||
"core_c0_c1_rms": [
|
||||
0.18725251016049418,
|
||||
0.046449510236699354
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.4773534845264775,
|
||||
0.04315935101392594
|
||||
],
|
||||
"adapter_eligible_seconds": 47.85871389900012,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.21255148222786066,
|
||||
0.046449510236699354
|
||||
]
|
||||
},
|
||||
"small_speed_2_8": {
|
||||
"seconds": 38.696717235999785,
|
||||
"core_c0_c1_rms": [
|
||||
0.209264275431127,
|
||||
0.014019080484620104
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.1593558816527209,
|
||||
0.01823849401244768
|
||||
],
|
||||
"adapter_eligible_seconds": 38.696717235999785,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.22422047076469706,
|
||||
0.014019080484620104
|
||||
]
|
||||
},
|
||||
"turn_speed_2_8": {
|
||||
"seconds": 0.30159887100000304,
|
||||
"core_c0_c1_rms": [
|
||||
2.090789090165622,
|
||||
0.21878942570759013
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
2.155728831937131,
|
||||
0.38375302254248855
|
||||
],
|
||||
"adapter_eligible_seconds": 0.30159887100000304,
|
||||
"adapter_c0_c1_rms": [
|
||||
2.2977717861671105,
|
||||
0.21878942570759013
|
||||
]
|
||||
},
|
||||
"small_speed_8_15": {
|
||||
"seconds": 79.2599167590001,
|
||||
"core_c0_c1_rms": [
|
||||
0.02250905681378238,
|
||||
0.005843904113223639
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.03477379324171293,
|
||||
0.006545440153133947
|
||||
],
|
||||
"adapter_eligible_seconds": 79.2599167590001,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.027461818062006878,
|
||||
0.005843904113223639
|
||||
]
|
||||
},
|
||||
"turn_speed_8_15": {
|
||||
"seconds": 0.022264622000001566,
|
||||
"core_c0_c1_rms": [
|
||||
0.20999999999999996,
|
||||
0.04349999999999998
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.25,
|
||||
0.04050000011920929
|
||||
],
|
||||
"adapter_eligible_seconds": 0.022264622000001566,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.20999999999999996,
|
||||
0.04349999999999998
|
||||
]
|
||||
},
|
||||
"small_speed_15_55": {
|
||||
"seconds": 357.2849851050004,
|
||||
"core_c0_c1_rms": [
|
||||
0.011622097035118838,
|
||||
0.003925571829874466
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.03809717934501699,
|
||||
0.0045587590980714285
|
||||
],
|
||||
"adapter_eligible_seconds": 357.2849851050004,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.017138026451004092,
|
||||
0.003925571829874466
|
||||
]
|
||||
},
|
||||
"turn_speed_15_55": {
|
||||
"seconds": 47.53485040600012,
|
||||
"core_c0_c1_rms": [
|
||||
0.08686835454709245,
|
||||
0.043216347944464766
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.4471065308273131,
|
||||
0.030663943784303503
|
||||
],
|
||||
"adapter_eligible_seconds": 47.53485040600012,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.10939067791228956,
|
||||
0.043216347944464766
|
||||
]
|
||||
},
|
||||
"pose_quiet": {
|
||||
"seconds": 427.99642020700026,
|
||||
"core_c0_c1_rms": [
|
||||
0.01261133542366393,
|
||||
0.00422389167351435
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.03724956081568198,
|
||||
0.0046880581807907966
|
||||
],
|
||||
"adapter_eligible_seconds": 427.99642020700026,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.017590935519277658,
|
||||
0.00422389167351435
|
||||
]
|
||||
}
|
||||
},
|
||||
"workspace_head": "01f8d51c82b3e863f1012d383b5994813ef01b81",
|
||||
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||
"opendbc_import_path": "/Users/ibpersonal/.codex/worktrees/b926/sunnypilot/opendbc_repo",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "7cddac7ce9e88cc32bc7afbec7f9df79b66bb4dfa2fa5f9b36654891fae23a19",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_path.py": "383538fc7cdae3bc28dffb71fe12ac5f3f9866ffbe6adfb7457f3593e9fc903a",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/route.npz": "51e8c26eedde253e171af47d704c1967ba45ae6825d883393bec1fb9e00251c1",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/metadata.json": "742afec55ed629155b22d387f376f878f7b2765221fe94f515a829052bdd916f",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/encoder_comparison.npz": "7a625d3ed5cbd8013d1028aa3bc421740551dcae5c3d60981208bd047af9794c",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/encoder_comparison.json": "64e1cc4be84394ac7ec408d383b99b48ada9b3fd129cdef2fd846e5fac620d66",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/pose_candidate/pose_replay.npz": "4457ccc0868354749da5b72c1dea0faf750f783dfdc87038101288fdcba1e707"
|
||||
}
|
||||
},
|
||||
"route95": {
|
||||
"scope": "Command construction and adapter reconstruction only; no counterfactual closed-loop score.",
|
||||
"calibration_approved": false,
|
||||
"executes_live_selector": false,
|
||||
"cycles": 54738,
|
||||
"core_active_cycles": 37614,
|
||||
"core_exact_archived_match": true,
|
||||
"cohorts_reproduced": true,
|
||||
"adapter_active_cycles": 37614,
|
||||
"adapter_status_counts": {
|
||||
"inactive": 17124,
|
||||
"active": 37614
|
||||
},
|
||||
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||
"core_active_path_shorter_than_7m_cycles": 44,
|
||||
"adapter_validity_differs_from_archive_cycles": 0,
|
||||
"adapter_command_differs_from_archive_cycles": 25912,
|
||||
"adapter_max_absolute_command_difference_c0_c1": [
|
||||
0.2999999999999998,
|
||||
0.0010000000000000009
|
||||
],
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 109476,
|
||||
"timing": "Original controls publication timestamps proxy computation time; repeated frames and gaps retained. No identified delay.",
|
||||
"eligibility": "Adapter checks recorded services independently; full SubMaster health is unavailable. Core uses archived validity.",
|
||||
"reference": "Recorded controlsState.desiredCurvature, already selected/limited. These two routes have no maneuver publications.",
|
||||
"host_yaw": "Extract cs.yaw equals -carState.yawRate; current adapter uses it for bounded damping.",
|
||||
"archived_core_revision": "5fc16abc7662020706e29f57d31a6d5e2bc1293a",
|
||||
"archived_core_source_sha256": "8f3bc5d68e0051776f614a2ccffae84a88f7898dc95bdc12c23dcfe10dfe676a",
|
||||
"cohorts": {
|
||||
"small_request": {
|
||||
"seconds": 160.93427628100014,
|
||||
"core_c0_c1_rms": [
|
||||
0.027283284164810895,
|
||||
0.006054573235641941
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.04189890174428264,
|
||||
0.0075342351227490395
|
||||
],
|
||||
"adapter_eligible_seconds": 160.93427628100014,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.03232621215993653,
|
||||
0.006054573235641941
|
||||
]
|
||||
},
|
||||
"turn": {
|
||||
"seconds": 58.39837079799993,
|
||||
"core_c0_c1_rms": [
|
||||
0.5425346915921071,
|
||||
0.09102985199238507
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.63700479987207,
|
||||
0.09691236120507696
|
||||
],
|
||||
"adapter_eligible_seconds": 58.39837079799993,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.5674989495695087,
|
||||
0.09102985199238507
|
||||
]
|
||||
},
|
||||
"small_speed_2_8": {
|
||||
"seconds": 8.083969085000064,
|
||||
"core_c0_c1_rms": [
|
||||
0.09941192804175551,
|
||||
0.016572084086884566
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.07597383189748476,
|
||||
0.016403469943482765
|
||||
],
|
||||
"adapter_eligible_seconds": 8.083969085000064,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.11170387210996009,
|
||||
0.016572084086884566
|
||||
]
|
||||
},
|
||||
"turn_speed_2_8": {
|
||||
"seconds": 2.9056519089999995,
|
||||
"core_c0_c1_rms": [
|
||||
2.286598281178436,
|
||||
0.3420570411209625
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
2.4371443775668484,
|
||||
0.37360610108051345
|
||||
],
|
||||
"adapter_eligible_seconds": 2.9056519089999995,
|
||||
"adapter_c0_c1_rms": [
|
||||
2.3669481787447944,
|
||||
0.3420570411209625
|
||||
]
|
||||
},
|
||||
"small_speed_8_15": {
|
||||
"seconds": 32.021085868,
|
||||
"core_c0_c1_rms": [
|
||||
0.028387860150467858,
|
||||
0.0067602044219821
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.036941169841952046,
|
||||
0.007349671742584858
|
||||
],
|
||||
"adapter_eligible_seconds": 32.021085868,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.033317964465740625,
|
||||
0.0067602044219821
|
||||
]
|
||||
},
|
||||
"turn_speed_8_15": {
|
||||
"seconds": 22.160645306000006,
|
||||
"core_c0_c1_rms": [
|
||||
0.28668316323581683,
|
||||
0.0671915746138579
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.3391022553367454,
|
||||
0.07297678510653233
|
||||
],
|
||||
"adapter_eligible_seconds": 22.160645306000006,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.32040564199313326,
|
||||
0.0671915746138579
|
||||
]
|
||||
},
|
||||
"small_speed_15_55": {
|
||||
"seconds": 120.82922132800007,
|
||||
"core_c0_c1_rms": [
|
||||
0.010802222799973712,
|
||||
0.004282512931613862
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.03987958989535526,
|
||||
0.0065793876512313535
|
||||
],
|
||||
"adapter_eligible_seconds": 120.82922132800007,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.016212091011218406,
|
||||
0.004282512931613862
|
||||
]
|
||||
},
|
||||
"turn_speed_15_55": {
|
||||
"seconds": 33.332073582999925,
|
||||
"core_c0_c1_rms": [
|
||||
0.07257996778378971,
|
||||
0.03628926246224237
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.3416081588451539,
|
||||
0.027322786376822172
|
||||
],
|
||||
"adapter_eligible_seconds": 33.332073582999925,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.08724830487921002,
|
||||
0.03628926246224237
|
||||
]
|
||||
},
|
||||
"pose_quiet": {
|
||||
"seconds": 145.24136911700015,
|
||||
"core_c0_c1_rms": [
|
||||
0.012566204405760934,
|
||||
0.004614194153565963
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.03855743841849719,
|
||||
0.006621274926345545
|
||||
],
|
||||
"adapter_eligible_seconds": 145.24136911700015,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.01710948709543248,
|
||||
0.004614194153565963
|
||||
]
|
||||
}
|
||||
},
|
||||
"workspace_head": "01f8d51c82b3e863f1012d383b5994813ef01b81",
|
||||
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||
"opendbc_import_path": "/Users/ibpersonal/.codex/worktrees/b926/sunnypilot/opendbc_repo",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "7cddac7ce9e88cc32bc7afbec7f9df79b66bb4dfa2fa5f9b36654891fae23a19",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_path.py": "383538fc7cdae3bc28dffb71fe12ac5f3f9866ffbe6adfb7457f3593e9fc903a",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/route.npz": "6e5438867ea618e53ca395b60ff4b9b146256f1bf6f07dc8e92d663286074154",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/metadata.json": "f7c95a102e0e084bbcc9938399219080c1d3b26274b4ea3c3a9514ff7dc8ea8d",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/encoder_comparison.npz": "23bd05c4b6400299844acaba1d97051c96c23c682bf17b46e89e7f2cca5fce38",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/encoder_comparison.json": "1ab24a796d14817b8672ea6dfd39711d65cb06744eaaef7dcc02a840c392c4d3",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/pose_candidate/pose_replay.npz": "9cfbb3c6f0b1fdb7e3d38e6b64b8c94cffbcc9fabe41f6344d84a9b657b6af9b"
|
||||
}
|
||||
},
|
||||
"route9b": {
|
||||
"scope": "Fixed-input command replay only; no physical improvement or stability claim.",
|
||||
"calibration_approved": false,
|
||||
"cycles": 82386,
|
||||
"eligible_cycles": 70703,
|
||||
"status_counts": {
|
||||
"inactive": 11683,
|
||||
"active": 70703
|
||||
},
|
||||
"c1_exactly_unchanged": true,
|
||||
"same_validity": true,
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 164772,
|
||||
"cohorts": {
|
||||
"eligible": {
|
||||
"cycles": 70703,
|
||||
"seconds": 711.571703289,
|
||||
"changed_c0_cycles": 10475,
|
||||
"mean_absolute_c0_change_m": 0.00219777369954635,
|
||||
"max_absolute_c0_change_m": 0.13000000000000078,
|
||||
"increased_absolute_c0_cycles": 9822,
|
||||
"decreased_absolute_c0_cycles": 633,
|
||||
"driver_input_percent": 3.0297623863274845,
|
||||
"baseline_peak_absolute_c0_m": 3.96,
|
||||
"candidate_peak_absolute_c0_m": 3.99
|
||||
},
|
||||
"driver_clean": {
|
||||
"cycles": 63267,
|
||||
"seconds": 636.7469998759999,
|
||||
"changed_c0_cycles": 8901,
|
||||
"mean_absolute_c0_change_m": 0.0014713899370902674,
|
||||
"max_absolute_c0_change_m": 0.05999999999999961,
|
||||
"increased_absolute_c0_cycles": 8314,
|
||||
"decreased_absolute_c0_cycles": 568,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 2.24,
|
||||
"candidate_peak_absolute_c0_m": 2.24
|
||||
},
|
||||
"driver_clean_low_request_above_8mps": {
|
||||
"cycles": 40656,
|
||||
"seconds": 409.13067679800054,
|
||||
"changed_c0_cycles": 7132,
|
||||
"mean_absolute_c0_change_m": 0.0017587822497730527,
|
||||
"max_absolute_c0_change_m": 0.020000000000000462,
|
||||
"increased_absolute_c0_cycles": 6888,
|
||||
"decreased_absolute_c0_cycles": 244,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 0.11000000000000032,
|
||||
"candidate_peak_absolute_c0_m": 0.11000000000000032
|
||||
},
|
||||
"turn": {
|
||||
"cycles": 5459,
|
||||
"seconds": 54.939139939000256,
|
||||
"changed_c0_cycles": 119,
|
||||
"mean_absolute_c0_change_m": 0.00021719119817404867,
|
||||
"max_absolute_c0_change_m": 0.010000000000000675,
|
||||
"increased_absolute_c0_cycles": 119,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 2.24,
|
||||
"candidate_peak_absolute_c0_m": 2.24
|
||||
},
|
||||
"right_entry": {
|
||||
"cycles": 298,
|
||||
"seconds": 2.9932484459999387,
|
||||
"changed_c0_cycles": 216,
|
||||
"mean_absolute_c0_change_m": 0.028050765542768245,
|
||||
"max_absolute_c0_change_m": 0.10000000000000009,
|
||||
"increased_absolute_c0_cycles": 216,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 20.395198628296086,
|
||||
"baseline_peak_absolute_c0_m": 2.95,
|
||||
"candidate_peak_absolute_c0_m": 3.0300000000000002
|
||||
},
|
||||
"right_exit": {
|
||||
"cycles": 115,
|
||||
"seconds": 1.156770048999988,
|
||||
"changed_c0_cycles": 0,
|
||||
"mean_absolute_c0_change_m": 0.0,
|
||||
"max_absolute_c0_change_m": 0.0,
|
||||
"increased_absolute_c0_cycles": 0,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 0.7199999999999998,
|
||||
"candidate_peak_absolute_c0_m": 0.7199999999999998
|
||||
}
|
||||
},
|
||||
"baseline_commands_vs_recorded_publications": {
|
||||
"paired_cycles": 70703,
|
||||
"within_one_quantum_cycles": 65890,
|
||||
"maximum_absolute_error_c0_c1": [
|
||||
0.25000004768371564,
|
||||
0.0005000143051147043
|
||||
]
|
||||
},
|
||||
"timing": "Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.",
|
||||
"baseline_version": "v3",
|
||||
"baseline_revision": "01f8d51c82b3e863f1012d383b5994813ef01b81",
|
||||
"candidate_version": "current",
|
||||
"candidate_revision": "working_tree",
|
||||
"focus_windows": [
|
||||
[
|
||||
"right_entry",
|
||||
"637",
|
||||
"640"
|
||||
],
|
||||
[
|
||||
"right_exit",
|
||||
"642.7",
|
||||
"643.852"
|
||||
]
|
||||
],
|
||||
"baseline_source_sha256": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||
"candidate_source_sha256": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||
"source_rlog_sha256": {
|
||||
"84865544361f55cb_0000009b--e4616dddaa--0--rlog.zst": "22746f7119109b73ed7f2c26ce8c99f87136e9124fb7fc14c9554409a28a7c3f",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--1--rlog.zst": "4c2e1d7083c31a2b37d0f8dd3be4d330898511b7e02c26f7d40ca9bc2779397d",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--2--rlog.zst": "62f3e049e220cd3681fadf386f2969537bd571998ae2f6ba2d08479428b5a28f",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--3--rlog.zst": "83bf0131b2d36b2ba7e5ba050bbc13c0a3350feb5c9b89dc9c87d3a37abebfb3",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--4--rlog.zst": "430985a80dd6e10f7abeb89457a17022e6bb6978617f415c905f584b1647603e",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--5--rlog.zst": "8c0c5ae6323ec33b3e14f84ca834f70cb56f6b29f471a350f1e3efc06b6ba553",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--6--rlog.zst": "db53dfa8156b9d66792c3eff0b2ce5d31b71ad41cc580dec85f528845593c184",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--7--rlog.zst": "91b0b3be10cb7d7d7f7dd2024d8f9ee99d1e9fd2204203a3a9a2f2f1c6e3fa03",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--8--rlog.zst": "687dbbfc49837efbfe8fa6bc091e40f7fad2908832234d7884f4616d1bc9ccff",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--9--rlog.zst": "a88ec4d25b04cdbf5844686fc77f6b28dca920c9b164e37ebf69844a3ae398fc",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--10--rlog.zst": "fe6b29580a6c94e1c236d13e18db4cd9f31cc1b25d52e1e6e19a5021125c9932",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--11--rlog.zst": "150d31b1944d7a1b8c562f3aee20b66cefa6c4e8d02660ec889907d835142f45",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--12--rlog.zst": "e105e5f703a70f36f1989c66fb46c35a65ff60a265b4332e10fa2e6875a2ced4",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--13--rlog.zst": "69304cf0c81401374f047c5022ca47d257ff9e59e30ec171325a2ee1e7ed13d4"
|
||||
},
|
||||
"opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/damping_replay.py": "8c4f1f987b65e2be146c473a6e529168f08b1f7ce3c9514e22a9874450fa430e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21"
|
||||
}
|
||||
},
|
||||
"route9e": {
|
||||
"scope": "Fixed-input command replay only; no physical improvement or stability claim.",
|
||||
"calibration_approved": false,
|
||||
"cycles": 83668,
|
||||
"eligible_cycles": 74669,
|
||||
"status_counts": {
|
||||
"inactive": 8999,
|
||||
"active": 74669
|
||||
},
|
||||
"c1_exactly_unchanged": true,
|
||||
"same_validity": true,
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 167336,
|
||||
"cohorts": {
|
||||
"eligible": {
|
||||
"cycles": 74669,
|
||||
"seconds": 751.5479497329998,
|
||||
"changed_c0_cycles": 8108,
|
||||
"mean_absolute_c0_change_m": 0.00147612892002983,
|
||||
"max_absolute_c0_change_m": 0.13000000000000034,
|
||||
"increased_absolute_c0_cycles": 7665,
|
||||
"decreased_absolute_c0_cycles": 423,
|
||||
"driver_input_percent": 2.7324748055386094,
|
||||
"baseline_peak_absolute_c0_m": 4.38,
|
||||
"candidate_peak_absolute_c0_m": 4.38
|
||||
},
|
||||
"driver_clean": {
|
||||
"cycles": 66321,
|
||||
"seconds": 667.5291971840106,
|
||||
"changed_c0_cycles": 7257,
|
||||
"mean_absolute_c0_change_m": 0.0012096637080993352,
|
||||
"max_absolute_c0_change_m": 0.08000000000000007,
|
||||
"increased_absolute_c0_cycles": 6883,
|
||||
"decreased_absolute_c0_cycles": 374,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 2.4699999999999998,
|
||||
"candidate_peak_absolute_c0_m": 2.4699999999999998
|
||||
},
|
||||
"driver_clean_low_request_above_8mps": {
|
||||
"cycles": 39635,
|
||||
"seconds": 398.9613925449994,
|
||||
"changed_c0_cycles": 5427,
|
||||
"mean_absolute_c0_change_m": 0.0013748276784907216,
|
||||
"max_absolute_c0_change_m": 0.020000000000000462,
|
||||
"increased_absolute_c0_cycles": 5341,
|
||||
"decreased_absolute_c0_cycles": 86,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 0.07000000000000028,
|
||||
"candidate_peak_absolute_c0_m": 0.07000000000000028
|
||||
},
|
||||
"turn": {
|
||||
"cycles": 6219,
|
||||
"seconds": 62.59491144700405,
|
||||
"changed_c0_cycles": 244,
|
||||
"mean_absolute_c0_change_m": 0.000391833417652352,
|
||||
"max_absolute_c0_change_m": 0.010000000000000675,
|
||||
"increased_absolute_c0_cycles": 244,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 2.4699999999999998,
|
||||
"candidate_peak_absolute_c0_m": 2.4699999999999998
|
||||
},
|
||||
"left_entry": {
|
||||
"cycles": 238,
|
||||
"seconds": 2.4048367620016506,
|
||||
"changed_c0_cycles": 204,
|
||||
"mean_absolute_c0_change_m": 0.03167584138917884,
|
||||
"max_absolute_c0_change_m": 0.07000000000000028,
|
||||
"increased_absolute_c0_cycles": 204,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 2.403816920672437,
|
||||
"baseline_peak_absolute_c0_m": 2.6100000000000003,
|
||||
"candidate_peak_absolute_c0_m": 2.6399999999999997
|
||||
},
|
||||
"left_peak": {
|
||||
"cycles": 288,
|
||||
"seconds": 2.891819394000777,
|
||||
"changed_c0_cycles": 6,
|
||||
"mean_absolute_c0_change_m": 0.00022373297976799967,
|
||||
"max_absolute_c0_change_m": 0.019999999999999574,
|
||||
"increased_absolute_c0_cycles": 6,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 6.41034524444729,
|
||||
"baseline_peak_absolute_c0_m": 2.9400000000000004,
|
||||
"candidate_peak_absolute_c0_m": 2.95
|
||||
},
|
||||
"left_exit": {
|
||||
"cycles": 219,
|
||||
"seconds": 2.203320298998733,
|
||||
"changed_c0_cycles": 11,
|
||||
"mean_absolute_c0_change_m": 0.0008062973780191794,
|
||||
"max_absolute_c0_change_m": 0.019999999999999574,
|
||||
"increased_absolute_c0_cycles": 11,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 9.936619705158035,
|
||||
"baseline_peak_absolute_c0_m": 2.51,
|
||||
"candidate_peak_absolute_c0_m": 2.51
|
||||
},
|
||||
"reversal": {
|
||||
"cycles": 554,
|
||||
"seconds": 5.570295188001182,
|
||||
"changed_c0_cycles": 54,
|
||||
"mean_absolute_c0_change_m": 0.002577813237060219,
|
||||
"max_absolute_c0_change_m": 0.04999999999999982,
|
||||
"increased_absolute_c0_cycles": 29,
|
||||
"decreased_absolute_c0_cycles": 25,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 0.21999999999999975,
|
||||
"candidate_peak_absolute_c0_m": 0.21999999999999975
|
||||
}
|
||||
},
|
||||
"baseline_commands_vs_recorded_publications": {
|
||||
"paired_cycles": 74669,
|
||||
"within_one_quantum_cycles": 69690,
|
||||
"maximum_absolute_error_c0_c1": [
|
||||
0.369999988079071,
|
||||
0.0010000016689301061
|
||||
]
|
||||
},
|
||||
"timing": "Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.",
|
||||
"baseline_version": "v3",
|
||||
"baseline_revision": "01f8d51c82b3e863f1012d383b5994813ef01b81",
|
||||
"candidate_version": "current",
|
||||
"candidate_revision": "working_tree",
|
||||
"focus_windows": [
|
||||
[
|
||||
"left_entry",
|
||||
"173",
|
||||
"175.4"
|
||||
],
|
||||
[
|
||||
"left_peak",
|
||||
"175.4",
|
||||
"178.3"
|
||||
],
|
||||
[
|
||||
"left_exit",
|
||||
"178.3",
|
||||
"180.5"
|
||||
],
|
||||
[
|
||||
"reversal",
|
||||
"728",
|
||||
"734"
|
||||
]
|
||||
],
|
||||
"baseline_source_sha256": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||
"candidate_source_sha256": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||
"source_rlog_sha256": {
|
||||
"84865544361f55cb_0000009e--592f7dc149--0--rlog.zst": "de63532ae6aedf5dc7fd3ac8e47a2d96a8f065614f4aca79ae2f120ea00390c7",
|
||||
"84865544361f55cb_0000009e--592f7dc149--1--rlog.zst": "67b58197158b3e8f0581643f6657d2a85c47b0bb75dd3e00306d9ffae790008b",
|
||||
"84865544361f55cb_0000009e--592f7dc149--2--rlog.zst": "a4840f338f51b5f1864c79ce3a4f2b11dc13d58d52b8459961c9da57237f2cc2",
|
||||
"84865544361f55cb_0000009e--592f7dc149--3--rlog.zst": "ff3ea8d948006ab19c4dbfeeff59a509de3f193da47b91c88faa27ecd0b3f1fc",
|
||||
"84865544361f55cb_0000009e--592f7dc149--4--rlog.zst": "5ff1996c2336299128a11e657c32bc21921716b7e3bd401de69a2d49484aa223",
|
||||
"84865544361f55cb_0000009e--592f7dc149--5--rlog.zst": "238763457e89896933afaf9a5df325469ee8c02ddc53550df252531bc94fc540",
|
||||
"84865544361f55cb_0000009e--592f7dc149--6--rlog.zst": "2ee86de80cfd762be10cd2dfb2895ddbee6b813706e9c7261460203e09b9bc4d",
|
||||
"84865544361f55cb_0000009e--592f7dc149--7--rlog.zst": "017801f080861c63d799f87aebe30ab57cdf82f078ac882e6187d3870c403538",
|
||||
"84865544361f55cb_0000009e--592f7dc149--8--rlog.zst": "3729016bd1f00bb1077613b63fe25b21ba7112822b994e0e0aa2b4cd93bdb940",
|
||||
"84865544361f55cb_0000009e--592f7dc149--9--rlog.zst": "c40b3c1f6eb9252b85f176fee32cae16c23eaad3db830f6bbf37a730034a5ccf",
|
||||
"84865544361f55cb_0000009e--592f7dc149--10--rlog.zst": "e0ce8f231798073e5bbc34551fbd9493f169a06a068adc1957ef8cd914b71fbb",
|
||||
"84865544361f55cb_0000009e--592f7dc149--11--rlog.zst": "90863353f7c862a05618b4d0761dda3fcedd3cf6b4234c21c6dcc2eee3ec20b2",
|
||||
"84865544361f55cb_0000009e--592f7dc149--12--rlog.zst": "e064f8abde9615b2daf00f469ef36b438cd4b3a1637069467c84de91bee3bdf1",
|
||||
"84865544361f55cb_0000009e--592f7dc149--13--rlog.zst": "790fc438ce2675e0690fdce3b07bf54fdf6d06b17bbe7f5ff447adfa71189754",
|
||||
"84865544361f55cb_0000009e--592f7dc149--14--rlog.zst": "b98f5a5be660013b1fe11250fdc24ee4fe1d9dd1301b787ddfdff1f2bcd3e27f"
|
||||
},
|
||||
"opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/damping_replay.py": "8c4f1f987b65e2be146c473a6e529168f08b1f7ce3c9514e22a9874450fa430e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ablation_comparison": {
|
||||
"route9b": {
|
||||
"lab_variant_commands_exactly_match_production": true,
|
||||
"opposed_nonzero_c0_commands": 24,
|
||||
"max_absolute_difference_on_opposed_commands_m": 0.05999999999999961
|
||||
},
|
||||
"route9e": {
|
||||
"lab_variant_commands_exactly_match_production": true,
|
||||
"opposed_nonzero_c0_commands": 30,
|
||||
"max_absolute_difference_on_opposed_commands_m": 0.02999999999999936
|
||||
}
|
||||
},
|
||||
"total_original_route_cycles": 299604,
|
||||
"total_float32_can_round_trips": 817346,
|
||||
"source_sha256": {
|
||||
"openpilot/selfdrive/controls/lib/ford_model_action.py": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action.py": "30cfffb86fcd3830320e9b2ec1f65cdfd219684a55d0bc0bb13f77966cc18b53",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "8ab5dd52958f13a2426e07f8713b7d8d56c52b8a05d173ffba87ffccc0087796",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_damping.py": "a7062d2c3c46b3abcd4c3facd97970644ce609fb55285daa0359681950948250",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_prediction.py": "eb50e1d6acdb5e7332fadc3dcfcfa1b3809545e607c47c54b0ba3de80b2df208",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_selection.py": "f826c6328f0abac2a61f1a0a6f8d119fdbab858e363cb466d84ba9a7783059cd",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_controlsd_logging.py": "175a1969ac83b14b88798bbde37371f740cd05cf3f8f6e5d21bbf9afe857bb57",
|
||||
"tools/ford_pscm_lab/__init__.py": "db4b8b7d2e317ed34ca0ec220bf9d53e7b80a23e766f4e1cb2c8224dada45f2e",
|
||||
"tools/ford_pscm_lab/damping_replay.py": "8c4f1f987b65e2be146c473a6e529168f08b1f7ce3c9514e22a9874450fa430e",
|
||||
"tools/ford_pscm_lab/model_action_replay.py": "7cddac7ce9e88cc32bc7afbec7f9df79b66bb4dfa2fa5f9b36654891fae23a19",
|
||||
"tools/ford_pscm_lab/stress_model_action.py": "66adef6cba120a1a3d8e9730ea987ef49f8fe283023362dbb99608dee0e3229f",
|
||||
"tools/ford_pscm_lab/test_model_action_replay.py": "1f7d90c06ec09b405571565632653dc7d984b106b955877916d4a0836c11e3b5",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "41ac31726b941e470f5be34e2523dc1fc2b25fb6a12705ab267a57ed4f3c27c8",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui.json": "c44c75e02042a0485b5bd7896e0b794761d768bbe513b6dc9c2c986ddd22b44c"
|
||||
},
|
||||
"artifact_sha256": {
|
||||
".cache/ford_prediction_uncapped/uncapped_red.txt": "09160dbc36320be886561cfdbdd21eef0c8bc420a11bae9bb1a8dd43f7b93a3c",
|
||||
".cache/ford_prediction_uncapped/suite_run.txt": "99b3bd2af256c9a4a083cb015a51a458a26ac02a8cad73c28c47470f9a5eb910",
|
||||
".cache/ford_prediction_uncapped/coverage.json": "50a282c90fa86147ed6925fdf37918471f8e5204a4c1e1ef9adb39335e220f42",
|
||||
".cache/ford_prediction_uncapped/stress.json": "e4226f403a3a914b9f3a7868b14a2b763215ec1f77e7d45ac06604859acc9fb6",
|
||||
".cache/ford_prediction_uncapped/route90/report.json": "c5cee3c56f9f7f292c8069be22dd1ffb1649055fccaf829b28d96bdec6558541",
|
||||
".cache/ford_prediction_uncapped/route95/report.json": "6fcc8a3bd7abb5b513888cb64426670e7e05f8b2100b1876be196a800cec2c90",
|
||||
".cache/ford_prediction_uncapped/final9b/report.json": "79faca3ff89e2015dad4823d4d988ea26ead55c7e51e4a594258102d32701b07",
|
||||
".cache/ford_prediction_uncapped/final9e/report.json": "9b003ce97225e65e03e351c5e133f3fed08dc79e026f73308098d51a98dd67ae",
|
||||
".cache/ford_prediction_uncapped/route90/commands.npz": "8787aabbe7007bcd7c68976c272085761aec6a4f360fa6cc8c8553bd1421c92b",
|
||||
".cache/ford_prediction_uncapped/route95/commands.npz": "349b716bd910cd030716b9ef519ee24f620fae5280776ffa871bc43318939400",
|
||||
".cache/ford_prediction_uncapped/final9b/commands.npz": "ff4a14fc74b144800f459c250be961ebb05a46a20e6ad1fc0394a659c9854394",
|
||||
".cache/ford_prediction_uncapped/final9e/commands.npz": "59f6046acb10b00ef00e7b9feb5b8547139a67a2f8767d7cbfe3603a59e6c77d"
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
{
|
||||
"version": "model-action-measured-pose-v6",
|
||||
"base_commit": "72e9d94f62487d07e9b7c5cb17d5988d5ddfbedb",
|
||||
"opendbc_revision": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||
"scope": "Offline runtime command replay on frozen recorded calibrated yaw, geometry and vehicle inputs; separate controlsd integration tests exercise health/freshness, calibration updates, publication and CAN. No counterfactual vehicle simulation or road-validation claim.",
|
||||
"calibration_approved": false,
|
||||
"source_sha256": {
|
||||
"openpilot/selfdrive/controls/controlsd.py": "c12e8d9be37e21f27a2132974b0efe98f3e681fd07e12c263e897ad67a44871b",
|
||||
"openpilot/selfdrive/controls/lib/ford_model_action.py": "326539389b04034932db41ca2c67794779403b2c0e97f408a81a9f5734114899",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_controlsd_logging.py": "2a55779b1a32f4dc3317b568365efbadc1f6aab06a0b3398514be891025d5e2d",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "0e131ca17380a5a4fe37754659f77aeda550f5e3c113edb27b576673e57bf6b4",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_measured_pose.py": "4d81efa0f09350559ff4290fd2ec11e744be221623fd414de85adfe2fecb4289",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_selection.py": "4e87ff347046d8de7248b9272a0393dd5d12e10df5430f00540b675331d7fe58",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_yaw.py": "063b066aeddf0410dc208fa0017a58cda78effb1ca33c2af8b53422dbdad06f3",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui.json": "6f8900aa642d7f3883b003fd9244f29997343808fc22119c5ca953c3e1946fde",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "c8f7cc4c44e39027fecb52efc61dd5f3902e030193b10ac2f2c36c51cc6d4010"
|
||||
},
|
||||
"validation": {
|
||||
"pytest": {
|
||||
"passed": 427,
|
||||
"subtests_passed": 26,
|
||||
"skipped": 0,
|
||||
"command": "python -m pytest -q openpilot/selfdrive/controls/tests/test_ford*.py tools/ford_pscm_lab opendbc_repo/opendbc/car/ford/tests/test_ford.py openpilot/sunnypilot/sunnylink/tests",
|
||||
"output_sha256": "ed2ecdf280dadc52a834b2dfaa777e64505f7763d1a271849dadc729a86abb5d"
|
||||
},
|
||||
"type_check": "ty check openpilot/selfdrive/controls/lib/ford_model_action.py openpilot/selfdrive/controls/controlsd.py: pass",
|
||||
"ruff_changed_python": "pass",
|
||||
"settings_compiler_check": "pass",
|
||||
"diff_check": "pass",
|
||||
"regression_red_green": "49 measured-pose tests and 4 state-freshness/calibration tests failed before their implementation and passed afterward"
|
||||
},
|
||||
"replay": {
|
||||
"cycles": 340757,
|
||||
"wire_round_trips": 681514,
|
||||
"command_matches_reviewed_candidate": true,
|
||||
"baseline_matches_archived_v5": true,
|
||||
"c1_eligibility_unchanged": true,
|
||||
"checks": [
|
||||
"finite values",
|
||||
"caps",
|
||||
"independent slew",
|
||||
"inactive reset",
|
||||
"Float32/CAN packing",
|
||||
"zero C2 and C3",
|
||||
"publication and filter-state freshness"
|
||||
],
|
||||
"routes": {
|
||||
"a2": {
|
||||
"cycles": 71111,
|
||||
"wire_round_trips": 142222,
|
||||
"runtime_matches_reviewed_lab_candidate": true,
|
||||
"baseline_matches_archive": true,
|
||||
"identical_c1_eligibility": true
|
||||
},
|
||||
"a0": {
|
||||
"cycles": 114582,
|
||||
"wire_round_trips": 229164,
|
||||
"runtime_matches_reviewed_lab_candidate": true,
|
||||
"baseline_matches_archive": true,
|
||||
"identical_c1_eligibility": true
|
||||
},
|
||||
"9b": {
|
||||
"cycles": 71396,
|
||||
"wire_round_trips": 142792,
|
||||
"runtime_matches_reviewed_lab_candidate": true,
|
||||
"baseline_matches_archive": true,
|
||||
"identical_c1_eligibility": true
|
||||
},
|
||||
"9e": {
|
||||
"cycles": 83668,
|
||||
"wire_round_trips": 167336,
|
||||
"runtime_matches_reviewed_lab_candidate": true,
|
||||
"baseline_matches_archive": true,
|
||||
"identical_c1_eligibility": true
|
||||
}
|
||||
},
|
||||
"report_sha256": {
|
||||
"a2_pose_report.json": "35d709991507ac7c10fd3148fd61dcf33a9606f97cd134d2bd94cee999f9cbe1",
|
||||
"a0_pose_report.json": "1e34fbcdb14d04b23ce755ddd2e66fa6ab12377ab925693474e9f719b6339530",
|
||||
"9b_pose_report.json": "e601662de44a354247f3943c233ca14ce5c449dbb4289ab9564ced21d524f02b",
|
||||
"9e_pose_report.json": "fb0ee49e7aab65d45b61251aa83ad2247cd90e91f6c077f86d2f8474af7013e4"
|
||||
},
|
||||
"reproduce": "PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=.:opendbc_repo python .cache/ford_v6/replay.py a2 a0 9b 9e",
|
||||
"reproduction_requirements": "Original route extracts in the local .cache directories and the matching scripts/reports in the ford-v6 artifact. Replay scripts and private recordings are not shipped on the device."
|
||||
},
|
||||
"limitations": [
|
||||
"All route extracts came from one truck; cross-PSCM behavior is unverified.",
|
||||
"C0 command changes are not percentages of physical steering improvement.",
|
||||
"The older overshoot example has nearby driver influence.",
|
||||
"The same C1 cap remains active during tight-turn peak misses.",
|
||||
"Quiet-path per-cycle C0-change RMS increases about 28%; physical centering is unverified.",
|
||||
"Hardware build, device boot and physical driving were not performed."
|
||||
]
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
# Experimental Ford selected-action controller, v5
|
||||
|
||||
V5 removes the excess-yaw C0 attenuation at the user's request. The damping
|
||||
function, 0.02 rad/s deadband and 0.2 s reduction scale are deleted. Valid measured
|
||||
yaw no longer changes either command target. Existing yaw input-health checks
|
||||
and logging remain. No replacement gain, increment gate or controller state is added.
|
||||
|
||||
The full 150 ms geometric prediction introduced in v4 remains, along with the
|
||||
7 m offset station and one-second heading scale. This is not a return to v1:
|
||||
v1 did not predict the offset. C0/C1 bounds, slew, quantization, service gates,
|
||||
engagement, downstream driver arbitration and zero C2/C3 are unchanged.
|
||||
|
||||
The latest supplied route a0 ran v2, not v4. Prior same-input comparisons found
|
||||
identical v1/v2 commands during its driver-clean minor-bend warning intervals
|
||||
and the preceding five seconds. That evidence does not identify damping as the
|
||||
cause of those misses. Removing damping can restore C0 demand where the damper
|
||||
was active, including turn exits; it is not evidence of improved tracking or
|
||||
reduced oversteer.
|
||||
|
||||
## Offline validation
|
||||
|
||||
The regression suite checks yaw-independent commands through mirrored turn
|
||||
entry, release and reversal, including valid yaw extremes and small yaw offsets.
|
||||
Six cases fail with v4 damping present and pass after removal. Invalid yaw still
|
||||
resets the controller. Actual controlsd selection, upstream limiting, Float32
|
||||
publication and downstream CAN tests cover both model and maneuver references.
|
||||
|
||||
Full-rlog comparisons run pinned v4 against production v5 on routes9b, 9e and a0.
|
||||
They preserve original clocks, exact consumed model frames and causal carState;
|
||||
publication times proxy computation time, and complete SubMaster health is
|
||||
unavailable. The numerical stress run checks independent geometric targets,
|
||||
scalar slew, mirrored turns and Float32/CAN packing. Recorded vehicle motion
|
||||
stays fixed: none of these checks establishes counterfactual steering response,
|
||||
closed-loop stability or a physical tracking improvement.
|
||||
|
||||
Results and source hashes are recorded in
|
||||
`ford_model_action_no_yaw_damping_validation.json`. Earlier validation documents
|
||||
remain archives of their specified controller versions.
|
||||
|
||||
Validation passes 356 tests and 26 subtests with 100% controller statement and
|
||||
branch coverage, 280,636 recorded route cycles and 779,410 Float32/CAN round trips,
|
||||
including 200,000 random stress cycles. C1 and input eligibility match v4 exactly
|
||||
on all three routes. Commands during all 1,578 driver-clean ordinary-bend warning
|
||||
cycles on route a0 also remain identical to v4. At the earlier right-turn exit,
|
||||
removing damping increases C0 magnitude by a mean 0.079 m, maximum 0.13 m; these
|
||||
are command offsets, not measured vehicle displacement.
|
||||
|
||||
The module is 171 total lines, or 111 code lines excluding blanks, comments and
|
||||
docstrings, with two control states. No hardware build or device boot was performed.
|
||||
|
||||
## Reproduce and select
|
||||
|
||||
Use the dependency setup and combined suite in the
|
||||
[drive-test guide](ford_model_action_drive_test.md). Replay and stress commands:
|
||||
|
||||
```sh
|
||||
python -m tools.ford_pscm_lab.damping_replay /path/to/route9b/rlogs --baseline v4 --candidate current --window right_entry 637 640 --window right_exit 642.7 643.852 --output /path/to/separate/route9b-results
|
||||
python -m tools.ford_pscm_lab.damping_replay /path/to/route9e/rlogs --baseline v4 --candidate current --window left_entry 173 175.4 --window left_peak 175.4 178.3 --window left_exit 178.3 180.5 --output /path/to/separate/route9e-results
|
||||
python -m tools.ford_pscm_lab.damping_replay /path/to/routea0/rlogs --baseline v4 --candidate current --window bends_5min 298 338 --window bend_7min 449 458 --window bend_9min 579 588 --output /path/to/separate/routea0-results
|
||||
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260908 --opendbc-revision c21a9013700734dd20b09e05aa68329ad8cc20f9 --output /path/to/stress.json
|
||||
```
|
||||
|
||||
The same default-off **Selected-Action Path Tracking (Experimental)** Sunnylink
|
||||
toggle selects v5 on the CAN FD F-150 Lightning. Deployment remains
|
||||
`sunnypilot/sunnypilot`, branch `hiimisaac-dev`. After updating, restart controlsd
|
||||
through a real offroad-to-onroad cycle. Diagnostics identify
|
||||
`model-action-c0-c1-prediction-v5`; `calibration_approved=false` remains explicit.
|
||||
@@ -1,304 +0,0 @@
|
||||
{
|
||||
"date": "2026-09-08",
|
||||
"baseline_commit": "7e63449749d112f096c56cb848dd289054e5f85b",
|
||||
"hypothesis": "model-action-c0-c1-prediction-v5",
|
||||
"scope": "Remove yaw damping only; retain full path prediction and existing input-health gates. Fixed-input command checks, not physical tracking or stability evidence.",
|
||||
"deployment_target": {
|
||||
"repository": "sunnypilot/sunnypilot",
|
||||
"branch": "hiimisaac-dev"
|
||||
},
|
||||
"calibration_approved": false,
|
||||
"hardware_build_and_device_boot": "not performed",
|
||||
"opendbc_revision": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||
"controller_size": {
|
||||
"total_lines": 171,
|
||||
"code_lines_excluding_blanks_comments_docstrings": 111,
|
||||
"core_persistent_values": 2,
|
||||
"adapter_timestamps": 3
|
||||
},
|
||||
"checks": {
|
||||
"combined_ford_params_sunnylink_suite": "356 passed, 26 subtests passed; no skips",
|
||||
"coverage": {
|
||||
"covered_lines": 102,
|
||||
"num_statements": 102,
|
||||
"percent_covered": 100.0,
|
||||
"percent_covered_display": "100",
|
||||
"missing_lines": 0,
|
||||
"excluded_lines": 0,
|
||||
"percent_statements_covered": 100.0,
|
||||
"percent_statements_covered_display": "100",
|
||||
"num_branches": 30,
|
||||
"num_partial_branches": 0,
|
||||
"covered_branches": 30,
|
||||
"missing_branches": 0,
|
||||
"percent_branches_covered": 100.0,
|
||||
"percent_branches_covered_display": "100"
|
||||
},
|
||||
"ruff": "pass",
|
||||
"ty_controller_and_lab": "pass",
|
||||
"settings_compiler_check": "pass",
|
||||
"removal_regression_probe": "6 cases fail with v4 damping present; all 21 yaw tests pass after removal.",
|
||||
"reviews": {
|
||||
"standards": {
|
||||
"remaining_findings": 0
|
||||
},
|
||||
"spec": {
|
||||
"remaining_findings": 0,
|
||||
"independent_focused_tests_passed": 107
|
||||
},
|
||||
"corrected_findings": [
|
||||
"Removed stale damping claim from Sunnylink YAML and regenerated JSON.",
|
||||
"Corrected replay yaw-use metadata to input-health checks and diagnostics."
|
||||
]
|
||||
}
|
||||
},
|
||||
"total_original_route_cycles": 280636,
|
||||
"total_float32_can_round_trips": 779410,
|
||||
"stress": {
|
||||
"seed": 20260908,
|
||||
"random_cycles": 200000,
|
||||
"mirrored_core_updates": 200000,
|
||||
"invalid_or_inactive_resets": 3537,
|
||||
"field_boundary_cases": 18138,
|
||||
"float32_can_round_trips": 218138,
|
||||
"analytic_targets_scalar_slew_and_mirror_checks_pass": true,
|
||||
"valid_yaw_does_not_affect_targets_checked": true,
|
||||
"full_geometric_prediction_checked": true,
|
||||
"direct_raw_float32_packing_matches_host_output": true,
|
||||
"max_continuous_step_c0_c1": [
|
||||
0.4000000000003041,
|
||||
0.05000000000000002
|
||||
],
|
||||
"calibration_approved": false,
|
||||
"scope": "Numerical construction only; no PSCM response or closed-loop performance claims.",
|
||||
"opendbc_import_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9"
|
||||
},
|
||||
"routes": {
|
||||
"route9b": {
|
||||
"cycles": 82386,
|
||||
"eligible_cycles": 70703,
|
||||
"same_validity": true,
|
||||
"c1_exactly_unchanged": true,
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 164772,
|
||||
"baseline_revision": "7e63449749d112f096c56cb848dd289054e5f85b",
|
||||
"baseline_source_sha256": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||
"candidate_source_sha256": "213a4dfa586092c28d8e6e2c27a7dd3622c5862615db9afd5a4e2d38144b1af8",
|
||||
"source_rlog_sha256": {
|
||||
"84865544361f55cb_0000009b--e4616dddaa--0--rlog.zst": "22746f7119109b73ed7f2c26ce8c99f87136e9124fb7fc14c9554409a28a7c3f",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--1--rlog.zst": "4c2e1d7083c31a2b37d0f8dd3be4d330898511b7e02c26f7d40ca9bc2779397d",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--2--rlog.zst": "62f3e049e220cd3681fadf386f2969537bd571998ae2f6ba2d08479428b5a28f",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--3--rlog.zst": "83bf0131b2d36b2ba7e5ba050bbc13c0a3350feb5c9b89dc9c87d3a37abebfb3",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--4--rlog.zst": "430985a80dd6e10f7abeb89457a17022e6bb6978617f415c905f584b1647603e",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--5--rlog.zst": "8c0c5ae6323ec33b3e14f84ca834f70cb56f6b29f471a350f1e3efc06b6ba553",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--6--rlog.zst": "db53dfa8156b9d66792c3eff0b2ce5d31b71ad41cc580dec85f528845593c184",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--7--rlog.zst": "91b0b3be10cb7d7d7f7dd2024d8f9ee99d1e9fd2204203a3a9a2f2f1c6e3fa03",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--8--rlog.zst": "687dbbfc49837efbfe8fa6bc091e40f7fad2908832234d7884f4616d1bc9ccff",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--9--rlog.zst": "a88ec4d25b04cdbf5844686fc77f6b28dca920c9b164e37ebf69844a3ae398fc",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--10--rlog.zst": "fe6b29580a6c94e1c236d13e18db4cd9f31cc1b25d52e1e6e19a5021125c9932",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--11--rlog.zst": "150d31b1944d7a1b8c562f3aee20b66cefa6c4e8d02660ec889907d835142f45",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--12--rlog.zst": "e105e5f703a70f36f1989c66fb46c35a65ff60a265b4332e10fa2e6875a2ced4",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--13--rlog.zst": "69304cf0c81401374f047c5022ca47d257ff9e59e30ec171325a2ee1e7ed13d4"
|
||||
},
|
||||
"focus_cohorts": {
|
||||
"right_entry": {
|
||||
"cycles": 298,
|
||||
"seconds": 2.9932484459999387,
|
||||
"changed_c0_cycles": 100,
|
||||
"mean_absolute_c0_change_m": 0.007683282515599457,
|
||||
"max_absolute_c0_change_m": 0.04999999999999982,
|
||||
"increased_absolute_c0_cycles": 100,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 20.395198628296086,
|
||||
"baseline_peak_absolute_c0_m": 3.0300000000000002,
|
||||
"candidate_peak_absolute_c0_m": 3.0300000000000002
|
||||
},
|
||||
"right_exit": {
|
||||
"cycles": 115,
|
||||
"seconds": 1.156770048999988,
|
||||
"changed_c0_cycles": 115,
|
||||
"mean_absolute_c0_change_m": 0.07934493768172877,
|
||||
"max_absolute_c0_change_m": 0.13000000000000078,
|
||||
"increased_absolute_c0_cycles": 115,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 0.7199999999999998,
|
||||
"candidate_peak_absolute_c0_m": 0.7800000000000002
|
||||
}
|
||||
}
|
||||
},
|
||||
"route9e": {
|
||||
"cycles": 83668,
|
||||
"eligible_cycles": 74669,
|
||||
"same_validity": true,
|
||||
"c1_exactly_unchanged": true,
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 167336,
|
||||
"baseline_revision": "7e63449749d112f096c56cb848dd289054e5f85b",
|
||||
"baseline_source_sha256": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||
"candidate_source_sha256": "213a4dfa586092c28d8e6e2c27a7dd3622c5862615db9afd5a4e2d38144b1af8",
|
||||
"source_rlog_sha256": {
|
||||
"84865544361f55cb_0000009e--592f7dc149--0--rlog.zst": "de63532ae6aedf5dc7fd3ac8e47a2d96a8f065614f4aca79ae2f120ea00390c7",
|
||||
"84865544361f55cb_0000009e--592f7dc149--1--rlog.zst": "67b58197158b3e8f0581643f6657d2a85c47b0bb75dd3e00306d9ffae790008b",
|
||||
"84865544361f55cb_0000009e--592f7dc149--2--rlog.zst": "a4840f338f51b5f1864c79ce3a4f2b11dc13d58d52b8459961c9da57237f2cc2",
|
||||
"84865544361f55cb_0000009e--592f7dc149--3--rlog.zst": "ff3ea8d948006ab19c4dbfeeff59a509de3f193da47b91c88faa27ecd0b3f1fc",
|
||||
"84865544361f55cb_0000009e--592f7dc149--4--rlog.zst": "5ff1996c2336299128a11e657c32bc21921716b7e3bd401de69a2d49484aa223",
|
||||
"84865544361f55cb_0000009e--592f7dc149--5--rlog.zst": "238763457e89896933afaf9a5df325469ee8c02ddc53550df252531bc94fc540",
|
||||
"84865544361f55cb_0000009e--592f7dc149--6--rlog.zst": "2ee86de80cfd762be10cd2dfb2895ddbee6b813706e9c7261460203e09b9bc4d",
|
||||
"84865544361f55cb_0000009e--592f7dc149--7--rlog.zst": "017801f080861c63d799f87aebe30ab57cdf82f078ac882e6187d3870c403538",
|
||||
"84865544361f55cb_0000009e--592f7dc149--8--rlog.zst": "3729016bd1f00bb1077613b63fe25b21ba7112822b994e0e0aa2b4cd93bdb940",
|
||||
"84865544361f55cb_0000009e--592f7dc149--9--rlog.zst": "c40b3c1f6eb9252b85f176fee32cae16c23eaad3db830f6bbf37a730034a5ccf",
|
||||
"84865544361f55cb_0000009e--592f7dc149--10--rlog.zst": "e0ce8f231798073e5bbc34551fbd9493f169a06a068adc1957ef8cd914b71fbb",
|
||||
"84865544361f55cb_0000009e--592f7dc149--11--rlog.zst": "90863353f7c862a05618b4d0761dda3fcedd3cf6b4234c21c6dcc2eee3ec20b2",
|
||||
"84865544361f55cb_0000009e--592f7dc149--12--rlog.zst": "e064f8abde9615b2daf00f469ef36b438cd4b3a1637069467c84de91bee3bdf1",
|
||||
"84865544361f55cb_0000009e--592f7dc149--13--rlog.zst": "790fc438ce2675e0690fdce3b07bf54fdf6d06b17bbe7f5ff447adfa71189754",
|
||||
"84865544361f55cb_0000009e--592f7dc149--14--rlog.zst": "b98f5a5be660013b1fe11250fdc24ee4fe1d9dd1301b787ddfdff1f2bcd3e27f"
|
||||
},
|
||||
"focus_cohorts": {
|
||||
"left_entry": {
|
||||
"cycles": 238,
|
||||
"seconds": 2.4048367620016506,
|
||||
"changed_c0_cycles": 0,
|
||||
"mean_absolute_c0_change_m": 0.0,
|
||||
"max_absolute_c0_change_m": 0.0,
|
||||
"increased_absolute_c0_cycles": 0,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 2.403816920672437,
|
||||
"baseline_peak_absolute_c0_m": 2.6399999999999997,
|
||||
"candidate_peak_absolute_c0_m": 2.6399999999999997
|
||||
},
|
||||
"left_peak": {
|
||||
"cycles": 288,
|
||||
"seconds": 2.891819394000777,
|
||||
"changed_c0_cycles": 0,
|
||||
"mean_absolute_c0_change_m": 0.0,
|
||||
"max_absolute_c0_change_m": 0.0,
|
||||
"increased_absolute_c0_cycles": 0,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 6.41034524444729,
|
||||
"baseline_peak_absolute_c0_m": 2.95,
|
||||
"candidate_peak_absolute_c0_m": 2.95
|
||||
},
|
||||
"left_exit": {
|
||||
"cycles": 219,
|
||||
"seconds": 2.203320298998733,
|
||||
"changed_c0_cycles": 129,
|
||||
"mean_absolute_c0_change_m": 0.037433836245882854,
|
||||
"max_absolute_c0_change_m": 0.11000000000000032,
|
||||
"increased_absolute_c0_cycles": 128,
|
||||
"decreased_absolute_c0_cycles": 1,
|
||||
"driver_input_percent": 9.936619705158035,
|
||||
"baseline_peak_absolute_c0_m": 2.51,
|
||||
"candidate_peak_absolute_c0_m": 2.51
|
||||
}
|
||||
}
|
||||
},
|
||||
"routea0": {
|
||||
"cycles": 114582,
|
||||
"eligible_cycles": 105382,
|
||||
"same_validity": true,
|
||||
"c1_exactly_unchanged": true,
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 229164,
|
||||
"baseline_revision": "7e63449749d112f096c56cb848dd289054e5f85b",
|
||||
"baseline_source_sha256": "7a226cf3cdf6dc8c3b15829078a7b93e486ba4722c4a1d93ceb55dd2e6c77b21",
|
||||
"candidate_source_sha256": "213a4dfa586092c28d8e6e2c27a7dd3622c5862615db9afd5a4e2d38144b1af8",
|
||||
"source_rlog_sha256": {
|
||||
"84865544361f55cb_000000a0--5e86c30dae--0--rlog.zst": "57b8113783f70f9176e1f2703e70185f64df07b3283f739a6fd7b182a2c92417",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--1--rlog.zst": "a821ce7df80c6466110a6f6433b2c9482f5f3a5b4227e8459182c3ee9fb8c7a7",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--2--rlog.zst": "984cca8e311be8a61444ca2bc23bbf3937c6f00264303faefb7efb8f0a3e8aa2",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--3--rlog.zst": "c6ea25b226aa875a15a8f8356522ab3b12501d280f63ebe0438a9d50b2116279",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--4--rlog.zst": "99b129e2965674fb3406855d1b022c0876083984adf2f2ef51c07f1e699974ea",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--5--rlog.zst": "428e2ce9a1ac0ca7135c00221590c286ab32b4ce5cf24d376de1519b39b25fec",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--6--rlog.zst": "efca7fedd0aaed4be4500fd1a0c67f01d8f805cfdd681a0a9c0439061ec2f36f",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--7--rlog.zst": "1da238f57b63efbbf7dfd2b147637c1b846736e198999741817fc0646d4f60b7",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--8--rlog.zst": "b61209325da26751b98da85fe166dab65ab9eaaef129b859a59fe99e4de5ab65",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--9--rlog.zst": "cf555f1c57a9afdbed702b5934892acb5b2174ef48f1fcf494422aba9bd01321",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--10--rlog.zst": "13ff83c236cca64b57c11ffa05cb74511af1258cde3cf0554395922769a39683",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--11--rlog.zst": "848a1d26fd72bbe4e119339bd405aedb0b9d7e6b96329d0b868a27bd01b6b128",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--12--rlog.zst": "9e8bf8597942cd74d8d04c783148b8201f63f56e55b766f7887f27465de276f2",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--13--rlog.zst": "e37575ed1424d574015c20a3066754bcca6fdacbb104db2bedce782428fd48ef",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--14--rlog.zst": "b67c7ed7ddd957bf5dcbcdb0604ddc9c52cd7af9d6aa70c1eae3e7622cee160d",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--15--rlog.zst": "6baaf9fdf64c72a9be70d261580914e2489f363230d95a1291b8479bcc249ba2",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--16--rlog.zst": "f84803fa2a615caf59a6bd4e9b2848059dd9f4a7f0c01ac4ec7e2347e317b5e7",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--17--rlog.zst": "f0f5f36cb155c18f8f9f90f700e1e91e169ef0442fd70670323be4a1c658040e",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--18--rlog.zst": "8674d5822949bafaefc0d0f30b0e91603354b9981de7566419a2e5b780054e64",
|
||||
"84865544361f55cb_000000a0--5e86c30dae--19--rlog.zst": "b5df55701d5bcfbfd750608a1dfbcba9b931a39fba7a82e87bc7915b8ca2333f"
|
||||
},
|
||||
"focus_cohorts": {
|
||||
"bends_5min": {
|
||||
"cycles": 3966,
|
||||
"seconds": 39.99197329400005,
|
||||
"changed_c0_cycles": 8,
|
||||
"mean_absolute_c0_change_m": 1.950586094527418e-05,
|
||||
"max_absolute_c0_change_m": 0.009999999999999787,
|
||||
"increased_absolute_c0_cycles": 8,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 14.825647085260368,
|
||||
"baseline_peak_absolute_c0_m": 0.3099999999999996,
|
||||
"candidate_peak_absolute_c0_m": 0.3099999999999996
|
||||
},
|
||||
"bend_7min": {
|
||||
"cycles": 893,
|
||||
"seconds": 8.995078784999919,
|
||||
"changed_c0_cycles": 0,
|
||||
"mean_absolute_c0_change_m": 0.0,
|
||||
"max_absolute_c0_change_m": 0.0,
|
||||
"increased_absolute_c0_cycles": 0,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 0.20000000000000018,
|
||||
"candidate_peak_absolute_c0_m": 0.20000000000000018
|
||||
},
|
||||
"bend_9min": {
|
||||
"cycles": 895,
|
||||
"seconds": 8.998970718999999,
|
||||
"changed_c0_cycles": 0,
|
||||
"mean_absolute_c0_change_m": 0.0,
|
||||
"max_absolute_c0_change_m": 0.0,
|
||||
"increased_absolute_c0_cycles": 0,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 0.22147398432951632,
|
||||
"baseline_peak_absolute_c0_m": 0.2400000000000002,
|
||||
"candidate_peak_absolute_c0_m": 0.2400000000000002
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routea0_ordinary_bend_warnings_v4_vs_v5": {
|
||||
"definition": "Driver-clean +/-1s, speed>=8m/s, absolute desired wheel angle 3 to30deg. Proxy for ordinary bends, not map geometry.",
|
||||
"cycles": 1578,
|
||||
"seconds": 15.92979767199978,
|
||||
"changed_c0_cycles": 0,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_c0_change_m": 0.0
|
||||
},
|
||||
"source_sha256": {
|
||||
"openpilot/selfdrive/controls/lib/ford_model_action.py": "213a4dfa586092c28d8e6e2c27a7dd3622c5862615db9afd5a4e2d38144b1af8",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "be610c09e4bbd93e84d1920cfbb3e9609b3fbb7dca8eb229b8e67befb4b2ca84",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui.json": "4d2dffdfd81190a871bd41e28e5832502aa95d8aacf91daa7e68382085588aca",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action.py": "30cfffb86fcd3830320e9b2ec1f65cdfd219684a55d0bc0bb13f77966cc18b53",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "7f8b13c4d85217cbb6bd32ec193ef18bdb30bdf1f2b59fa7fa9d40988cb20bea",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_prediction.py": "eb50e1d6acdb5e7332fadc3dcfcfa1b3809545e607c47c54b0ba3de80b2df208",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_selection.py": "f826c6328f0abac2a61f1a0a6f8d119fdbab858e363cb466d84ba9a7783059cd",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_yaw.py": "d646f89e2c4d4e112d03e77c5c34b7541b0bdba800419d472a4a69753637c515",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_controlsd_logging.py": "9697db696d5a01838ea5cdcf4f1771d813a647f5038cabe9f0460778a2936929",
|
||||
"tools/ford_pscm_lab/__init__.py": "db4b8b7d2e317ed34ca0ec220bf9d53e7b80a23e766f4e1cb2c8224dada45f2e",
|
||||
"tools/ford_pscm_lab/damping_replay.py": "d21565a29b9c2b4668fafe07f995c15e113888a1f4787f37d72d02d36998f9cf",
|
||||
"tools/ford_pscm_lab/model_action_replay.py": "a90caed1c46c6f964fecb50bd0f531ae355505f1e84be9a1e37243d910b292b4",
|
||||
"tools/ford_pscm_lab/stress_model_action.py": "123c6c0c9f53a5af2ab50bb568e47c0f8960b7d49fad2fb6ea74de5c63e03ad5",
|
||||
"tools/ford_pscm_lab/test_model_action_replay.py": "bf1a1612474cb7308b3640cba535ee534999fdecdf055ae6d7bc83b9a8e66f63"
|
||||
},
|
||||
"artifact_sha256": {
|
||||
".cache/ford_no_yaw_damping/removal_red.txt": "4aa79e5af1066d66db65853faad3923f8645f5dff8383fa3106489a3e53cae18",
|
||||
".cache/ford_no_yaw_damping/suite_run.txt": "434d80389bc7d2859070c9f9df0cac1ac0f854151b5888dcedc2ce38df23d519",
|
||||
".cache/ford_no_yaw_damping/coverage.json": "d99baeb8729a1394b41d9a4739b538372aad27b56d379f4b9cefe682ed99d662",
|
||||
".cache/ford_no_yaw_damping/stress.json": "3e2c49dcc473d90ea03c95dc12905c2216ee573a691891b8a1f56ba82fd78251",
|
||||
".cache/ford_no_yaw_damping/route9b/report.json": "446371173fed1ff632ece4286e53d0d941491be590d467af960d1931431212ca",
|
||||
".cache/ford_no_yaw_damping/route9b/commands.npz": "d543a92991f06b56e20d0ed58daf6e4ef6a0021b79c027570b8d997658336b9d",
|
||||
".cache/ford_no_yaw_damping/route9e/report.json": "fb8ddd8d2c242a14a63993593c96604e7306dcd97219d073bbcfcbeba68af45b",
|
||||
".cache/ford_no_yaw_damping/route9e/commands.npz": "375a94227f3cb1a161a1295496908baa428d5ef0e0b79134a728f876477dbd6b",
|
||||
".cache/ford_no_yaw_damping/routea0/report.json": "7959b77af715016b4da45b9bc8f095872702f662950324f64a0f4d332c814eb0",
|
||||
".cache/ford_no_yaw_damping/routea0/commands.npz": "4f9dcde39ad9e20958ad04e53dbb32830c6a933dc7f743fdffe5a612252cb16b"
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
# Experimental Ford path prediction, v3
|
||||
|
||||
This document and its counts describe archived v3. The
|
||||
[current v4 experiment](ford_model_action_full_prediction.md) removes the extra
|
||||
15 cm / 25% prediction adjustment cap.
|
||||
|
||||
The latest driven route9e used v1 (`5fc16abc7`), before the v2 yaw damping.
|
||||
It often follows the requested steering angle closely, but some tight turns
|
||||
fall behind after a reasonable initial turn-in. The requested angle is replanned
|
||||
from the car's changing position; a large late request may partly be a recovery
|
||||
request after arriving wide. It is not proof that the original turn required
|
||||
that much steering. The generic PSCM limit flag does not identify a torque,
|
||||
rate or mechanical limit, and the miss starts before our C1 cap in the clearest
|
||||
left turn.
|
||||
|
||||
Short measured-motion integrations against earlier frozen model paths are
|
||||
consistent with a growing miss, but model uncertainty, reference timing and
|
||||
some driver input prevent a conclusive causal attribution. V3 tests a bounded
|
||||
change to initial path demand. No counterfactual physical tracking score is
|
||||
claimed from replaying fixed logs.
|
||||
|
||||
## Change and bounds
|
||||
|
||||
Start with the current model's lateral offset at 7 m of path arc length, as in
|
||||
v1/v2. Advance the reference pose by speed × 0.15 s along the **selected,
|
||||
upstream-limited curvature**, and read the same model path 7 m beyond that
|
||||
advance, expressed in the predicted ego frame. Bound the change from the
|
||||
original offset to both ±0.15 m and ±25% of its magnitude. Prediction cannot
|
||||
reverse that target or create C0 from a zero offset.
|
||||
|
||||
For advance `d`, selected curvature `k`, rotation `theta = k*d`, and model
|
||||
point `(x, y)` at arc station `7+d`, the predicted lateral coordinate is:
|
||||
|
||||
```
|
||||
y_predicted = cos(theta)*y - sin(theta)*x + (1-cos(theta))/k
|
||||
```
|
||||
|
||||
The code evaluates the last term continuously at zero curvature without
|
||||
cancellation. Available path horizon limits `d`; prediction tapers to zero as
|
||||
the horizon approaches 7 m. Nonfinite prediction falls back to the validated
|
||||
current offset. The existing endpoint hold remains for paths shorter than 7 m.
|
||||
|
||||
A matched constant-radius path retains essentially the same C0, subject to
|
||||
sample interpolation. Developing and flattening bends can move the target
|
||||
earlier. This is a geometric hypothesis assuming motion along selected
|
||||
curvature, not an identified 150 ms actuator delay or a calibrated plant model.
|
||||
Errors in that assumption can increase or reduce useful steering demand.
|
||||
|
||||
The predicted offset passes through the existing ±5.11 m clip, v2 excess-yaw
|
||||
damping, independent 4 m/s slew and 0.01 m quantization. C1 construction, clip,
|
||||
slew and quantization are unchanged. C2=C3=0. Input sanity, freshness, service
|
||||
health and startup selection gates are unchanged. There are still only two
|
||||
control states (C0 and C1 slew positions), plus three adapter timestamps.
|
||||
The module is 193 total lines, including 125 code lines excluding blanks,
|
||||
comments and docstrings (18 more code lines than v2). No model history, integral
|
||||
or turn state machine is added. The bounds above
|
||||
apply to the prediction target, not arbitrary differences between separately
|
||||
slewed controllers after different histories.
|
||||
|
||||
## Offline results and tradeoff
|
||||
|
||||
All four supplied routes run at their original controls timestamps. Routes90/95
|
||||
also reproduce the archived v1 command construction exactly. The newer routes
|
||||
compare immutable v2 code with v3 using identical measured yaw, selected
|
||||
curvature, exact consumed model and causal carState. Publication times proxy
|
||||
computation time; complete SubMaster health is unavailable. Neither v2 nor v3
|
||||
was driven on these recordings. V2-versus-recorded error is therefore not a
|
||||
reconstruction accuracy measurement.
|
||||
|
||||
| Recorded interval | Command change versus v2 |
|
||||
| --- | --- |
|
||||
| route9e left entry, 173–175.4 s | Mean C0 magnitude +0.143 m; same 2.0 m level reached 0.203 s earlier |
|
||||
| route9e left peak, 175.4–178.3 s | Mean magnitude +0.095 m; prediction also increases some late demand |
|
||||
| route9e reversal, 728–734 s | Peak C0 magnitude 0.24 → 0.22 m |
|
||||
| route9b right exit, 642.7–643.852 s | Mean C0 +0.024 m, partially offsetting v2 damping |
|
||||
| Driver-clean low requests above 8 m/s, routes9b/9e | Mean absolute C0 change ≈0.0015 m; maximum 0.02 m |
|
||||
|
||||
The entry C0 crossings at 0.5, 1.0, 1.5, 2.0 and 2.4 m move earlier by 61, 64,
|
||||
367, 203 and 90 ms respectively. These are command-level crossing times,
|
||||
not measured improvements in wheel response. Several entry/peak windows
|
||||
contain driver input, quantified in the validation record.
|
||||
|
||||
On all 115 earlier right-exit cycles before strong intervention, v3 remains
|
||||
below the driven v1 reconstruction: mean C0 is 0.370 m for v1, 0.290 m for v2,
|
||||
and 0.314 m for v3. This tradeoff is retained explicitly; v3 does not improve
|
||||
every exit command relative to v2. There is no evidence yet that it reduces
|
||||
the late model request or the physical miss.
|
||||
|
||||
Route9b now includes full rlogs 12/13, added after the archived v2 evaluation.
|
||||
Its 14-rlog totals therefore differ from the historical 12-rlog report. The
|
||||
focused segment-10 comparison uses identical timestamps and data.
|
||||
|
||||
Validation passes 372 tests and 26 subtests, including the real extracted
|
||||
controlsd selection/limiter/publication path and downstream CAN builder, with
|
||||
100% controller statement and branch coverage. Four routes cover 299,604
|
||||
original cycles. Randomized testing adds 200,000 core updates plus their
|
||||
mirrors against an independent analytic geometry/damping/slew oracle; boundary
|
||||
and route checks total 817,346 Float32/CAN round trips. These checks establish
|
||||
command construction and retained gates, not closed-loop vehicle behavior.
|
||||
See `ford_model_action_prediction_validation.json` for provenance and counts.
|
||||
|
||||
## Reproduce and select
|
||||
|
||||
To reproduce the archived suite and stress results, use v3 commit
|
||||
`01f8d51c82b3e863f1012d383b5994813ef01b81` with the native dependencies and
|
||||
suite command in the [drive-test guide](ford_model_action_drive_test.md).
|
||||
Current replay tooling can select the immutable v3 source explicitly. New-route comparisons use
|
||||
the deployment opendbc pin `c21a9013700734dd20b09e05aa68329ad8cc20f9`:
|
||||
|
||||
```sh
|
||||
python -m tools.ford_pscm_lab.damping_replay /path/to/route9e/rlogs --baseline v2 --candidate v3 --window left_entry 173 175.4 --window left_peak 175.4 178.3 --window left_exit 178.3 180.5 --window reversal 728 734 --window final_entry 822 825.5 --output /path/to/separate/route9e-results
|
||||
python -m tools.ford_pscm_lab.damping_replay /path/to/route9b/rlogs --baseline v2 --candidate v3 --window right_entry 637 640 --window right_exit_before_strong_input 642.7 643.852 --output /path/to/separate/route9b-results
|
||||
python -m tools.ford_pscm_lab.stress_model_action --cycles 200000 --seed 20260907 --opendbc-revision c21a9013700734dd20b09e05aa68329ad8cc20f9 --output /path/to/stress.json
|
||||
```
|
||||
|
||||
Historical replay loads trusted controller source from immutable local Git
|
||||
commits; those objects must exist in the checkout. The original replay tool
|
||||
still requires its explicit historical opendbc pin. Neither tool downloads
|
||||
code or drives the car.
|
||||
|
||||
The existing default-off Sunnylink **Selected-Action Path Tracking
|
||||
(Experimental)** toggle selects v3 on the CAN FD F-150 Lightning. Updating
|
||||
with that toggle already enabled selects v3 at the next controlsd startup.
|
||||
Diagnostics identify `model-action-c0-c1-prediction-v3` and keep
|
||||
`calibration_approved=false`. No new device installation, hardware build,
|
||||
physical calibration or device boot is part of this offline validation.
|
||||
@@ -1,849 +0,0 @@
|
||||
{
|
||||
"date": "2026-09-07",
|
||||
"baseline_commit": "744a97d9bc08d8743b250eceff7c88585b5480de",
|
||||
"deployment_target": {
|
||||
"repository": "sunnypilot/sunnypilot",
|
||||
"branch": "hiimisaac-dev"
|
||||
},
|
||||
"hypothesis": "model-action-c0-c1-prediction-v3",
|
||||
"scope": "Bounded geometric prediction; fixed-input offline evidence only.",
|
||||
"calibration_approved": false,
|
||||
"hardware_build_and_device_boot": "not performed",
|
||||
"controller_size": {
|
||||
"total_lines": 193,
|
||||
"code_lines_excluding_blanks_comments_docstrings": 125,
|
||||
"core_persistent_values": 2,
|
||||
"adapter_timestamps": 3
|
||||
},
|
||||
"checks": {
|
||||
"combined_ford_params_sunnylink_suite": "372 passed, 26 subtests passed; no skips",
|
||||
"suite_log_sha256": "b9546bd4cac8349ab9a699f175ba569ed41fc8a9e85e79a0018c79d3af24c11d",
|
||||
"coverage": {
|
||||
"covered_lines": 116,
|
||||
"num_statements": 116,
|
||||
"percent_covered": 100.0,
|
||||
"percent_covered_display": "100",
|
||||
"missing_lines": 0,
|
||||
"excluded_lines": 0,
|
||||
"percent_statements_covered": 100.0,
|
||||
"percent_statements_covered_display": "100",
|
||||
"num_branches": 32,
|
||||
"num_partial_branches": 0,
|
||||
"covered_branches": 32,
|
||||
"missing_branches": 0,
|
||||
"percent_branches_covered": 100.0,
|
||||
"percent_branches_covered_display": "100"
|
||||
},
|
||||
"ruff": "pass",
|
||||
"ty_controller_and_lab": "pass",
|
||||
"settings_compiler_check": "pass",
|
||||
"prediction_red_probe": "Disabling prediction fails all four mirrored developing/flattening-bend cases (4 failed, 34 passed); all 38 pass with prediction.",
|
||||
"standards_review_remaining_findings": 0,
|
||||
"spec_review_remaining_findings": 0,
|
||||
"review_scope": "Tracked and untracked v3 changes since 744a97d9bc08d8743b250eceff7c88585b5480de; final loader unit test checked separately.",
|
||||
"test_portability": "Archive loader unit test mocks Git source retrieval; full offline route runs use the actual immutable commit sources."
|
||||
},
|
||||
"stress": {
|
||||
"seed": 20260907,
|
||||
"random_cycles": 200000,
|
||||
"mirrored_core_updates": 200000,
|
||||
"invalid_or_inactive_resets": 3537,
|
||||
"field_boundary_cases": 18138,
|
||||
"float32_can_round_trips": 218138,
|
||||
"analytic_targets_scalar_slew_and_mirror_checks_pass": true,
|
||||
"bounded_excess_yaw_damping_checked": true,
|
||||
"bounded_geometric_prediction_checked": true,
|
||||
"direct_raw_float32_packing_matches_host_output": true,
|
||||
"max_continuous_step_c0_c1": [
|
||||
0.40000000000015523,
|
||||
0.05000000000000002
|
||||
],
|
||||
"calibration_approved": false,
|
||||
"scope": "Numerical construction only; no PSCM response or closed-loop performance claims.",
|
||||
"opendbc_import_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/stress_model_action.py": "be77689e5327a13b7b4d0e18a3fc1863b57cab76be714339efc609124400489f",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "a872cf4594e364988796394b09f531d18cc3d6732e0c92f45eed9fbf6f5cbee6"
|
||||
}
|
||||
},
|
||||
"routes": {
|
||||
"route90": {
|
||||
"scope": "Command construction and adapter reconstruction only; no counterfactual closed-loop score.",
|
||||
"calibration_approved": false,
|
||||
"executes_live_selector": false,
|
||||
"cycles": 78812,
|
||||
"core_active_cycles": 73055,
|
||||
"core_exact_archived_match": true,
|
||||
"cohorts_reproduced": true,
|
||||
"adapter_active_cycles": 73055,
|
||||
"adapter_status_counts": {
|
||||
"inactive": 5757,
|
||||
"active": 73055
|
||||
},
|
||||
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||
"core_active_path_shorter_than_7m_cycles": 0,
|
||||
"adapter_validity_differs_from_archive_cycles": 0,
|
||||
"adapter_command_differs_from_archive_cycles": 27264,
|
||||
"adapter_max_absolute_command_difference_c0_c1": [
|
||||
0.16000000000000014,
|
||||
0.0020000000000000018
|
||||
],
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 157624,
|
||||
"timing": "Original controls publication timestamps proxy computation time; repeated frames and gaps retained. No identified delay.",
|
||||
"eligibility": "Adapter checks recorded services independently; full SubMaster health is unavailable. Core uses archived validity.",
|
||||
"reference": "Recorded controlsState.desiredCurvature, already selected/limited. These two routes have no maneuver publications.",
|
||||
"host_yaw": "Extract cs.yaw equals -carState.yawRate; current adapter uses it for bounded damping.",
|
||||
"archived_core_revision": "5fc16abc7662020706e29f57d31a6d5e2bc1293a",
|
||||
"archived_core_source_sha256": "8f3bc5d68e0051776f614a2ccffae84a88f7898dc95bdc12c23dcfe10dfe676a",
|
||||
"cohorts": {
|
||||
"small_request": {
|
||||
"seconds": 475.24161910000026,
|
||||
"core_c0_c1_rms": [
|
||||
0.06125184365338734,
|
||||
0.0057692154738982855
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.057970374224682646,
|
||||
0.007060795713496332
|
||||
],
|
||||
"adapter_eligible_seconds": 475.24161910000026,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.06384544973437024,
|
||||
0.0057692154738982855
|
||||
]
|
||||
},
|
||||
"turn": {
|
||||
"seconds": 47.85871389900012,
|
||||
"core_c0_c1_rms": [
|
||||
0.18725251016049418,
|
||||
0.046449510236699354
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.4773534845264775,
|
||||
0.04315935101392594
|
||||
],
|
||||
"adapter_eligible_seconds": 47.85871389900012,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.20375259142277982,
|
||||
0.046449510236699354
|
||||
]
|
||||
},
|
||||
"small_speed_2_8": {
|
||||
"seconds": 38.696717235999785,
|
||||
"core_c0_c1_rms": [
|
||||
0.209264275431127,
|
||||
0.014019080484620104
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.1593558816527209,
|
||||
0.01823849401244768
|
||||
],
|
||||
"adapter_eligible_seconds": 38.696717235999785,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.21664870054721863,
|
||||
0.014019080484620104
|
||||
]
|
||||
},
|
||||
"turn_speed_2_8": {
|
||||
"seconds": 0.30159887100000304,
|
||||
"core_c0_c1_rms": [
|
||||
2.090789090165622,
|
||||
0.21878942570759013
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
2.155728831937131,
|
||||
0.38375302254248855
|
||||
],
|
||||
"adapter_eligible_seconds": 0.30159887100000304,
|
||||
"adapter_c0_c1_rms": [
|
||||
2.2043400076988204,
|
||||
0.21878942570759013
|
||||
]
|
||||
},
|
||||
"small_speed_8_15": {
|
||||
"seconds": 79.2599167590001,
|
||||
"core_c0_c1_rms": [
|
||||
0.02250905681378238,
|
||||
0.005843904113223639
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.03477379324171293,
|
||||
0.006545440153133947
|
||||
],
|
||||
"adapter_eligible_seconds": 79.2599167590001,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.026080258136122546,
|
||||
0.005843904113223639
|
||||
]
|
||||
},
|
||||
"turn_speed_8_15": {
|
||||
"seconds": 0.022264622000001566,
|
||||
"core_c0_c1_rms": [
|
||||
0.20999999999999996,
|
||||
0.04349999999999998
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.25,
|
||||
0.04050000011920929
|
||||
],
|
||||
"adapter_eligible_seconds": 0.022264622000001566,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.20999999999999996,
|
||||
0.04349999999999998
|
||||
]
|
||||
},
|
||||
"small_speed_15_55": {
|
||||
"seconds": 357.2849851050004,
|
||||
"core_c0_c1_rms": [
|
||||
0.011622097035118838,
|
||||
0.003925571829874466
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.03809717934501699,
|
||||
0.0045587590980714285
|
||||
],
|
||||
"adapter_eligible_seconds": 357.2849851050004,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.013693372023947319,
|
||||
0.003925571829874466
|
||||
]
|
||||
},
|
||||
"turn_speed_15_55": {
|
||||
"seconds": 47.53485040600012,
|
||||
"core_c0_c1_rms": [
|
||||
0.08686835454709245,
|
||||
0.043216347944464766
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.4471065308273131,
|
||||
0.030663943784303503
|
||||
],
|
||||
"adapter_eligible_seconds": 47.53485040600012,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.10462899220936993,
|
||||
0.043216347944464766
|
||||
]
|
||||
},
|
||||
"pose_quiet": {
|
||||
"seconds": 427.99642020700026,
|
||||
"core_c0_c1_rms": [
|
||||
0.01261133542366393,
|
||||
0.00422389167351435
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.03724956081568198,
|
||||
0.0046880581807907966
|
||||
],
|
||||
"adapter_eligible_seconds": 427.99642020700026,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.014639447114479471,
|
||||
0.00422389167351435
|
||||
]
|
||||
}
|
||||
},
|
||||
"workspace_head": "744a97d9bc08d8743b250eceff7c88585b5480de",
|
||||
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||
"opendbc_import_path": "/Users/ibpersonal/.codex/worktrees/b926/sunnypilot/opendbc_repo",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "a872cf4594e364988796394b09f531d18cc3d6732e0c92f45eed9fbf6f5cbee6",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_path.py": "383538fc7cdae3bc28dffb71fe12ac5f3f9866ffbe6adfb7457f3593e9fc903a",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/route.npz": "51e8c26eedde253e171af47d704c1967ba45ae6825d883393bec1fb9e00251c1",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/metadata.json": "742afec55ed629155b22d387f376f878f7b2765221fe94f515a829052bdd916f",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/encoder_comparison.npz": "7a625d3ed5cbd8013d1028aa3bc421740551dcae5c3d60981208bd047af9794c",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/encoder_comparison.json": "64e1cc4be84394ac7ec408d383b99b48ada9b3fd129cdef2fd846e5fac620d66",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route90/pose_candidate/pose_replay.npz": "4457ccc0868354749da5b72c1dea0faf750f783dfdc87038101288fdcba1e707"
|
||||
}
|
||||
},
|
||||
"route95": {
|
||||
"scope": "Command construction and adapter reconstruction only; no counterfactual closed-loop score.",
|
||||
"calibration_approved": false,
|
||||
"executes_live_selector": false,
|
||||
"cycles": 54738,
|
||||
"core_active_cycles": 37614,
|
||||
"core_exact_archived_match": true,
|
||||
"cohorts_reproduced": true,
|
||||
"adapter_active_cycles": 37614,
|
||||
"adapter_status_counts": {
|
||||
"inactive": 17124,
|
||||
"active": 37614
|
||||
},
|
||||
"adapter_matches_current_core_with_yaw_and_fresh_engagement_dt": true,
|
||||
"core_active_path_shorter_than_7m_cycles": 44,
|
||||
"adapter_validity_differs_from_archive_cycles": 0,
|
||||
"adapter_command_differs_from_archive_cycles": 20110,
|
||||
"adapter_max_absolute_command_difference_c0_c1": [
|
||||
0.16999999999999993,
|
||||
0.0010000000000000009
|
||||
],
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 109476,
|
||||
"timing": "Original controls publication timestamps proxy computation time; repeated frames and gaps retained. No identified delay.",
|
||||
"eligibility": "Adapter checks recorded services independently; full SubMaster health is unavailable. Core uses archived validity.",
|
||||
"reference": "Recorded controlsState.desiredCurvature, already selected/limited. These two routes have no maneuver publications.",
|
||||
"host_yaw": "Extract cs.yaw equals -carState.yawRate; current adapter uses it for bounded damping.",
|
||||
"archived_core_revision": "5fc16abc7662020706e29f57d31a6d5e2bc1293a",
|
||||
"archived_core_source_sha256": "8f3bc5d68e0051776f614a2ccffae84a88f7898dc95bdc12c23dcfe10dfe676a",
|
||||
"cohorts": {
|
||||
"small_request": {
|
||||
"seconds": 160.93427628100014,
|
||||
"core_c0_c1_rms": [
|
||||
0.027283284164810895,
|
||||
0.006054573235641941
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.04189890174428264,
|
||||
0.0075342351227490395
|
||||
],
|
||||
"adapter_eligible_seconds": 160.93427628100014,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.02970169836480558,
|
||||
0.006054573235641941
|
||||
]
|
||||
},
|
||||
"turn": {
|
||||
"seconds": 58.39837079799993,
|
||||
"core_c0_c1_rms": [
|
||||
0.5425346915921071,
|
||||
0.09102985199238507
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.63700479987207,
|
||||
0.09691236120507696
|
||||
],
|
||||
"adapter_eligible_seconds": 58.39837079799993,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.5622074838217578,
|
||||
0.09102985199238507
|
||||
]
|
||||
},
|
||||
"small_speed_2_8": {
|
||||
"seconds": 8.083969085000064,
|
||||
"core_c0_c1_rms": [
|
||||
0.09941192804175551,
|
||||
0.016572084086884566
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.07597383189748476,
|
||||
0.016403469943482765
|
||||
],
|
||||
"adapter_eligible_seconds": 8.083969085000064,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.1039594708778471,
|
||||
0.016572084086884566
|
||||
]
|
||||
},
|
||||
"turn_speed_2_8": {
|
||||
"seconds": 2.9056519089999995,
|
||||
"core_c0_c1_rms": [
|
||||
2.286598281178436,
|
||||
0.3420570411209625
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
2.4371443775668484,
|
||||
0.37360610108051345
|
||||
],
|
||||
"adapter_eligible_seconds": 2.9056519089999995,
|
||||
"adapter_c0_c1_rms": [
|
||||
2.342145227147664,
|
||||
0.3420570411209625
|
||||
]
|
||||
},
|
||||
"small_speed_8_15": {
|
||||
"seconds": 32.021085868,
|
||||
"core_c0_c1_rms": [
|
||||
0.028387860150467858,
|
||||
0.0067602044219821
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.036941169841952046,
|
||||
0.007349671742584858
|
||||
],
|
||||
"adapter_eligible_seconds": 32.021085868,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.032473217223426755,
|
||||
0.0067602044219821
|
||||
]
|
||||
},
|
||||
"turn_speed_8_15": {
|
||||
"seconds": 22.160645306000006,
|
||||
"core_c0_c1_rms": [
|
||||
0.28668316323581683,
|
||||
0.0671915746138579
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.3391022553367454,
|
||||
0.07297678510653233
|
||||
],
|
||||
"adapter_eligible_seconds": 22.160645306000006,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.32040564199313326,
|
||||
0.0671915746138579
|
||||
]
|
||||
},
|
||||
"small_speed_15_55": {
|
||||
"seconds": 120.82922132800007,
|
||||
"core_c0_c1_rms": [
|
||||
0.010802222799973712,
|
||||
0.004282512931613862
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.03987958989535526,
|
||||
0.0065793876512313535
|
||||
],
|
||||
"adapter_eligible_seconds": 120.82922132800007,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.013133007539405142,
|
||||
0.004282512931613862
|
||||
]
|
||||
},
|
||||
"turn_speed_15_55": {
|
||||
"seconds": 33.332073582999925,
|
||||
"core_c0_c1_rms": [
|
||||
0.07257996778378971,
|
||||
0.03628926246224237
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.3416081588451539,
|
||||
0.027322786376822172
|
||||
],
|
||||
"adapter_eligible_seconds": 33.332073582999925,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.08556161843867907,
|
||||
0.03628926246224237
|
||||
]
|
||||
},
|
||||
"pose_quiet": {
|
||||
"seconds": 145.24136911700015,
|
||||
"core_c0_c1_rms": [
|
||||
0.012566204405760934,
|
||||
0.004614194153565963
|
||||
],
|
||||
"recorded_v8_c0_c1_rms": [
|
||||
0.03855743841849719,
|
||||
0.006621274926345545
|
||||
],
|
||||
"adapter_eligible_seconds": 145.24136911700015,
|
||||
"adapter_c0_c1_rms": [
|
||||
0.014652655970248427,
|
||||
0.004614194153565963
|
||||
]
|
||||
}
|
||||
},
|
||||
"workspace_head": "744a97d9bc08d8743b250eceff7c88585b5480de",
|
||||
"opendbc_import_head": "72a775d35e54c21ff5c5798acef22016eedcc0a7",
|
||||
"opendbc_import_path": "/Users/ibpersonal/.codex/worktrees/b926/sunnypilot/opendbc_repo",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "a872cf4594e364988796394b09f531d18cc3d6732e0c92f45eed9fbf6f5cbee6",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_path.py": "383538fc7cdae3bc28dffb71fe12ac5f3f9866ffbe6adfb7457f3593e9fc903a",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/route.npz": "6e5438867ea618e53ca395b60ff4b9b146256f1bf6f07dc8e92d663286074154",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/metadata.json": "f7c95a102e0e084bbcc9938399219080c1d3b26274b4ea3c3a9514ff7dc8ea8d",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/encoder_comparison.npz": "23bd05c4b6400299844acaba1d97051c96c23c682bf17b46e89e7f2cca5fce38",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/encoder_comparison.json": "1ab24a796d14817b8672ea6dfd39711d65cb06744eaaef7dcc02a840c392c4d3",
|
||||
"/Users/ibpersonal/.codex/worktrees/3548/sunnypilot/analysis/controller_search_20260904/route95/pose_candidate/pose_replay.npz": "9cfbb3c6f0b1fdb7e3d38e6b64b8c94cffbcc9fabe41f6344d84a9b657b6af9b"
|
||||
}
|
||||
},
|
||||
"route9b": {
|
||||
"scope": "Fixed-input command replay only; no physical improvement or stability claim.",
|
||||
"calibration_approved": false,
|
||||
"cycles": 82386,
|
||||
"eligible_cycles": 70703,
|
||||
"status_counts": {
|
||||
"inactive": 11683,
|
||||
"active": 70703
|
||||
},
|
||||
"c1_exactly_unchanged": true,
|
||||
"same_validity": true,
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 164772,
|
||||
"cohorts": {
|
||||
"eligible": {
|
||||
"cycles": 70703,
|
||||
"seconds": 711.571703289,
|
||||
"changed_c0_cycles": 20858,
|
||||
"mean_absolute_c0_change_m": 0.006916798541216091,
|
||||
"max_absolute_c0_change_m": 0.15000000000000124,
|
||||
"increased_absolute_c0_cycles": 17700,
|
||||
"decreased_absolute_c0_cycles": 3157,
|
||||
"driver_input_percent": 3.0297623863274845,
|
||||
"baseline_peak_absolute_c0_m": 3.89,
|
||||
"candidate_peak_absolute_c0_m": 3.96
|
||||
},
|
||||
"driver_clean": {
|
||||
"cycles": 63267,
|
||||
"seconds": 636.7469998759999,
|
||||
"changed_c0_cycles": 16024,
|
||||
"mean_absolute_c0_change_m": 0.002923399405403562,
|
||||
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||
"increased_absolute_c0_cycles": 13587,
|
||||
"decreased_absolute_c0_cycles": 2437,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 2.16,
|
||||
"candidate_peak_absolute_c0_m": 2.24
|
||||
},
|
||||
"driver_clean_low_request_above_8mps": {
|
||||
"cycles": 40656,
|
||||
"seconds": 409.13067679800054,
|
||||
"changed_c0_cycles": 6007,
|
||||
"mean_absolute_c0_change_m": 0.0014813245503935623,
|
||||
"max_absolute_c0_change_m": 0.020000000000000462,
|
||||
"increased_absolute_c0_cycles": 5099,
|
||||
"decreased_absolute_c0_cycles": 908,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 0.08999999999999986,
|
||||
"candidate_peak_absolute_c0_m": 0.11000000000000032
|
||||
},
|
||||
"turn": {
|
||||
"cycles": 5459,
|
||||
"seconds": 54.939139939000256,
|
||||
"changed_c0_cycles": 3156,
|
||||
"mean_absolute_c0_change_m": 0.007655026845650452,
|
||||
"max_absolute_c0_change_m": 0.08999999999999986,
|
||||
"increased_absolute_c0_cycles": 2938,
|
||||
"decreased_absolute_c0_cycles": 218,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 2.16,
|
||||
"candidate_peak_absolute_c0_m": 2.24
|
||||
},
|
||||
"right_entry": {
|
||||
"cycles": 298,
|
||||
"seconds": 2.9932484459999387,
|
||||
"changed_c0_cycles": 298,
|
||||
"mean_absolute_c0_change_m": 0.14871908015683724,
|
||||
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||
"increased_absolute_c0_cycles": 298,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 20.395198628296086,
|
||||
"baseline_peak_absolute_c0_m": 2.8000000000000003,
|
||||
"candidate_peak_absolute_c0_m": 2.95
|
||||
},
|
||||
"right_exit_before_strong_input": {
|
||||
"cycles": 115,
|
||||
"seconds": 1.156770048999988,
|
||||
"changed_c0_cycles": 115,
|
||||
"mean_absolute_c0_change_m": 0.02362929150320746,
|
||||
"max_absolute_c0_change_m": 0.040000000000000036,
|
||||
"increased_absolute_c0_cycles": 115,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 0.7000000000000002,
|
||||
"candidate_peak_absolute_c0_m": 0.7199999999999998
|
||||
}
|
||||
},
|
||||
"baseline_commands_vs_recorded_publications": {
|
||||
"paired_cycles": 70703,
|
||||
"within_one_quantum_cycles": 69862,
|
||||
"maximum_absolute_error_c0_c1": [
|
||||
0.20999998569488554,
|
||||
0.0005000143051147043
|
||||
]
|
||||
},
|
||||
"timing": "Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.",
|
||||
"baseline_version": "v2",
|
||||
"baseline_revision": "744a97d9bc08d8743b250eceff7c88585b5480de",
|
||||
"candidate_version": "current",
|
||||
"candidate_revision": "working_tree",
|
||||
"focus_windows": [
|
||||
[
|
||||
"right_entry",
|
||||
"637",
|
||||
"640"
|
||||
],
|
||||
[
|
||||
"right_exit_before_strong_input",
|
||||
"642.7",
|
||||
"643.852"
|
||||
]
|
||||
],
|
||||
"baseline_source_sha256": "59d66297a017557f3d4f28b115be3f6220b800566c11935e2284b3814783fb7e",
|
||||
"candidate_source_sha256": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||
"source_rlog_sha256": {
|
||||
"84865544361f55cb_0000009b--e4616dddaa--0--rlog.zst": "22746f7119109b73ed7f2c26ce8c99f87136e9124fb7fc14c9554409a28a7c3f",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--1--rlog.zst": "4c2e1d7083c31a2b37d0f8dd3be4d330898511b7e02c26f7d40ca9bc2779397d",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--2--rlog.zst": "62f3e049e220cd3681fadf386f2969537bd571998ae2f6ba2d08479428b5a28f",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--3--rlog.zst": "83bf0131b2d36b2ba7e5ba050bbc13c0a3350feb5c9b89dc9c87d3a37abebfb3",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--4--rlog.zst": "430985a80dd6e10f7abeb89457a17022e6bb6978617f415c905f584b1647603e",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--5--rlog.zst": "8c0c5ae6323ec33b3e14f84ca834f70cb56f6b29f471a350f1e3efc06b6ba553",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--6--rlog.zst": "db53dfa8156b9d66792c3eff0b2ce5d31b71ad41cc580dec85f528845593c184",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--7--rlog.zst": "91b0b3be10cb7d7d7f7dd2024d8f9ee99d1e9fd2204203a3a9a2f2f1c6e3fa03",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--8--rlog.zst": "687dbbfc49837efbfe8fa6bc091e40f7fad2908832234d7884f4616d1bc9ccff",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--9--rlog.zst": "a88ec4d25b04cdbf5844686fc77f6b28dca920c9b164e37ebf69844a3ae398fc",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--10--rlog.zst": "fe6b29580a6c94e1c236d13e18db4cd9f31cc1b25d52e1e6e19a5021125c9932",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--11--rlog.zst": "150d31b1944d7a1b8c562f3aee20b66cefa6c4e8d02660ec889907d835142f45",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--12--rlog.zst": "e105e5f703a70f36f1989c66fb46c35a65ff60a265b4332e10fa2e6875a2ced4",
|
||||
"84865544361f55cb_0000009b--e4616dddaa--13--rlog.zst": "69304cf0c81401374f047c5022ca47d257ff9e59e30ec171325a2ee1e7ed13d4"
|
||||
},
|
||||
"opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/damping_replay.py": "663674fa9686291228766c4fd2990dcfc620b155f6d74e2f957bbb33e5593a5d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0"
|
||||
}
|
||||
},
|
||||
"route9e": {
|
||||
"scope": "Fixed-input command replay only; no physical improvement or stability claim.",
|
||||
"calibration_approved": false,
|
||||
"cycles": 83668,
|
||||
"eligible_cycles": 74669,
|
||||
"status_counts": {
|
||||
"inactive": 8999,
|
||||
"active": 74669
|
||||
},
|
||||
"c1_exactly_unchanged": true,
|
||||
"same_validity": true,
|
||||
"field_slew_zero_c2_c3_pass": true,
|
||||
"float32_can_round_trips": 167336,
|
||||
"cohorts": {
|
||||
"eligible": {
|
||||
"cycles": 74669,
|
||||
"seconds": 751.5479497329998,
|
||||
"changed_c0_cycles": 23282,
|
||||
"mean_absolute_c0_change_m": 0.00617904439256301,
|
||||
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||
"increased_absolute_c0_cycles": 19880,
|
||||
"decreased_absolute_c0_cycles": 3402,
|
||||
"driver_input_percent": 2.7324748055386094,
|
||||
"baseline_peak_absolute_c0_m": 4.33,
|
||||
"candidate_peak_absolute_c0_m": 4.38
|
||||
},
|
||||
"driver_clean": {
|
||||
"cycles": 66321,
|
||||
"seconds": 667.5291971840106,
|
||||
"changed_c0_cycles": 18123,
|
||||
"mean_absolute_c0_change_m": 0.003409158953346422,
|
||||
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||
"increased_absolute_c0_cycles": 16136,
|
||||
"decreased_absolute_c0_cycles": 1987,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 2.3600000000000003,
|
||||
"candidate_peak_absolute_c0_m": 2.4699999999999998
|
||||
},
|
||||
"driver_clean_low_request_above_8mps": {
|
||||
"cycles": 39635,
|
||||
"seconds": 398.9613925449994,
|
||||
"changed_c0_cycles": 5866,
|
||||
"mean_absolute_c0_change_m": 0.0014778774629256156,
|
||||
"max_absolute_c0_change_m": 0.020000000000000462,
|
||||
"increased_absolute_c0_cycles": 5377,
|
||||
"decreased_absolute_c0_cycles": 489,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 0.05999999999999961,
|
||||
"candidate_peak_absolute_c0_m": 0.07000000000000028
|
||||
},
|
||||
"turn": {
|
||||
"cycles": 6219,
|
||||
"seconds": 62.59491144700405,
|
||||
"changed_c0_cycles": 5129,
|
||||
"mean_absolute_c0_change_m": 0.011069748090899225,
|
||||
"max_absolute_c0_change_m": 0.10999999999999943,
|
||||
"increased_absolute_c0_cycles": 5098,
|
||||
"decreased_absolute_c0_cycles": 31,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 2.3600000000000003,
|
||||
"candidate_peak_absolute_c0_m": 2.4699999999999998
|
||||
},
|
||||
"left_entry": {
|
||||
"cycles": 238,
|
||||
"seconds": 2.4048367620016506,
|
||||
"changed_c0_cycles": 238,
|
||||
"mean_absolute_c0_change_m": 0.14261645650107144,
|
||||
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||
"increased_absolute_c0_cycles": 238,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 2.403816920672437,
|
||||
"baseline_peak_absolute_c0_m": 2.46,
|
||||
"candidate_peak_absolute_c0_m": 2.6100000000000003
|
||||
},
|
||||
"left_peak": {
|
||||
"cycles": 288,
|
||||
"seconds": 2.891819394000777,
|
||||
"changed_c0_cycles": 288,
|
||||
"mean_absolute_c0_change_m": 0.095420590363382,
|
||||
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||
"increased_absolute_c0_cycles": 288,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 6.41034524444729,
|
||||
"baseline_peak_absolute_c0_m": 2.79,
|
||||
"candidate_peak_absolute_c0_m": 2.9400000000000004
|
||||
},
|
||||
"left_exit": {
|
||||
"cycles": 219,
|
||||
"seconds": 2.203320298998733,
|
||||
"changed_c0_cycles": 185,
|
||||
"mean_absolute_c0_change_m": 0.0422943841629376,
|
||||
"max_absolute_c0_change_m": 0.09000000000000075,
|
||||
"increased_absolute_c0_cycles": 56,
|
||||
"decreased_absolute_c0_cycles": 129,
|
||||
"driver_input_percent": 9.936619705158035,
|
||||
"baseline_peak_absolute_c0_m": 2.4299999999999997,
|
||||
"candidate_peak_absolute_c0_m": 2.51
|
||||
},
|
||||
"reversal": {
|
||||
"cycles": 554,
|
||||
"seconds": 5.570295188001182,
|
||||
"changed_c0_cycles": 167,
|
||||
"mean_absolute_c0_change_m": 0.007060409634059627,
|
||||
"max_absolute_c0_change_m": 0.040000000000000036,
|
||||
"increased_absolute_c0_cycles": 20,
|
||||
"decreased_absolute_c0_cycles": 147,
|
||||
"driver_input_percent": 0.0,
|
||||
"baseline_peak_absolute_c0_m": 0.2400000000000002,
|
||||
"candidate_peak_absolute_c0_m": 0.21999999999999975
|
||||
},
|
||||
"final_entry": {
|
||||
"cycles": 348,
|
||||
"seconds": 3.4958847329980927,
|
||||
"changed_c0_cycles": 348,
|
||||
"mean_absolute_c0_change_m": 0.11950442609746584,
|
||||
"max_absolute_c0_change_m": 0.15000000000000036,
|
||||
"increased_absolute_c0_cycles": 348,
|
||||
"decreased_absolute_c0_cycles": 0,
|
||||
"driver_input_percent": 3.556656282920533,
|
||||
"baseline_peak_absolute_c0_m": 3.31,
|
||||
"candidate_peak_absolute_c0_m": 3.4400000000000004
|
||||
}
|
||||
},
|
||||
"baseline_commands_vs_recorded_publications": {
|
||||
"paired_cycles": 74669,
|
||||
"within_one_quantum_cycles": 74156,
|
||||
"maximum_absolute_error_c0_c1": [
|
||||
0.29000000834465034,
|
||||
0.0010000016689301061
|
||||
]
|
||||
},
|
||||
"timing": "Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.",
|
||||
"baseline_version": "v2",
|
||||
"baseline_revision": "744a97d9bc08d8743b250eceff7c88585b5480de",
|
||||
"candidate_version": "current",
|
||||
"candidate_revision": "working_tree",
|
||||
"focus_windows": [
|
||||
[
|
||||
"left_entry",
|
||||
"173",
|
||||
"175.4"
|
||||
],
|
||||
[
|
||||
"left_peak",
|
||||
"175.4",
|
||||
"178.3"
|
||||
],
|
||||
[
|
||||
"left_exit",
|
||||
"178.3",
|
||||
"180.5"
|
||||
],
|
||||
[
|
||||
"reversal",
|
||||
"728",
|
||||
"734"
|
||||
],
|
||||
[
|
||||
"final_entry",
|
||||
"822",
|
||||
"825.5"
|
||||
]
|
||||
],
|
||||
"baseline_source_sha256": "59d66297a017557f3d4f28b115be3f6220b800566c11935e2284b3814783fb7e",
|
||||
"candidate_source_sha256": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||
"source_rlog_sha256": {
|
||||
"84865544361f55cb_0000009e--592f7dc149--0--rlog.zst": "de63532ae6aedf5dc7fd3ac8e47a2d96a8f065614f4aca79ae2f120ea00390c7",
|
||||
"84865544361f55cb_0000009e--592f7dc149--1--rlog.zst": "67b58197158b3e8f0581643f6657d2a85c47b0bb75dd3e00306d9ffae790008b",
|
||||
"84865544361f55cb_0000009e--592f7dc149--2--rlog.zst": "a4840f338f51b5f1864c79ce3a4f2b11dc13d58d52b8459961c9da57237f2cc2",
|
||||
"84865544361f55cb_0000009e--592f7dc149--3--rlog.zst": "ff3ea8d948006ab19c4dbfeeff59a509de3f193da47b91c88faa27ecd0b3f1fc",
|
||||
"84865544361f55cb_0000009e--592f7dc149--4--rlog.zst": "5ff1996c2336299128a11e657c32bc21921716b7e3bd401de69a2d49484aa223",
|
||||
"84865544361f55cb_0000009e--592f7dc149--5--rlog.zst": "238763457e89896933afaf9a5df325469ee8c02ddc53550df252531bc94fc540",
|
||||
"84865544361f55cb_0000009e--592f7dc149--6--rlog.zst": "2ee86de80cfd762be10cd2dfb2895ddbee6b813706e9c7261460203e09b9bc4d",
|
||||
"84865544361f55cb_0000009e--592f7dc149--7--rlog.zst": "017801f080861c63d799f87aebe30ab57cdf82f078ac882e6187d3870c403538",
|
||||
"84865544361f55cb_0000009e--592f7dc149--8--rlog.zst": "3729016bd1f00bb1077613b63fe25b21ba7112822b994e0e0aa2b4cd93bdb940",
|
||||
"84865544361f55cb_0000009e--592f7dc149--9--rlog.zst": "c40b3c1f6eb9252b85f176fee32cae16c23eaad3db830f6bbf37a730034a5ccf",
|
||||
"84865544361f55cb_0000009e--592f7dc149--10--rlog.zst": "e0ce8f231798073e5bbc34551fbd9493f169a06a068adc1957ef8cd914b71fbb",
|
||||
"84865544361f55cb_0000009e--592f7dc149--11--rlog.zst": "90863353f7c862a05618b4d0761dda3fcedd3cf6b4234c21c6dcc2eee3ec20b2",
|
||||
"84865544361f55cb_0000009e--592f7dc149--12--rlog.zst": "e064f8abde9615b2daf00f469ef36b438cd4b3a1637069467c84de91bee3bdf1",
|
||||
"84865544361f55cb_0000009e--592f7dc149--13--rlog.zst": "790fc438ce2675e0690fdce3b07bf54fdf6d06b17bbe7f5ff447adfa71189754",
|
||||
"84865544361f55cb_0000009e--592f7dc149--14--rlog.zst": "b98f5a5be660013b1fe11250fdc24ee4fe1d9dd1301b787ddfdff1f2bcd3e27f"
|
||||
},
|
||||
"opendbc_head": "c21a9013700734dd20b09e05aa68329ad8cc20f9",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/damping_replay.py": "663674fa9686291228766c4fd2990dcfc620b155f6d74e2f957bbb33e5593a5d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"focus_comparison": {
|
||||
"scope": "Same recorded inputs; crossing a C0 level earlier does not identify vehicle response time.",
|
||||
"left_entry_crossings": [
|
||||
{
|
||||
"c0_magnitude_m": 0.5,
|
||||
"v2_time_s": 173.04409075799958,
|
||||
"v3_time_s": 172.98298211199835,
|
||||
"earlier_ms": 61.10864600123023
|
||||
},
|
||||
{
|
||||
"c0_magnitude_m": 1.0,
|
||||
"v2_time_s": 173.41054691999852,
|
||||
"v3_time_s": 173.3469171409979,
|
||||
"earlier_ms": 63.629779000621056
|
||||
},
|
||||
{
|
||||
"c0_magnitude_m": 1.5,
|
||||
"v2_time_s": 174.09016323599826,
|
||||
"v3_time_s": 173.7234842099988,
|
||||
"earlier_ms": 366.6790259994741
|
||||
},
|
||||
{
|
||||
"c0_magnitude_m": 2.0,
|
||||
"v2_time_s": 174.69427409499804,
|
||||
"v3_time_s": 174.49167145299907,
|
||||
"earlier_ms": 202.60264199896483
|
||||
},
|
||||
{
|
||||
"c0_magnitude_m": 2.4,
|
||||
"v2_time_s": 175.08411706099832,
|
||||
"v3_time_s": 174.99415714599854,
|
||||
"earlier_ms": 89.95991499978118
|
||||
}
|
||||
],
|
||||
"right_exit": {
|
||||
"mean_c0_v1_m": 0.3704272817752263,
|
||||
"mean_c0_v2_m": 0.29049595970311604,
|
||||
"mean_c0_v3_m": 0.31412525120632345,
|
||||
"v3_below_driven_v1_every_cycle": true,
|
||||
"cycles": 115
|
||||
}
|
||||
},
|
||||
"total_original_route_cycles": 299604,
|
||||
"total_float32_can_round_trips": 817346,
|
||||
"source_sha256": {
|
||||
"openpilot/selfdrive/controls/lib/ford_model_action.py": "5b9f7946f29b5fcd94ca93e53be14a12b18ae74d7ca27380e92b90c8c599dce0",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action.py": "8851104f811757f6a31b5e23ec5933c7bfc18c17012af9a0a78f851217eca525",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_adapter.py": "04daed84093330c05ae1669c155ed9c197f95ebb02fd4e7ca1c7a10ce70d03da",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_damping.py": "a7062d2c3c46b3abcd4c3facd97970644ce609fb55285daa0359681950948250",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_prediction.py": "c05ae173c809228a9f4782af32a174b1b928f506a76c454aa89a51ef0ed17bdd",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_selection.py": "f826c6328f0abac2a61f1a0a6f8d119fdbab858e363cb466d84ba9a7783059cd",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_controlsd_logging.py": "fd4095bc4f1363fd014c43a9e0776a2245105e8fb2703d87da367d0f33dc22dc",
|
||||
"tools/ford_pscm_lab/__init__.py": "db4b8b7d2e317ed34ca0ec220bf9d53e7b80a23e766f4e1cb2c8224dada45f2e",
|
||||
"tools/ford_pscm_lab/damping_replay.py": "663674fa9686291228766c4fd2990dcfc620b155f6d74e2f957bbb33e5593a5d",
|
||||
"tools/ford_pscm_lab/model_action_replay.py": "a872cf4594e364988796394b09f531d18cc3d6732e0c92f45eed9fbf6f5cbee6",
|
||||
"tools/ford_pscm_lab/stress_model_action.py": "be77689e5327a13b7b4d0e18a3fc1863b57cab76be714339efc609124400489f",
|
||||
"tools/ford_pscm_lab/test_model_action_replay.py": "d2a3602e145db4f969ecf1402fe66684c3b1f884e3ed429e95f5fb5dcaddefb3",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "b5951a90cb01bfcd79b82705600ad2d165b3cc5f7e33c7404536eb989bc3ce39",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui.json": "6badb0080d8a999d63d063430c0a320f1a58884dfe0c390774f90a39cbf0e0e2"
|
||||
},
|
||||
"artifact_sha256": {
|
||||
".cache/ford_model_action_prediction/prediction_red.txt": "81e765edd1fc73783855ef30aa3c4aa8f111c001d7e9e0951d5791e18a860f4e",
|
||||
".cache/ford_model_action_prediction/stress.json": "e358a7caafe457a83637857cee7f0b1b2b60e0e4020f64b46630b8462e5edff5",
|
||||
".cache/ford_model_action_prediction/coverage.json": "333ed4414632adbb12a8a1f3ef2f9dc7a62dd9fde08bbc6b8a3d372cf9a6869b",
|
||||
".cache/ford_model_action_prediction/focus_comparison.json": "b0af320c0eb7e535420702ed78df30c43134f8f9e2a977e5f88f765ba0626c69",
|
||||
".cache/ford_model_action_prediction/route90/report.json": "b58f738269d9fe176672670f5bf4116f3226d319f43b5b407ac221c288aba174",
|
||||
".cache/ford_model_action_prediction/route95/report.json": "379a6f5bc9c98d8d1bccf687d61d46c54eda439284508e321d49b5312c1ca9f2",
|
||||
".cache/ford_model_action_prediction/route9b/report.json": "650ce4056b663124e2f72fae94cd1b66423ff74de42611a99501271c2c846bc2",
|
||||
".cache/ford_model_action_prediction/route9e/report.json": "196b3b242e3e056ed531cd6e657a235cc9bf8ed4357ce696b1bec1e5f0a49a39",
|
||||
".cache/ford_model_action_prediction/route9b/commands.npz": "5d82318935253d4fa8a1b8c30cc7f2d465c151ae1cb9109fa09a622d5be49871",
|
||||
".cache/ford_model_action_prediction/route9e/commands.npz": "2917bbe801a78711340fa4eae63d5bf1cb4a4464c45d8538c9a46a77b5511e06",
|
||||
".cache/ford_model_action_prediction/plot_commands.py": "6a5bd97fedeb9bd951925dddc656e508082f6d457dbc1a6c9d4db769afcdd921",
|
||||
"/Users/ibpersonal/.codex/visualizations/2026/09/07/01a07c33-1f6c-7292-8030-ad4b01923034/ford-route9e/prediction_command_comparison.png": "86e68a0497490352065997f73cd5a798ff93dcabc93152028669ccfb36a4fefe",
|
||||
"/Users/ibpersonal/.codex/visualizations/2026/09/07/01a07c33-1f6c-7292-8030-ad4b01923034/ford-route9e/prediction_command_comparison.svg": "2310b2978b60b97619886c2e10ecca88c73b5fde3477452347e6db0a5bbcd5b3"
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
# Ford model-point candidate v7
|
||||
|
||||
## Decision
|
||||
|
||||
Use the model's own position and orientation at one shared path point. This
|
||||
implements the request to follow model geometry through C0/C1, with C2/C3 zero,
|
||||
without a fitted PSCM plant, new strength gain, yaw integral, or release mode.
|
||||
|
||||
Let `s(t)` be cumulative planar arc distance along model position. Choose:
|
||||
|
||||
```
|
||||
station = min(path_end, max(7 metres, s(1 second)))
|
||||
C0_target = model.position.y at station
|
||||
C1_target = unwrapped model.orientation.z at station
|
||||
C2 = C3 = 0
|
||||
```
|
||||
|
||||
Use the published model timestamps, not `speed × 1 second`, so the point also
|
||||
follows the model's predicted acceleration/braking. The seven-metre floor keeps
|
||||
the existing low-speed preview distance; it can select a time beyond one second.
|
||||
If the entire path is shorter, both fields hold the same endpoint. Interpolation
|
||||
uses the same arc segment and weights for position and heading.
|
||||
|
||||
One second keeps C1 near the old one-second heading scale in ordinary driving.
|
||||
Using that same point for C0 represents a meaningful change in faster bends.
|
||||
This is a chosen local approximation; Ford's expected reference point and
|
||||
preview are unknown. Matching metres/radians does not prove PSCM equivalence.
|
||||
|
||||
The former 150 ms vehicle-pose forecast is removed. C1 no longer uses
|
||||
`max(7, speed) × selected desiredCurvature`. Measured yaw remains an input-health
|
||||
check only; calibrated yaw has no command role. Freshness, independent slew,
|
||||
packing, the shared startup toggle, 20 Hz sends, and Panda safety remain.
|
||||
The upstream scalar curvature is still logged but does not limit this geometry;
|
||||
C0/C1 retain their existing amplitude and slew limits. The scalar-only lateral
|
||||
maneuver test reference is explicitly unsupported and disengages this candidate.
|
||||
|
||||
## Offline evidence
|
||||
|
||||
The broad run passed **585 tests and 9,146 subtests**, with 178 inherited safety
|
||||
cases skipped as inapplicable. Tests of the removed yaw forecast were retired;
|
||||
new tests cover actual model clocks, a nonconstant-speed trajectory, shared-point
|
||||
sampling, the distance floor, endpoint holding, heading unwrap, source selection,
|
||||
invalid geometry, reset, independent slew, and actual CAN delivery.
|
||||
|
||||
The stress run covers 200,000 random cycles plus mirrored turns, 18,138 field
|
||||
boundary cases, and 218,138 Float32/CAN round trips. An independent scalar oracle
|
||||
checks analytic model points and slew. No vehicle plant is simulated.
|
||||
|
||||
Five recorded routes (a5, a2, a0, 9b, 9e) supply **379,718 controller cycles** and
|
||||
**76,294 model frames**. Original rlog hashes and position/orientation timestamps
|
||||
were checked. An independently implemented segment-weight oracle matches every
|
||||
sampled target and eligible slew state. Every cycle passed packing/bound checks;
|
||||
all **75,947** scheduled 20 Hz requests passed the actual unchanged Panda TX hook.
|
||||
This is TX acceptance with controlled eligibility, not a full Panda RX watchdog
|
||||
or vehicle-response replay. The [validation manifest](ford_model_points_validation.json)
|
||||
binds the results to their source and input hashes.
|
||||
|
||||
On a5's 61.24-second driver-clean ordinary-bend cohort, mean absolute C0 changes
|
||||
from 0.075 m recorded to 0.167 m replayed; C1 changes from 0.02287 to 0.02224 rad.
|
||||
Neither field target clips in that cohort. These are command differences, not
|
||||
predicted changes in steering strength or tracking error.
|
||||
|
||||
| a5 time | Recorded C0 / C1 | Candidate C0 / C1 | Selected station / time |
|
||||
| --- | --- | --- | --- |
|
||||
| 107.995 s, ordinary bend | +0.20 m / +0.068 rad | +0.55 m / +0.0685 rad | 13.71 m / 1.00 s |
|
||||
| 390.681 s, sustained bend | −0.52 m / −0.1235 rad | −0.75 m / −0.1215 rad | 9.37 m / 1.00 s |
|
||||
| 374.889 s, tight turn | −3.76 m / −0.50 rad | −3.46 m / −0.50 rad | 7.00 m / 1.49 s |
|
||||
| 17.607 s, exit overshoot | −0.01 m / −0.015 rad | −0.05 m / −0.012 rad | 7.00 m / 1.85 s |
|
||||
|
||||
The exit example retains more C0 into the turn than v6; physical unwind behavior
|
||||
must be evaluated. Tight-turn C1 clipping remains (14.92 eligible seconds on a5).
|
||||
No root cause or physical fix is proven by frozen inputs. The latest a5 road
|
||||
logs used 100 Hz sends, whereas the immediately preceding code revision already
|
||||
changed to 20 Hz; a comparison against that drive also includes the cadence change.
|
||||
|
||||
## Unwind comparison
|
||||
|
||||
The follow-up a5 comparison includes all seven clearly separated tight turns and
|
||||
four completed ordinary bends identified in the full-route command plot. A fifth
|
||||
bend runs into the next turn and is excluded from the summary. All scored windows
|
||||
remain continuously paired-active, without control gaps over 30 ms. The tight
|
||||
turns contain driver input; this compares instructions on frozen inputs, not
|
||||
unassisted tracking or hypothetical truck motion.
|
||||
|
||||
Measure the first time each instruction falls below the same fixed level on
|
||||
exit and stays below for 100 ms. All seven tight turns exceed these levels:
|
||||
|
||||
| Instruction | Candidate minus recorded clearance time |
|
||||
| --- | --- |
|
||||
| C0 below 0.5 m into the turn | 0.10 s later median; five later by 0.01–0.46 s, two unchanged |
|
||||
| C1 below 0.1 rad into the turn | 0.10 s earlier median; all seven 0.04–0.17 s earlier |
|
||||
|
||||
These are control-publication times. Actual packet timing also includes the
|
||||
20 Hz send phase; shifts of only a few tens of milliseconds should not be
|
||||
interpreted as equally precise changes at the PSCM.
|
||||
|
||||
The report also compares half of each command's own peak: C1 reaches that level
|
||||
0.25 s earlier in the median tight turn and C0 0.12 s later. Those normalized
|
||||
crossings have different absolute thresholds when amplitudes differ. Comparing
|
||||
half the smaller peak at an identical level instead gives C0 earlier in one
|
||||
turn and later in six. No single threshold captures the whole release waveform.
|
||||
|
||||
For the ordinary bend around 108 s, C0 reaches half of its own peak 0.20 s later;
|
||||
C1 reaches half-peak 0.24 s earlier. The larger C0 takes 0.95 s longer to fall
|
||||
below the same 0.05 m threshold. On the last tight turn, C0 clears 0.05 m 0.23 s
|
||||
later. Near-zero thresholds are sensitive to small residual model offsets; the
|
||||
complete report retains 90%, 50%, 10%, common-level and near-zero crossings
|
||||
rather than treating any one threshold as a physical success criterion.
|
||||
|
||||
The plotted candidate target and command largely coincide during these exits:
|
||||
the longer C0 tail comes from the selected model point continuing to ask for
|
||||
lateral offset, rather than a retained yaw correction. C1 can release sooner
|
||||
because it now follows model orientation directly. These results do not show
|
||||
that every command unwinds earlier, or that the vehicle will unwind earlier.
|
||||
|
||||
Ten additional end-to-end sender cases cover both signs and all five CAN send
|
||||
phases. After a full-cap turn, a zero model target starts reducing both states on
|
||||
the first control update and appears on the next scheduled CAN message, 0–40 ms
|
||||
later at nominal 100 Hz calculation. There is no additional release hold. The
|
||||
unchanged slew itself takes 1.28 s of updates to clear 5.11 m C0 and 1.00 s to
|
||||
clear 0.5 rad C1, plus scheduling to transmit zero. These are software timing
|
||||
checks, not measured PSCM dynamics. The [unwind report](ford_model_points_unwind.json)
|
||||
records the episode timings, method and source hashes.
|
||||
|
||||
Older offline utilities now fail explicitly when their input lacks original
|
||||
model clocks or their historical unchanged-C1 comparison cannot support v7.
|
||||
This prevents missing model inputs from producing an all-invalid apparent match.
|
||||
|
||||
`calibration_approved=false` remains. Installation instructions and restore
|
||||
behavior are in the [drive-test guide](ford_model_action_drive_test.md).
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,632 +0,0 @@
|
||||
{
|
||||
"baseline_root": "e1cd61166c529f39cb47c815db47e80de720a778",
|
||||
"opendbc": "87ca78e6e641eefb2d654f260a6ab08df3058bd5",
|
||||
"scope": "Model-point construction only; physical tracking not validated.",
|
||||
"calibration_approved": false,
|
||||
"tests": {
|
||||
"passed": 585,
|
||||
"subtests_passed": 9146,
|
||||
"inapplicable_skips": 178
|
||||
},
|
||||
"source_sha256": {
|
||||
"openpilot/selfdrive/controls/lib/ford_model_action.py": "e7502ab68d04aef52edcffcef4b1d42c04e9008ab6892789aac3a865c100cb6d",
|
||||
"openpilot/selfdrive/controls/controlsd.py": "bea720b8ec68d6b8a6ad4376ae37324e2404833fb13097a8ab7710a1cedf37a5",
|
||||
"tools/ford_pscm_lab/stress_model_action.py": "0221f85ea2f5757d3b54d077ed8be1faaa6a5d2ed8371de1137f3b6f3d70bf47",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui_src/pages/vehicle.yaml": "ec5e0d9023f7260ae619a4cc6ec538d39053ac13e4515bd0de7d854b79d8abf6",
|
||||
"openpilot/sunnypilot/sunnylink/settings_ui.json": "62e5178a8deee32ef5a88026094cedde359130741688e8a4bcfa82155ef4cca3",
|
||||
"tools/ford_pscm_lab/model_action_replay.py": "95d546cc87c065fa7d581b41382e1ab78bacc4030931b81388cb69c61adf32f7",
|
||||
"tools/ford_pscm_lab/damping_replay.py": "1c253b99342d8113739b0a0551ca66ec6ced9e0aa96338ce6cb42e09c04178a4",
|
||||
"tools/ford_pscm_lab/test_model_action_replay.py": "7aa01cbf197155175215aa4494cfc909029526fbacad0a1e1a2a214583064c4b",
|
||||
"openpilot/selfdrive/controls/tests/test_ford_model_action_cadence.py": "d647e33c83581257f2662c3fec725bb9cd245fb48d579c2e38388318592a2690"
|
||||
},
|
||||
"routes": {
|
||||
"a5": {
|
||||
"route": "a5",
|
||||
"scope": "Frozen inputs; command construction and Panda TX acceptance only. No counterfactual vehicle response.",
|
||||
"cycles": 38961,
|
||||
"models": 7817,
|
||||
"round_trips": 38961,
|
||||
"panda_accepted_sends": 7793,
|
||||
"source_model_clocks_verified": true,
|
||||
"independent_point_and_slew_oracle_passed": true,
|
||||
"cohorts": {
|
||||
"eligible": {
|
||||
"seconds": 359.186785903,
|
||||
"old_mean_abs_c0": 0.34434291232781916,
|
||||
"new_mean_abs_c0": 0.3706073530276498,
|
||||
"old_mean_abs_c1": 0.05784747586810226,
|
||||
"new_mean_abs_c1": 0.06183462577629167,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 14.919213676999902
|
||||
},
|
||||
"road_speed": {
|
||||
"seconds": 261.730126543,
|
||||
"old_mean_abs_c0": 0.10481662146430282,
|
||||
"new_mean_abs_c0": 0.16319455033344274,
|
||||
"old_mean_abs_c1": 0.025151988851597765,
|
||||
"new_mean_abs_c1": 0.02471229299756163,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 0.0
|
||||
},
|
||||
"low_speed": {
|
||||
"seconds": 97.45665935999997,
|
||||
"old_mean_abs_c0": 0.9876160024355487,
|
||||
"new_mean_abs_c0": 0.9276362872461182,
|
||||
"old_mean_abs_c1": 0.14565465097969882,
|
||||
"new_mean_abs_c1": 0.16153056158564297,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 14.919213676999902
|
||||
},
|
||||
"clean": {
|
||||
"seconds": 249.91415584299997,
|
||||
"old_mean_abs_c0": 0.08470877473478762,
|
||||
"new_mean_abs_c0": 0.1191542994782463,
|
||||
"old_mean_abs_c1": 0.017726187650852487,
|
||||
"new_mean_abs_c1": 0.018543416153046232,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 1.056498005999913
|
||||
},
|
||||
"clean_ordinary_bends": {
|
||||
"seconds": 61.23773978999962,
|
||||
"old_mean_abs_c0": 0.07537829679720884,
|
||||
"new_mean_abs_c0": 0.16659385323910988,
|
||||
"old_mean_abs_c1": 0.022868833214909796,
|
||||
"new_mean_abs_c1": 0.022235380107927534,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 0.0
|
||||
}
|
||||
},
|
||||
"points": [
|
||||
{
|
||||
"t": 17.606574576,
|
||||
"old_c0_c1": [
|
||||
-0.009999999776482582,
|
||||
-0.014999999664723873
|
||||
],
|
||||
"new_c0_c1": [
|
||||
-0.04999999999999982,
|
||||
-0.01200000000000001
|
||||
],
|
||||
"raw_target": [
|
||||
-0.04535145975205448,
|
||||
-0.012038111718131041
|
||||
],
|
||||
"sample_station_m": 7.0,
|
||||
"sample_time_s": 1.8525775632377612
|
||||
},
|
||||
{
|
||||
"t": 107.99483939600002,
|
||||
"old_c0_c1": [
|
||||
0.20000000298023224,
|
||||
0.06800000369548798
|
||||
],
|
||||
"new_c0_c1": [
|
||||
0.5499999999999998,
|
||||
0.0685
|
||||
],
|
||||
"raw_target": [
|
||||
0.548033630847931,
|
||||
0.06853501158101219
|
||||
],
|
||||
"sample_station_m": 13.711669224561392,
|
||||
"sample_time_s": 1.0
|
||||
},
|
||||
{
|
||||
"t": 374.888921539,
|
||||
"old_c0_c1": [
|
||||
-3.759999990463257,
|
||||
-0.5
|
||||
],
|
||||
"new_c0_c1": [
|
||||
-3.46,
|
||||
-0.5
|
||||
],
|
||||
"raw_target": [
|
||||
-3.497294441592979,
|
||||
-0.6981158781127829
|
||||
],
|
||||
"sample_station_m": 7.0,
|
||||
"sample_time_s": 1.490557194102553
|
||||
},
|
||||
{
|
||||
"t": 390.68080671100006,
|
||||
"old_c0_c1": [
|
||||
-0.5199999809265137,
|
||||
-0.12349999696016312
|
||||
],
|
||||
"new_c0_c1": [
|
||||
-0.75,
|
||||
-0.12150000000000005
|
||||
],
|
||||
"raw_target": [
|
||||
-0.7522029416901727,
|
||||
-0.1214686983398029
|
||||
],
|
||||
"sample_station_m": 9.370794504969545,
|
||||
"sample_time_s": 1.0
|
||||
}
|
||||
],
|
||||
"hashes": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "e7502ab68d04aef52edcffcef4b1d42c04e9008ab6892789aac3a865c100cb6d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_model_points/replay.py": "762ebaac3b31737ad609d5b9788514147c696e548fd1ee09458314a0eea30434",
|
||||
".cache/ford_routea5/route.npz": "815d1e248ff5c3e5e5cfc11dfbd0690d0d13ddafe1436890e239975a72dae9b8",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_model_points/a5_models.npz": "84cf4b6e0102d8d5a3f6c3e29bf332e929d7f28d0262eb46220fccc631b902aa"
|
||||
}
|
||||
},
|
||||
"a2": {
|
||||
"route": "a2",
|
||||
"scope": "Frozen inputs; command construction and Panda TX acceptance only. No counterfactual vehicle response.",
|
||||
"cycles": 71111,
|
||||
"models": 14287,
|
||||
"round_trips": 71111,
|
||||
"panda_accepted_sends": 14223,
|
||||
"source_model_clocks_verified": true,
|
||||
"independent_point_and_slew_oracle_passed": true,
|
||||
"cohorts": {
|
||||
"eligible": {
|
||||
"seconds": 437.0644468180001,
|
||||
"old_mean_abs_c0": 0.2155311761983153,
|
||||
"new_mean_abs_c0": 0.31863289785799315,
|
||||
"old_mean_abs_c1": 0.04341179516514646,
|
||||
"new_mean_abs_c1": 0.045887913019549746,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 11.38161301699995
|
||||
},
|
||||
"road_speed": {
|
||||
"seconds": 328.02165902499996,
|
||||
"old_mean_abs_c0": 0.06680861343584246,
|
||||
"new_mean_abs_c0": 0.21392942512598495,
|
||||
"old_mean_abs_c1": 0.025146318618058133,
|
||||
"new_mean_abs_c1": 0.025146126320953914,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 0.0
|
||||
},
|
||||
"low_speed": {
|
||||
"seconds": 109.04278779300012,
|
||||
"old_mean_abs_c0": 0.6629172230811673,
|
||||
"new_mean_abs_c0": 0.6336010633494195,
|
||||
"old_mean_abs_c1": 0.09835785845941926,
|
||||
"new_mean_abs_c1": 0.10828319309008405,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 11.38161301699995
|
||||
},
|
||||
"clean": {
|
||||
"seconds": 332.6767952400001,
|
||||
"old_mean_abs_c0": 0.07338523837198829,
|
||||
"new_mean_abs_c0": 0.1946808147540538,
|
||||
"old_mean_abs_c1": 0.023104103590461375,
|
||||
"new_mean_abs_c1": 0.022903078905402382,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 2.902278352000053
|
||||
},
|
||||
"clean_ordinary_bends": {
|
||||
"seconds": 111.90846122999926,
|
||||
"old_mean_abs_c0": 0.07917757541079931,
|
||||
"new_mean_abs_c0": 0.3589047266588909,
|
||||
"old_mean_abs_c1": 0.03830142702025842,
|
||||
"new_mean_abs_c1": 0.03807353528971872,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 0.0
|
||||
}
|
||||
},
|
||||
"points": [],
|
||||
"hashes": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "e7502ab68d04aef52edcffcef4b1d42c04e9008ab6892789aac3a865c100cb6d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_model_points/replay.py": "762ebaac3b31737ad609d5b9788514147c696e548fd1ee09458314a0eea30434",
|
||||
".cache/ford_routea2/route.npz": "af4128d278be6be0042f72a02255ddbec96a8a5aa435b9852c8202e4a5cb39e5",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_model_points/a2_models.npz": "c3a366796ca5fb27f8c0dc85732ccd6f0ec61b4c8646af3dfe55600e026c0810"
|
||||
}
|
||||
},
|
||||
"a0": {
|
||||
"route": "a0",
|
||||
"scope": "Frozen inputs; command construction and Panda TX acceptance only. No counterfactual vehicle response.",
|
||||
"cycles": 114582,
|
||||
"models": 23038,
|
||||
"round_trips": 114582,
|
||||
"panda_accepted_sends": 22917,
|
||||
"source_model_clocks_verified": true,
|
||||
"independent_point_and_slew_oracle_passed": true,
|
||||
"cohorts": {
|
||||
"eligible": {
|
||||
"seconds": 1061.1945591,
|
||||
"old_mean_abs_c0": 0.16681197800600925,
|
||||
"new_mean_abs_c0": 0.25477442195222594,
|
||||
"old_mean_abs_c1": 0.036905620742387873,
|
||||
"new_mean_abs_c1": 0.0381744756199198,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 14.894412850999998
|
||||
},
|
||||
"road_speed": {
|
||||
"seconds": 901.0129238640001,
|
||||
"old_mean_abs_c0": 0.038282177648169546,
|
||||
"new_mean_abs_c0": 0.13938686570168032,
|
||||
"old_mean_abs_c1": 0.016746723820478274,
|
||||
"new_mean_abs_c1": 0.01614716305741802,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 0.0
|
||||
},
|
||||
"low_speed": {
|
||||
"seconds": 160.18163523600006,
|
||||
"old_mean_abs_c0": 0.889785064486564,
|
||||
"new_mean_abs_c0": 0.9038231052276234,
|
||||
"old_mean_abs_c1": 0.15029831167554059,
|
||||
"new_mean_abs_c1": 0.1620769021843944,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 14.894412850999998
|
||||
},
|
||||
"clean": {
|
||||
"seconds": 884.2159145080002,
|
||||
"old_mean_abs_c0": 0.05133685312289168,
|
||||
"new_mean_abs_c0": 0.13569127462997554,
|
||||
"old_mean_abs_c1": 0.017060990972111164,
|
||||
"new_mean_abs_c1": 0.01680535356441334,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 4.094436286000047
|
||||
},
|
||||
"clean_ordinary_bends": {
|
||||
"seconds": 229.50590253700045,
|
||||
"old_mean_abs_c0": 0.06277285849992259,
|
||||
"new_mean_abs_c0": 0.29272144832989233,
|
||||
"old_mean_abs_c1": 0.032915757288861924,
|
||||
"new_mean_abs_c1": 0.03226126900978203,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 0.0
|
||||
}
|
||||
},
|
||||
"points": [],
|
||||
"hashes": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "e7502ab68d04aef52edcffcef4b1d42c04e9008ab6892789aac3a865c100cb6d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_model_points/replay.py": "762ebaac3b31737ad609d5b9788514147c696e548fd1ee09458314a0eea30434",
|
||||
".cache/ford_routea0/route.npz": "01ce8b0361c3154d07d5449525a650baa2cc261675095cb1f04ec9e37a9174d3",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_model_points/a0_models.npz": "72a72c950d244f5afcffe8cb1cd09b9731297f7565c93814197c728d4b25e8cb"
|
||||
}
|
||||
},
|
||||
"9b": {
|
||||
"route": "9b",
|
||||
"scope": "Frozen inputs; command construction and Panda TX acceptance only. No counterfactual vehicle response.",
|
||||
"cycles": 71396,
|
||||
"models": 14346,
|
||||
"round_trips": 71396,
|
||||
"panda_accepted_sends": 14280,
|
||||
"source_model_clocks_verified": true,
|
||||
"independent_point_and_slew_oracle_passed": true,
|
||||
"cohorts": {
|
||||
"eligible": {
|
||||
"seconds": 651.159063169,
|
||||
"old_mean_abs_c0": 0.09686840630728118,
|
||||
"new_mean_abs_c0": 0.16616909035237892,
|
||||
"old_mean_abs_c1": 0.020769878239136775,
|
||||
"new_mean_abs_c1": 0.02325818302804746,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 4.1034965929999885
|
||||
},
|
||||
"road_speed": {
|
||||
"seconds": 549.860784016,
|
||||
"old_mean_abs_c0": 0.025487998809066218,
|
||||
"new_mean_abs_c0": 0.10707029577162376,
|
||||
"old_mean_abs_c1": 0.011795500864550536,
|
||||
"new_mean_abs_c1": 0.01141539602134342,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 0.0
|
||||
},
|
||||
"low_speed": {
|
||||
"seconds": 101.29827915299992,
|
||||
"old_mean_abs_c0": 0.484330929447169,
|
||||
"new_mean_abs_c0": 0.48696535455636186,
|
||||
"old_mean_abs_c1": 0.06948401455523531,
|
||||
"new_mean_abs_c1": 0.08754243546441211,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 4.1034965929999885
|
||||
}
|
||||
},
|
||||
"points": [],
|
||||
"hashes": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "e7502ab68d04aef52edcffcef4b1d42c04e9008ab6892789aac3a865c100cb6d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_model_points/replay.py": "762ebaac3b31737ad609d5b9788514147c696e548fd1ee09458314a0eea30434",
|
||||
".cache/ford_route9b/route.npz": "8942b281b76eac012ee660f8ae21bbcad0df95d515291d2497ce5897d5738647",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_model_points/9b_models.npz": "fa444360a2775b0df38f274ab4c99568bbe47a3d2777c31ab0193705fce2b1fb"
|
||||
}
|
||||
},
|
||||
"9e": {
|
||||
"route": "9e",
|
||||
"scope": "Frozen inputs; command construction and Panda TX acceptance only. No counterfactual vehicle response.",
|
||||
"cycles": 83668,
|
||||
"models": 16806,
|
||||
"round_trips": 83668,
|
||||
"panda_accepted_sends": 16734,
|
||||
"source_model_clocks_verified": true,
|
||||
"independent_point_and_slew_oracle_passed": true,
|
||||
"cohorts": {
|
||||
"eligible": {
|
||||
"seconds": 751.5479497329998,
|
||||
"old_mean_abs_c0": 0.09489001052394747,
|
||||
"new_mean_abs_c0": 0.16056913479509022,
|
||||
"old_mean_abs_c1": 0.02256825583285058,
|
||||
"new_mean_abs_c1": 0.022300154628921095,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 7.398116462998587
|
||||
},
|
||||
"road_speed": {
|
||||
"seconds": 624.6602415809975,
|
||||
"old_mean_abs_c0": 0.023757802428838078,
|
||||
"new_mean_abs_c0": 0.10203005034223132,
|
||||
"old_mean_abs_c1": 0.01124063950870769,
|
||||
"new_mean_abs_c1": 0.010770200237869725,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 0.0
|
||||
},
|
||||
"low_speed": {
|
||||
"seconds": 126.88770815200223,
|
||||
"old_mean_abs_c0": 0.4450694167092398,
|
||||
"new_mean_abs_c0": 0.4487533818653312,
|
||||
"old_mean_abs_c1": 0.07833340166635812,
|
||||
"new_mean_abs_c1": 0.07906139809550021,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 7.398116462998587
|
||||
},
|
||||
"clean": {
|
||||
"seconds": 639.296275887018,
|
||||
"old_mean_abs_c0": 0.02396620128468532,
|
||||
"new_mean_abs_c0": 0.09600545527466989,
|
||||
"old_mean_abs_c1": 0.01021397220255641,
|
||||
"new_mean_abs_c1": 0.010198642694698857,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 0.0
|
||||
},
|
||||
"clean_ordinary_bends": {
|
||||
"seconds": 168.17771330400137,
|
||||
"old_mean_abs_c0": 0.05087679617819314,
|
||||
"new_mean_abs_c0": 0.2527786285635614,
|
||||
"old_mean_abs_c1": 0.02663914589176354,
|
||||
"new_mean_abs_c1": 0.026386933889696897,
|
||||
"c0_target_capped_s": 0.0,
|
||||
"c1_target_capped_s": 0.0
|
||||
}
|
||||
},
|
||||
"points": [],
|
||||
"hashes": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "e7502ab68d04aef52edcffcef4b1d42c04e9008ab6892789aac3a865c100cb6d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_model_points/replay.py": "762ebaac3b31737ad609d5b9788514147c696e548fd1ee09458314a0eea30434",
|
||||
".cache/ford_route9e/route.npz": "9f169338caf2b5a5377332d15914d1dde57be8b922e25c1bbe16264e0f1be6fb",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_model_points/9e_models.npz": "acacf8f0d16fd986b6d74e4a02d704fa5b5b3327fe66dcbe149806f2a5bc3d65"
|
||||
}
|
||||
}
|
||||
},
|
||||
"stress": {
|
||||
"seed": 20260908,
|
||||
"random_cycles": 200000,
|
||||
"mirrored_core_updates": 200000,
|
||||
"invalid_or_inactive_resets": 3537,
|
||||
"field_boundary_cases": 18138,
|
||||
"float32_can_round_trips": 218138,
|
||||
"analytic_targets_scalar_slew_and_mirror_checks_pass": true,
|
||||
"valid_yaw_does_not_affect_targets_checked": true,
|
||||
"shared_model_point_checked": true,
|
||||
"direct_raw_float32_packing_matches_host_output": true,
|
||||
"max_continuous_step_c0_c1": [
|
||||
0.40000000000000147,
|
||||
0.05000000000000002
|
||||
],
|
||||
"calibration_approved": false,
|
||||
"scope": "Numerical construction only; no PSCM response or closed-loop performance claims.",
|
||||
"opendbc_import_head": "87ca78e6e641eefb2d654f260a6ab08df3058bd5",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/stress_model_action.py": "0221f85ea2f5757d3b54d077ed8be1faaa6a5d2ed8371de1137f3b6f3d70bf47",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "e7502ab68d04aef52edcffcef4b1d42c04e9008ab6892789aac3a865c100cb6d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/model_action_replay.py": "95d546cc87c065fa7d581b41382e1ab78bacc4030931b81388cb69c61adf32f7"
|
||||
}
|
||||
},
|
||||
"unwind": {
|
||||
"report": "docs/ford_model_points_unwind.json",
|
||||
"sha256": "7395a4e66a275fd72d744f5a8244f1b9c8840bb7efff2eec225f15b9c033cd6e",
|
||||
"full_cap_release_send_phase_cases": 10,
|
||||
"summary": {
|
||||
"tight": {
|
||||
"episodes": 7,
|
||||
"c0": {
|
||||
"below_90pct_s": {
|
||||
"n": 7,
|
||||
"median_s": 0.0,
|
||||
"min_s": -0.12269046900000546,
|
||||
"max_s": 0.04266988000000538,
|
||||
"earlier": 2,
|
||||
"later": 2
|
||||
},
|
||||
"below_50pct_s": {
|
||||
"n": 7,
|
||||
"median_s": 0.12129962300002717,
|
||||
"min_s": 0.0848213179999675,
|
||||
"max_s": 0.8246327600000001,
|
||||
"earlier": 0,
|
||||
"later": 7
|
||||
},
|
||||
"below_10pct_s": {
|
||||
"n": 7,
|
||||
"median_s": 0.1376267710000434,
|
||||
"min_s": 0.0,
|
||||
"max_s": 0.45046578099999124,
|
||||
"earlier": 0,
|
||||
"later": 6
|
||||
},
|
||||
"near_zero_s": {
|
||||
"n": 5,
|
||||
"median_s": 0.1399018539999588,
|
||||
"min_s": -0.5224231490000193,
|
||||
"max_s": 1.9510819790000085,
|
||||
"earlier": 1,
|
||||
"later": 4
|
||||
},
|
||||
"common_half_s": {
|
||||
"n": 7,
|
||||
"median_s": 0.07685077599995793,
|
||||
"min_s": -0.2591174110000338,
|
||||
"max_s": 0.5661551039999893,
|
||||
"earlier": 1,
|
||||
"later": 6
|
||||
},
|
||||
"fixed_clearance_s": {
|
||||
"n": 7,
|
||||
"median_s": 0.10080577899998389,
|
||||
"min_s": 0.0,
|
||||
"max_s": 0.4609723959999883,
|
||||
"earlier": 0,
|
||||
"later": 5
|
||||
}
|
||||
},
|
||||
"c1": {
|
||||
"below_90pct_s": {
|
||||
"n": 7,
|
||||
"median_s": -0.3856273459999784,
|
||||
"min_s": -0.5246007750000103,
|
||||
"max_s": 0.5545458859999997,
|
||||
"earlier": 6,
|
||||
"later": 1
|
||||
},
|
||||
"below_50pct_s": {
|
||||
"n": 7,
|
||||
"median_s": -0.2540558009999927,
|
||||
"min_s": -0.46442294700000275,
|
||||
"max_s": -0.21144918100003451,
|
||||
"earlier": 7,
|
||||
"later": 0
|
||||
},
|
||||
"below_10pct_s": {
|
||||
"n": 6,
|
||||
"median_s": -0.15774414349999688,
|
||||
"min_s": -0.20159088100001554,
|
||||
"max_s": 0.0,
|
||||
"earlier": 5,
|
||||
"later": 0
|
||||
},
|
||||
"near_zero_s": {
|
||||
"n": 5,
|
||||
"median_s": 0.0,
|
||||
"min_s": -0.148710938000022,
|
||||
"max_s": 1.5534367700000047,
|
||||
"earlier": 2,
|
||||
"later": 2
|
||||
},
|
||||
"common_half_s": {
|
||||
"n": 7,
|
||||
"median_s": -0.2528352759999848,
|
||||
"min_s": -0.45502116000000115,
|
||||
"max_s": -0.15168304499999863,
|
||||
"earlier": 7,
|
||||
"later": 0
|
||||
},
|
||||
"fixed_clearance_s": {
|
||||
"n": 7,
|
||||
"median_s": -0.10267424500000288,
|
||||
"min_s": -0.1688626670000417,
|
||||
"max_s": -0.040530361999969955,
|
||||
"earlier": 7,
|
||||
"later": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ordinary": {
|
||||
"episodes": 4,
|
||||
"c0": {
|
||||
"below_90pct_s": {
|
||||
"n": 4,
|
||||
"median_s": 0.49771416100000465,
|
||||
"min_s": 0.2530698499999744,
|
||||
"max_s": 3.195503137000003,
|
||||
"earlier": 0,
|
||||
"later": 4
|
||||
},
|
||||
"below_50pct_s": {
|
||||
"n": 4,
|
||||
"median_s": 0.3417062340000143,
|
||||
"min_s": 0.19507780300000377,
|
||||
"max_s": 0.4595077099999685,
|
||||
"earlier": 0,
|
||||
"later": 4
|
||||
},
|
||||
"below_10pct_s": {
|
||||
"n": 4,
|
||||
"median_s": 0.11352378200000146,
|
||||
"min_s": 0.09310488600004874,
|
||||
"max_s": 1.1537201169999776,
|
||||
"earlier": 0,
|
||||
"later": 4
|
||||
},
|
||||
"near_zero_s": {
|
||||
"n": 4,
|
||||
"median_s": 0.6988682280000234,
|
||||
"min_s": 0.40513631699997177,
|
||||
"max_s": 3.1885321799999815,
|
||||
"earlier": 0,
|
||||
"later": 4
|
||||
},
|
||||
"common_half_s": {
|
||||
"n": 4,
|
||||
"median_s": 1.0263607459999946,
|
||||
"min_s": 0.7940629349999995,
|
||||
"max_s": 2.846801904000017,
|
||||
"earlier": 0,
|
||||
"later": 4
|
||||
},
|
||||
"fixed_clearance_s": {
|
||||
"n": 2,
|
||||
"median_s": 1.97568246000003,
|
||||
"min_s": 1.5486082510000188,
|
||||
"max_s": 2.4027566690000413,
|
||||
"earlier": 0,
|
||||
"later": 2
|
||||
}
|
||||
},
|
||||
"c1": {
|
||||
"below_90pct_s": {
|
||||
"n": 4,
|
||||
"median_s": -0.0037014085000066643,
|
||||
"min_s": -0.25255103900002496,
|
||||
"max_s": 0.3999567310000316,
|
||||
"earlier": 2,
|
||||
"later": 1
|
||||
},
|
||||
"below_50pct_s": {
|
||||
"n": 4,
|
||||
"median_s": -0.17102558000001977,
|
||||
"min_s": -0.2489269939999872,
|
||||
"max_s": -0.046780566999984785,
|
||||
"earlier": 4,
|
||||
"later": 0
|
||||
},
|
||||
"below_10pct_s": {
|
||||
"n": 4,
|
||||
"median_s": -0.10130154349999998,
|
||||
"min_s": -0.29764589699999533,
|
||||
"max_s": 0.05121605200002932,
|
||||
"earlier": 2,
|
||||
"later": 1
|
||||
},
|
||||
"near_zero_s": {
|
||||
"n": 4,
|
||||
"median_s": -0.022888650000027155,
|
||||
"min_s": -0.10325871399999187,
|
||||
"max_s": 0.24679704999999785,
|
||||
"earlier": 2,
|
||||
"later": 1
|
||||
},
|
||||
"common_half_s": {
|
||||
"n": 4,
|
||||
"median_s": -0.22263683349999042,
|
||||
"min_s": -0.2573995380000156,
|
||||
"max_s": -0.09939024700003074,
|
||||
"earlier": 4,
|
||||
"later": 0
|
||||
},
|
||||
"fixed_clearance_s": {
|
||||
"n": 2,
|
||||
"median_s": -0.17009618150001415,
|
||||
"min_s": -0.24998850800000127,
|
||||
"max_s": -0.09020385500002703,
|
||||
"earlier": 2,
|
||||
"later": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
Submodule opendbc_repo updated: 87ca78e6e6...c21a901370
@@ -12,7 +12,7 @@ from openpilot.common.realtime import config_realtime_process, DT_CTRL, Priority
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.ford.values import FordFlags, FordFlagsSP
|
||||
from opendbc.car.ford.values import FordFlags
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import clip_curvature
|
||||
from openpilot.selfdrive.controls.lib.ford_model_action import FordModelActionController, select_model_action_controller
|
||||
@@ -59,7 +59,7 @@ class Controls(ControlsExt):
|
||||
self.ford_pscm_observer = (self.CP.brand == "ford" and self.CP.flags & FordFlags.CANFD and
|
||||
self.params.get_bool("FordPscmObserver"))
|
||||
self.ford_path_controller = FordPscmObserverPathController() if self.ford_pscm_observer else FordPathController()
|
||||
self.ford_path_controller = select_model_action_controller(self.CP, bool(self.CP_SP.flags & FordFlagsSP.MODEL_ACTION),
|
||||
self.ford_path_controller = select_model_action_controller(self.CP, self.params.get_bool("FordModelActionController"),
|
||||
self.ford_path_controller)
|
||||
self.ford_model_action = isinstance(self.ford_path_controller, FordModelActionController)
|
||||
if self.CP.brand == "ford":
|
||||
@@ -171,15 +171,12 @@ class Controls(ControlsExt):
|
||||
if self.CP.brand == "ford":
|
||||
ford_model = model_v2 if self.sm.valid['modelV2'] else None
|
||||
if self.ford_model_action:
|
||||
assert isinstance(self.ford_path_controller, FordModelActionController)
|
||||
reference_service = 'lateralManeuverPlan' if self.sm.valid['lateralManeuverPlan'] else 'modelV2'
|
||||
now = time.monotonic()
|
||||
self.ford_path = self.ford_path_controller.update(
|
||||
ford_model, self.desired_curvature, yaw_rate=-CS.yawRate, speed=CS.vEgo, now=now,
|
||||
ford_model, self.desired_curvature, yaw_rate=-CS.yawRate, speed=CS.vEgo, now=time.monotonic(),
|
||||
measurement_time=self.sm.logMonoTime['carState'] * 1e-9,
|
||||
model_time=self.sm.logMonoTime['modelV2'] * 1e-9,
|
||||
reference_time=self.sm.logMonoTime[reference_service] * 1e-9,
|
||||
reference_source=reference_service,
|
||||
active=CC.latActive, valid=CS.canValid and self.sm.all_checks(['carState', 'vehicleParameters', 'modelV2', reference_service]),
|
||||
)
|
||||
if not self.ford_path.valid:
|
||||
@@ -191,7 +188,6 @@ class Controls(ControlsExt):
|
||||
measured_curvature=self.curvature,
|
||||
**self.ford_path_controller.diagnostics)
|
||||
elif self.ford_pscm_observer:
|
||||
assert isinstance(self.ford_path_controller, FordPscmObserverPathController)
|
||||
self.ford_path = self.ford_path_controller.update(ford_model, self.desired_curvature,
|
||||
current_curvature=self.curvature, v_ego=CS.vEgo,
|
||||
v_ego_raw=CS.vEgoRaw, active=CC.latActive)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Experimental Ford C2-free controller using one shared model-path point.
|
||||
"""Experimental Ford C2-free controller: nearby offset and selected-action heading.
|
||||
|
||||
The one-second preview and seven-metre minimum are engineering choices,
|
||||
not identified Ford reference points or PSCM calibration.
|
||||
Selected only by its explicit toggle. The 7 m station and one-second scale are
|
||||
engineering choices, not identified PSCM gains or physical calibration.
|
||||
"""
|
||||
import math
|
||||
import struct
|
||||
@@ -12,8 +12,8 @@ from opendbc.car.ford.values import FordFlags
|
||||
from openpilot.selfdrive.controls.lib.ford_path import FordPath, _model_path
|
||||
|
||||
|
||||
MIN_STATION_M = 7.0
|
||||
PREVIEW_TIME_S = 1.0
|
||||
OFFSET_STATION_M = 7.0
|
||||
HEADING_TIME_S = 1.0
|
||||
CALIBRATION_APPROVED = False
|
||||
|
||||
|
||||
@@ -31,34 +31,31 @@ def _finite(*values):
|
||||
|
||||
|
||||
def encode_model_action(model, desired_curvature, speed):
|
||||
"""Sample model lateral position and heading at the same arc station.
|
||||
"""Encode y(7) and max(7, v*1s)*selected limited curvature.
|
||||
|
||||
Use the model's distance at one second, with a seven-metre minimum and an
|
||||
endpoint hold. Selected curvature remains a health/diagnostic input; it does
|
||||
not reconstruct heading or rotate the model geometry in this experiment.
|
||||
Preserve the reviewed core's endpoint hold when the path ends before 7 m.
|
||||
This samples the available geometry; it does not extrapolate an unseen path.
|
||||
"""
|
||||
if not _finite(desired_curvature, speed) or not .3 <= speed <= 55 or abs(desired_curvature) > 1:
|
||||
return FordPath()
|
||||
try:
|
||||
path = _model_path(model)
|
||||
times = [float(t) for t in model.position.t]
|
||||
heading_times = [float(t) for t in model.orientation.t]
|
||||
except (AttributeError, TypeError, ValueError, OverflowError):
|
||||
except OverflowError:
|
||||
return FordPath()
|
||||
if path is None or not all(_finite(*values) for values in path):
|
||||
return FordPath()
|
||||
station, _, lateral, heading = path
|
||||
if (len(times) != len(station) or not times or times[0] != 0. or times != heading_times or
|
||||
not _finite(*times) or any(b <= a for a, b in zip(times, times[1:], strict=False))):
|
||||
return FordPath()
|
||||
sample_station = min(station[-1], max(MIN_STATION_M, float(np.interp(PREVIEW_TIME_S, times, station))))
|
||||
c0 = float(np.interp(sample_station, station, lateral))
|
||||
c1 = float(np.interp(sample_station, station, heading))
|
||||
station, _, lateral, _ = path
|
||||
c0 = float(np.interp(min(OFFSET_STATION_M, station[-1]), station, lateral))
|
||||
c1 = max(OFFSET_STATION_M, speed*HEADING_TIME_S)*desired_curvature
|
||||
return FordPath(True, c0, c1, 0., 0.) if _finite(c0, c1) else FordPath()
|
||||
|
||||
|
||||
class ModelActionController:
|
||||
"""Only two states: independently slewed C0/C1 model-point requests."""
|
||||
"""Only two control states: unquantized, independently slewed C0 and C1.
|
||||
|
||||
Freshness and engagement belong to the caller. No measured yaw, model
|
||||
history, heading integral, blending or release modes enter the law.
|
||||
"""
|
||||
__slots__ = ('c0', 'c1')
|
||||
|
||||
def __init__(self):
|
||||
@@ -67,9 +64,8 @@ class ModelActionController:
|
||||
def reset(self):
|
||||
self.c0 = self.c1 = 0.
|
||||
|
||||
def update(self, model, desired_curvature, *, speed, dt, yaw_rate=0., active=True, valid=True):
|
||||
# Raw Ford yaw remains an input-health check, not a pose measurement.
|
||||
if not active or not valid or not _finite(dt, yaw_rate) or not .002 <= dt <= .1 or abs(yaw_rate) > 3:
|
||||
def update(self, model, desired_curvature, *, speed, dt, active=True, valid=True):
|
||||
if not active or not valid or not _finite(dt) or not .002 <= dt <= .1:
|
||||
self.reset()
|
||||
return FordPath()
|
||||
target = encode_model_action(model, desired_curvature, speed)
|
||||
@@ -84,11 +80,16 @@ class ModelActionController:
|
||||
|
||||
|
||||
class FordModelActionController:
|
||||
"""Freshness, engagement and reference checks for model-point tracking.
|
||||
"""Input adapter for the opt-in selected-action controller.
|
||||
|
||||
Scalar-only maneuver references cannot supply this controller's model pose.
|
||||
Reject them explicitly rather than silently following a different reference.
|
||||
Measured yaw checks input health only; it never modifies valid geometry.
|
||||
controlsd owns upstream selection/limiting and service health. This adapter
|
||||
checks ages and clock order, then supplies elapsed time to the two-state
|
||||
core. Its timestamps and diagnostics never affect the targets. Raw model
|
||||
geometry is checked on every cycle, even at a repeated model timestamp.
|
||||
|
||||
Yaw is checked only for the inherited finite/range input gate. Engagement
|
||||
and downstream driver arbitration still apply. This controller does not use
|
||||
PSCM status or driver torque as control-law inputs.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.core = ModelActionController()
|
||||
@@ -97,16 +98,14 @@ class FordModelActionController:
|
||||
def reset(self, status='inactive'):
|
||||
self.core.reset()
|
||||
self.last_time = self.last_measurement_time = self.last_model_time = None
|
||||
self.diagnostics = {'status': status, 'hypothesis': 'model-pose-one-second-v7',
|
||||
self.diagnostics = {'status': status, 'hypothesis': 'model-action-c0-c1-v1',
|
||||
'calibration_approved': CALIBRATION_APPROVED, 'command': (0., 0., 0., 0.)}
|
||||
|
||||
def update(self, model, desired_curvature, *, yaw_rate, speed, now, measurement_time, model_time, reference_time,
|
||||
active, valid=True, reference_source="modelV2"):
|
||||
active, valid=True):
|
||||
reason = None
|
||||
if not active:
|
||||
reason = 'inactive'
|
||||
elif reference_source != 'modelV2':
|
||||
reason = 'unsupported_reference'
|
||||
elif not valid:
|
||||
reason = 'invalid_service'
|
||||
elif not _finite(desired_curvature, yaw_rate, speed, now, measurement_time, model_time, reference_time):
|
||||
@@ -125,15 +124,13 @@ class FordModelActionController:
|
||||
):
|
||||
self.reset('timing_reset')
|
||||
return FordPath()
|
||||
command = self.core.update(model, desired_curvature, speed=speed, dt=dt, yaw_rate=yaw_rate)
|
||||
command = self.core.update(model, desired_curvature, speed=speed, dt=dt)
|
||||
if not command.valid:
|
||||
self.reset('invalid_path')
|
||||
return command
|
||||
self.last_time, self.last_measurement_time, self.last_model_time = now, measurement_time, model_time
|
||||
self.diagnostics = {'status': 'active', 'hypothesis': 'model-pose-one-second-v7',
|
||||
self.diagnostics = {'status': 'active', 'hypothesis': 'model-action-c0-c1-v1',
|
||||
'calibration_approved': CALIBRATION_APPROVED, 'desired_curvature': desired_curvature,
|
||||
'yaw_rate': yaw_rate, 'pose_source': 'model',
|
||||
'preview_time_s': PREVIEW_TIME_S, 'minimum_station_m': MIN_STATION_M,
|
||||
'model_age': now - model_time, 'measurement_age': now - measurement_time, 'reference_age': now - reference_time,
|
||||
'dt': dt, 'offset_request': self.core.c0, 'heading_request': self.core.c1,
|
||||
'command': (command.path_offset, command.path_angle, 0., 0.)}
|
||||
|
||||
@@ -53,11 +53,7 @@ class TestFordControlsLogging(unittest.TestCase):
|
||||
controls = SimpleNamespace(ford_path_controller=controller, desired_curvature=.005, curvature=.0025,
|
||||
sm=SimpleNamespace(logMonoTime={'modelV2': 123456789, 'carState': 123450000}))
|
||||
record = self.emit_controls_event('Ford C2-free path tracking', controls)
|
||||
self.assertEqual(record['hypothesis'], 'model-pose-one-second-v7')
|
||||
self.assertEqual(record['hypothesis'], 'model-action-c0-c1-v1')
|
||||
self.assertIs(record['calibration_approved'], False)
|
||||
self.assertEqual(record['command'][2:], [0., 0.])
|
||||
self.assertEqual(record['status'], controller.diagnostics['status'])
|
||||
if active and valid:
|
||||
self.assertEqual(record['pose_source'], 'model')
|
||||
self.assertEqual(record['preview_time_s'], 1.)
|
||||
self.assertEqual(record['minimum_station_m'], 7.)
|
||||
|
||||
@@ -12,8 +12,7 @@ from openpilot.selfdrive.controls.lib.ford_model_action import ModelActionContro
|
||||
|
||||
|
||||
def make_model(x, y, heading):
|
||||
times = np.linspace(0., 3., len(x))
|
||||
return SimpleNamespace(position=SimpleNamespace(t=times, x=x, y=y), orientation=SimpleNamespace(t=times, z=heading))
|
||||
return SimpleNamespace(position=SimpleNamespace(x=x, y=y), orientation=SimpleNamespace(z=heading))
|
||||
|
||||
|
||||
def circle(curvature):
|
||||
@@ -21,26 +20,26 @@ def circle(curvature):
|
||||
return make_model(np.sin(curvature*s)/curvature, (1-np.cos(curvature*s))/curvature, curvature*s)
|
||||
|
||||
|
||||
def straight(offset=0., heading=0.):
|
||||
def straight(offset=0.):
|
||||
x = np.linspace(0., 60., 121)
|
||||
return make_model(x*np.cos(heading), offset+x*np.sin(heading), np.full_like(x, heading))
|
||||
return make_model(x, np.full_like(x, offset), np.zeros_like(x))
|
||||
|
||||
|
||||
def test_model_heading_is_used_even_when_scalar_action_differs():
|
||||
def test_selected_action_controls_heading_even_when_model_previews_another_turn():
|
||||
model = circle(.02)
|
||||
for desired in (0., -.004, .004):
|
||||
target = encode_model_action(model, desired, 20.)
|
||||
assert target.path_angle == pytest.approx(.4)
|
||||
assert target.path_offset == pytest.approx((1-math.cos(.4))/.02)
|
||||
assert encode_model_action(model, 0., 20.).path_angle == 0.
|
||||
assert encode_model_action(model, -.004, 20.).path_angle == pytest.approx(-.08)
|
||||
assert encode_model_action(model, 0., 20.).path_offset > 0.
|
||||
|
||||
|
||||
def test_straight_centering_and_matched_model_circles():
|
||||
def test_centering_information_is_independent_of_action_and_not_scaled_with_speed():
|
||||
for speed in (2., 7., 20., 35.):
|
||||
assert encode_model_action(straight(.4), 0., speed) == FordPath(True, .4, 0., 0., 0.)
|
||||
target = encode_model_action(straight(.4), 0., speed)
|
||||
assert target == FordPath(True, .4, 0., 0., 0.)
|
||||
for sign in (-1, 1):
|
||||
target = encode_model_action(circle(sign*.01), sign*.01, 20.)
|
||||
assert target.path_offset == pytest.approx(sign*(1-math.cos(.2))/.01)
|
||||
assert target.path_angle == pytest.approx(sign*.2)
|
||||
assert target.path_offset == pytest.approx(sign*(1-math.cos(.07))/.01, abs=1e-6)
|
||||
assert target.path_angle == pytest.approx(sign*.2) # No 10 m cap at highway speed.
|
||||
|
||||
|
||||
def test_two_actuator_positions_are_sufficient_for_every_next_output():
|
||||
@@ -55,18 +54,18 @@ def test_two_actuator_positions_are_sufficient_for_every_next_output():
|
||||
assert controller.update(model, desired, **kwargs) == copied.update(model, desired, **kwargs)
|
||||
|
||||
|
||||
def test_held_turn_releases_using_new_model_geometry_without_retained_bias():
|
||||
def test_held_turn_releases_without_a_bias_tail_or_sign_reversal():
|
||||
for sign in (-1., 1.):
|
||||
controller = ModelActionController()
|
||||
for _ in range(400):
|
||||
out = controller.update(circle(sign*.01), sign*.01, speed=20., dt=.01)
|
||||
assert out.path_angle == pytest.approx(sign*.2)
|
||||
previous = np.array([controller.c0, controller.c1])
|
||||
for _ in range(100):
|
||||
out = controller.update(straight(), sign*.01, speed=20., dt=.01)
|
||||
expected = previous+np.clip(-previous, [-.04, -.005], [.04, .005])
|
||||
values = np.array([controller.c0, controller.c1])
|
||||
np.testing.assert_allclose(values, expected, atol=1e-10)
|
||||
previous = np.array([out.path_offset, out.path_angle])
|
||||
for desired in sign*np.linspace(.01, 0., 101):
|
||||
out = controller.update(straight(), desired, speed=20., dt=.01)
|
||||
values = np.array([out.path_offset, out.path_angle])
|
||||
assert (abs(values) <= abs(previous)+1e-8).all()
|
||||
assert (sign*values >= -1e-8).all()
|
||||
previous = values
|
||||
assert out == FordPath(True, 0., 0., 0., 0.)
|
||||
|
||||
@@ -74,7 +73,7 @@ def test_held_turn_releases_using_new_model_geometry_without_retained_bias():
|
||||
def test_current_model_replacement_leaves_only_independent_actuator_slew():
|
||||
controller = ModelActionController()
|
||||
for _ in range(150):
|
||||
controller.update(straight(1.-20*math.sin(.4), .4), .04, speed=20., dt=.01)
|
||||
controller.update(straight(1.), .04, speed=20., dt=.01)
|
||||
for _ in range(25):
|
||||
out = controller.update(straight(), 0., speed=20., dt=.01)
|
||||
assert out.path_offset == pytest.approx(0.)
|
||||
@@ -109,7 +108,7 @@ def test_selected_core_reversal_through_float32_and_wire_keeps_sign_and_zero_c2(
|
||||
previous = np.zeros(2)
|
||||
for i in range(600):
|
||||
sign = 1. if i < 300 else -1.
|
||||
out = controller.update(straight(sign*8., sign*.8), sign*.1, speed=30., dt=.01)
|
||||
out = controller.update(straight(sign*8.), sign*.1, speed=30., dt=.01)
|
||||
fields = np.array([out.path_offset, out.path_angle])
|
||||
assert (abs(fields) <= [5.1100001, .5000001]).all()
|
||||
assert (abs(fields-previous) <= [.0500001, .0055001]).all()
|
||||
@@ -128,7 +127,7 @@ def test_selected_core_reversal_through_float32_and_wire_keeps_sign_and_zero_c2(
|
||||
|
||||
def test_short_path_holds_available_endpoint_without_extrapolation():
|
||||
model = make_model([0., 1.], [0., .1], [0., 0.])
|
||||
assert encode_model_action(model, .01, 20.) == FordPath(True, .1, 0., 0., 0.)
|
||||
assert encode_model_action(model, .01, 20.) == FordPath(True, .1, .2, 0., 0.)
|
||||
|
||||
|
||||
def test_overflowing_arc_resets_instead_of_publishing_invalid_geometry():
|
||||
@@ -177,18 +176,18 @@ def test_domain_and_elapsed_time_boundaries(field, value, valid):
|
||||
assert ModelActionController().update(straight(.4), **kwargs).valid == valid
|
||||
|
||||
|
||||
def test_arc_station_floor_not_forward_x_determines_offset():
|
||||
def test_arc_station_not_forward_x_or_model_heading_determines_offset():
|
||||
x = np.array([0., 6., 12.])
|
||||
y = .4+x*.75
|
||||
target = encode_model_action(make_model(x, y, [.4, .4, .4]), -.01, 20.)
|
||||
# At one second arc station is 5 m; the 7 m minimum gives x=5.6, y=4.6.
|
||||
target = encode_model_action(make_model(x, y, [2., -2., 1.]), -.01, 20.)
|
||||
# Arc length is 1.25*x on this line, so y(arc=7)=.4+.75*(7/1.25).
|
||||
assert target.path_offset == pytest.approx(4.6)
|
||||
assert target.path_angle == pytest.approx(.4)
|
||||
assert target.path_angle == pytest.approx(-.2)
|
||||
|
||||
|
||||
def test_duplicate_stations_keep_valid_geometry_and_first_cycle_slew():
|
||||
model = make_model([0., 0., 10.], [.4, .4, .4], [0., 0., 0.])
|
||||
assert encode_model_action(model, 0., 20.) == FordPath(True, .4, 0., 0., 0.)
|
||||
assert encode_model_action(model, .01, 20.) == FordPath(True, .4, .2, 0., 0.)
|
||||
out = ModelActionController().update(model, .01, speed=20., dt=.002)
|
||||
assert out.path_offset == pytest.approx(.01)
|
||||
assert out.path_angle == 0.
|
||||
assert out.path_angle == pytest.approx(.001)
|
||||
|
||||
@@ -26,7 +26,7 @@ from openpilot.selfdrive.controls.tests.test_ford_model_action_selection import
|
||||
|
||||
|
||||
def update(controller, now=1., **overrides):
|
||||
kwargs = {'model': straight(.4, .1), 'desired_curvature': .01, 'speed': 20., 'yaw_rate': 0., 'now': now,
|
||||
kwargs = {'model': straight(.4), 'desired_curvature': .01, 'speed': 20., 'yaw_rate': 0., 'now': now,
|
||||
'model_time': now, 'measurement_time': now, 'reference_time': now, 'active': True}
|
||||
kwargs.update(overrides)
|
||||
return controller.update(**kwargs)
|
||||
@@ -95,7 +95,7 @@ def test_yaw_offset_does_not_change_the_base():
|
||||
for i in range(100):
|
||||
outputs = [update(c, 1.+i*.01, **kwargs) for c, kwargs in zip(controllers, variants, strict=True)]
|
||||
assert all(out == outputs[0] for out in outputs)
|
||||
assert outputs[0].path_angle == pytest.approx(.1)
|
||||
assert outputs[0].path_angle == pytest.approx(.2)
|
||||
|
||||
|
||||
def test_reference_source_can_change_to_an_older_but_fresh_publication():
|
||||
@@ -104,7 +104,7 @@ def test_reference_source_can_change_to_an_older_but_fresh_publication():
|
||||
assert update(controller, 1.01, reference_time=.98).valid
|
||||
|
||||
|
||||
def test_current_model_geometry_controls_both_fields_independently_of_scalar_action():
|
||||
def test_release_keeps_current_geometry_and_may_grow_c0_while_c1_decreases():
|
||||
for sign in (-1., 1.):
|
||||
controller = FordModelActionController()
|
||||
for i in range(100):
|
||||
@@ -112,10 +112,11 @@ def test_current_model_geometry_controls_both_fields_independently_of_scalar_act
|
||||
for i in range(100):
|
||||
after = update(controller, 2.+i*.01, model=circle(sign*.02), desired_curvature=sign*.004)
|
||||
assert abs(after.path_offset) > abs(before.path_offset)
|
||||
assert abs(after.path_angle) > abs(before.path_angle)
|
||||
assert abs(after.path_angle) < abs(before.path_angle)
|
||||
for i in range(100):
|
||||
released = update(controller, 3.+i*.01, model=circle(sign*.02), desired_curvature=0.)
|
||||
assert released == after # A scalar reference change does not fabricate a different model pose.
|
||||
assert released.path_offset == after.path_offset
|
||||
assert released.path_angle == pytest.approx(0.)
|
||||
|
||||
|
||||
def _method(filename, class_name, method):
|
||||
@@ -149,12 +150,9 @@ class Subscriptions:
|
||||
|
||||
def __init__(self, maneuver):
|
||||
self.valid = {'lateralManeuverPlan': maneuver, 'modelV2': True}
|
||||
self.logMonoTime = {'carState': 995_000_000, 'modelV2': 980_000_000, 'lateralManeuverPlan': 990_000_000,
|
||||
'deviceMotion': 980_000_000, 'extrinsicsCalibration': 750_000_000}
|
||||
self.logMonoTime = {'carState': 995_000_000, 'modelV2': 980_000_000, 'lateralManeuverPlan': 990_000_000}
|
||||
self.failed = set()
|
||||
self.messages = {'carStateSP': custom.CarStateSP.new_message(), 'lateralManeuverPlan': SimpleNamespace(desiredCurvature=-.1),
|
||||
'deviceMotion': SimpleNamespace(angularVelocityDevice=SimpleNamespace(valid=True), sensorsOK=True, inputsOK=True,
|
||||
timestamp=970_000_000)}
|
||||
self.messages = {'carStateSP': custom.CarStateSP.new_message(), 'lateralManeuverPlan': SimpleNamespace(desiredCurvature=-.1)}
|
||||
|
||||
def __getitem__(self, service):
|
||||
return self.messages[service]
|
||||
@@ -164,44 +162,31 @@ class Subscriptions:
|
||||
|
||||
|
||||
@pytest.mark.parametrize('maneuver', [False, True])
|
||||
@pytest.mark.parametrize('host_yaw', [.0072, .3])
|
||||
@pytest.mark.parametrize('initial_curvature', [0., .005])
|
||||
def test_actual_controlsd_selection_limiting_publication_and_downstream_can(pipeline, maneuver, host_yaw, initial_curvature):
|
||||
def test_actual_controlsd_selection_limiting_publication_and_downstream_can(pipeline, maneuver):
|
||||
call, publication = pipeline
|
||||
sm = Subscriptions(maneuver)
|
||||
controls = startup()
|
||||
controller = controls.ford_path_controller
|
||||
initial_curvature *= -1 if maneuver else 1
|
||||
controls.sm, controls.desired_curvature, controls.curvature = sm, initial_curvature, 0.
|
||||
if initial_curvature:
|
||||
# Start at the old target so startup slew cannot hide prediction on the real call path.
|
||||
controller.core.c0, controller.core.c1 = .4, .1
|
||||
model = straight(.4, .1)
|
||||
controls.sm, controls.desired_curvature, controls.curvature = sm, 0., 0.
|
||||
model = straight(.4)
|
||||
model.action = SimpleNamespace(desiredCurvature=.1)
|
||||
cc = structs.CarControl(latActive=True)
|
||||
cs = SimpleNamespace(vEgo=20., yawRate=-host_yaw, canValid=True, steeringPressed=False, steeringTorque=0.)
|
||||
environment = {'FordModelActionController': FordModelActionController, 'self': controls, 'CS': cs, 'CC': cc,
|
||||
'actuators': cc.actuators, 'model_v2': model, 'lp': SimpleNamespace(roll=0.),
|
||||
cs = SimpleNamespace(vEgo=20., yawRate=-.0072, canValid=True, steeringPressed=False, steeringTorque=0.)
|
||||
environment = {'self': controls, 'CS': cs, 'CC': cc, 'actuators': cc.actuators, 'model_v2': model, 'lp': SimpleNamespace(roll=0.),
|
||||
'clip_curvature': clip_curvature, 'time': SimpleNamespace(monotonic=lambda: 1.)}
|
||||
exec(call, environment)
|
||||
expected_curvature = initial_curvature+(-1 if maneuver else 1)*.000125
|
||||
expected_curvature = (-1 if maneuver else 1)*.000125
|
||||
assert controls.desired_curvature == pytest.approx(expected_curvature)
|
||||
if maneuver:
|
||||
assert controls.ford_path == FordPath() and not cc.latActive
|
||||
assert controller.diagnostics['status'] == 'unsupported_reference'
|
||||
else:
|
||||
assert controls.ford_path.path_offset == pytest.approx(.44 if initial_curvature else .04)
|
||||
assert controls.ford_path.path_angle == pytest.approx(.1 if initial_curvature else .005)
|
||||
assert controller.diagnostics['yaw_rate'] == host_yaw
|
||||
assert controller.diagnostics['pose_source'] == 'model'
|
||||
assert cc.latActive and cc.actuators.curvature == 0.
|
||||
assert controller.diagnostics['reference_age'] == pytest.approx(.02)
|
||||
assert controls.ford_path.path_angle == pytest.approx(20.*expected_curvature)
|
||||
assert controls.ford_path.path_offset == pytest.approx(.04)
|
||||
assert cc.latActive and cc.actuators.curvature == 0.
|
||||
assert controller.diagnostics['reference_age'] == pytest.approx(.01 if maneuver else .02)
|
||||
|
||||
cp = structs.CarParams(flags=int(FordFlags.CANFD), carFingerprint='FORD_F_150_LIGHTNING_MK1')
|
||||
downstream = CarController({Bus.pt: 'ford_lincoln_base_pt'}, cp, controls.CP_SP)
|
||||
downstream = CarController({Bus.pt: 'ford_lincoln_base_pt'}, cp, structs.CarParamsSP())
|
||||
vehicle = SimpleNamespace(out=structs.CarState(vEgo=20., vEgoRaw=20.), acc_tja_status_stock_values=defaultdict(int),
|
||||
lkas_status_stock_values=defaultdict(int), buttons_stock_values=defaultdict(int))
|
||||
parser = CANParser('ford_lincoln_base_pt', [('LateralMotionControl2', 20)], downstream.CAN.main)
|
||||
parser = CANParser('ford_lincoln_base_pt', [('LateralMotionControl2', 100)], downstream.CAN.main)
|
||||
for i, fail in enumerate((False, True)):
|
||||
if fail:
|
||||
sm.failed.add('modelV2')
|
||||
@@ -209,17 +194,13 @@ def test_actual_controlsd_selection_limiting_publication_and_downstream_can(pipe
|
||||
assert not cc.latActive and controls.ford_path == FordPath()
|
||||
msg = custom.CarControlSP.new_message()
|
||||
exec(publication, {'self': controls, 'CC_SP': msg})
|
||||
for tick in range(5 if fail else 1):
|
||||
now_nanos = (i + tick + 1) * 10_000_000
|
||||
_, packets = downstream.update(cc.as_reader(), convert_carControlSP(msg.as_reader()), vehicle, now_nanos)
|
||||
lateral = [p for p in packets if p[0] == 0x3d6]
|
||||
assert len(lateral) == int(not fail or tick == 4)
|
||||
parser.update([now_nanos, packets])
|
||||
_, packets = downstream.update(cc.as_reader(), convert_carControlSP(msg.as_reader()), vehicle, (i+1)*10_000_000)
|
||||
parser.update([(i+1)*10_000_000, packets])
|
||||
wire = parser.vl['LateralMotionControl2']
|
||||
assert wire['LatCtlPathOffst_L_Actl'] == pytest.approx(-controls.ford_path.path_offset)
|
||||
assert wire['LatCtlPath_An_Actl'] == pytest.approx(-controls.ford_path.path_angle)
|
||||
assert wire['LatCtlCurv_No_Actl'] == wire['LatCtlCrv_NoRate2_Actl'] == 0.
|
||||
assert wire['LatCtl_D2_Rq'] == (0 if fail or maneuver else 2)
|
||||
assert wire['LatCtl_D2_Rq'] == (0 if fail else 2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('maneuver', [False, True])
|
||||
@@ -233,37 +214,6 @@ def test_actual_controlsd_service_gates(pipeline, maneuver, failed):
|
||||
cs = SimpleNamespace(vEgo=20., yawRate=0., canValid=True, steeringPressed=False, steeringTorque=0.)
|
||||
model = straight()
|
||||
model.action = SimpleNamespace(desiredCurvature=.1)
|
||||
exec(pipeline[0], {'FordModelActionController': FordModelActionController, 'self': controls, 'CS': cs, 'CC': cc,
|
||||
'actuators': cc.actuators, 'model_v2': model, 'lp': SimpleNamespace(roll=0.),
|
||||
exec(pipeline[0], {'self': controls, 'CS': cs, 'CC': cc, 'actuators': cc.actuators, 'model_v2': model, 'lp': SimpleNamespace(roll=0.),
|
||||
'clip_curvature': clip_curvature, 'time': SimpleNamespace(monotonic=lambda: 1.)})
|
||||
assert controls.ford_path.valid == cc.latActive == (failed == 'lateralManeuverPlan' and not maneuver)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('service', ['deviceMotion', 'extrinsicsCalibration'])
|
||||
def test_optional_pose_services_do_not_modify_model_point_requests(pipeline, service):
|
||||
sm = Subscriptions(False)
|
||||
sm.failed.add(service)
|
||||
controls = startup()
|
||||
controls.sm, controls.desired_curvature, controls.curvature = sm, .01, 0.
|
||||
controls.calibrated_pose = None
|
||||
controls.ford_path_controller.core.c0, controls.ford_path_controller.core.c1 = .4, .1
|
||||
cc = structs.CarControl(latActive=True)
|
||||
cs = SimpleNamespace(vEgo=20., yawRate=-.3, canValid=True, steeringPressed=False, steeringTorque=0.)
|
||||
model = straight(.4, .1)
|
||||
model.action = SimpleNamespace(desiredCurvature=.01)
|
||||
exec(pipeline[0], {'FordModelActionController': FordModelActionController, 'self': controls, 'CS': cs, 'CC': cc,
|
||||
'actuators': cc.actuators, 'model_v2': model, 'lp': SimpleNamespace(roll=0.),
|
||||
'clip_curvature': clip_curvature, 'time': SimpleNamespace(monotonic=lambda: 1.)})
|
||||
assert cc.latActive and controls.ford_path.valid
|
||||
assert controls.ford_path.path_offset == pytest.approx(.44)
|
||||
assert controls.ford_path.path_angle == pytest.approx(.1)
|
||||
assert controls.ford_path_controller.diagnostics['pose_source'] == 'model'
|
||||
|
||||
|
||||
def test_maneuver_reference_clears_existing_model_point_requests():
|
||||
controller = FordModelActionController()
|
||||
update(controller)
|
||||
assert update(controller, 1.01, reference_source='lateralManeuverPlan') == FordPath()
|
||||
assert controller.diagnostics['status'] == 'unsupported_reference'
|
||||
assert (controller.core.c0, controller.core.c1) == (0., 0.)
|
||||
assert update(controller, 1.02).path_angle == pytest.approx(.005)
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
"""Exercise 100Hz calculation and 20Hz transmission through the real CAN sender."""
|
||||
from collections import defaultdict
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.can import CANParser
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.ford.carcontroller import CarController
|
||||
from opendbc.car.ford.fordcan import calculate_lat_ctl2_checksum
|
||||
from opendbc.car.ford.values import FordFlags, FordFlagsSP, FordSafetyFlags
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
from openpilot.selfdrive.controls.lib.ford_model_action import ModelActionController
|
||||
from openpilot.selfdrive.controls.tests.test_ford_model_action import straight
|
||||
|
||||
|
||||
def sender(canfd=True, selected=True):
|
||||
cp = structs.CarParams(flags=int(FordFlags.CANFD) if canfd else 0, carFingerprint='FORD_F_150_LIGHTNING_MK1',
|
||||
safetyConfigs=[structs.CarParams.SafetyConfig()])
|
||||
cp_sp = structs.CarParamsSP(flags=int(FordFlagsSP.MODEL_ACTION) if selected else 0)
|
||||
controller = CarController({Bus.pt: 'ford_lincoln_base_pt'}, cp, cp_sp)
|
||||
cs = SimpleNamespace(out=structs.CarState(vEgo=20., vEgoRaw=20.), acc_tja_status_stock_values=defaultdict(int),
|
||||
lkas_status_stock_values=defaultdict(int), buttons_stock_values=defaultdict(int))
|
||||
return controller, cs
|
||||
|
||||
|
||||
@pytest.mark.parametrize('canfd,selected,step', [(True, True, 5), (True, False, 1), (False, True, 5), (False, False, 5)])
|
||||
def test_send_intervals_latest_sample_counter_and_checksum(canfd, selected, step):
|
||||
controller, cs = sender(canfd, selected)
|
||||
cc, sp = structs.CarControl(latActive=True), structs.CarControlSP()
|
||||
sp.fordLateralPath.valid = True
|
||||
name = 'LateralMotionControl2' if canfd else 'LateralMotionControl'
|
||||
address = 0x3d6 if canfd else 0x3d3
|
||||
parser = CANParser('ford_lincoln_base_pt', [(name, 0)], controller.CAN.main)
|
||||
sent = []
|
||||
for frame in range(1000):
|
||||
sp.fordLateralPath.pathOffset = (frame % 101 - 50) * .01
|
||||
sp.fordLateralPath.pathAngle = (frame % 101 - 50) * .0005
|
||||
_, packets = controller.update(cc.as_reader(), sp, cs, frame * 10_000_000)
|
||||
lateral = [p for p in packets if p[0] == address]
|
||||
assert len(lateral) == int(frame % step == 0)
|
||||
if not lateral:
|
||||
continue
|
||||
sent.append(frame)
|
||||
parser.update([frame * 10_000_000, lateral])
|
||||
wire = parser.vl[name]
|
||||
assert wire['LatCtlPathOffst_L_Actl'] == pytest.approx(-sp.fordLateralPath.pathOffset)
|
||||
assert wire['LatCtlPath_An_Actl'] == pytest.approx(-sp.fordLateralPath.pathAngle)
|
||||
if canfd:
|
||||
counter = (len(sent) - 1) % 16
|
||||
assert wire['LatCtlPath_No_Cnt'] == counter
|
||||
assert wire['LatCtlPath_No_Cs'] == calculate_lat_ctl2_checksum(2, counter, lateral[0][1])
|
||||
assert sent == list(range(0, 1000, step))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('failure_frame', range(1, 6))
|
||||
@pytest.mark.parametrize('disengage', [False, True])
|
||||
def test_next_scheduled_frame_clears_invalid_or_inactive_path(failure_frame, disengage):
|
||||
controller, cs = sender()
|
||||
cc, sp = structs.CarControl(latActive=True), structs.CarControlSP()
|
||||
sp.fordLateralPath.valid = True
|
||||
sp.fordLateralPath.pathOffset, sp.fordLateralPath.pathAngle = .4, .1
|
||||
parser = CANParser('ford_lincoln_base_pt', [('LateralMotionControl2', 0)], controller.CAN.main)
|
||||
for frame in range(11):
|
||||
if frame == failure_frame:
|
||||
if disengage:
|
||||
cc.latActive = False
|
||||
else:
|
||||
sp.fordLateralPath.valid = False
|
||||
_, packets = controller.update(cc.as_reader(), sp, cs, frame * 10_000_000)
|
||||
lateral = [p for p in packets if p[0] == 0x3d6]
|
||||
assert len(lateral) == int(frame % 5 == 0)
|
||||
if lateral and frame >= failure_frame:
|
||||
parser.update([frame * 10_000_000, lateral])
|
||||
wire = parser.vl['LateralMotionControl2']
|
||||
assert wire['LatCtl_D2_Rq'] == (0 if disengage else 2)
|
||||
assert all(wire[k] == 0. for k in ('LatCtlPathOffst_L_Actl', 'LatCtlPath_An_Actl', 'LatCtlCurv_No_Actl', 'LatCtlCrv_NoRate2_Actl'))
|
||||
|
||||
|
||||
def test_core_slew_per_second_and_actual_panda_acceptance():
|
||||
controller, cs = sender()
|
||||
core = ModelActionController()
|
||||
cc, sp = structs.CarControl(latActive=True), structs.CarControlSP()
|
||||
parser = CANParser('ford_lincoln_base_pt', [('LateralMotionControl2', 0)], controller.CAN.main)
|
||||
safety = libsafety_py.libsafety
|
||||
assert safety.set_safety_hooks(structs.CarParams.SafetyModel.ford, FordSafetyFlags.CANFD) == 0
|
||||
safety.init_tests()
|
||||
safety.set_controls_allowed(True)
|
||||
frames = []
|
||||
for frame in range(100):
|
||||
command = core.update(straight(10., 1.), .1, speed=20., dt=.01)
|
||||
assert core.c0 == pytest.approx((frame + 1) * .04)
|
||||
assert core.c1 == pytest.approx((frame + 1) * .005)
|
||||
sp.fordLateralPath.valid = command.valid
|
||||
sp.fordLateralPath.pathOffset, sp.fordLateralPath.pathAngle = command.path_offset, command.path_angle
|
||||
_, packets = controller.update(cc.as_reader(), sp, cs, frame * 10_000_000)
|
||||
for address, data, bus in packets:
|
||||
if address != 0x3d6:
|
||||
continue
|
||||
frames.append(frame)
|
||||
safety.set_timer(frame * 10_000)
|
||||
assert safety.safety_tx_hook(libsafety_py.make_CANPacket(address, bus, data))
|
||||
parser.update([frame * 10_000_000, [(address, data, bus)]])
|
||||
wire = parser.vl['LateralMotionControl2']
|
||||
assert wire['LatCtlPathOffst_L_Actl'] == pytest.approx(-command.path_offset)
|
||||
assert wire['LatCtlPath_An_Actl'] == pytest.approx(-command.path_angle)
|
||||
assert wire['LatCtlCurv_No_Actl'] == wire['LatCtlCrv_NoRate2_Actl'] == 0.
|
||||
assert frames == list(range(0, 100, 5))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('sign', [-1., 1.])
|
||||
@pytest.mark.parametrize('phase', range(5))
|
||||
def test_saturated_turn_unwinds_on_first_update_and_next_scheduled_can_frame(sign, phase):
|
||||
controller, cs = sender()
|
||||
core = ModelActionController()
|
||||
cc, sp = structs.CarControl(latActive=True), structs.CarControlSP()
|
||||
parser = CANParser('ford_lincoln_base_pt', [('LateralMotionControl2', 0)], controller.CAN.main)
|
||||
change_frame = 200 + phase
|
||||
first_unwind = first_zero_c0 = first_zero_c1 = None
|
||||
turn, released = straight(sign*10., sign), straight()
|
||||
for frame in range(change_frame+135):
|
||||
command = core.update(turn if frame < change_frame else released, sign*.1, speed=20., dt=.01)
|
||||
if frame >= change_frame:
|
||||
elapsed = (frame-change_frame+1)*.01
|
||||
assert sign*core.c0 == pytest.approx(max(0., 5.11-4.*elapsed), abs=1e-10)
|
||||
assert sign*core.c1 == pytest.approx(max(0., .5-.5*elapsed), abs=1e-10)
|
||||
sp.fordLateralPath.valid = command.valid
|
||||
sp.fordLateralPath.pathOffset, sp.fordLateralPath.pathAngle = command.path_offset, command.path_angle
|
||||
_, packets = controller.update(cc.as_reader(), sp, cs, frame*10_000_000)
|
||||
lateral = [p for p in packets if p[0] == 0x3d6]
|
||||
if not lateral or frame < change_frame:
|
||||
continue
|
||||
parser.update([frame*10_000_000, lateral])
|
||||
wire = parser.vl['LateralMotionControl2']
|
||||
c0, c1 = -sign*wire['LatCtlPathOffst_L_Actl'], -sign*wire['LatCtlPath_An_Actl']
|
||||
assert 0. <= c0 < 5.11 and 0. <= c1 < .5
|
||||
assert wire['LatCtlCurv_No_Actl'] == wire['LatCtlCrv_NoRate2_Actl'] == 0.
|
||||
if first_unwind is None:
|
||||
first_unwind = frame
|
||||
if c0 == 0. and first_zero_c0 is None:
|
||||
first_zero_c0 = frame
|
||||
if c1 == 0. and first_zero_c1 is None:
|
||||
first_zero_c1 = frame
|
||||
assert first_unwind == ((change_frame+4)//5)*5
|
||||
assert first_zero_c0 == ((change_frame+127+4)//5)*5
|
||||
assert first_zero_c1 == ((change_frame+99+4)//5)*5
|
||||
@@ -7,14 +7,10 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.values import FordFlags, FordFlagsSP
|
||||
from openpilot.cereal import custom
|
||||
from opendbc.car.ford.values import FordFlags
|
||||
from openpilot.common.params import Params, ParamKeyFlag, ParamKeyType
|
||||
from openpilot.selfdrive.car.helpers import convert_to_capnp
|
||||
from openpilot.selfdrive.controls.lib.ford_model_action import FordModelActionController, select_model_action_controller
|
||||
from openpilot.selfdrive.controls.lib.ford_path import FordPath, FordPathController, FordPscmObserverPathController
|
||||
from openpilot.sunnypilot.mads.helpers import set_car_specific_params
|
||||
|
||||
|
||||
def car_params(**overrides):
|
||||
@@ -22,7 +18,7 @@ def car_params(**overrides):
|
||||
'carFw': []} | overrides))
|
||||
|
||||
|
||||
def startup(cp=None, params=None, cp_sp=None):
|
||||
def startup(cp=None, params=None):
|
||||
filename = Path(__file__).resolve().parents[1]/'controlsd.py'
|
||||
tree = ast.parse(filename.read_text())
|
||||
cls = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == 'Controls')
|
||||
@@ -31,15 +27,8 @@ def startup(cp=None, params=None, cp_sp=None):
|
||||
end = next(i for i, n in enumerate(body) if isinstance(n, ast.Assign) and ast.unparse(n.targets[0]) == 'self.ford_path')
|
||||
if params is None:
|
||||
params = SimpleNamespace(get_bool=lambda key: key == 'FordModelActionController')
|
||||
cp = cp or car_params()
|
||||
# card snapshots the toggle into CarParamsSP before controlsd starts.
|
||||
if cp_sp is None:
|
||||
cp_sp = structs.CarParamsSP()
|
||||
if cp.brand == 'ford':
|
||||
set_car_specific_params(cp, cp_sp, params)
|
||||
controls = SimpleNamespace(CP=cp, CP_SP=cp_sp, params=params, calibrated_pose=None,
|
||||
pose_calibrator=SimpleNamespace(calib_valid=False))
|
||||
environment = {'self': controls, 'FordFlags': FordFlags, 'FordFlagsSP': FordFlagsSP, 'FordPath': FordPath,
|
||||
controls = SimpleNamespace(CP=cp or car_params(), params=params)
|
||||
environment = {'self': controls, 'FordFlags': FordFlags, 'FordPath': FordPath,
|
||||
'FordPathController': FordPathController, 'FordPscmObserverPathController': FordPscmObserverPathController,
|
||||
'FordModelActionController': FordModelActionController,
|
||||
'select_model_action_controller': select_model_action_controller,
|
||||
@@ -56,23 +45,9 @@ def test_actual_startup_priority(candidate, observer):
|
||||
expected = FordModelActionController if candidate else previous
|
||||
assert type(selected.ford_path_controller) is expected
|
||||
assert selected.ford_model_action == candidate
|
||||
assert bool(selected.CP_SP.flags & FordFlagsSP.MODEL_ACTION) == candidate
|
||||
assert selected.ford_path == FordPath()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('selected', [False, True])
|
||||
def test_controller_and_sender_share_card_snapshot_when_stored_toggle_changes(selected):
|
||||
cp, cp_sp = car_params(), structs.CarParamsSP(flags=128)
|
||||
set_car_specific_params(cp, cp_sp, SimpleNamespace(get_bool=lambda key: selected))
|
||||
with custom.CarParamsSP.from_bytes(convert_to_capnp(cp_sp).to_bytes()) as snapshot:
|
||||
controls = startup(cp, SimpleNamespace(get_bool=lambda key: not selected), snapshot)
|
||||
assert controls.ford_model_action == selected
|
||||
assert bool(controls.CP_SP.flags & FordFlagsSP.MODEL_ACTION) == selected
|
||||
assert controls.CP_SP.flags & 128
|
||||
set_car_specific_params(cp, cp_sp, SimpleNamespace(get_bool=lambda key: False))
|
||||
assert cp_sp.flags == 128
|
||||
|
||||
|
||||
@pytest.mark.parametrize('overrides', [{'brand': 'tesla'}, {'flags': 0}, {'carFingerprint': 'FORD_F_150_MK14'}])
|
||||
@pytest.mark.parametrize('observer', [False, True])
|
||||
def test_other_vehicles_keep_their_previous_selection(overrides, observer):
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
"""Raw Ford yaw gates input health but cannot change path demand."""
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.selfdrive.controls.lib.ford_model_action import ModelActionController
|
||||
from openpilot.selfdrive.controls.lib.ford_path import FordPath
|
||||
from openpilot.selfdrive.controls.tests.test_ford_model_action import straight
|
||||
|
||||
|
||||
@pytest.mark.parametrize('sign', [-1., 1.])
|
||||
@pytest.mark.parametrize('yaw', [-3., -.2, -.008, 0., .008, .2, 3.])
|
||||
def test_valid_yaw_cannot_change_commands_during_entry_release_or_reversal(sign, yaw):
|
||||
reference, measured = ModelActionController(), ModelActionController()
|
||||
for i in range(400):
|
||||
offset, desired = ((.4, .02), (.4, .001), (.12, -.0004078), (-.4, -.02))[i//100]
|
||||
model = straight(sign*offset)
|
||||
expected = reference.update(model, sign*desired, speed=10., dt=.01)
|
||||
actual = measured.update(model, sign*desired, speed=10., dt=.01, yaw_rate=yaw)
|
||||
assert actual == expected
|
||||
assert actual.curvature == actual.curvature_rate == 0.
|
||||
|
||||
|
||||
@pytest.mark.parametrize('yaw', [math.nan, math.inf, -math.inf, None, 'bad', 3.001, -3.001])
|
||||
def test_invalid_yaw_still_resets_core(yaw):
|
||||
controller = ModelActionController()
|
||||
controller.update(straight(.4), .01, speed=10., dt=.01)
|
||||
assert controller.update(straight(.4), .01, speed=10., dt=.01, yaw_rate=yaw) == FordPath()
|
||||
assert controller.c0 == controller.c1 == 0.
|
||||
@@ -1,54 +0,0 @@
|
||||
"""Model geometry contract, independent of any PSCM response model."""
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from openpilot.selfdrive.controls.lib.ford_model_action import encode_model_action
|
||||
|
||||
|
||||
def model_points(t, x, y, heading):
|
||||
return SimpleNamespace(position=SimpleNamespace(t=t, x=x, y=y), orientation=SimpleNamespace(t=t, z=heading))
|
||||
|
||||
|
||||
def test_both_fields_sample_the_same_model_time_without_constant_speed_assumption():
|
||||
# Accelerating plan: one second is 12 m along this straight inclined path.
|
||||
s = np.array([0., 4., 12., 30.])
|
||||
heading = .1
|
||||
m = model_points([0., .5, 1., 2.], s*np.cos(heading), .3+s*np.sin(heading), np.full(4, heading))
|
||||
out = encode_model_action(m, -.01, 20.)
|
||||
assert out.path_offset == pytest.approx(.3+12*np.sin(heading))
|
||||
assert out.path_angle == pytest.approx(.1)
|
||||
assert out.curvature == out.curvature_rate == 0.
|
||||
assert encode_model_action(m, .01, 30.) == out
|
||||
|
||||
|
||||
def test_low_speed_floor_uses_one_shared_seven_metre_station():
|
||||
m = model_points([0., 1., 2.], [0., 3., 9.], [0., 0., 0.], [0., .03, .09])
|
||||
out = encode_model_action(m, .01, 3.)
|
||||
assert out.path_offset == 0.
|
||||
assert out.path_angle == pytest.approx(.07)
|
||||
|
||||
|
||||
def test_short_plan_holds_both_endpoint_values_without_extrapolation():
|
||||
m = model_points([0., .5], [0., 2.], [0., .4], [0., .2])
|
||||
out = encode_model_action(m, -.01, 20.)
|
||||
assert out.path_offset == .4
|
||||
assert out.path_angle == pytest.approx(.2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('times', [[], [0.], [0., 0., 1.], [0., 1., .5], [0., float('nan'), 1.], [.1, .5, 1.]])
|
||||
def test_invalid_model_clock_cannot_publish_an_active_path(times):
|
||||
m = model_points(times, [0., 10., 20.], [0., .1, .4], [0., .02, .04])
|
||||
assert not encode_model_action(m, .01, 20.).valid
|
||||
|
||||
|
||||
def test_orientation_and_position_must_describe_the_same_times():
|
||||
m = model_points([0., .5, 1.], [0., 10., 20.], [0., .1, .4], [0., .02, .04])
|
||||
m.orientation.t = [0., .6, 1.]
|
||||
assert not encode_model_action(m, .01, 20.).valid
|
||||
|
||||
|
||||
def test_model_heading_unwraps_before_interpolation():
|
||||
m = model_points([0., .5, 1.5], [0., 10., 30.], [0., 0., 0.], [3., 3.1, -3.1])
|
||||
assert encode_model_action(m, 0., 20.).path_angle == pytest.approx(np.pi)
|
||||
@@ -7,7 +7,6 @@ See the LICENSE.md file in the root directory for more details.
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.values import FordFlags, FordFlagsSP
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP, HyundaiSafetyFlagsSP
|
||||
from opendbc.sunnypilot.car.tesla.values import MadsScreenButtonType, TeslaFlagsSP
|
||||
@@ -55,11 +54,6 @@ def set_alternative_experience(CP: structs.CarParams, CP_SP: structs.CarParamsSP
|
||||
|
||||
|
||||
def set_car_specific_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params):
|
||||
if CP.brand == "ford":
|
||||
CP_SP.flags &= ~FordFlagsSP.MODEL_ACTION.value
|
||||
if CP.flags & FordFlags.CANFD and CP.carFingerprint == "FORD_F_150_LIGHTNING_MK1" and params.get_bool("FordModelActionController"):
|
||||
CP_SP.flags |= FordFlagsSP.MODEL_ACTION.value
|
||||
|
||||
if CP.brand == "hyundai":
|
||||
# TODO-SP: This should be separated from MADS module for future implementations
|
||||
# Use "HyundaiLongitudinalMainCruiseToggleable" param
|
||||
|
||||
@@ -2183,8 +2183,8 @@
|
||||
"widget": "toggle",
|
||||
"needs_onroad_cycle": true,
|
||||
"title": "Selected-Action Path Tracking (Experimental)",
|
||||
"description": "Follow model position and heading from one shared path point on the Ford CAN FD F-150 Lightning.",
|
||||
"details": "Uses the model's lateral position and heading one second ahead, with a seven-metre minimum distance and no extrapolation beyond the available path. Sends requests at 20 Hz with the existing command limits and rate limits. Default off; this model-point experiment is not road-validated. Enable only for controlled testing. Lateral maneuver test mode is unsupported and disengages this controller. On the Ford CAN FD F-150 Lightning this takes priority over PSCM Coefficient Observer; other vehicles retain their existing controller. Turning it off restores PSCM Coefficient Observer if selected, otherwise the original Ford path controller. Changes apply after a real offroad-to-onroad cycle, not immediately or on disengagement alone.",
|
||||
"description": "Follow the selected steering plan with nearby model-path centering on the Ford CAN FD F-150 Lightning.",
|
||||
"details": "Uses nearby model-path offset and a heading request based directly on selected planned curvature. There is no accumulated measured-turning correction. Default off; physical tracking and turn-exit behavior are not road-validated. Enable only for controlled testing. On the Ford CAN FD F-150 Lightning this takes priority over PSCM Coefficient Observer; other vehicles retain their existing controller. Turning it off restores PSCM Coefficient Observer if selected, otherwise the original Ford path controller. Changes apply after a real offroad-to-onroad cycle, not immediately or on disengagement alone.",
|
||||
"enablement": [
|
||||
{
|
||||
"type": "offroad_only"
|
||||
|
||||
@@ -14,8 +14,8 @@ sections:
|
||||
widget: toggle
|
||||
needs_onroad_cycle: true
|
||||
title: Selected-Action Path Tracking (Experimental)
|
||||
description: Follow model position and heading from one shared path point on the Ford CAN FD F-150 Lightning.
|
||||
details: Uses the model's lateral position and heading one second ahead, with a seven-metre minimum distance and no extrapolation beyond the available path. Sends requests at 20 Hz with the existing command limits and rate limits. Default off; this model-point experiment is not road-validated. Enable only for controlled testing. Lateral maneuver test mode is unsupported and disengages this controller. On the Ford CAN FD F-150 Lightning this takes priority over PSCM Coefficient Observer; other vehicles retain their existing controller. Turning it off restores PSCM Coefficient Observer if selected, otherwise the original Ford path controller. Changes apply after a real offroad-to-onroad cycle, not immediately or on disengagement alone.
|
||||
description: Follow the selected steering plan with nearby model-path centering on the Ford CAN FD F-150 Lightning.
|
||||
details: Uses nearby model-path offset and a heading request based directly on selected planned curvature. There is no accumulated measured-turning correction. Default off; physical tracking and turn-exit behavior are not road-validated. Enable only for controlled testing. On the Ford CAN FD F-150 Lightning this takes priority over PSCM Coefficient Observer; other vehicles retain their existing controller. Turning it off restores PSCM Coefficient Observer if selected, otherwise the original Ford path controller. Changes apply after a real offroad-to-onroad cycle, not immediately or on disengagement alone.
|
||||
enablement:
|
||||
- $ref: '#/macros/offroad'
|
||||
- key: FordPscmObserver
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
"""Compare pinned historical selected-action controllers on complete rlogs.
|
||||
|
||||
Original controls publication times proxy computation time. Consumed model
|
||||
timestamps are exact; carState is causal and carControl is matched within 5 ms.
|
||||
This compares commands on a fixed recording, never counterfactual vehicle motion.
|
||||
"""
|
||||
import argparse
|
||||
from collections import Counter
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
import zstandard
|
||||
|
||||
from openpilot.cereal import log
|
||||
from openpilot.selfdrive.controls.lib import ford_model_action
|
||||
from tools.ford_pscm_lab.model_action_replay import (
|
||||
V1_REVISION, V2_REVISION, V3_REVISION, V4_REVISION, WireCheck, field_checks, load_controller, sample, verify_dependency,
|
||||
)
|
||||
|
||||
|
||||
DEPLOYMENT_OPENDBC = 'c21a9013700734dd20b09e05aa68329ad8cc20f9'
|
||||
|
||||
|
||||
def extract(directory):
|
||||
columns = {'cs': 't valid can_valid speed yaw torque pressed', 'controls': 't valid desired model_ns',
|
||||
'cc': 't valid active', 'params': 't valid', 'path': 't valid active c0 c1', 'model': 't valid ns'}
|
||||
rows = {name: [] for name in columns}
|
||||
models, sources = [], {}
|
||||
t0 = None
|
||||
files = sorted(directory.glob('*--rlog.zst'), key=lambda p: int(p.name.split('--')[-2]))
|
||||
if not files:
|
||||
raise ValueError('No complete rlogs found')
|
||||
for file in files:
|
||||
compressed = file.read_bytes()
|
||||
sources[file.name] = hashlib.sha256(compressed).hexdigest()
|
||||
data = zstandard.ZstdDecompressor().stream_reader(compressed).read()
|
||||
for event in log.Event.read_multiple_bytes(data):
|
||||
kind, t, valid = event.which(), event.logMonoTime*1e-9, event.valid
|
||||
if t0 is None:
|
||||
t0 = t
|
||||
if kind == 'carState':
|
||||
cs = event.carState
|
||||
rows['cs'].append((t, valid, cs.canValid, cs.vEgo, -cs.yawRate, cs.steeringTorque, cs.steeringPressed))
|
||||
elif kind == 'controlsState':
|
||||
cs = event.controlsState
|
||||
rows['controls'].append((t, valid, cs.desiredCurvature, cs.lateralPlanMonoTime))
|
||||
elif kind == 'carControl':
|
||||
rows['cc'].append((t, valid, event.carControl.latActive))
|
||||
elif kind == 'vehicleParameters':
|
||||
rows['params'].append((t, valid))
|
||||
elif kind == 'carControlSP':
|
||||
path = event.carControlSP.fordLateralPath
|
||||
rows['path'].append((t, valid, path.valid, path.pathOffset, path.pathAngle))
|
||||
elif kind == 'modelV2':
|
||||
model = event.modelV2
|
||||
models.append(SimpleNamespace(position=SimpleNamespace(x=list(model.position.x), y=list(model.position.y)),
|
||||
orientation=SimpleNamespace(z=list(model.orientation.z))))
|
||||
rows['model'].append((t, valid, event.logMonoTime))
|
||||
elif kind == 'lateralManeuverPlan':
|
||||
raise ValueError('This replay requires routes without a separate maneuver reference')
|
||||
streams = {}
|
||||
for name, fields in columns.items():
|
||||
values = np.array(rows[name])
|
||||
if not len(values) or np.any(np.diff(values[:, 0]) < 0.):
|
||||
raise ValueError(f'Missing or backward {name} stream')
|
||||
streams[name] = dict(zip(fields.split(), values.T, strict=True))
|
||||
return streams, models, sources, t0
|
||||
|
||||
|
||||
def run(directory, output, baseline_version='v1', candidate_version='v2', windows=()):
|
||||
directory, output = directory.resolve(), output.resolve()
|
||||
if output == directory or directory in output.parents:
|
||||
raise ValueError('Output must be outside the source route directory')
|
||||
if candidate_version == 'current':
|
||||
raise ValueError('This historical damping replay requires unchanged C1 and does not preserve model clocks; ' +
|
||||
'select a pinned v2/v3/v4 candidate, or use a model-point replay for current code')
|
||||
verify_dependency(DEPLOYMENT_OPENDBC)
|
||||
revisions = {'v1': V1_REVISION, 'v2': V2_REVISION, 'v3': V3_REVISION, 'v4': V4_REVISION}
|
||||
baseline_source = load_controller(revisions[baseline_version])
|
||||
candidate_source = load_controller(revisions[candidate_version])
|
||||
streams, models, sources, t0 = extract(directory)
|
||||
controls, model = streams['controls'], streams['model']
|
||||
t = controls['t']
|
||||
cs, params = (sample(streams[name], t) for name in ('cs', 'params'))
|
||||
cc, recorded = (sample(streams[name], t, nearest=True) for name in ('cc', 'path'))
|
||||
mi = np.clip(np.searchsorted(model['ns'], controls['model_ns']), 0, len(models)-1)
|
||||
exact = model['ns'][mi] == controls['model_ns']
|
||||
services = ((controls['valid'] == 1) & (cc['valid'] == 1) & (abs(cc['t']-t) < .005) &
|
||||
(cs['valid'] == 1) & (cs['can_valid'] == 1) & (params['valid'] == 1) &
|
||||
(t-params['t'] >= 0.) & (t-params['t'] <= .15) & exact & (model['valid'][mi] == 1))
|
||||
baseline, candidate, wire = baseline_source.FordModelActionController(), candidate_source.FordModelActionController(), WireCheck()
|
||||
before, after = np.zeros((len(t), 4)), np.zeros((len(t), 4))
|
||||
eligible = np.zeros(len(t), bool)
|
||||
reasons = Counter()
|
||||
for i, now in enumerate(t):
|
||||
kwargs = {'speed': cs['speed'][i], 'now': now, 'measurement_time': cs['t'][i], 'model_time': model['t'][mi[i]],
|
||||
'reference_time': model['t'][mi[i]], 'active': bool(cc['active'][i]), 'valid': bool(services[i])}
|
||||
geometry = models[mi[i]] if exact[i] else None
|
||||
a = baseline.update(geometry, controls['desired'][i], yaw_rate=cs['yaw'][i], **kwargs)
|
||||
b = candidate.update(geometry, controls['desired'][i], yaw_rate=cs['yaw'][i], **kwargs)
|
||||
assert a.valid == b.valid and a.path_angle == b.path_angle
|
||||
before[i] = a.path_offset, a.path_angle, a.curvature, a.curvature_rate
|
||||
after[i] = b.path_offset, b.path_angle, b.curvature, b.curvature_rate
|
||||
eligible[i] = b.valid
|
||||
reasons[candidate.diagnostics['status']] += 1
|
||||
wire.check(a)
|
||||
wire.check(b)
|
||||
field_checks(before, eligible, t)
|
||||
field_checks(after, eligible, t)
|
||||
clean = eligible & (cs['pressed'] == 0) & (abs(cs['torque']) <= 1.)
|
||||
# Erode driver eligibility by one second in each direction on original time.
|
||||
bad = np.r_[0, np.cumsum(~clean)]
|
||||
left, right = np.searchsorted(t, t-1.), np.searchsorted(t, t+1., side='right')
|
||||
clean &= (bad[right] == bad[left]) & (t >= t[0]+1.) & (t <= t[-1]-1.)
|
||||
relative = t-t0
|
||||
masks = {'eligible': eligible, 'driver_clean': clean,
|
||||
'driver_clean_low_request_above_8mps': clean & (cs['speed'] >= 8.) & (abs(controls['desired'])*cs['speed']**2 < .15),
|
||||
'turn': clean & (abs(controls['desired'])*cs['speed']**2 >= .5)}
|
||||
for label, start, end in windows:
|
||||
start, end = float(start), float(end)
|
||||
if not np.isfinite([start, end]).all() or start >= end or label in masks:
|
||||
raise ValueError('Focus windows need unique labels and finite increasing bounds')
|
||||
masks[label] = eligible & (relative >= start) & (relative < end)
|
||||
weight = np.minimum(np.diff(t, append=t[-1]+.01), .03)
|
||||
difference = abs(before[:, 0]-after[:, 0])
|
||||
cohorts = {}
|
||||
for name, mask in masks.items():
|
||||
if mask.any():
|
||||
cohorts[name] = {'cycles': int(mask.sum()), 'seconds': float(weight[mask].sum()),
|
||||
'changed_c0_cycles': int((difference[mask] > 1e-9).sum()),
|
||||
'mean_absolute_c0_change_m': float(np.average(difference[mask], weights=weight[mask])),
|
||||
'max_absolute_c0_change_m': float(difference[mask].max()),
|
||||
'increased_absolute_c0_cycles': int((abs(after[mask, 0])-abs(before[mask, 0]) > 1e-9).sum()),
|
||||
'decreased_absolute_c0_cycles': int((abs(before[mask, 0])-abs(after[mask, 0]) > 1e-9).sum()),
|
||||
'driver_input_percent': float(100*np.average((cs['pressed'][mask] == 1) | (abs(cs['torque'][mask]) > 1.), weights=weight[mask])),
|
||||
'baseline_peak_absolute_c0_m': float(abs(before[mask, 0]).max()),
|
||||
'candidate_peak_absolute_c0_m': float(abs(after[mask, 0]).max())}
|
||||
paired = eligible & (recorded['valid'] == 1) & (recorded['active'] == 1) & (abs(recorded['t']-t) < .005)
|
||||
actual = np.column_stack((recorded['c0'], recorded['c1']))
|
||||
error = abs(before[:, :2]-actual)
|
||||
report = {'scope': 'Fixed-input command replay only; no physical improvement or stability claim.', 'calibration_approved': False,
|
||||
'cycles': len(t), 'eligible_cycles': int(eligible.sum()), 'status_counts': dict(reasons),
|
||||
'c1_exactly_unchanged': True, 'same_validity': True, 'field_slew_zero_c2_c3_pass': True,
|
||||
'float32_can_round_trips': wire.count, 'cohorts': cohorts,
|
||||
'baseline_commands_vs_recorded_publications': {'paired_cycles': int(paired.sum()),
|
||||
'within_one_quantum_cycles': int(np.all(error[paired] <= [.010001, .0005001], axis=1).sum()),
|
||||
'maximum_absolute_error_c0_c1': np.max(error[paired], axis=0).tolist()},
|
||||
'timing': 'Publication-time proxy, causal carState, exact consumed model; full SubMaster health unavailable.',
|
||||
'baseline_version': baseline_version, 'baseline_revision': revisions[baseline_version],
|
||||
'candidate_version': candidate_version, 'candidate_revision': revisions.get(candidate_version, 'working_tree'),
|
||||
'focus_windows': windows, 'baseline_source_sha256': baseline_source.source_sha256,
|
||||
'candidate_source_sha256': candidate_source.source_sha256,
|
||||
'source_rlog_sha256': sources, 'opendbc_head': DEPLOYMENT_OPENDBC,
|
||||
'source_sha256': {str(p): hashlib.sha256(p.read_bytes()).hexdigest() for p in (Path(__file__), Path(ford_model_action.__file__))}}
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
np.savez_compressed(output/'commands.npz', t=relative, before=before, after=after, eligible=eligible,
|
||||
speed=cs['speed'], yaw=cs['yaw'], desired=controls['desired'], torque=cs['torque'])
|
||||
(output/'report.json').write_text(json.dumps(report, indent=2, allow_nan=False)+'\n')
|
||||
print(json.dumps({k: v for k, v in report.items() if k not in ('source_rlog_sha256', 'source_sha256')}, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('rlog_directory', type=Path)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
parser.add_argument('--baseline', choices=['v1', 'v2', 'v3', 'v4'], default='v1')
|
||||
parser.add_argument('--candidate', choices=['v2', 'v3', 'v4', 'current'], default='v2')
|
||||
parser.add_argument('--window', action='append', nargs=3, metavar=('LABEL', 'START_SECONDS', 'END_SECONDS'), default=[])
|
||||
args = parser.parse_args()
|
||||
run(args.rlog_directory, args.output, args.baseline, args.candidate, args.window)
|
||||
@@ -1,23 +1,18 @@
|
||||
"""Replay the selected core and its adapter on route90/95 original-time extracts.
|
||||
|
||||
The historical pass uses pinned v1 source and the archived eligibility mask to check
|
||||
command compatibility. The separate current adapter pass reconstructs input eligibility
|
||||
The historical pass deliberately uses the archived eligibility mask to check
|
||||
command compatibility. The separate adapter pass reconstructs input eligibility
|
||||
from service records, never from candidate/baseline output validity. Neither
|
||||
pass scores counterfactual motion. Source extracts and archived reports are
|
||||
read-only; --output selects a separate destination.
|
||||
|
||||
Current comparisons require model_position_t and model_orientation_t arrays
|
||||
preserved from the rlogs. Older extracts lack these clocks and must be replayed
|
||||
using their archived tool revision; do not synthesize timestamps for them.
|
||||
"""
|
||||
import argparse
|
||||
from collections import Counter
|
||||
from functools import lru_cache
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from types import ModuleType, SimpleNamespace
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
import opendbc
|
||||
@@ -30,24 +25,6 @@ from openpilot.selfdrive.controls.lib.ford_path import _model_path
|
||||
|
||||
|
||||
PINNED_OPENDBC = '72a775d35e54c21ff5c5798acef22016eedcc0a7'
|
||||
V1_REVISION = '5fc16abc7662020706e29f57d31a6d5e2bc1293a'
|
||||
V2_REVISION = '744a97d9bc08d8743b250eceff7c88585b5480de'
|
||||
V3_REVISION = '01f8d51c82b3e863f1012d383b5994813ef01b81'
|
||||
V4_REVISION = '7e63449749d112f096c56cb848dd289054e5f85b'
|
||||
|
||||
|
||||
@lru_cache(maxsize=4)
|
||||
def load_controller(commit):
|
||||
"""Load exact archived Python source for offline comparisons, never production."""
|
||||
if len(commit) != 40 or any(c not in '0123456789abcdef' for c in commit):
|
||||
raise ValueError('A full immutable commit hash is required')
|
||||
filename = 'openpilot/selfdrive/controls/lib/ford_model_action.py'
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
source = subprocess.check_output(['git', '-C', str(root), 'show', f'{commit}:{filename}'])
|
||||
module = ModuleType(f'ford_model_action_{commit}')
|
||||
exec(compile(source, f'{commit}:{filename}', 'exec'), module.__dict__)
|
||||
module.source_sha256 = hashlib.sha256(source).hexdigest()
|
||||
return module
|
||||
|
||||
|
||||
def revision(directory):
|
||||
@@ -66,20 +43,6 @@ def table(raw, name):
|
||||
return dict(zip(raw[name+'_names'], raw[name].T, strict=True))
|
||||
|
||||
|
||||
def extract_models(raw):
|
||||
clock_fields = ('model_position_t', 'model_orientation_t')
|
||||
if any(name not in raw for name in clock_fields):
|
||||
raise ValueError('Current replay requires original model_position_t and model_orientation_t; ' +
|
||||
'use the archived replay revision for older extracts, or re-extract the original rlogs with both clocks')
|
||||
paths = raw['model_paths']
|
||||
if paths.ndim != 3 or paths.shape[1] != 4 or len(paths) == 0:
|
||||
raise ValueError('Expected nonempty model_paths with four arrays per model')
|
||||
if any(raw[name].shape != (len(paths), paths.shape[2]) for name in clock_fields):
|
||||
raise ValueError('Model clocks must match the extracted model and point counts')
|
||||
return [SimpleNamespace(position=SimpleNamespace(x=p[1], y=p[2], t=pt), orientation=SimpleNamespace(z=p[3], t=ht))
|
||||
for p, pt, ht in zip(paths, raw[clock_fields[0]], raw[clock_fields[1]], strict=True)]
|
||||
|
||||
|
||||
def sample(stream, query, *, nearest=False):
|
||||
if len(stream['t']) == 0 or np.any(np.diff(stream['t']) < 0):
|
||||
raise ValueError('Replay requires nonempty streams in original timestamp order')
|
||||
@@ -140,10 +103,10 @@ def run(directory, output):
|
||||
raise ValueError('Output must be outside the source route directory')
|
||||
dependency = verify_dependency()
|
||||
with np.load(directory/'route.npz', allow_pickle=False) as raw:
|
||||
models = extract_models(raw)
|
||||
streams = {name: table(raw, name) for name in ('controls', 'cs', 'cc', 'model', 'params', 'path')}
|
||||
if len(raw['maneuver']):
|
||||
raise ValueError('This extract cannot identify the selected maneuver service per cycle; use the integration tests for that source')
|
||||
models = [SimpleNamespace(position=SimpleNamespace(x=p[1], y=p[2]), orientation=SimpleNamespace(z=p[3])) for p in raw['model_paths']]
|
||||
with np.load(directory/'encoder_comparison.npz', allow_pickle=False) as archive:
|
||||
baseline = {key: archive[key] for key in ('t', 'valid', 'action_heading')}
|
||||
with np.load(directory/'pose_candidate/pose_replay.npz', allow_pickle=False) as pose:
|
||||
@@ -161,8 +124,7 @@ def run(directory, output):
|
||||
(cs['valid'] == 1) & (cs['can_valid'] == 1) & (params['valid'] == 1) &
|
||||
(t-params['t'] >= 0.) & (t-params['t'] <= .15) & exact & (model['valid'] == 1))
|
||||
dt = np.r_[.01, np.diff(t)]
|
||||
archived = load_controller(V1_REVISION)
|
||||
core, entry_clock_core, adapter, wire = archived.ModelActionController(), ModelActionController(), FordModelActionController(), WireCheck()
|
||||
core, entry_clock_core, adapter, wire = ModelActionController(), ModelActionController(), FordModelActionController(), WireCheck()
|
||||
commands = np.zeros((len(t), 4))
|
||||
adapted = np.zeros_like(commands)
|
||||
valid = np.zeros(len(t), bool)
|
||||
@@ -181,10 +143,11 @@ def run(directory, output):
|
||||
adapter_valid[i] = new_gate.valid
|
||||
reasons[adapter.diagnostics['status']] += 1
|
||||
wire.check(new_gate)
|
||||
# Current core receives actual yaw and a fresh 10 ms engagement tick.
|
||||
# Isolate the adapter's fresh 10 ms engagement tick from the archived
|
||||
# harness, which used the preceding publication interval even on engage.
|
||||
entry_dt = dt[i] if i > 0 and baseline['valid'][i-1] else .01
|
||||
expected_adapter = entry_clock_core.update(selected_model, controls['desired'][i], speed=cs['speed'][i], dt=entry_dt,
|
||||
yaw_rate=cs['yaw'][i], active=bool(baseline['valid'][i]))
|
||||
active=bool(baseline['valid'][i]))
|
||||
assert new_gate == expected_adapter, f'Unexplained adapter difference at cycle {i}'
|
||||
np.testing.assert_array_equal(valid, baseline['valid'])
|
||||
np.testing.assert_array_equal(commands[:, :2], baseline['action_heading'])
|
||||
@@ -227,7 +190,7 @@ def run(directory, output):
|
||||
'calibration_approved': False, 'executes_live_selector': False, 'cycles': len(t),
|
||||
'core_active_cycles': int(valid.sum()), 'core_exact_archived_match': True, 'cohorts_reproduced': True,
|
||||
'adapter_active_cycles': int(adapter_valid.sum()), 'adapter_status_counts': dict(reasons),
|
||||
'adapter_matches_current_core_with_yaw_and_fresh_engagement_dt': True,
|
||||
'adapter_exact_match_with_fresh_engagement_dt': True,
|
||||
'core_active_path_shorter_than_7m_cycles': int(np.sum(valid & (coverage < 7.))),
|
||||
'adapter_validity_differs_from_archive_cycles': int(np.sum(adapter_valid != valid)),
|
||||
'adapter_command_differs_from_archive_cycles': int(np.any(abs(adapted-commands) > 1e-9, axis=1).sum()),
|
||||
@@ -236,8 +199,7 @@ def run(directory, output):
|
||||
'timing': 'Original controls publication timestamps proxy computation time; repeated frames and gaps retained. No identified delay.',
|
||||
'eligibility': 'Adapter checks recorded services independently; full SubMaster health is unavailable. Core uses archived validity.',
|
||||
'reference': 'Recorded controlsState.desiredCurvature, already selected/limited. These two routes have no maneuver publications.',
|
||||
'host_yaw': 'Extract cs.yaw equals -carState.yawRate; current adapter uses it for input-health checks and diagnostics only.',
|
||||
'archived_core_revision': V1_REVISION, 'archived_core_source_sha256': archived.source_sha256,
|
||||
'host_yaw': 'Extract cs.yaw already equals -carState.yawRate. Used only for inherited finite/range gate, never feedback.',
|
||||
'cohorts': cohorts, 'workspace_head': revision(root), 'opendbc_import_head': revision(dependency),
|
||||
'opendbc_import_path': str(dependency),
|
||||
'source_sha256': {str(p.resolve()): hashlib.sha256(p.read_bytes()).hexdigest() for p in sources}}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Deterministic numerical stress and exhaustive field-boundary CAN checks.
|
||||
|
||||
Analytic inclined lines check the shared model point; the slew oracle uses
|
||||
scalar arithmetic. Packing is checked against direct Float32/CAN packing of the
|
||||
continuous state, independently of host _packed.
|
||||
Analytic straight/rotated paths supply an independent y(7) oracle. The
|
||||
reference slew uses scalar arithmetic. Packing is checked against direct
|
||||
Float32/CAN packing of the continuous state, independently of host _packed.
|
||||
No synthetic plant is fitted or used to claim vehicle tracking performance.
|
||||
"""
|
||||
import argparse
|
||||
@@ -21,10 +21,10 @@ from opendbc.car.ford.fordcan import create_lat_ctl2_msg
|
||||
from tools.ford_pscm_lab.model_action_replay import PINNED_OPENDBC, WireCheck, verify_dependency, revision
|
||||
|
||||
|
||||
def line(offset, heading=0., speed=20.):
|
||||
def line(offset, heading=0.):
|
||||
s = np.linspace(0., 30., 33)
|
||||
return SimpleNamespace(position=SimpleNamespace(t=s/speed, x=s*math.cos(heading), y=offset+s*math.sin(heading)),
|
||||
orientation=SimpleNamespace(t=s/speed, z=np.full_like(s, heading)))
|
||||
return SimpleNamespace(position=SimpleNamespace(x=s*math.cos(heading), y=offset+s*math.sin(heading)),
|
||||
orientation=SimpleNamespace(z=np.full_like(s, heading)))
|
||||
|
||||
|
||||
def check_raw_packing(wire, controller, path):
|
||||
@@ -56,24 +56,21 @@ def run(cycles, seed, output, opendbc_revision=PINNED_OPENDBC):
|
||||
offset, heading = float(rng.uniform(-8., 8.)), float(rng.uniform(-1.2, 1.2))
|
||||
speed = float(rng.uniform(.3, 55.))
|
||||
desired = float(rng.uniform(-.15, .15))
|
||||
yaw = float(rng.uniform(-3., 3.))
|
||||
dt = dt_values[i % len(dt_values)]
|
||||
active = i % 137 != 0
|
||||
valid = i % 211 != 0
|
||||
if i % 307 == 0:
|
||||
dt = .101
|
||||
model, mirror = line(offset, heading, speed), line(-offset, -heading, speed)
|
||||
model, mirror = line(offset, heading), line(-offset, -heading)
|
||||
if i % 401 == 0:
|
||||
model.position.y[4] = mirror.position.y[4] = math.nan
|
||||
out = controller.update(model, desired, speed=speed, dt=dt, yaw_rate=yaw, active=active, valid=valid)
|
||||
other = mirrored.update(mirror, -desired, speed=speed, dt=dt, yaw_rate=-yaw, active=active, valid=valid)
|
||||
out = controller.update(model, desired, speed=speed, dt=dt, active=active, valid=valid)
|
||||
other = mirrored.update(mirror, -desired, speed=speed, dt=dt, active=active, valid=valid)
|
||||
expected_valid = active and valid and dt <= .1 and i % 401 != 0
|
||||
assert out.valid == other.valid == expected_valid
|
||||
previous = np.array([c0, c1])
|
||||
if expected_valid:
|
||||
station = min(30., max(7., speed))
|
||||
target = (max(-5.11, min(5.11, offset+station*math.sin(heading))), max(-.5, min(.5, heading)))
|
||||
# Independent scalar slew oracle. Valid measured yaw cannot alter demand.
|
||||
target = (max(-5.11, min(5.11, offset+7.*math.sin(heading))), max(-.5, min(.5, max(7., speed)*desired)))
|
||||
c0 += max(-4.*dt, min(4.*dt, target[0]-c0))
|
||||
c1 += max(-.5*dt, min(.5*dt, target[1]-c1))
|
||||
step = abs(np.array([controller.c0, controller.c1])-previous)
|
||||
@@ -102,14 +99,12 @@ def run(cycles, seed, output, opendbc_revision=PINNED_OPENDBC):
|
||||
selected = float(np.clip(scalar, low, high))
|
||||
offset, heading = (selected, 0.) if field == 0 else (0., selected)
|
||||
controller.c0, controller.c1 = offset, heading
|
||||
out = controller.update(line(offset-20.*math.sin(heading), heading), heading/20., speed=20., dt=.01)
|
||||
out = controller.update(line(offset), heading/20., speed=20., dt=.01)
|
||||
check_raw_packing(wire, controller, out)
|
||||
boundary_cases += 1
|
||||
report = {'seed': seed, 'random_cycles': cycles, 'mirrored_core_updates': cycles,
|
||||
'invalid_or_inactive_resets': resets, 'field_boundary_cases': boundary_cases,
|
||||
'float32_can_round_trips': wire.count, 'analytic_targets_scalar_slew_and_mirror_checks_pass': True,
|
||||
'valid_yaw_does_not_affect_targets_checked': True,
|
||||
'shared_model_point_checked': True,
|
||||
'direct_raw_float32_packing_matches_host_output': True, 'max_continuous_step_c0_c1': max_continuous_step.tolist(),
|
||||
'calibration_approved': False, 'scope': 'Numerical construction only; no PSCM response or closed-loop performance claims.',
|
||||
'opendbc_import_head': revision(dependency),
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import hashlib
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from openpilot.selfdrive.controls.lib.ford_path import FordPath
|
||||
from tools.ford_pscm_lab import model_action_replay as replay
|
||||
from tools.ford_pscm_lab import damping_replay
|
||||
|
||||
|
||||
def test_service_sampling_keeps_original_gaps_and_never_pulls_future_inputs():
|
||||
@@ -28,35 +25,6 @@ def test_dependency_mismatch_fails_before_replaying(monkeypatch):
|
||||
replay.verify_dependency()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('revision', ['HEAD', '744a97d9b', '--help', 'z'*40])
|
||||
def test_archived_controller_requires_an_immutable_commit(revision):
|
||||
with pytest.raises(ValueError, match='immutable'):
|
||||
replay.load_controller(revision)
|
||||
|
||||
|
||||
def test_archived_loader_uses_exact_source_and_records_its_hash(monkeypatch):
|
||||
# Unit tests must also work in shallow checkouts. Full replays verify real archived code.
|
||||
calls = []
|
||||
source = b'archived_value = 42\n'
|
||||
|
||||
def read_source(command):
|
||||
calls.append(command)
|
||||
return source
|
||||
|
||||
monkeypatch.setattr(replay.subprocess, 'check_output', read_source)
|
||||
replay.load_controller.cache_clear()
|
||||
try:
|
||||
for commit in (replay.V1_REVISION, replay.V2_REVISION, replay.V3_REVISION, replay.V4_REVISION):
|
||||
module = replay.load_controller(commit)
|
||||
assert module.archived_value == 42
|
||||
assert module.source_sha256 == hashlib.sha256(source).hexdigest()
|
||||
assert calls[-1][-2:] == ['show', f'{commit}:openpilot/selfdrive/controls/lib/ford_model_action.py']
|
||||
assert replay.load_controller(commit) is module
|
||||
assert len(calls) == 4
|
||||
finally:
|
||||
replay.load_controller.cache_clear()
|
||||
|
||||
|
||||
def test_explicit_stress_dependency_still_requires_an_exact_match(monkeypatch):
|
||||
monkeypatch.setattr(replay, 'revision', lambda _: 'deployment_commit')
|
||||
replay.verify_dependency('deployment_commit')
|
||||
@@ -71,32 +39,6 @@ def test_source_route_directory_cannot_be_overwritten(tmp_path):
|
||||
replay.run(tmp_path, tmp_path/'selected_controller')
|
||||
|
||||
|
||||
def test_current_replay_rejects_missing_model_clocks_before_comparing_outputs(tmp_path, monkeypatch):
|
||||
source = tmp_path/'source'
|
||||
source.mkdir()
|
||||
np.savez(source/'route.npz', model_paths=np.zeros((1, 4, 33)))
|
||||
monkeypatch.setattr(replay, 'verify_dependency', lambda: tmp_path)
|
||||
with pytest.raises(ValueError, match='requires original model_position_t and model_orientation_t'):
|
||||
replay.run(source, tmp_path/'output')
|
||||
assert not (tmp_path/'output').exists()
|
||||
|
||||
|
||||
def test_extract_models_preserves_both_actual_clocks_and_rejects_count_mismatch():
|
||||
clocks = np.array([[0., .3, 1.7]])
|
||||
raw = {'model_paths': np.zeros((1, 4, 3)), 'model_position_t': clocks, 'model_orientation_t': clocks+.01}
|
||||
model, = replay.extract_models(raw)
|
||||
np.testing.assert_array_equal(model.position.t, clocks[0])
|
||||
np.testing.assert_array_equal(model.orientation.t, clocks[0]+.01)
|
||||
raw['model_orientation_t'] = clocks[:, :2]
|
||||
with pytest.raises(ValueError, match='must match'):
|
||||
replay.extract_models(raw)
|
||||
|
||||
|
||||
def test_historical_damping_replay_rejects_current_controller(tmp_path):
|
||||
with pytest.raises(ValueError, match='historical damping replay requires unchanged C1'):
|
||||
damping_replay.run(tmp_path/'source', tmp_path/'output', candidate_version='current')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('field,value', [(0, 5.12), (1, .501), (2, .00002), (3, .000001), (0, np.nan)])
|
||||
def test_field_validation_catches_range_and_zero_c2_c3_violations(field, value):
|
||||
command = np.zeros((2, 4))
|
||||
|
||||
Reference in New Issue
Block a user