Skip to content

Q20: L3/L4 discrimination, hardpoint wiring, kinematics_attached (trace schema 0.4) - #14

Merged
Onlyti merged 5 commits into
mainfrom
feat/l4-kinematics-provenance
Sep 22, 2026
Merged

Onlyti merged 5 commits into
mainfrom
feat/l4-kinematics-provenance

Conversation

@Onlyti

@Onlyti Onlyti commented Sep 22, 2026

Copy link
Copy Markdown
Owner

Q20 — L3/L4 discrimination, hardpoint wiring, kinematics_attached (schema 0.4)

Decisions: 18_dev_briefing_0903 §51 / §53, 04 decisions 101–103 and 107–109.

What changes

Item Change
(i) Resident tests prove the level label carries no physics: bare L3 == bare L4 and L3+hardpoints == L4+hardpoints (bit-identical). Attaching hardpoints is the only discriminator.
(ii) vdsim_lab._build_session() is the single seam for Experiment / Simulation / Sim; resolve_susp_kinematics + attach_kinematics; bindings attach_{front,rear}_kinematics_from_yaml. Missing YAML → FileNotFoundError, non-L3/L4 → RuntimeError. Provenance records stems only.
(iii) Trace schema 0.4: kinematics_attached required. 0.4 missing → TraceSchemaError; 0.3 and older → one warning, value unknown (never False). golden_v0_3.vdtrace untouched, golden_v0_4.vdtrace added. Value comes from the attach return, not from YAML presence.
H1 _build_session() refuses level="L4" with nothing attached (ValueError, message names kin=… / level='L3').
(v) FORBIDDEN_AXES emptied — level axis re-enabled now that H1 blocks false labels.
(A) VDSimPlant(level="L4") refused with the same leading reason.
(B) Experiment.from_config reads kinematics: {front, rear} via resolve_susp_kinematics (no copy).
(C) README / README.ko schema text updated to 0.4.

(iv) cosim attach_susp_parts silent downgrade is not in this PR (separate PR, decision 109).

Registration surface and expected count

  • Branch synced with main by merge (no rebase, decision 98). Merge commit 6e2592f, parents 00b1401 / daa2a80.
  • Registration-surface diff vs main (CMakePresets.json, all CMakeLists.txt, cmake/**): 0 files.
  • One new gtest discovered via gtest_discover_tests → expected registered count 506 (main canonical 505 + 1).
  • docs/VALIDATION.md currency is PENDING; filled only after 3 consecutive equal CI runs on a fixed head.

Local evidence (candidate values, not canonical)

  • ctest -N: Total Tests: 506. Serial full run ×2: 100% tests passed, 0 tests failed out of 506.
  • H1 negative check: guard removed → rc=1, restored → rc=0. (A)/(B) negative checks likewise.

Out-of-scope observation (not fixed here)

With ctest -j8, import_part_pack failed once because tests/scripts/test_part_store.py creates and deletes a temporary part under the shared configs/catalog/packages/gui_custom/ tree while another test lists the catalog. The canonical validation test preset runs serially, so the canonical measurement is unaffected. Reported for a separate decision.

🤖 Generated with Claude Code

Onlyti and others added 5 commits September 22, 2026 16:03
…_lab

KinematicFourteenDOFDynamics overrides level() and nothing else, and
attach_front/rear_kinematics dynamic_cast to the shared base, so the level
label never discriminates: bare L4 is bit-identical to bare L3, and L3 with
hardpoints is bit-identical to L4 with hardpoints.  The attach is the
discriminator.  Four resident checks in test_experiment_api keep that fact
measured rather than remembered.

vdsim_lab could not attach at all (0 call sites), so Experiment/Simulation/Sim
went through one new seam, _build_session(), which resolves a hardpoint
reference the same way cosim/realtime_server.cpp resolve_susp_yaml() does and
refuses instead of skipping: a missing YAML raises FileNotFoundError, a
non-Ld3 level raises RuntimeError.  Two bindings accept native hardpoint YAML
(the existing pair only took a lookup CSV, and no CSV assets exist).

H2 came out the opposite way from reading the source: world_scenario.cpp only
parses an explicit front_susp key, but load_scene() materializes blueprints
through python/catalog/materialize.py, which does carry front_chassis /
rear_chassis across.  configs/scenes/l4_sedan_kinematics.yaml is therefore a
backed L4, not a false label, and the new gtest pins that.

docs/VALIDATION.md goes back to PENDING: the registered count moves 505 -> 506.
Schema 0.4 adds one required manifest field, kinematics_attached. It follows
the 0.1 -> 0.2 role precedent: SCHEMA_VERSION 0.4, READABLE adds 0.4, a 0.4
manifest without the field (or with a non-bool) is a TraceSchemaError, and a
0.1-0.3 trace reads as unknown (None, never False) with one warning raised on
first access. TraceWriter takes it as a keyword-only argument with no
default. The 0.3 gates now apply to 0.3 and later instead of == "0.3".

The value comes from what the attach returned. Experiment therefore stages
its writer in enable_trace() and opens it in run() once _build_session() has
attached; finalize_trace() before run() raises instead of inventing a state.
VDSimPlant has no attach call and records false.

golden_v0_3.vdtrace is untouched and its maker now refuses to run past 0.3.
golden_v0_4.vdtrace re-declares the same channels, sample for sample, under
the 0.4 writer with kinematics_attached false.

H1: _build_session() raises ValueError for level L4 with nothing attached,
naming kin=... and level='L3'. It is the seam every vdsim_lab path shares, so
traced and untraced runs are covered alike. L3 with hardpoints stays legal.
The bare-L3 == bare-L4 fact is still measured, one layer down on the raw
session factory, and cross-checked against Sim so the probe cannot drift.

C-1 (v): with bare L4 refused at the seam, the campaign level axis is no
longer forbidden. FORBIDDEN_AXES is empty; a bare L4 run in a level sweep is
an error row with no trace. Campaign docs and mu_sweep.yaml say so.

No new ctest target: the checks live in trace_schema_0_3, experiment_api and
campaign_runner. Registration surface untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…README 0.4

(A) VDSimPlant has no hardpoint input, so level='L4' raises ValueError with
    the same leading reason as vdsim_lab._build_session; a test compares them.
(B) Experiment.from_config reads kinematics: {front, rear} and hands it to
    Experiment.kinematics(); resolution stays in resolve_susp_kinematics().
    Campaign regression: an L3 cell and an L4 cell with hardpoints both run
    and their channels are not bit-identical.
(C) README / README.ko: the writer emits schema 0.4; document
    kinematics_attached and the L4 refusals.

No new ctest target.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ovenance

# Conflicts:
#	docs/VALIDATION.md
#	tests/scripts/test_experiment_api.py
@Onlyti
Onlyti merged commit 1f056fe into main Sep 22, 2026
5 checks passed
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.

2 participants