Skip to content

Commit 126ecca

Browse files
committed
update: bump code coverage
1 parent 16f8a77 commit 126ecca

19 files changed

Lines changed: 1256 additions & 5 deletions

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@
3737
- Tests are xUnit (`tests/FixedMathSharp.Tests`). Keep one feature area per test file (e.g., `Vector3d.Tests.cs`, `Bounds/BoundingBox.Tests.cs`).
3838
- Use helper assertions from `tests/FixedMathSharp.Tests/Support/FixedMathTestHelper.cs` for tolerance/range checks rather than ad-hoc epsilon logic.
3939
- For deterministic RNG changes, validate same-seed reproducibility and bounds/argument exceptions like in `DeterministicRandom.Tests.cs`.
40+
- Cyclomatic complexity exceptions are documented in `docs/complexity-exceptions.md`; update that register when touching methods above the review threshold instead of refactoring hot deterministic paths just to lower a metric.
4041

4142
## Agent editing guidance
4243

4344
- Keep public API shape stable unless the task explicitly requests API changes.
4445
- Match existing style (regions, XML docs, explicit namespaces, no implicit usings).
4546
- Make focused edits in the relevant numeric/bounds module and update corresponding tests in the parallel test file.
46-
- For serialization changes, ensure MemoryPack attributes are correctly applied and roundtrip tests are updated.
47+
- For serialization changes, ensure MemoryPack attributes are correctly applied and roundtrip tests are updated.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ FixedMathSharp is optimized for high-performance deterministic calculations:
194194

195195
The library is covered by xUnit tests for core arithmetic, vectors, bounds, serialization, and deterministic random behavior. Fuzzy comparisons are used where a tolerance-based check is more appropriate than exact equality.
196196

197+
Cyclomatic complexity exceptions are tracked in [`docs/complexity-exceptions.md`](docs/complexity-exceptions.md). The register explains why specific hot-path or fixed-shape methods exceed the review threshold and what should trigger revisiting them.
198+
197199
To run the tests:
198200

