Validate the write path against a scanner, and match .NET's doubles - #6
Merged
Conversation
Everything this package wrote had been checked against what the console
writes, never against what a loader accepts -- a different claim, and the
one that mattered. Five archives were built with one mapped parameter
changed per scan, 41 in all, and loaded on an XA60 system. Every scan
loaded and every value survived: SHA-1 addressing, our DEFLATE, sparse
array insert and delete, bit packing, enum encoding, unit scaling, the
per-slice arrays and the container rewiring are now tested rather than
assumed. The scanner's own PDF prints the values back.
What the returns then taught was worth as much as the pass.
Not one ASCCONV field differed between what we sent and what came back,
across 1778 keys per protocol -- so the writes were already canonical and
nothing was normalised. The churn fields did not move either, which
refines the model: tCheckUUID, the GUID leading sWipMemBlock.tFree and the
date inside sSpecPara.lFinalMatrixSize* are regenerated when a protocol is
edited on the console, not when one is loaded and exported again.
The single remaining divergence was one double in a 116 KB payload:
2.893620014190674 from Python against .NET's 2.8936200141906738, the same
value spelled two ways. That could be documented but not fixed without
ground truth, since no amount of reading says which legal spelling a
foreign serializer chose; a scanner re-saving our own edited protocols
supplied it. .NET writes fifteen significant figures where they round-trip
and seventeen where they do not, and envelope.dotnet_double now does the
same: all 596 content blobs across every console-authored archive
re-encode to their stored bytes and hash back to their stored address,
where repr missed one. json.dumps has no hook for float formatting, so
floats travel through the encoder as marked strings and are substituted
after -- the marker has to be printable, because control characters are
escaped whatever ensure_ascii says.
Also fixes apply() resolving an ambiguous step name to an arbitrary scan.
These archives repeat one sequence with an option varied per copy, so a
dozen scans share a name, and the caller believed it had named one. It now
refuses and says how many matched. Found while building the load test,
which drove the API the way a caller does rather than the way the tests
did.
Adds sequence_stamp: sWipMemBlock.tFree carries the CMRR build behind the
GUID that churns -- R017, its branch, commit and build time -- and only
the GUID is churn. Treating the whole field as churn, as this file did,
would discard the only record of which binary wrote a protocol. That
matters because the Special card can change between builds and nothing
would announce it, so a mapping is only verified for the build it came
from.
The scanner's returns ship as examples/XA60/*_loadtest.{exar1,pdf} beside
their sources. Corpus is 52 snapshots and 984 scans; the pinned unaccounted
set gains the same five MGH sequences under the names the load-test
generator gave them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everything this package wrote had been checked against what the console
writes, never against what a loader accepts. Those are different claims,
and the second is the one the whole
.exar1effort rests on.The test
Five archives, built from the shipped examples with one mapped parameter
changed per scan -- 41 scans in all, covering every entry in
MAPPINGSincluding the whole Special card. Loaded on an XA60 scanner, then saved back.
Nothing dropped, clamped or reset. That exercises SHA-1 content addressing,
our DEFLATE, sparse-array insert and delete, bit packing into
alFree[0],enum encoding, ms-to-us scaling, the per-slice arrays, the derived basis and
the container rewiring. The scanner's own PDF prints the values back, which
closes the loop independently of the archive reader.
What the returns taught
The writes were already canonical. Not one ASCCONV field differs between
what we sent and what came back, across 1778 keys per protocol. Nothing was
normalised.
Import is not the same as editing. No churn field moved either.
tCheckUUID, the GUID leadingsWipMemBlock.tFreeand the date hidden insidesSpecPara.lFinalMatrixSize*are regenerated when a protocol is edited on theconsole, and left alone when one is merely loaded and exported again. The
churn list came from an edited pair, so it describes the wider case.
The last divergence, now closed. Fifteen protocols differed in content hash
while no ASCCONV value did. The cause, across a 116 KB payload, was one double:
That had been documented but not fixable, because no amount of reading says
which of two legal spellings a foreign serializer chose -- it needs a sample of
that serializer's output for a value you picked. A scanner re-saving our own
edited protocols is exactly that sample.
envelope.dotnet_doublenowreproduces 596 of 596 content blobs across every console-authored archive
byte for byte, each hashing back to its stored address;
reprmissed one.json.dumpsoffers no hook for float formatting, so floats travel through theencoder as marked strings and are substituted after. The marker has to be
printable: control characters are escaped whatever
ensure_asciisays.Two other fixes
apply()resolved an ambiguous step name to an arbitrary scan. Thesearchives repeat one sequence with an option varied per copy, so a dozen scans
share a name and the caller believes it has named one. It now refuses and says
how many matched. Found while building the load test, which drove the API the
way a caller does rather than the way the tests did.
sequence_stamp.sWipMemBlock.tFreecarries the CMRR build behind theGUID that churns --
R017, its branch, commit and build time -- and only theGUID is churn. Treating the whole field as churn, as CLAUDE.md did, would
discard the only record of which binary wrote a protocol. That matters because
the Special card can change between builds and nothing would announce it, so a
mapping is only verified for the build it came from. Note what the stamp does
not say: it reads
R017whether the binary was 017pre15 or a later 017, so thecommit and build time are what identify one exactly.
Corpus
The scanner's returns ship as
examples/XA60/*_loadtest.{exar1,pdf}besidetheir sources, and
test_exar_patch.pyasserts no scan went missing and thatevery ASCCONV field differing from the source is one a mapping writes. The
archives that were sent are under
loadtest/with a manifest saying what eachscan changes.
52 snapshots, 984 scans. The pinned unaccounted set gains the same five MGH
sequences under the names the load-test generator gave them.
Verification
Still open
No archive has been assembled -- new scans, new instances, new changesets.
Every file here edits an existing export in place, which is what isolates "does
the loader accept our values" from "does the loader accept a tree we built".
The first is now answered; the second is what a PDF-to-archive generator needs.
🤖 Generated with Claude Code