Skip to content

Say what a vector quantity's bytes are - #194

Merged
matt-edmondson merged 2 commits into
mainfrom
claude/blissful-euler-fzuap2
Sep 14, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
claude/blissful-euler-fzuap2

Conversation

@matt-edmondson

@matt-edmondson matt-edmondson commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

The reflection table's representation says what the bytes of a member are, and for a quantity it said the number the quantity is stored in. For a magnitude that is right — a Mass over a float is a float. For a vector form it is false: a Velocity3D over a float is three floats, which is a Vector3.

False in the way that reads as true, which is what makes it a fix rather than a note.

How it goes wrong

A consumer walking the table asks the representation how many lanes a member has. Holotype's validator does exactly that:

case TypeKind::Vector2:
case TypeKind::Vector3:
case TypeKind::Vector4: {
    const std::uint8_t count = detail::lanes<TypeKind>(member.representation);
    const std::uint32_t lane_size = member.size / count;

Told Float, a Velocity3D never reaches that arm at all — it takes the scalar one, which reads the member's first four bytes as the whole value and checks that against the range while the other two components go unexamined. Its network codec's "is this numeric" test has the same shape. A check that quietly examines a third of what it was given is worse than one that declines.

I found this pinning Holotype's reflection_test.cpp, which asserts kind and representation for each member — the assertions this would have silently changed the meaning of.

The fix

A quantity of two, three or four components reports Vector2, Vector3 or Vector4; one of zero or one reports its storage. The arity comes from QuantityRegistry, which reads it off the IVectorN the quantity implements, so it cannot disagree with the type it describes.

kind is unaffected — all of them are still Quantity. That is the whole point of the table carrying both: kind is what the schema said, representation is what the bytes are.

Tests

QuantityCppTests gains TheTableSaysWhatAQuantitysBytesAre, which generates a class holding one quantity of each arity and asserts that all of them are kind = Quantity while the representations differ — so the distinction is pinned rather than implied.

Every arity is covered rather than a sample of them, and every one is a real quantity rather than defensive code: the vocabulary has 148 magnitudes, 27 signed scalars, and 8, 22 and 7 quantities of two, three and four components, so Mass, Heading, Velocity2D, Velocity3D and Velocity4D reach all four arms of the mapping.

That second commit is what took coverage on new code from 75% to 100%, which is what SonarCloud's gate first failed on — but the arms were worth covering on their own account, since the rule the table states is only half-stated by asserting two of its four cases. It is verified by regression rather than by the gate: with the Vector2 arm returning the storage instead, Drift reports Float and the test fails.

Schema.Cpp.Test is 102 of 102 locally, and CI is green on all three platforms.

Context

This is the third in the chain behind matt-edmondson/Holotype#16, which merged and left Holotype's main red. Holotype needs a kschema with this in it before its reflection_test can assert anything true, so I would rather it pin one version than pin 1.34.1 and then have to change the assertions again.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UGHDsYaaTQdzVR4XBR6miu


Generated by Claude Code

The reflection table's `representation` says what the bytes of a member
are, and for a quantity it said the number the quantity is stored in. For
a magnitude that is right: a Mass over a float is a float. For a vector
form it is false - a Velocity3D over a float is three floats, which is a
Vector3.

False in the way that reads as true, which is what makes it worth a fix
rather than a note. A consumer walking the table asks the representation
how many lanes a member has. Told Float, Holotype's validator takes a
Velocity3D's first four bytes for the whole value and checks one
component against the member's range while the other two go unexamined -
and its network codec's numeric test has the same shape. A check that
quietly examines a third of what it was given is worse than one that
declines.

So a quantity of two, three or four components reports Vector2, Vector3
or Vector4, and one of zero or one reports its storage. The arity comes
from QuantityRegistry, which reads it off the IVectorN the quantity
implements, so it cannot disagree with the type it describes.

The `kind` is unaffected: all of them are still declared Quantity. That
is the whole point of the table carrying both - kind is what the schema
said, representation is what the bytes are.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGHDsYaaTQdzVR4XBR6miu
SonarCloud's quality gate failed the branch at 75% coverage on new code
against a required 80%. The two uncovered lines were the `Vector2` and
`Vector4` arms of `QuantityRepresentation`, which the test reached
neither of.

Covering them is worth more than the gate. The rule the table states is
that a quantity's representation is the shape of its bytes, and the test
asserted it for a magnitude and a three-component vector -- a sample of
the rule, from which the other two arms have to be inferred. All four
arities are real: the vocabulary has 148 magnitudes, 27 signed scalars,
and 8, 22 and 7 quantities of two, three and four components, so
`Velocity2D` and `Velocity4D` pin the remaining arms with types a schema
may actually name rather than with defensive code.

Verified by regression rather than by the gate: with the `Vector2` arm
returning the storage instead, `Drift` reports `Float` and the test
fails. 102 of 102 pass with the arm restored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGHDsYaaTQdzVR4XBR6miu
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit d303d31 into main Sep 14, 2026
13 checks passed
@matt-edmondson
matt-edmondson deleted the claude/blissful-euler-fzuap2 branch September 14, 2026 11:25
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