Novolis Docs
novolis-governance / completed-plans/math_libs_bcl_refactor_fc747904.plan.md

Math libraries: BCL-first, no dimension suffixes

dotnetgovernancenovolis

name: Math libs BCL refactor overview: Refactor novolis-math into BCL-first, dimension-suffix-free public APIs (Ray, Sphere, AxisAlignedBox), extract Novolis.Math.Topology, centralize intersection/BVH in Geometry, then update Physics/Rendering consumers and governance docs in a coordinated NuGet publish wave. todos:

  • id: docs-naming-policy

content: Update novolis-math/docs/design.md and library-boundaries.md with facet DAG, no 3/2D public API rule, and Ray/Sphere/AxisAlignedBox names status: completed

  • id: topology-package

content: Create Novolis.Math.Topology project; move Polygon/Edge/Face/Shape + factories; Geometry references Topology; slnx + tests status: completed

  • id: rename-primitives

content: Rename Ray3/Sphere3/AxisAlignedBox3 to Ray/Sphere/AxisAlignedBox in math and all consumers (no obsolete Ray3 public type) status: completed

  • id: intersection-bvh-api

content: Public AxisAlignedBox.RayInterval + BvhRaycast; refactor TriangleBvh to use shared traversal; add parity unit tests status: completed

  • id: pose-helpers

content: Add RigidTransform and ViewBasis; obsolete mutable Transform; remove unused Math.Camera and Math.Grid if unreferenced status: completed

  • id: physics-consolidate

content: BvhStaticWorld uses TriangleBvhBuilder + shared raycast; delete Physics TriangleRay duplicate; update ObsoleteNumerics messages status: completed

  • id: rendering-consolidate

content: Unify BvhNode with TriangleBvhNode; PathTracerEngine uses Math traversal; CameraSnapshot uses ViewBasis on host status: completed

  • id: cross-repo-sweep

content: Update simulation, dogfooding DoomLite3D, Directory.Packages.props, templates; verify-nuget-only + build/test + pack publish wave status: completed isProject: false


North star

Math owns spatial algorithms on `System.Numerics`, in small facets. Public API rules:

  • BCL types at the surface (Vector3, Quaternion, Matrix4x4, Plane).
  • No `*3` / `*2D` suffixes on public Math types or members (stack is 3D-only; planar work uses Vector3 with Y = 0 via `Vector3PlanarExtensions.cs`).
  • No `Vector2` in stack code (existing governance).
  • No obsolete public shims named `Ray3` — breaking rename in one wave (avoid perpetuating forbidden names).

Backend-only duplicates (Float3 in ILGPU) stay in `Novolis.Rendering.Backends.Igpu`.

flowchart BT
  Arrays[Novolis.Math.Arrays]
  Topology[Novolis.Math.Topology]
  Geometry[Novolis.Math.Geometry]
  Topology --> Arrays
  Geometry --> Arrays
  Geometry --> Topology
  Physics[Novolis.Physics.*]
  Rendering[Novolis.Rendering.*]
  Physics --> Geometry
  Rendering --> Geometry

Phase 1 — Governance and math design docs

Update canonical policy so agents and consumers match implementation:

DocChanges
[`novolis-math/docs/design.md`](d:\novolis\novolis-math\docs\design.md)Facet DAG, naming rules (no `*3`/`*2D` in public API), BCL-first, what stays out of Math
[`novolis-governance/docs/library-boundaries.md`](d:\novolis\novolis-governance\docs\library-boundaries.md)Replace `Ray3`/`Sphere3` examples with `Ray`/`Sphere`; add `Novolis.Math.Topology`; BVH ownership
Package READMEs[`Novolis.Math.Geometry/README.md`](d:\novolis\novolis-math\src\Novolis.Math.Geometry\README.md), new Topology README

Add a short naming table (single source of truth):

Old (remove)New
`Ray3``Ray`
`Sphere3``Sphere`
`AxisAlignedBox3``AxisAlignedBox`
(new)`RigidTransform`
(new)`ViewBasis`

Files to rename in Geometry: `Ray3.cs`Ray.cs, `Sphere3.cs`Sphere.cs, `AxisAlignedBox3.cs`AxisAlignedBox.cs.


Phase 2 — Extract `Novolis.Math.Topology`

New project: src/Novolis.Math.Topology/Novolis.Math.Topology.csproj (packable, net10.0, no deps except BCL).

Move from Geometry (namespace Novolis.Math.Topology):

Keep in Geometry (metric / queries):

  • `TriangleMesh.cs`, primitives, transforms, intersection, BVH, lattice types, Rgba32, serializers

Wire solution: `Novolis.Math.slnx` + `Directory.Packages.props`.

Geometry csproj: ProjectReference → Topology (Topology does not reference Geometry).

Move tests: PolygonTest, PolygonFactoryTeststests/Novolis.Math.Unit/Topology/.


Phase 3 — Centralize intersection and BVH in Geometry

Today `TriangleBvh.cs` keeps RaySlabIntersect private while `PathTracerEngine.cs` and `BvhStaticWorld.cs` duplicate ~80 lines each.

Add public APIs (names without dimension suffix):

  1. `AxisAlignedBox.RayInterval` (or static RayExtensions.IntervalAgainstBox) — slab test used by BVH and shadow rays.
  2. `TriangleBvhNode` — already suffix-free; keep as the single node struct.
  3. `TriangleBvhBuilder.Build` — unchanged entry; returns TriangleBvh.
  4. `BvhRaycast` (static helper) — traverse ReadOnlySpan<TriangleBvhNode> + triangleOrder + callback bool TryHitTriangle(int triIndex, in Ray ray, float maxT, out float t, out Vector3 normal) so material index lives in the callback (Rendering) while Physics uses triangle index only.

