Skip to content

fix: cg_height is the whole-vehicle CG on the L5 strut and DAE path - #37

Open
Onlyti wants to merge 7 commits into
mainfrom
feat/cg-height-vehicle
Open

Onlyti wants to merge 7 commits into
mainfrom
feat/cg-height-vehicle

Conversation

@Onlyti

@Onlyti Onlyti commented Nov 7, 2026 •

Copy link
Copy Markdown
Owner

cg_height now means the whole-vehicle CG height (sprung + unsprung) on the L5 spatial-strut and corner-DAE path, the same meaning L1-L4 and the L5 penalty path already use. The strut body (sprung mass only) sits at the derived h_s = (m·h − Σm_u·R)/(m − Σm_u); wheel mounts, the spawn settle and the aero arm origin follow it. On the same path the tire longitudinal force now reacts on the wheel spin about the wheel-centre height R0 − penetration instead of the effective rolling radius (scope extension, below). cg_height_ref: sprung restores the whole 0.8.1 strut-path behaviour. Breaking for every strut-path run.

Gates (work order 47 sec. 3, fixed before measurement)

Commit a7bd99f fixes every gate and the R2 expectation (h = cg_height on every path) before the core change in dd870ec.

ID Result (local, HEAD dd870ec / a3eebaa)
G1 R2 re-judgement at dd870ec: accel front +3.34 % / rear +2.88 %, brake front +1.33 % / rear +1.68 % — outside +/-2 % for accel. Re-judged after the reaction-arm fix as H1 (below): passes
G2 static system CG height, z_sys − (cg_height − δ̄)
G3 L1-L4 + L5 penalty bit-identical to 645780c 10/10 serialized runs identical (generic_sedan, fs_aero_example); RL pedal/accel obs+reward digests identical; closed-loop deterministic arrays identical (only the wall-clock t_compute_s / compute_* timing arrays differ, as between any two runs)
G4 cg_height_ref: sprung bit-identical on L5 strut / DAE 4/4 serialized runs identical to 645780c
G5 0.8.1 release tests + drag height on the strut body R1, R6, A3 L5, DAE warning tests pass; new drag-height test (in-air one-step pitch moment, ±1 mm) 8/8 within ±0.09 mm
G6 negative: h_s derivation removed G2 fails 4/4 (−24.0 / −23.8 / −7.15 / −7.12 mm), restored passes 4/4
G7 key tests C++ CgHeightRefKeys.* 6 tests; Python cg_height_ref 1 ctest (20 checks)
G8 same-head CI ×3 → currency commit → final run + wheels (below)

G1 note: on the strut path without a corner model unsprung resolves like sprung, so sprung↔unsprung are bit-identical by design; the ≤ 1 % sub-gate has no discriminating power there.

Scope extension: strut-path reaction arm (user decision 11-07, 0.8.2 R2 handling option 3)

R2 has to pass before 0.8.2 ships. The wheel-spin reaction T_react = Fx · arm uses arm = R0 − penetration on the strut / DAE path; the penalty path, slip, the tire model and every other use of the effective rolling radius are unchanged. cg_height_ref: sprung keeps the old arm so G4 stays bit-identical. Commit order: 0630ee6 gate + arm helper and unit tests (core unchanged) → f2e16b9 core → 32459a2 R2 back in the verdict → ceb1c4c docs, currency PENDING.

ID Gate (fixed before measurement) Result (local, f2e16b9 / ceb1c4c)
H1 R2 4 combos (strut, no DAE) per axle ±2 %, sprung↔unsprung ≤ 1 % of ΔFz accel front +0.57 % / rear +0.13 %; brake front −1.60 % / rear −1.26 % (margin 0.40 / 0.74 %p); sprung↔unsprung 0
H2 L1-L4, L5 penalty bit-identical to 645780c, RL digests 10/10 identical; RL pedal/accel identical; closed-loop deterministic arrays identical
H3 cg_height_ref: sprung bit-identical to 645780c (strut / DAE × 2 cars) 4/4 identical
H4 existing gates R1, R6, A3 L5, R5, DAE warning, G2, G5 targeted 70/70 pass; canonical 615/615 (H6)
H5 negative arm fix reverted → R2 accel fails, restored → passes reverted: R2 accel FAILED (+3.34 %), brake passes; restored: both pass
H6 canonical full local validation 615/615

Unit: L5ReactionArm.* 3 tests (strut → R0 − penetration; penalty and sprung → Re).

Information (not gates):

  • Same drive / brake command on the strut path now gives about 5 % more longitudinal acceleration in the R2 manoeuvre (accel a_g 1.142 → 1.196, brake −1.525 → −1.602 m/s²), because the same torque now meets a shorter arm. Penalty path and L1-L4 unchanged (H2).
  • Acceleration-only residual term (decomposition of the 1st reply): −1.38 % before, −1.18 % after the arm fix — still present, cause unknown. Braking rest −0.11 % → −0.25 %.

