Skip to content

Add GravitationalParameter, SpecificEnergy and SpecificAngularMomentum [minor] - #262

Merged
matt-edmondson merged 4 commits into
mainfrom
claude/semantics-240-orbital-dimensions
Sep 22, 2026
Merged

matt-edmondson merged 4 commits into
mainfrom
claude/semantics-240-orbital-dimensions

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #240

Adds the three dimensions orbital mechanics cannot be written without. dimensions.json goes from 72 entries over 63 exponent vectors to 75 over 64 — only GravitationalParameter brings a vector of its own.

Added Exponents Already on that vector
GravitationalParameter L³ T⁻² nothing — this is the 64th vector
SpecificEnergy L² T⁻² AbsorbedDose, EquivalentDose
SpecificAngularMomentum L² T⁻¹ KinematicViscosity

The two decisions a reviewer should not reverse

SpecificEnergy's base is a signed vector1, not a vector0. This is the one that must not be got backwards, and it will look inconsistent with its neighbours, which are vector0. Specific orbital energy is ε = −μ/2a, negative for every bound orbit. A vector0 base runs Vector0Guards.EnsureNonNegative and would throw ArgumentException on the entirely ordinary case of a satellite in orbit — a type that fails its own assertion on every real input. The generated code confirms the split:

  • SpecificEnergy<T>.FromJoulePerKilogram → Create(value), no guard
  • SpecificEnergyMagnitude<T>.FromJoulePerKilogram → Create(Vector0Guards.EnsureNonNegative(...))

A vector0 form sits alongside it, carrying SpecificKineticEnergy, which genuinely is non-negative.

cross(Displacement3D, Velocity3D) is declared on Length, not Velocity. h = r × v, not v × r, and the exponents cannot tell the two apart because a cross product and its negation have identical dimensions — SEM008 passes either way. Declaring it on Length is what puts the operands in that order. This is exactly how the Force-vs-Length declaration of torque went wrong before.

How each formula was checked

Following #216's method — two witnesses rather than intuition — each formula was checked against the units it carries and the relationships it participates in, not against a recollection of the physics:

  • GravitationalParameter / Area → Acceleration — L³T⁻² − L² = L T⁻², and the test asserts μ_Earth / R_Earth² lands on ~9.82 m/s²
  • GravitationalParameter / Length → SpecificEnergy — L³T⁻² − L = L²T⁻²
  • cross(Length, Velocity) → SpecificAngularMomentum — L + L T⁻¹ = L² T⁻¹

All three were emitted; the build reports no SEM008.

Evidence the tests are load-bearing

The sign assertion was verified two ways rather than assumed:

  1. Flipping the expected sign (+5.0 → −5.0 on h.Z) failed exactly 1 of 1290 tests — so the assertion pins a real value, and the emitted operator really is r × v.
  2. Moving the declaration to Velocity — the mistake the torque relationship once made — produced error CS1503: cannot convert from 'Velocity3D<double>' to 'Displacement3D<double>', with no SEM008 diagnostic, confirming the exponents cannot catch it and only the declaration site and the test can.

Worth recording from (2): for a cross product, declaring it on the wrong dimension does not silently invert an answer — only the declared direction is emitted, so the call site stops compiling. It is the sign of an existing call that nothing but a test can check.

Two deviations from the issue, both deliberate

  • Unit naming. The issue proposed MeterCubedPerSecondSquared / KilometerCubedPerSecondSquared. The repo's convention is a Cubic<X> prefix — CubicMeter, CubicMeterPerSecond, CubicCentimeter, WattPerCubicMeter — so these are CubicMeterPerSecondSquared and CubicKilometerPerSecondSquared.
  • physicalConstraints: { minExclusive: "0" } on GravitationalParameter is not set. The issue called it "arguably right", but PhysicalConstraints is declared only on OverloadDefinition, not on VectorFormDefinition, so it cannot be placed on a base without a model change. The vector0 base already carries the non-negativity guard; only the upgrade to strictly-positive is unavailable. Left as a separate question rather than widening this PR.

SquareMeterPerSecond is reused for SpecificAngularMomentum rather than duplicated, making it the second unit claimed by two dimensions after SquareMeter. Both collide on identical exponents, so the IUnit.Dimension answer differs in name only — the rule CLAUDE.md already documents.

Verification

Run locally on the committed tree, mirroring verify-generated.yml:

  • dotnet build Semantics.Quantities -c Release → succeeded, 0 warnings, no SEM diagnostics
  • pwsh scripts/Generate-AliasProps.ps1 → 220 → 228 aliases across all four storage packages, matching the 8 new types exactly
  • git diff --exit-code after rebuild + regenerate → clean, so the verify-generated gate passes
  • Semantics.Test → 1290 total, 0 failed (1282 passed, 8 skipped Windows-only); 7 of these are new
  • Semantics.Cpp.Test → 43 total, 0 failed, so the shared Semantics.Vocabulary check is happy on both projections