Refactor `TriangleBvh.Raycast` to call shared traversal internally.

Add unit tests: brute vs BVH parity, analytic ray–box, ray–triangle edge cases; shared epsilon via small GeometryConstants type.


Phase 4 — BCL-centric pose helpers (no camera types)

Add (Geometry, System.Numerics only):

  • `RigidTransform`readonly struct with Vector3 Position, Quaternion Rotation, float UniformScale; ToMatrix4x4(), TransformPoint/TransformDirection.
  • `ViewBasis`FromLookAt(eye, target, upHint) → orthonormal (Forward, Right, Up); `PrimaryRayDirection(basis, u, v, tanHalfFov, aspect)` for trace kernels.

Do not add RigidTransform3, ViewBasis3, or new Camera types.

Remove unused obsolete surface from Geometry if unreferenced after grep: `Camera.cs`, `Grid.cs` (Simulation already uses `DenseGrid<T>`).

Mark mutable `Transform` class [Obsolete("Use RigidTransform")]; keep for one release if templates still reference it, then migrate callers.


Phase 5 — Physics consolidation

FileAction
[`BvhStaticWorld.cs`](d:\novolis\novolis-physics\src\Novolis.Physics.Collision.Simple\BvhStaticWorld.cs)Delete private `BuildRecursive` / `BvhNode` / slab copy; build with `TriangleBvhBuilder` over mesh vertices/indices; traverse via `BvhRaycast` or wrap `TriangleBvh`
[`TriangleRay.cs`](d:\novolis\novolis-physics\src\Novolis.Physics.Collision.Simple\TriangleRay.cs)Remove duplicate Möller–Trumbore; call `Novolis.Math.Geometry.TriangleRay.TryHit` with `float` distance (cast at boundary if `HitInfo` needs `double`)
[`ObsoleteNumericsForwards.cs`](d:\novolis\novolis-physics\src\Novolis.Physics.Numerics\ObsoleteNumericsForwards.cs)Update obsolete messages: `Ray3` → `Ray`
Abstractions / testsRename `Ray3`/`Sphere3`/`AxisAlignedBox3` → new names (~12 files under `novolis-physics`)

Physics packages already reference Geometry only — no new Topology reference unless a project uses Polygon directly.


Phase 6 — Rendering consolidation

AreaAction
[`SceneCompiler.cs`](d:\novolis\novolis-rendering\src\Novolis.Rendering.Compile\SceneCompiler.cs)Map `TriangleBvhNode` directly into compiled scene (drop duplicate [`BvhNode`](d:\novolis\novolis-rendering\src\Novolis.Rendering.Runtime\BvhNode.cs) struct or make it a type alias to `TriangleBvhNode`)
[`PathTracerEngine.cs`](d:\novolis\novolis-rendering\src\Novolis.Rendering.Backends.Cpu\PathTracerEngine.cs)Replace local `TraverseBvh` / `RaySlabIntersect` with Math `BvhRaycast` + `TriangleRay`; use `ViewBasis` in [`CameraSnapshot`](d:\novolis\novolis-rendering\src\Novolis.Rendering.Runtime\CameraSnapshot.cs) `LookAt`
[`IlgpuPathTracerKernels.cs`](d:\novolis\novolis-rendering\src\Novolis.Rendering.Backends.Igpu\IlgpuPathTracerKernels.cs)Keep `Float3` on device; **host** uses `Ray`/`AxisAlignedBox`; optional follow-up to align GPU slab math with tested CPU helper (comments cite parity)
GPU layout structs`GpuBvhNode` continues to use `Float3` bounds — adapter only

Out of scope: moving GGX/glass/sky into Math (Rendering-only).


Phase 7 — Cross-repo consumer sweep

Mechanical rename in all .cs referencing old types (~25 files today):

  • novolis-physics (abstractions, collision, ballistics, tests)
  • novolis-rendering (compile, runtime, CPU backend)
  • novolis-simulation (`PlanarAgent.cs`, builder tests)
  • `DoomLite3D/PlayerController.cs` — delete local Ray3 struct; use Novolis.Math.Geometry.Ray

Bump central package versions where needed:

MonoGame template content referencing Polygon may need PackageReference to Topology or Geometry (transitive).


Phase 8 — Verification and publish

Per workspace rule (`nuget-only-dependencies`):

  1. pwsh -File novolis-governance/scripts/verify-nuget-only.ps1 (exit 0)
  2. dotnet build / dotnet test `Novolis.Math.slnx`, then Physics + Rendering solutions
  3. Local pack feed or GPR publish Novolis.Math.Arrays, Novolis.Math.Topology, Novolis.Math.Geometry (2026.1.*)
  4. dotnet restore + dotnet build consumers with package refs only

Breaking change note: no public Ray3 obsolete alias (forbidden name). Release notes list rename table and Topology package install (dotnet add package Novolis.Math.Topology only when using polygon APIs directly; Geometry consumers get Topology transitively).


Risk notes

  • `Ray` name: lives in Novolis.Math.Geometry namespace — no BCL Ray conflict; avoid using aliases that pull in unrelated Ray types.
  • ILGPU: device code may keep duplicated slab/triangle until a shared-source or codegen step exists; CPU/Physics path must be single implementation.
  • Coordinate publish order: Math packages first, then Physics/Rendering PRs that bump PackageReference versions.