Generate the vector forms in the C++ projection - #215
Merged
Conversation
dimensions.json declares 122 dimension-and-form entries and the projection covered 72 of them. The other 50 are the vector forms, and a vocabulary that can say Speed but not Velocity3D is not much use to a renderer. 212 classes now: 148 magnitudes, 27 signed scalars, and 37 vectors of two to four components, plus the overloads that refine them. A vector form is a class of its own, not an alias for Vector3<Quantity<D>>: it holds its components, names them x through w, and is exactly its components in memory - sizeof is three floats, trivially copyable, standard layout - because Holotype copies one whole across a language boundary and onto the wire. Componentwise arithmetic is written out rather than looped, which is the fourth measured rule and the one nothing had yet obeyed because nothing had yet needed to. Obeying it costs a generator nothing, and that is worth writing down: a hand-written library spells Vector3<Q> once over every Q, so expanding rather than looping means an index-sequence fold and the machinery around it; a generator has the components in hand while it writes the class, so the expanded form is simply what there is to write. Each signed form answers magnitude() with the magnitude form of the same dimension, and the dimension works out rather than being arranged: the sum of the squares has twice a component's dimension and sqrt halves it again. That makes the bridge between the signed and unsigned halves of the vocabulary something the compiler checks. magnitude_squared() answers with a bare Quantity for the honest reason - the square of a dimension usually has no name, and where it has one it is not unique, since Area and NuclearCrossSection are the same exponents. A relationship reaches the vector forms by carrying its form on the left operand and the result, with the right operand staying a magnitude: Velocity3D * Duration -> Displacement3D. There is no reading in which the duration has three components. That is the rule the .NET generator already follows, and `forms` on a relationship constrains it - a cross product is declared at [3] because that is where a cross product exists. A fifth relationship is refused, and it is a second kind of refusal that the vector forms are what surfaced. dot(Force, Length) -> Energy is dimensionally true and still unkeepable: a force opposing a displacement does negative work, and a magnitude form cannot be negative, so the generated operator would build a type that fails its own assertion on an ordinary input. The message names the fix rather than guessing at it - Energy needs a vector1 form for the result to land in. One thing the exponents cannot check is left alone rather than quietly changed. Force x Length -> Torque emits as cross(Force3D, Displacement3D), which is F x r, and the convention is tau = r x F. A cross product and its negation have identical dimensions, so nothing here can tell them apart; which operand comes first is a claim the metadata makes and a physics call to change, the same as Sensitivity. Eleven more tests, and two of them are the ones that matter. The whole vocabulary still compiles clean under g++ and clang++ with -Wall -Wextra, and a componentwise product whose exponents disagree with its result must not compile - the case that would slip past a test which only looked at the shape, because the components can be expanded perfectly and the dimension still be wrong. A third asserts what the forms mean rather than that they parse, entirely in static_assert, so it needs no run: that the length of (3, 4, 0) is 5, that a velocity scaled by a duration lands in the right type and the right component, that an overload survives the trip out to its base and back. Two boundaries are deliberate. Arithmetic belongs to the signed forms and stops there: Length - Length has a question in it that Displacement3D - Displacement3D does not, which the .NET side settled as the absolute difference and which is a decision about the magnitude form rather than one to settle alongside the vectors. And a relationship is emitted in the direction the metadata declares it, so Duration * Velocity3D is not an overload; the .NET generator emits the commutative and inverse forms too, and matching it is a change to every form at once rather than part of this. The prelude gains abs, written out rather than calling std::abs, which is not constexpr before C++23. Nothing existing calls it, so the measured shapes are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UGHDsYaaTQdzVR4XBR6miu
|
This was referenced Sep 11, 2026
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.



