Skip to content

Say what a vector is a vector of - #156

Merged
matt-edmondson merged 2 commits into
mainfrom
claude/peaceful-mayer-oo6l2u
Sep 10, 2026
Merged

Say what a vector is a vector of#156
matt-edmondson merged 2 commits into
mainfrom
claude/peaceful-mayer-oo6l2u

Conversation

@matt-edmondson

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

Copy link
Copy Markdown
Contributor

A velocity is three metres per second and a position is three metres, and a schema that said only "three floats" left the one fact worth knowing about either of them to a comment. That is the argument Semantic already makes for a single value; this applies it to three of them.

{
  "TypeName": "Vector3",
  "elementType": { "TypeName": "Semantic", "semanticTypeName": "MetresPerSecond" }
}

What changed

Vector gains an ElementType, inherited by Vector2, Vector3, Vector4 and — since they derive from the vectors — ColorRGB and ColorRGBA.

It defaults to Float, so a vector that says nothing about its components is what a vector has always been, and it is omitted from the file when it is: a file whose vectors are vectors of floats is written exactly as it was before the property existed. A non-nullable property has no ignore condition meaning "the same as saying nothing", so the omission is a contract modifier on the serializer's options rather than an attribute.

Validation refuses a component that is not a number, or a semantic type represented as one — a vector of objects is a collection of things rather than one value with components, which is what Array is for. A colour is held to Float, because its components are the channels every consumer of one reads as floats.

Two things that fall out of it

  • ValidateType had no arm for Semantic. A member naming a semantic type the schema does not declare was silently accepted. It is reported now, the same way a dangling class or interface reference is — the vector rule needs it to mean anything, and without it a typo'd component name would pass.
  • The C# generator maps a vector to System.Numerics only when its components are floats, because those types hold nothing else. A vector of anything else falls through to the same object? every type that generator cannot yet say does, rather than being emitted as a type it is not.

Format version

formatVersion moves to 4 for the reason version 2 did: the change is additive and a file that uses none of it is byte-identical to the version 3 file it would have been, but a version 3 reader would load a Vec3 of a semantic type, drop what it is three of, and write it back as three bare floats.

Also here: the generator table is no longer fixed

SchemaGenerator's table of generators was a private dictionary built once, which assumes every generator can live in this project. Not every one can: this library publishes net8.0, and the C++ generator being written next is built on an AST that ships no net8.0 assembly, so it has to live somewhere with a higher framework floor.

Register lets one do that and still be found by the language a schema names; IsRegistered is the other half, so a host can ask whether a schema's language will resolve before running anything. Registration replaces rather than duplicates, so a host may override a built-in generator, and a generator naming no language is refused outright — it could never be found again, so an entry under an empty key would only fail later and further away. The table is process-wide and unsynchronised, meant to be filled once at startup; the remarks say so.

Not in this change

The editor's type picker does not offer a component yet, for the same reason it does not offer a Semantic, a Span or a HandleSchema.GetSelectableElementTypes still lists only the built-ins, the enums and the classes.

Testing

Schema.Test 376 passed, SchemaEditor.Test 201 passed, whole solution builds with no warnings. Twelve new tests in VectorElementTypeTests cover the default, inequality by component and by shape, resolution against the schema, each validation rule, the round trip, and that a vector of floats writes no component. Seven more in GeneratorRegistryTests cover registration, case-insensitive lookup, replacement, and both refusals.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AhoPJ5AbxP8QEBNxPQYEPk

A velocity is three metres per second and a position is three metres, and a
schema that said only "three floats" left the one fact worth knowing about
either of them to a comment. That is the argument Semantic already makes for a
single value; this applies it to three of them.

Vector gains an ElementType, inherited by Vector2, Vector3, Vector4 and - since
they derive from the vectors - ColorRGB and ColorRGBA. It defaults to Float, so
a vector that says nothing about its components is what a vector has always
been, and it is omitted from the file when it is: a file whose vectors are
vectors of floats is written exactly as it was before the property existed. A
non-nullable property has no ignore condition meaning "the same as saying
nothing", so the omission is a contract modifier on the serializer's options.

Validation refuses a component that is not a number, or a semantic type
represented as one: a vector of objects is a collection of things rather than
one value with components, which is what Array is for. A colour is held to
Float, because its components are the channels every consumer of one reads as
floats.

Two things fall out of the change:

- ValidateType had no arm for Semantic, so a member naming a semantic type the
  schema does not declare was silently accepted. It is reported now, the same
  way a dangling class or interface reference is; the vector rule needs it to
  mean anything.
- The C# generator maps a vector to System.Numerics only when its components
  are floats, because those types hold nothing else. A vector of anything else
  falls through to the same object? every type that generator cannot yet say
  does, rather than being emitted as a type it is not.

formatVersion moves to 4 for the reason version 2 did: a version 3 reader would
load a Vec3 of a semantic type, drop what it is three of, and write it back as
three bare floats.

The editor's type picker does not offer a component yet, for the same reason it
does not offer a Semantic, a Span or a Handle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhoPJ5AbxP8QEBNxPQYEPk
Comment thread Schema/SchemaSerializer.cs Fixed
The contract modifier walks every property to find the one it cares about,
which reads as a loop over all of them. Saying Where once says what is being
looked for where a reader looks for it.

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

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 1a9dae9 into main Sep 10, 2026
12 checks passed
@matt-edmondson
matt-edmondson deleted the claude/peaceful-mayer-oo6l2u branch September 10, 2026 12:32
matt-edmondson pushed a commit that referenced this pull request Sep 10, 2026
MSTEST0037: comparing a count to zero says less than asserting emptiness, and
Assert.AreEqual(0, ...) reports "expected 0, was 2" where Assert.IsEmpty names
what it found. The message follows the convention the rest of the suite already
uses for a validation assertion - joining the issues, so a failure says which
rule fired rather than only that one did.

Reported by SonarCloud on #156, which had already merged. The
same shape appears throughout the suite and predates that change; only the new
line is touched here, since a sweep would be a large diff for no behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhoPJ5AbxP8QEBNxPQYEPk
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