diff --git a/Semantics.Test/Quantities/SemanticOverloadTests.cs b/Semantics.Test/Quantities/SemanticOverloadTests.cs
new file mode 100644
index 0000000..0d256af
--- /dev/null
+++ b/Semantics.Test/Quantities/SemanticOverloadTests.cs
@@ -0,0 +1,151 @@
+// Copyright (c) ktsu.dev
+// All rights reserved.
+// Licensed under the MIT license.
+
+namespace ktsu.Semantics.Test.Quantities;
+
+using ktsu.Semantics.Quantities;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+///
+/// Covers semantic overload conversions and metadata-driven relationships.
+/// Issue #55.
+///
+[TestClass]
+public sealed class SemanticOverloadTests
+{
+ private const double Tolerance = 1e-10;
+
+ // ----------------------------------------- Implicit widening to base
+
+ [TestMethod]
+ public void Weight_Widens_Implicitly_To_ForceMagnitude()
+ {
+ Weight w = Weight.FromNewton(686.0);
+ ForceMagnitude baseValue = w; // implicit conversion
+ Assert.AreEqual(686.0, baseValue.Value, Tolerance);
+ }
+
+ [TestMethod]
+ public void Distance_Widens_Implicitly_To_Length()
+ {
+ Distance d = Distance.FromMeter(42.0);
+ Length len = d;
+ Assert.AreEqual(42.0, len.Value, Tolerance);
+ }
+
+ [TestMethod]
+ public void Diameter_Widens_Implicitly_To_Length()
+ {
+ Diameter diam = Diameter.FromMeter(10.0);
+ Length len = diam;
+ Assert.AreEqual(10.0, len.Value, Tolerance);
+ }
+
+ // ---------------------------------------- Explicit narrowing from base
+
+ [TestMethod]
+ public void ForceMagnitude_Narrows_Explicitly_To_Weight()
+ {
+ ForceMagnitude fm = ForceMagnitude.FromNewton(686.0);
+ Weight w = (Weight)fm;
+ Assert.AreEqual(686.0, w.Value, Tolerance);
+ }
+
+ [TestMethod]
+ public void Length_Narrows_Explicitly_To_Distance()
+ {
+ Length len = Length.FromMeter(42.0);
+ Distance d = (Distance)len;
+ Assert.AreEqual(42.0, d.Value, Tolerance);
+ }
+
+ // --------------------------------------------- From(base) factory
+
+ [TestMethod]
+ public void Weight_From_ForceMagnitude_Constructs()
+ {
+ ForceMagnitude fm = ForceMagnitude.FromNewton(100.0);
+ Weight w = Weight.From(fm);
+ Assert.AreEqual(100.0, w.Value, Tolerance);
+ }
+
+ [TestMethod]
+ public void Distance_From_Length_Constructs()
+ {
+ Length len = Length.FromMeter(7.0);
+ Distance d = Distance.From(len);
+ Assert.AreEqual(7.0, d.Value, Tolerance);
+ }
+
+ // -------------------- Round-trip widen/narrow preserves value
+
+ [TestMethod]
+ public void Weight_RoundTrip_Through_ForceMagnitude_Preserves_Value()
+ {
+ Weight original = Weight.FromNewton(123.456);
+ ForceMagnitude widened = original;
+ Weight narrowed = (Weight)widened;
+ Assert.AreEqual(original.Value, narrowed.Value, Tolerance);
+ }
+
+ // ------------------ Metadata-defined relationship: Diameter <-> Radius
+
+ [TestMethod]
+ public void Diameter_ToRadius_Halves_Value()
+ {
+ Diameter d = Diameter.FromMeter(10.0);
+ Radius r = d.ToRadius();
+ Assert.AreEqual(5.0, r.Value, Tolerance);
+ }
+
+ [TestMethod]
+ public void Diameter_FromRadius_Doubles_Value()
+ {
+ Radius r = Radius.FromMeter(5.0);
+ Diameter d = Diameter.FromRadius(r);
+ Assert.AreEqual(10.0, d.Value, Tolerance);
+ }
+
+ [TestMethod]
+ public void Diameter_RoundTrip_Through_Radius_Preserves_Value()
+ {
+ Diameter d = Diameter.FromMeter(20.0);
+ Radius r = d.ToRadius();
+ Diameter back = Diameter.FromRadius(r);
+ Assert.AreEqual(d.Value, back.Value, Tolerance);
+ }
+
+ // ----------------- V0 overload subtraction
+ // Locked in #52: V0 - V0 returns the same V0 of T.Abs(a - b).
+ // Generator currently emits a Force1D-returning subtraction for Weight - Weight,
+ // which violates that rule. The current behaviour is documented here so the fix
+ // in #52 can replace this test with the correct shape.
+
+ [TestMethod]
+ public void Weight_Minus_Weight_Currently_Returns_Force1D_PendingFix52()
+ {
+ Weight a = Weight.FromNewton(100.0);
+ Weight b = Weight.FromNewton(150.0);
+ Force1D diff = a - b; // current generator behaviour; #52 plans Weight of |a - b|.
+ Assert.AreEqual(-50.0, diff.Value, Tolerance);
+ }
+
+ // ----------------- Storage-type genericity sanity
+
+ [TestMethod]
+ public void Diameter_ToRadius_Works_With_Float_Storage()
+ {
+ Diameter d = Diameter.FromMeter(10.0f);
+ Radius r = d.ToRadius();
+ Assert.AreEqual(5.0f, r.Value, 1e-6f);
+ }
+
+ [TestMethod]
+ public void Diameter_ToRadius_Works_With_Decimal_Storage()
+ {
+ Diameter d = Diameter.FromMeter(10m);
+ Radius r = d.ToRadius();
+ Assert.AreEqual(5m, r.Value);
+ }
+}
diff --git a/Semantics.Test/Quantities/VectorQuantityTests.cs b/Semantics.Test/Quantities/VectorQuantityTests.cs
new file mode 100644
index 0000000..492952d
--- /dev/null
+++ b/Semantics.Test/Quantities/VectorQuantityTests.cs
@@ -0,0 +1,212 @@
+// Copyright (c) ktsu.dev
+// All rights reserved.
+// Licensed under the MIT license.
+
+namespace ktsu.Semantics.Test.Quantities;
+
+using ktsu.Semantics.Quantities;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+///
+/// Covers .. contracts:
+/// magnitude extraction, typed dot/cross products, vector arithmetic, and V0 invariants.
+/// Issue #54.
+///
+[TestClass]
+public sealed class VectorQuantityTests
+{
+ private const double Tolerance = 1e-10;
+
+ // -------------------------------------------------------------- Magnitude
+
+ [TestMethod]
+ public void Velocity3D_Magnitude_Of_3_4_0_Is_Speed_5()
+ {
+ Velocity3D v = new() { X = 3.0, Y = 4.0, Z = 0.0 };
+ Speed s = v.Magnitude();
+ Assert.AreEqual(5.0, s.Value, Tolerance);
+ }
+
+ [TestMethod]
+ public void Force3D_Magnitude_Is_Always_NonNegative_Even_With_Negative_Components()
+ {
+ Force3D f = new() { X = -3.0, Y = -4.0, Z = 0.0 };
+ ForceMagnitude m = f.Magnitude();
+ Assert.AreEqual(5.0, m.Value, Tolerance);
+ }
+
+ [TestMethod]
+ public void Velocity3D_Magnitude_Returns_Speed_Type_Statically()
+ {
+ Velocity3D v = new() { X = 1.0, Y = 0.0, Z = 0.0 };
+ Speed s = v.Magnitude();
+ Assert.IsInstanceOfType>(s);
+ }
+
+ [TestMethod]
+ public void Velocity3D_Magnitude_Of_Zero_Vector_Is_Zero()
+ {
+ Velocity3D zero = Velocity3D.Zero;
+ Speed s = zero.Magnitude();
+ Assert.AreEqual(0.0, s.Value, Tolerance);
+ }
+
+ // ------------------------------------------------------ Typed dot product
+
+ [TestMethod]
+ public void Force3D_Dot_Displacement3D_Returns_Energy_Aligned()
+ {
+ Force3D f = new() { X = 10.0, Y = 0.0, Z = 0.0 };
+ Displacement3D r = new() { X = 2.0, Y = 0.0, Z = 0.0 };
+ Energy work = f.Dot(r);
+ Assert.AreEqual(20.0, work.Value, Tolerance);
+ }
+
+ [TestMethod]
+ public void Force3D_Dot_Displacement3D_Is_Zero_For_Perpendicular()
+ {
+ Force3D f = new() { X = 10.0, Y = 0.0, Z = 0.0 };
+ Displacement3D r = new() { X = 0.0, Y = 5.0, Z = 0.0 };
+ Energy work = f.Dot(r);
+ Assert.AreEqual(0.0, work.Value, Tolerance);
+ }
+
+ // ---------------------------------------------------- Typed cross product
+
+ [TestMethod]
+ public void Force3D_Cross_Displacement3D_Returns_Torque3D()
+ {
+ Force3D f = new() { X = 0.0, Y = 10.0, Z = 0.0 };
+ Displacement3D r = new() { X = 0.5, Y = 0.0, Z = 0.0 };
+ Torque3D t = f.Cross(r);
+ // (Y*rZ - Z*rY, Z*rX - X*rZ, X*rY - Y*rX) = (0, 0, -5)
+ Assert.AreEqual(0.0, t.X, Tolerance);
+ Assert.AreEqual(0.0, t.Y, Tolerance);
+ Assert.AreEqual(-5.0, t.Z, Tolerance);
+ }
+
+ [TestMethod]
+ public void Force3D_Cross_Self_Is_Zero_Vector()
+ {
+ Force3D f = new() { X = 1.0, Y = 2.0, Z = 3.0 };
+ // Same-dimension structural cross returns Force3D (the dimension itself, not a typed dimensional product).
+ Force3D c = f.Cross(f);
+ Assert.AreEqual(0.0, c.X, Tolerance);
+ Assert.AreEqual(0.0, c.Y, Tolerance);
+ Assert.AreEqual(0.0, c.Z, Tolerance);
+ }
+
+ // --------------------------------------------- Same-dimension dot product
+
+ [TestMethod]
+ public void Velocity3D_Dot_Velocity3D_Returns_Raw_Storage_Scalar()
+ {
+ // Same-dimension Dot is structural and returns the raw storage type; it isn't
+ // a typed dimensional product (no "Speed²" exists in the type system).
+ Velocity3D a = new() { X = 1.0, Y = 2.0, Z = 3.0 };
+ Velocity3D b = new() { X = 4.0, Y = 5.0, Z = 6.0 };
+ double dot = a.Dot(b);
+ Assert.AreEqual(32.0, dot, Tolerance);
+ }
+
+ // ------------------------------------------------ Vector form arithmetic
+
+ [TestMethod]
+ public void Force3D_Plus_Force3D_Stays_Force3D_Componentwise()
+ {
+ Force3D a = new() { X = 1.0, Y = 2.0, Z = 3.0 };
+ Force3D b = new() { X = 4.0, Y = 5.0, Z = 6.0 };
+ Force3D sum = a + b;
+ Assert.AreEqual(5.0, sum.X, Tolerance);
+ Assert.AreEqual(7.0, sum.Y, Tolerance);
+ Assert.AreEqual(9.0, sum.Z, Tolerance);
+ }
+
+ [TestMethod]
+ public void Force3D_Minus_Force3D_Componentwise()
+ {
+ Force3D a = new() { X = 5.0, Y = 7.0, Z = 9.0 };
+ Force3D b = new() { X = 1.0, Y = 2.0, Z = 3.0 };
+ Force3D diff = a - b;
+ Assert.AreEqual(4.0, diff.X, Tolerance);
+ Assert.AreEqual(5.0, diff.Y, Tolerance);
+ Assert.AreEqual(6.0, diff.Z, Tolerance);
+ }
+
+ [TestMethod]
+ public void Force3D_Negation_Inverts_Each_Component()
+ {
+ Force3D f = new() { X = 1.0, Y = -2.0, Z = 3.0 };
+ Force3D n = -f;
+ Assert.AreEqual(-1.0, n.X, Tolerance);
+ Assert.AreEqual(2.0, n.Y, Tolerance);
+ Assert.AreEqual(-3.0, n.Z, Tolerance);
+ }
+
+ // ------------------------------------------------------------- V0 + V0
+
+ [TestMethod]
+ public void Mass_Plus_Mass_Returns_Mass()
+ {
+ Mass a = Mass.FromKilogram(3.0);
+ Mass b = Mass.FromKilogram(5.0);
+ Mass sum = a + b;
+ Assert.AreEqual(8.0, sum.Value, Tolerance);
+ Assert.IsInstanceOfType>(sum);
+ }
+
+ [TestMethod]
+ public void Speed_Plus_Speed_Returns_Speed()
+ {
+ Speed a = Speed.FromMetersPerSecond(3.0);
+ Speed b = Speed.FromMetersPerSecond(5.0);
+ Speed sum = a + b;
+ Assert.AreEqual(8.0, sum.Value, Tolerance);
+ }
+
+ // ------------------------------------------------------------- V0 - V0
+ // Locked design decision in #52: V0 - V0 should return the same V0 of T.Abs(a - b).
+ // Generator currently emits unsigned subtraction via the SemanticQuantity base, which
+ // can produce a negative magnitude. Tracked as a follow-up.
+
+ [TestMethod]
+ [Ignore("Locked in #52: V0 - V0 should return the same V0 of T.Abs(a - b). Generator currently emits unsigned subtraction.")]
+ public void Mass_Minus_Mass_Returns_Absolute_Difference_Pending52()
+ {
+ Mass a = Mass.FromKilogram(3.0);
+ Mass b = Mass.FromKilogram(5.0);
+ Mass diff = a - b;
+ Assert.AreEqual(2.0, diff.Value, Tolerance);
+ }
+
+ // ---------------------------------------------------- V0 non-negativity
+ // Tracked in #50: factories on Vector0 quantities should reject negative inputs
+ // with ArgumentException. The current generator does not emit guards.
+
+ [TestMethod]
+ [Ignore("Tracked in #50: V0 factories should reject negative inputs.")]
+ public void Speed_From_Negative_Throws_Pending50()
+ {
+ _ = Assert.ThrowsExactly(
+ () => Speed.FromMetersPerSecond(-1.0));
+ }
+
+ [TestMethod]
+ [Ignore("Tracked in #50: V0 factories should reject negative inputs.")]
+ public void Mass_From_Negative_Throws_Pending50()
+ {
+ _ = Assert.ThrowsExactly(
+ () => Mass.FromKilogram(-1.0));
+ }
+
+ // -------------------------------------------------- Magnitude on V1
+ // Velocity1D.Magnitude() should return Speed of T.Abs(value).
+
+ [TestMethod]
+ public void Velocity1D_Magnitude_Of_Negative_Is_Positive_Speed()
+ {
+ Velocity1D v = Velocity1D.FromMetersPerSecond(-3.5);
+ Speed s = v.Magnitude();
+ Assert.AreEqual(3.5, s.Value, Tolerance);
+ }
+}