199201
```bash

docs/complexity-exceptions.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Cyclomatic Complexity Exception Register
2+
3+
This document records methods that intentionally exceed the current cyclomatic complexity review threshold.
4+
5+
## Policy
6+
7+
- Review threshold: cyclomatic complexity greater than 10.
8+
- Risk threshold: CRAP score greater than 30 requires immediate test hardening or refactoring.
9+
- Current status: the fresh coverage/CRAP report generated on 2026-05-18 has no methods above CRAP 30.
10+
- Source report: `tests/FixedMathSharp.Tests/TestResults/coverage-analysis/reports/Summary.txt`.
11+
12+
Complexity exceptions are acceptable when the method is a hot deterministic math path, a direct component-wise value comparison, a fixed-shape assertion helper, or an algorithm where extraction would add indirection without reducing real maintenance risk. These exceptions should be revisited when coverage drops, behavior changes, or the implementation becomes harder to reason about.
13+
14+
## Exception Register
15+
16+
| Module | Method | Complexity | Coverage | Rationale | Revisit if |
17+
| --- | --- | ---: | --- | --- | --- |
18+
| `FixedMathSharp.FluentAssertions` | `FixedAssertionHelpers.AreComponentApproximatelyEqual(Fixed4x4, Fixed4x4, Fixed64)` | 30 | 100% line / 100% branch | Fixed-shape assertion over all matrix components. The explicit checks keep failure location and assertion intent clear. | Assertion diagnostics degrade, matrix shape changes, or repeated assertion logic grows further. |
19+
| `FixedMathSharp` | `Fixed4x4.Equals(Fixed4x4)` | 30 | 100% line / 100% branch | Direct 4x4 value comparison avoids loops, allocations, and indexer overhead on a hot value type. | Equality semantics change or a generated/source-shared component comparison becomes available without runtime cost. |
20+
| `FixedMathSharp` | `BoundingSphere.CreateFromPointList(IReadOnlyList<Vector3d>)` | 26 | 98.2% line / 100% branch | Ritter-style bounding sphere construction has fixed selection and expansion branches; keeping it local preserves data flow and avoids extra passes. | More sphere construction modes are added, coverage drops, or the algorithm needs accuracy/performance tuning. |
21+
| `FixedMathSharp` | `FixedMath.Sin(Fixed64)` | 24 | 100% line / 100% branch | Trigonometric range reduction and approximation are performance-sensitive and deterministic. Extraction would split a compact numeric routine. | Approximation strategy changes or benchmark evidence shows a helper split is neutral or faster. |
22+
| `FixedMathSharp` | `Fixed64.op_Division(Fixed64, Fixed64)` | 24 | 100% line / 100% branch | Saturating fixed-point division uses low-level bit operations and guarded overflow paths that should remain explicit. | Division semantics change or additional edge cases make the method difficult to audit. |
23+
| `FixedMathSharp` | `FixedMath.Sqrt(Fixed64)` | 18 | 100% line / 100% branch | Integer square-root logic is branchy by nature and sits on a core deterministic math path. | A simpler algorithm is adopted with equal determinism and performance. |
24+
| `FixedMathSharp` | `Fixed3x3Extensions.FuzzyEqual(Fixed3x3, Fixed3x3, Fixed64?)` | 18 | 100% line / 100% branch | Component-wise fuzzy equality is intentionally explicit to avoid allocation and preserve inlining. | The matrix equality helpers are generated or centralized without adding runtime overhead. |
25+
| `FixedMathSharp` | `BoundingSphere.ContainsBoxLike(Vector3d, Vector3d)` | 18 | 100% line / 100% branch | Checks all box-like corners against the sphere; explicit shape avoids temporary corner arrays. | Bounds internals move to a shared corner iterator that is allocation-free. |
26+
| `FixedMathSharp` | `Fixed4x4.get_Item(int)` | 17 | 100% line / 100% branch | Switch-based fixed matrix indexing is direct and avoids table allocation or reflection. | The matrix layout changes or generated indexer code becomes part of the build. |
27+
| `FixedMathSharp` | `Fixed4x4.set_Item(int, Fixed64)` | 17 | 100% line / 100% branch | Switch-based fixed matrix indexing is direct and avoids table allocation or reflection. | The matrix layout changes or generated indexer code becomes part of the build. |
28+
| `FixedMathSharp` | `Fixed64.op_Multiply(Fixed64, Fixed64)` | 16 | 95.6% line / 81.2% branch | Full-width multiply with saturating and round-half-to-even behavior requires explicit guarded paths. | Additional uncovered reachable branches appear, arithmetic semantics change, or benchmarks support a simpler equivalent. |
29+
| `FixedMathSharp` | `Fixed3x3Extensions.FuzzyEqualAbsolute(Fixed3x3, Fixed3x3, Fixed64)` | 16 | 100% line / 100% branch | Direct component-wise comparison avoids loops and keeps the extension inlinable. | The matrix equality helpers are generated or centralized without adding runtime overhead. |
30+
| `FixedMathSharp` | `Fixed3x3.Equals(Fixed3x3)` | 16 | 100% line / 100% branch | Direct 3x3 value comparison avoids loops, allocations, and indexer overhead on a hot value type. | Equality semantics change or a generated/source-shared component comparison becomes available without runtime cost. |
31+
| `FixedMathSharp` | `FixedCurve.Evaluate(Fixed64)` | 16 | 95.2% line / 87.5% branch | Curve evaluation combines clamping, segment search, and interpolation mode dispatch in one readable flow. | More interpolation modes are added or segment lookup becomes a performance bottleneck. |
32+
| `FixedMathSharp` | `FixedMath.Pow2(Fixed64)` | 16 | 100% line / 100% branch | Fixed-point exponent approximation is compact, deterministic, and performance-sensitive. | Approximation strategy changes or benchmark evidence supports decomposition. |
33+
| `FixedMathSharp` | `FixedPlane.IntersectsBoxLike(Vector3d, Vector3d)` | 16 | 100% line / 100% branch | Plane-vs-bounds classification is branch-heavy but fixed-shape and allocation-free. | Additional bound shapes are added and a shared allocation-free helper becomes clearer. |
34+
| `FixedMathSharp` | `BoundingFrustum.Intersects(FixedRay)` | 16 | 100% line / 93.8% branch | Slab-style frustum clipping has unavoidable enter/exit branches and benefits from locality. | More ray/frustum edge cases are discovered or clipping logic is shared elsewhere. |
35+
| `FixedMathSharp` | `FixedMath.Atan(Fixed64)` | 16 | 100% line / 100% branch | Trigonometric approximation and range handling are deterministic hot-path math. | Approximation strategy changes or benchmark evidence supports decomposition. |
36+
| `FixedMathSharp` | `FixedMath.Asin(Fixed64)` | 16 | 100% line / 100% branch | Trigonometric domain handling and approximation are deterministic hot-path math. | Approximation strategy changes or benchmark evidence supports decomposition. |
37+
| `FixedMathSharp` | `FixedMath.Tan(Fixed64)` | 16 | 95.6% line / 100% branch | Tangent delegates through fixed trigonometric identities and guarded edge handling. | New domain behavior is added or uncovered lines become reachable with valid input. |
38+
| `FixedMathSharp.FluentAssertions` | `FixedAssertionHelpers.AreComponentApproximatelyEqual(Fixed3x3, Fixed3x3, Fixed64)` | 16 | 100% line / 100% branch | Fixed-shape assertion over all matrix components. The explicit checks keep failure location and assertion intent clear. | Assertion diagnostics degrade, matrix shape changes, or repeated assertion logic grows further. |
39+
| `FixedMathSharp` | `BoundingFrustum.IntersectsFrustum(BoundingFrustum)` | 14 | 91.3% line / 85.7% branch | Separating-axis frustum checks are algorithmically branch-heavy and intentionally avoid allocations. | A robust shared SAT helper can improve clarity without extra allocations or worse benchmarks. |
40+
| `FixedMathSharp` | `BoundingBox.ClosestPointOnSurface(Vector3d)` | 14 | 100% line / 100% branch | The nearest-face selection is fixed-shape and explicit; helper extraction would not reduce real complexity. | Box surface projection grows beyond nearest-face selection. |
41+
| `FixedMathSharp` | `Fixed3x3.get_Item(int)` | 12 | 100% line / 100% branch | Switch-based fixed matrix indexing is direct and avoids table allocation or reflection. | The matrix layout changes or generated indexer code becomes part of the build. |
42+
| `FixedMathSharp` | `Fixed3x3.set_Item(int, Fixed64)` | 12 | 100% line / 100% branch | Switch-based fixed matrix indexing is direct and avoids table allocation or reflection. | The matrix layout changes or generated indexer code becomes part of the build. |
43+
| `FixedMathSharp` | `BoundingFrustum.Contains(BoundingFrustum)` | 12 | 100% line / 100% branch | Frustum containment needs null handling, equality fast-path, corner checks, and intersection fallback. | Frustum containment semantics change or the corner loop is shared with other bound types. |
44+
| `FixedMathSharp` | `FixedMath.Acos(Fixed64)` | 12 | 100% line / 100% branch | Trigonometric domain handling and approximation are deterministic hot-path math. | Approximation strategy changes or benchmark evidence supports decomposition. |
45+
| `FixedMathSharp` | `FixedQuaternion.FromEulerAngles(Fixed64, Fixed64, Fixed64)` | 12 | 100% line / 100% branch | Validates three angles and builds the quaternion in a compact deterministic path. | Angle validation policy changes or Euler conversion variants are added. |
46+
| `FixedMathSharp` | `FixedRay.Intersects(BoundingSphere)` | 12 | 100% line / 91.7% branch | Ray-sphere intersection has standard geometric early exits and should remain local for clarity and speed. | New ray semantics are added or branch gaps reveal reachable untested behavior. |
47+
48+
## Review Notes
49+
50+
- Methods at 100% line and branch coverage with direct component-wise logic should usually remain explicit unless a zero-overhead generated approach is introduced.
51+
- For fixed-point arithmetic and trigonometric routines, prefer benchmark-backed refactors over structural changes made only to reduce the reported complexity number.
52+
- If a method remains above the complexity threshold and below full branch coverage, prefer adding focused tests for reachable branches before refactoring.
53+
- Re-run coverage and CRAP analysis after any changes that touch the methods listed above, then update this register.

src/FixedMathSharp/Geometry/Bounds/BoundingSphere.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,6 @@ public static BoundingSphere CreateMerged(BoundingSphere original, BoundingSpher
155155
if (distance + original.Radius <= additional.Radius)
156156
return additional;
157157

158-
if (distance == Fixed64.Zero)
159-
return original.Radius >= additional.Radius ? original : additional;
160-
161158
Fixed64 radius = (distance + original.Radius + additional.Radius) * Fixed64.Half;
162159
Vector3d center = original.Center + centerOffset * ((radius - original.Radius) / distance);
163160

tests/FixedMathSharp.Tests/Bounds/BoundingArea.Tests.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public void MinMaxDimensionsAndBounds_AreCorrect_WhenCornersAreReversed()
5959
Assert.Equal(new Fixed64(3), area.Width);
6060
Assert.Equal(new Fixed64(3), area.Height);
6161
Assert.Equal(new Fixed64(3), area.Depth);
62+
Assert.Equal(new Fixed64(5), new BoundingArea(new Vector3d(1, 5, 3), new Vector3d(4, 2, 6)).MaxY);
6263
}
6364

6465
[Fact]
@@ -170,11 +171,21 @@ public void Intersects_WithBoundingFrustum_ReturnsTrueOnlyWhenOverlapping()
170171
Assert.False(disjoint.Intersects(frustum));
171172
}
172173

174+
[Fact]
175+
public void ContainsAndIntersects_NullFrustum_Throw()
176+
{
177+
var area = new BoundingArea(new Vector3d(-1, -1, -1), new Vector3d(1, 1, 1));
178+
179+
Assert.Throws<System.ArgumentNullException>(() => area.Contains((BoundingFrustum)null!));
180+
Assert.Throws<System.ArgumentNullException>(() => area.Intersects((BoundingFrustum)null!));
181+
}
182+
173183
[Fact]
174184
public void Contains_TypedBounds_ReturnsContainmentClassification()
175185
{
176186
var area = new BoundingArea(new Vector3d(-2, -2, 0), new Vector3d(2, 2, 2));
177187
var containedArea = new BoundingArea(new Vector3d(-1, -1, 0), new Vector3d(1, 1, 1));
188+
var disjointArea = new BoundingArea(new Vector3d(5, 5, 5), new Vector3d(6, 6, 6));
178189
var crossingBox = new BoundingBox(new Vector3d(2, 0, 1), new Vector3d(2, 1, 1));
179190
var containedSphere = new BoundingSphere(new Vector3d(0, 0, 1), Fixed64.Half);
180191
var crossingSphere = new BoundingSphere(new Vector3d(2, 0, 1), Fixed64.One);
@@ -183,14 +194,19 @@ public void Contains_TypedBounds_ReturnsContainmentClassification()
183194
var crossingFrustumMatrix = Fixed4x4.Identity;
184195
crossingFrustumMatrix.m30 = Fixed64.Two;
185196
var crossingFrustum = new BoundingFrustum(crossingFrustumMatrix);
197+
var disjointFrustumMatrix = Fixed4x4.Identity;
198+
disjointFrustumMatrix.m30 = new Fixed64(6);
199+
var disjointFrustum = new BoundingFrustum(disjointFrustumMatrix);
186200

187201
Assert.Equal(ContainmentType.Contains, area.Contains(containedArea));
202+
Assert.Equal(ContainmentType.Disjoint, area.Contains(disjointArea));
188203
Assert.Equal(ContainmentType.Intersects, area.Contains(crossingBox));
189204
Assert.Equal(ContainmentType.Contains, area.Contains(containedSphere));
190205
Assert.Equal(ContainmentType.Intersects, area.Contains(crossingSphere));
191206
Assert.Equal(ContainmentType.Disjoint, area.Contains(disjointSphere));
192207
Assert.Equal(ContainmentType.Contains, area.Contains(containedFrustum));
193208
Assert.Equal(ContainmentType.Intersects, area.Contains(crossingFrustum));
209+
Assert.Equal(ContainmentType.Disjoint, area.Contains(disjointFrustum));
194210
}
195211

196212
[Fact]
@@ -220,6 +236,66 @@ public void Intersects_FlatYZAreas_ReturnsTrue()
220236
Assert.True(area1.Intersects(area2));
221237
}
222238

239+
[Theory]
240+
[InlineData(0)]
241+
[InlineData(1)]
242+
[InlineData(2)]
243+
[InlineData(3)]
244+
public void Intersects_FlatXYAreas_ReturnsFalseForEachSeparatedAxisSide(int separatedSide)
245+
{
246+
var area = new BoundingArea(new Vector3d(0, 0, 0), new Vector3d(4, 4, 0));
247+
BoundingArea other = separatedSide switch
248+
{
249+
0 => new BoundingArea(new Vector3d(5, 1, 0), new Vector3d(6, 3, 0)),
250+
1 => new BoundingArea(new Vector3d(-6, 1, 0), new Vector3d(-5, 3, 0)),
251+
2 => new BoundingArea(new Vector3d(1, 5, 0), new Vector3d(3, 6, 0)),
252+
3 => new BoundingArea(new Vector3d(1, -6, 0), new Vector3d(3, -5, 0)),
253+
_ => throw new System.ArgumentOutOfRangeException(nameof(separatedSide)),
254+
};
255+
256+
Assert.False(area.Intersects(other));
257+
}
258+
259+
[Theory]
260+
[InlineData(0)]
261+
[InlineData(1)]
262+
[InlineData(2)]
263+
[InlineData(3)]
264+
public void Intersects_FlatXZAreas_ReturnsFalseForEachSeparatedAxisSide(int separatedSide)
265+
{
266+
var area = new BoundingArea(new Vector3d(0, 0, 0), new Vector3d(4, 0, 4));
267+
BoundingArea other = separatedSide switch
268+
{
269+
0 => new BoundingArea(new Vector3d(5, 0, 1), new Vector3d(6, 0, 3)),
270+
1 => new BoundingArea(new Vector3d(-6, 0, 1), new Vector3d(-5, 0, 3)),
271+
2 => new BoundingArea(new Vector3d(1, 0, 5), new Vector3d(3, 0, 6)),
272+
3 => new BoundingArea(new Vector3d(1, 0, -6), new Vector3d(3, 0, -5)),
273+
_ => throw new System.ArgumentOutOfRangeException(nameof(separatedSide)),
274+
};
275+
276+
Assert.False(area.Intersects(other));
277+
}
278+
279+
[Theory]
280+
[InlineData(0)]
281+
[InlineData(1)]
282+
[InlineData(2)]
283+
[InlineData(3)]
284+
public void Intersects_FlatYZAreas_ReturnsFalseForEachSeparatedAxisSide(int separatedSide)
285+
{
286+
var area = new BoundingArea(new Vector3d(0, 0, 0), new Vector3d(0, 4, 4));
287+
BoundingArea other = separatedSide switch
288+
{
289+
0 => new BoundingArea(new Vector3d(0, 5, 1), new Vector3d(0, 6, 3)),
290+
1 => new BoundingArea(new Vector3d(0, -6, 1), new Vector3d(0, -5, 3)),
291+
2 => new BoundingArea(new Vector3d(0, 1, 5), new Vector3d(0, 3, 6)),
292+
3 => new BoundingArea(new Vector3d(0, 1, -6), new Vector3d(0, 3, -5)),
293+
_ => throw new System.ArgumentOutOfRangeException(nameof(separatedSide)),
294+
};
295+
296+
Assert.False(area.Intersects(other));
297+
}
298+
223299
[Fact]
224300
public void ProjectPoint_ClampsPointWithinBounds()
225301
{
@@ -292,6 +368,7 @@ public void Inequality_AndObjectEqualityBehaveCorrectly()
292368
var area2 = new BoundingArea(new Vector3d(1, 2, 3), new Vector3d(4, 5, 7));
293369

294370
Assert.True(area1 != area2);
371+
Assert.True(area1.Equals((object)new BoundingArea(new Vector3d(1, 2, 3), new Vector3d(4, 5, 6))));
295372
Assert.False(area1.Equals("not-an-area"));
296373
}
297374

0 commit comments

Comments
 (0)