dimensions.jsondeclares 122 dimension-and-form entries and the C++ projection covered 72 of them. The other 50 are the vector forms, and a vocabulary that can saySpeedbut notVelocity3Dis not much use to a renderer.212 classes, and each form is one of its own
148 magnitudes, 27 signed scalars, 37 vectors of two to four components — plus the overloads that refine them.
A vector form is a class, not an alias for
Vector3<Quantity<D>>. It holds its components, names themxthroughw, and is exactly its components in memory —sizeofis three floats, trivially copyable, standard layout — because Holotype copies one whole across a language boundary and onto the wire.Rule four, and why it is free here
Componentwise arithmetic is written out, not looped. That is the fourth of the four measured rules, and the one nothing had yet obeyed because nothing had yet needed to — a loop over a runtime subscript is what took the same spike from 1.01 to 4.51 on MSVC.
Obeying it costs a generator nothing, and that is worth writing down: a hand-written library spells
Vector3<Q>once over everyQ, so expanding rather than looping means an index-sequence fold and the machinery around it. A generator has the components in hand while it writes the class, so the expanded form is simply what there is to write.The dimension works out rather than being arranged
Each signed form answers
magnitude()with the magnitude form of the same dimension. The sum of the squares of the components has twice a component's dimension andsqrthalves it again — so the bridge between the signed and unsigned halves of the vocabulary is something the compiler checks rather than something the generator asserts.magnitude_squared()answers with a bareQuantityfor the honest reason: the square of a dimension usually has no name, and where it has one it is not unique, sinceAreaandNuclearCrossSectionare the same exponents. That is exactly what the structural layer is for.How a relationship reaches the vector forms
It carries its form on the left operand and the result, with the right operand staying a magnitude:
There is no reading in which the duration has three components. That is the rule the .NET generator already follows, and
formson a relationship constrains it — a cross product is declared at[3]because that is where a cross product exists.dot(Force, Length) -> Energyis dimensionally true and still unkeepable. A force opposing a displacement does negative work; a magnitude form cannot be negative. The generated operator would build a type that fails its own assertion on an ordinary input.So it is refused, with the fix named rather than guessed at:
Until now every refusal was the exponents disagreeing. This is the first one where they agree and the claim is still wrong, and the vector forms are what surfaced it.
Force × Length → Torqueemits ascross(Force3D, Displacement3D)— that is F × r, and the convention is τ = r × F. A cross product and its negation have identical dimensions, so nothing here can tell them apart. Which operand comes first is a claim the metadata makes and a physics call to change, the same asSensitivity, so it is emitted as declared and reported rather than quietly reordered.Tests
Eleven more, 27 in the project, and two of them are the ones that matter:
TheWholeVocabularyCompiles— 216 headers throughg++ -std=c++20 -Wall -Wextra, clean. (Also verified againstclang++.)AComponentwiseProductWithTheWrongDimensionDoesNotCompile— the case that would slip past a test which only looked at the shape: the components can be expanded perfectly and the dimension still be wrong.TheVectorFormsMeanWhatTheySay— what the forms mean, not that they parse, entirely instatic_assertso it needs no run: that the length of (3, 4, 0) is 5, that a velocity scaled by a duration lands in the right type and the right component, that an overload survives the trip out to its base and back, that aDisplacement3Dis three floats and nothing else.Existing suite: 1,125 passed, unchanged.
dimensions.jsonis untouched this time, so no generated C# moves.Two deliberate boundaries
Length - Lengthhas a question in it thatDisplacement3D - Displacement3Ddoes not — what it means when the answer would be negative — which the .NET side settled as the absolute difference. That is a decision about the magnitude form, not one to settle alongside the vectors.Duration * Velocity3Dis not an overload. The .NET generator emits the commutative and inverse forms too; matching it is a change to every form at once rather than part of this.The prelude gains
abs, written out rather than callingstd::abs, which is notconstexprbefore C++23. Nothing existing calls it, so the measured shapes are unchanged.🤖 Generated with Claude Code
https://claude.ai/code/session_01UGHDsYaaTQdzVR4XBR6miu
Generated by Claude Code