Documentation

CLAUDE.md updated: the counts (75-over-64, 220 classes — 153 magnitudes, 29 signed scalars, 38 vectors), the angle-axis figure (62 → 64), the second twice-claimed unit, the two new collisions as the clearest illustration of why the nominal layer exists, and the h = r × v declaration rule beside the torque one. The before-figures in that file were reproduced exactly by the counting script before changing them, which is what makes the new ones trustworthy.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HLTGLrPaSc3E9APdgUq6wn


Generated by Claude Code

…m [minor]

The three dimensions orbital mechanics cannot be written without. mu = GM
(L³T⁻²) had no vector at all and could only be carried as a bare T; the other
two are name collisions on vectors that already exist, which is the clearest
illustration the library has of why the nominal layer is needed.

SpecificEnergy's base is a signed vector1, not a vector0. Specific orbital
energy is epsilon = -mu/2a, negative for every bound orbit, so a vector0 base
would run Vector0Guards.EnsureNonNegative and throw on the ordinary case of a
satellite in orbit. A vector0 form sits alongside it for specific kinetic
energy, which genuinely is non-negative.

cross(Displacement3D, Velocity3D) is declared on Length so the operands land
as h = r x v rather than its negation. SEM008 cannot tell the two apart -- a
cross product and its negation have identical dimensions -- so a test pins the
sign, as the torque relationship already does.

Units are named for the repo's Cubic<X> convention rather than the issue's
Meter Cubed<X> wording: CubicMeterPerSecondSquared and
CubicKilometerPerSecondSquared, beside CubicMeterPerSecond. SquareMeterPerSecond
is reused for SpecificAngularMomentum, the second unit claimed by two
dimensions.

Fixes #240

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

matt-edmondson commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor Author

github-advanced-security is red, and it is not this PR

Standing down on that one check rather than pushing at it. The job log gives the reason outright:

Error creating PR review request: SessionModelError: You have exceeded your monthly quota
errorType: 'quota', statusCode: 402

That is Copilot Autofix's account-level monthly quota, hit while creating the review request — it failed 24 seconds in, before reading any of the diff. Nothing in this change reaches it, and no code change can clear it. A re-run would not help either, since the quota is monthly rather than per-run, so I have not spent one on it.

It fails identically on #263, a completely unrelated change, which is the evidence that it is not diff-specific. It will keep failing on every PR in the org until the quota resets or the plan is topped up, so it is worth knowing about beyond this PR. Flagged to the maintainer separately.

Everything that actually tests this change is green on 35b2e8c, the head of this branch:

Check Result
Test on ubuntu-latest ✅
Test on macos-latest ✅
Test on windows-latest ✅
Generated files up to date ✅
CodeQL ✅
Analyze (csharp) ×2, Analyze (actions) ✅

The Generated files up to date pass is the one worth calling out: it rebuilds and re-runs Generate-AliasProps.ps1, so it independently confirms the committed generated source and the 228 alias entries match the metadata in this PR.

(Edited to correct the commit SHA — an earlier version of this comment cited ce49ffa, which is not a commit on this branch. The head is 35b2e8c; the check results above are unchanged.)


Generated by Claude Code

SonarCloud flagged MSTEST0037 on the new test: Assert.IsTrue(x < 0.0) hides
the compared values from the failure message. Assert.IsLessThan reports them.

Argument order verified rather than assumed -- IsLessThan(upperBound, value)
asserts value < upperBound, confirmed by flipping the expected energy positive
and watching this test, and only this test, fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLTGLrPaSc3E9APdgUq6wn
Merging main brought in #263, which adds DistanceTo() to every vector form
and fully qualifies the magnitude return type. SpecificAngularMomentum3D is
new on this branch, so it was written by the pre-#263 generator and the merge
had no way to know it needed reemitting. Regenerated with the repo's tooling
rather than hand-edited.

The two changes compose as intended: the new vector form now carries the same
typed Magnitude() and DistanceTo() as every other.

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

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 20dcff1 into main Sep 22, 2026
12 of 13 checks passed
@matt-edmondson
matt-edmondson deleted the claude/semantics-240-orbital-dimensions branch September 22, 2026 00:40
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.

Add GravitationalParameter, SpecificEnergy and SpecificAngularMomentum — the three dimensions orbital mechanics cannot be written without

2 participants