This directory holds the on-disk compatibility contract of the SHiP data
model. Two independent safety nets are exercised by ctest (part of
pixi run test and CI):
- Compat read tests (
compat_read_<version>): every reference file must be readable with the current library, materialized into the current structs, with all values matching the canonical recipe. Members that did not exist in the writing version must read back default-initialized (ROOT RNTuple automatic schema evolution). - Schema snapshot (
schema_snapshot):schema_snapshot.txtis a text dump of the persistent schema (TClass layout of every dictionary class + the RNTuple field tree). CI fails on any schema change — even a backward-compatible one — until the snapshot is deliberately regenerated in the same PR, so every schema change is a conscious, reviewable decision.
reference_v<X.Y.Z>.root— frozen forever, never modified or regenerated. Written at release time byscripts/release.shfrom exactly the tagged code with the ROOT version pinned inpixi.lockat that moment, so it captures both the schema and the writing ROOT. Exception: the files for v0.1.0–v0.4.0 predate this suite and were backfilled withscripts/backfill_reference_files.sh— they were written by each tag's headers (defining the on-disk schema) but by ROOT 6.40.02, not the historical ROOT versions.reference_head.root— tracksmain; asserts "current code reads the current schema". Regenerated in the same PR as any event-model change.reco_fixture_v0.5.0.root— frozen like the release files, see Reconstruction fixture.schema_snapshot.txt— committed schema dump, see above.
Each reference file is an RNTuple named events with 2 entries and
top-level fields event_header (v0.4.0+), mcParticles, simHits,
simParticles, recParticles, simResult.
The reference files don't contain TrackFitResult or any detector wrapper,
so nothing in them reads those classes back. reco_fixture_v0.5.0.root
covers them for the v0.5.0 layout. It holds two RNTuples with 2 entries
each: trackfits, with a trackFitResults field, and wrappers, with a
ubtHits field (UBTHit stands in for all five wrappers, which share one
shape). tests/write_reco_fixture.cpp wrote it against the v0.5.0 headers
through scripts/backfill_reference_files.sh. It is frozen like the
reference files. compat_read_reco_trackfits_v0.5.0 and
compat_read_reco_wrappers_v0.5.0 read one RNTuple each, so a failure in
one class cannot hide the result for the other.
Since v0.5.0 every persistent class carries an explicit version, declared in
include/SHiP/LinkDef.h as options=version(N) (required for RNTuple I/O
customization rules, root-project/root#23146) — bump it together with any
layout change; the schema_snapshot test records versions, so forgetting is
visible in the diff. Numbering starts at 2, because rootcling already emits 1
for classes without ClassDef and TClass reports that back as -1. Files
from v0.1.0–v0.4.0 were written by unversioned classes.
-
schema_snapshotfails,compat_read_*pass: you changed the persistent schema in a backward-compatible way (e.g. added a member). If intentional, run and commit in the same PR:pixi run update-schema-snapshot pixi run update-reference-head
This is at least a minor version bump.
-
compat_read_v*fails: your change breaks reading of existing files (e.g. renaming a member silently drops its on-disk values — RNTuple matches members by name). Either make the change compatible (e.g. an I/O customization rule mapping the old name), or accept it as a breaking change: mark the commit!/BREAKING CHANGE(major version bump) and adjust the expectations intests/test_read_reference.cpp(masking table) — never by editing the frozen files. -
compat_read_headfails but frozen versions pass: the current schema andreference_head.rootare out of sync — runpixi run update-reference-head(plus the snapshot) in this PR.
Expected values are defined in tests/reference_values.hpp and are part of
the contract (a future non-C++ reader can check against the same formulas).
For members that existed at v0.1.0 they equal the SHiP::test::make*
generators in tests/test_utils.hpp; members added later have their own
formulas there (e.g. SimHit::geometryNodeId = 900 + 7*i + offset,
MCParticle::mothers = {} for i == 0 and {i - 1, (i + 1) % 3} otherwise
— its entries are indices into this same 3-element collection, so unlike the
other members they carry no offset; the recipe encodes the documented
invariant, namely that mothers[0] equals motherId and that an entry
without a mother has an empty list rather than a -1 in it —
RecParticle::hits filled from makeSimHits(offset + 2)). Per entry
e (0-based): field offsets are e for the top-level collections and
e + 5 inside simResult; each collection has 3 elements. A reference file
written by version V contains values for exactly the members existing in V;
newer members read back default-initialized and are masked accordingly in
test_read_reference.cpp.
The mother-index invariants the MCParticle recipe encodes are machine-checked
rather than merely asserted here: SHiP::mothersAreConsistent (declared beside
the struct in include/SHiP/MCParticle.hpp) is exercised against both recipes
by the mc_mothers test and against every file this suite reads, so a future
recipe change cannot quietly contradict them.