Changed after measurement

  • L5Strut.SettlesToStaticEquilibrium: upper bound cg_height + 5 mm → h_s + 5 mm, derived from the new definition (PO 1st verdict item 4), not from a measured value.
  • Scope extension after the G1 measurement (user decision 11-07): the strut-path reaction arm fix above. H1-H6 were fixed before its measurement; gate and R2 expectation unchanged.
  • L5Reaction.R2_* moved out of label known_failure into the integration suite; the vdsim_known_failure_tests executable, which held only these two, is removed.

Interfaces

  • Public: VehicleParams::cg_height_ref, validate_cg_height_ref, cg_height_ref_is_sprung, l5_strut_body_height (params.hpp). The Python binding exposes the field.
  • Not public: the spawn settle with an explicit body height is internal (core/src/spawn_settle_internal.hpp); the reaction-arm helper is internal (core/src/free_3d_reaction_internal.hpp); settle_spawn_on_ground() and IContactProvider are unchanged. No environment variables.
  • cosim/realtime_server.cpp never enables the strut path, so it keeps the public settle.
  • Aero: drag stays at cg_height above ground (four-key) or aero_drag_height; elements keep their z. Only the arm origin moves to h_s on the strut path.
  • python/vdsim_render3d.py: optional trace geometry key body_cg_height_m; absent = unchanged. Smoke render once (no gate).

Documentation impact (W1)

CHANGELOG [0.8.2] (Breaking, Added, Fixed: reaction arm and R2 back in the verdict; the R2 known limitation is removed), params.hpp field comments, docs/theory/20_ld5_stunt.md (h, h_o, h_s, aero arm, strut reaction arm), docs/VALIDATION.md currency PENDING until the CI measurement, R2 exclusion lines removed.

Local verification

Canonical validation preset (ctest --preset validation --parallel 4) at ceb1c4c: 615/615 (main 595 + 20: C++ unit 9, C++ integration 10 incl. R2 ×2, Python 1). ctest -L known_failure: 0 tests. Doc audit at ceb1c4c: A1 version and A2 currency PENDING (intentional until the CI measurement) fail; main fails A1 only; links, nav, theory index, README, private strings pass.

CI measurement

PENDING: same-head complete successes ×3, currency confirmation, final run and wheels. Run 42379950076 (ceb1c4c) did not start — account billing lock; not counted, not retried. No merge, tag or publication performed.

🤖 Generated with Claude Code

Onlyti and others added 7 commits November 7, 2026 21:30
Add the cg_height_ref key (vehicle | sprung) with YAML, to_yaml and Python
plumbing, the derived sprung-body height helper, and the gates for the
whole-vehicle cg_height meaning on the L5 strut and DAE path: static system
CG height, drag height on the strut body, key round trip, rejection and the
single warning off the strut path. The R2 expectation uses cg_height on every
path. The core still places the strut body at cg_height in this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The strut and DAE body is the sprung mass alone, so its CG now sits at the
derived h_s and the wheel mounts, the spawn settle and the aero arm origin
follow it. The penalty path and L1-L4 keep the body origin at cg_height.
cg_height_ref sprung restores the previous meaning. The spawn settle gets the
body height through an internal overload, so the public contact interface is
unchanged. The 3D renderer reads an optional body_cg_height_m geometry key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Record the re-judged strut-path axle load transfer as a known limitation of
this release and mark the validation currency pending until the same-head
CI measurement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the reaction-arm helper for the wheel-spin balance with its unit tests: on
the L5 strut and DAE path with the whole-vehicle cg_height the tire force acts
at the wheel-centre height, while the penalty path and cg_height_ref sprung keep
the effective rolling radius. The R2 gate and expectation are unchanged and are
re-judged after this fix. The core still uses the rolling radius in this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the strut and DAE path the wheel is its own particle above the contact, so
the tire force reacts on the wheel spin about the wheel-centre height rather than
the effective rolling radius. Slip and the tire model keep the rolling radius.
The penalty path is unchanged, and cg_height_ref sprung keeps the previous arm so
it still reproduces the 0.8.1 strut-path behaviour as a whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rdict

L5Reaction.R2 passes its unchanged gate after the strut-path reaction arm fix,
so both tests move back into the integration suite and the known_failure
executable, which held only them, goes away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Note the wheel-centre reaction arm on the L5 strut and DAE path, what the
sprung reference reproduces, and the axle load transfer gate returning to the
verdict. The validation currency stays pending until the same-head CI run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant