Platform library gaps + dogfood PackRef migration
Policies that keep the org coherent
name: Platform gaps PackRefs overview: Add the dogfood-driven platform types across Physics.Ballistics, Simulation (World/Builders/View), and Raylib.Game; bump local packages to 0.4.0-local; convert BouncingBall, DoomLite3D, and ArtillerySimulator to PackageReference-only consumers; refactor each app to use the new APIs. todos:
- id: physics-ballistics-core
content: Add IHeightSampler, AxisAlignedRangeBox, ProjectileTerrainStepper, BallisticTrajectoryRunner, tests, INTEGRATION.md; bump physics to 0.4.0-local status: completed
- id: simulation-heightfield-planar
content: Add BoundedHeightfield, HeightfieldMeshBuilder, PlanarOccupancy.TryRaycastDisc, tests; bump simulation to 0.4.0-local status: completed
- id: simulation-view-cameras
content: Add ViewPose, OrbitCameraRig, FreeLookCameraRig, TrackingCameraRig + tests status: completed
- id: raylib-diagnostics
content: Add DiagnosticsOverlay, SmoothedFps, optional DrawHeightfieldWires; bump raylib to 0.4.0-local status: completed
- id: pack-and-versions
content: Run pack-novolis-local.ps1; update dogfooding Directory.Packages.props to 0.4.0-local status: completed
- id: migrate-bouncingball
content: "BouncingBall: PackageReference-only csproj + DiagnosticsOverlay" status: completed
- id: migrate-doomlite
content: "DoomLite3D: PackageReference-only + TryRaycastDisc + ViewPose bridge + DiagnosticsOverlay" status: completed
- id: migrate-artillery
content: "ArtillerySimulator: PackageReference-only + library terrain/runner/cameras; delete ProjectileRun/BallisticArcPreview/ArtilleryCamera" status: completed
- id: validate-dogfood
content: Release build slnx + manual run acceptance for all three apps status: completed isProject: false
Goal
Implement the framework types identified from BouncingBall, DoomLite3D, and ArtillerySimulator, ship them via existing NuGet packages (no new repos), bump `0.4.0-local`, pack to `artifacts/nuget-local`, and migrate the three apps to `PackageReference` only per design.md.
Constraints (governance)
- Stack: Math → Physics → Simulation; Raylib must not reference Simulation (library-boundaries.md).
ViewPoselives in Simulation.View; GPU bridge stays a one-liner in apps:RayCamera.Perspective(pose.Position, pose.Target, pose.Up, pose.FieldOfView).- Procedural terrain styles (
TerrainHeightSampler,TerrainStyle) stay in the Artillery app; libraries takeIHeightSamplerdelegates.
flowchart TB
subgraph physics [novolis-physics]
IHeightSampler[IHeightSampler]
Stepper[ProjectileTerrainStepper]
Runner[BallisticTrajectoryRunner]
end
subgraph simulation [novolis-simulation]
BHF[BoundedHeightfield]
HFB[HeightfieldMeshBuilder]
Orbit[OrbitCameraRig]
Disc[PlanarOccupancy.TryRaycastDisc]
end
subgraph raylib [novolis-raylib]
Diag[DiagnosticsOverlay]
end
AS[ArtillerySimulator]
DL[DoomLite3D]
BB[BouncingBall]
IHeightSampler --> BHF
BHF --> HFB
Stepper --> Runner
HFB --> AS
Runner --> AS
BHF --> AS
Orbit --> AS
Disc --> DL
Diag --> BB
Diag --> DL
Diag --> ASPhase 1 — Physics.Abstractions + Physics.Ballistics (P0)
Repo: novolis-physics
New types (Novolis.Physics.Abstractions)
| Type | Role |
|---|---|
IHeightSampler | float SampleHeight(float x, float z) for terrain contact |
AxisAlignedRangeBox | XZ extent [0, extent] helpers used by bounded ballistics |
New types (Novolis.Physics.Ballistics)
| Type | Role | ||
|---|---|---|---|
ProjectileTerrainImpactReason | TerrainMesh, Heightfield, BeyondRange, MaxSteps | ||
ProjectileTerrainImpact | Position, velocity, time, horizontal range, reason | ||
ProjectileTerrainStepOptions | dt, sphere radius, max sweep per substep, max steps, substep budget | ||
ProjectileTerrainStepper | Canonical integrate-then-sweep loop matching `ProjectileSemiImplicitIntegrator`: displacement = candidate.Position - startPos; sub-split when ` | disp | > maxSweep; BallisticsQueries.SweepProjectileSphere`; heightfield + range-box checks |
BallisticTrajectoryRunner | Stateful runner: Begin / AdvanceWithBudget / trail buffer / Impact; shared by flight and preview | ||
HeightfieldContact | Segment height test + surface projection (replaces app-only TryHeightfieldContact / misuse of `GroundImpact` for terrain) | ||
SegmentImpactInterpolator | Lerp impact point/time/velocity along sweep hit (extract from test patterns in `CollisionSweepScenarioTests`) |
Bounded range behavior (encode range-limit fix): when segment exits AxisAlignedRangeBox, impact position = project XZ to box edge + `IHeightSampler` Y (same semantics as completed fix_range-limit plan), not in-air Y.
Tests
- New
ProjectileTerrainStepperTestsin Novolis.Physics.Unit: flat mesh hit, heightfield hit, range-box ground projection, large-step sub-split (parity withSweepLimitationScenarioTests). - Extend INTEGRATION.md §2 with “Terrain flight loop” section pointing at
ProjectileTerrainStepper(not hand-rolled sweeps).
Version
Bump `Novolis.Physics.Packaging.props` → `0.4.0-local`.
Phase 2 — Simulation.World + World.Builders (P1)
Repo: novolis-simulation
Novolis.Simulation.World
| Type | Role |
|---|---|
BoundedHeightfield | Wraps IHeightSampler + AxisAlignedRangeBox: IsInside, SampleHeight, TrySegmentLeavesRange, ProjectOntoSurface, TryHeightfieldContact (move logic from `TerrainWorld.cs`) |
WorldExtentOptions | ExtentMeters, collision grid resolution, draw decimation |
PlanarOccupancy.TryRaycastDisc + PlanarDiscHit | XZ ray vs columnar target (replaces `CombatRaycast`) |
Novolis.Simulation.World.Builders
| Type | Role |
|---|---|
HeightfieldMeshBuilder | Height grid → StaticTriangleMesh + BvhStaticWorld (extract from TerrainWorld.BuildMeshes) |
HeightfieldBuildResult | Collision BVH + optional decimated draw vertices |
Tests
BoundedHeightfieldTests,HeightfieldMeshBuilderTestsin simulation test projects.PlanarOccupancyDiscRaycastTests(hit/miss/max range).
Version
Bump `Novolis.Simulation.Packaging.props` → `0.4.0-local`.
Phase 3 — Simulation.View cameras (P2)
Repo: novolis-simulation
| Type | Role |
|---|---|
ViewPose | Position, Target, Up, FieldOfViewDegrees (platform observer output) |
OrbitCameraRig | Target, distance, yaw/pitch, smoothing → ViewPose |
FreeLookCameraRig | WASD-style free position + yaw/pitch → ViewPose |
TrackingCameraRig | Optional: smooth follow moving target (shot trail / gun pivot) |
Port behavior from `ArtilleryCamera.cs` into rigs; keep product FOV/speed constants in app.
Tests
OrbitCameraRigTests(snap, smooth, clamp pitch).
Phase 4 — Raylib.Game diagnostics (P4)
Repo: novolis-raylib
| Type | Role |
|---|---|
SmoothedFps | EMA FPS helper |
FrameDiagnostics | Working set, GC heap, optional custom lines |
DiagnosticsOverlay | F3 toggle, panel draw via RayGameContext |
Replace duplicated `DiagnosticsHud.cs` / DoomLite copy with overlay + app-specific Action<FrameDiagnostics> lines.
Optional (same phase if cheap): RayGameContext.DrawHeightfieldWires(vertices, cellsX, cellsZ) to replace Artillery bolt loop in `TerrainWorld.Draw`.
Version
Bump `Novolis.Raylib.Packaging.props` → `0.4.0-local`.
Phase 5 — Pack and central versions
- Run `scripts/pack-novolis-local.ps1` (math/physics/simulation/raylib in dependency order).
- Update novolis-dogfooding/Directory.Packages.props: all Novolis
0.3.0-local→ `0.4.0-local` (add any missing package IDs:Novolis.Physics.Ballisticsalready listed; ensureNovolis.Raylibpulls updated Game assembly).
Phase 6 — App migrations (PackageReference only)
Shared csproj pattern
Replace all ProjectReference to novolis-* with PackageReference (no version in csproj; central versions only). Keep app Program.cs + Game/ unchanged in structure.
| App | PackageReferences |
|---|---|
| BouncingBall.csproj | Novolis.Raylib, Novolis.Math.Arrays, Novolis.Simulation.World, Novolis.Simulation.World.Builders, Novolis.Physics.Collision.Simple |
| DoomLite3D.csproj | Novolis.Raylib, Novolis.Math.Arrays, Novolis.Math.Geometry, Novolis.Simulation.World, Novolis.Simulation.View, Novolis.Simulation.Kinematics |
| ArtillerySimulator.csproj | Novolis.Raylib, Novolis.Physics.Ballistics, Novolis.Physics.Collision.Simple, Novolis.Simulation.World, Novolis.Simulation.World.Builders, Novolis.Simulation.View |
BouncingBall
- Swap in
DiagnosticsOverlay(custom lines: ball count, contacts, substeps fromBallWorldstats). - No pile/spawn library work (optional
SpawnSphereLayoutdeferred).
DoomLite3D
EnemySystem/ weapon hit: usePlanarOccupancy.TryRaycastDisc; deleteCombatRaycast.cs.PlayerController.BuildRaylibCamera: build fromYawPitchController→ViewPose→RayCamera.Perspective(...).DiagnosticsOverlaywith enemy/level seed lines.
ArtillerySimulator (largest refactor)
| App file | Becomes |
|---|---|
| `TerrainWorld.cs` | Thin wrapper: BoundedHeightfield + HeightfieldMeshBuilder + app TerrainHeightSampler adapter implementing IHeightSampler; draw uses optional DrawHeightfieldWires |
| `ProjectileRun.cs` | Deleted — use BallisticTrajectoryRunner |
| `BallisticArcPreview.cs` | Deleted — BallisticTrajectoryRunner preview mode (coarser dt / point cap) |
| `ArtilleryCamera.cs` | Deleted — OrbitCameraRig + FreeLookCameraRig |
| `SimulationUnits.cs` | Keep as app tuning; pass into WorldExtentOptions |
| `AtmosphereModel.cs` | Unchanged (product preset over StandardAtmosphere) |
`ArtillerySimulatorGame.cs` wires runner + rigs + overlay only.
Docs
- Update design.md app table with new package columns.
- Update README.md quick start: pack → restore → build (no project-ref path for these three apps).
Validation
d:\novolis\scripts\pack-novolis-local.ps1
cd d:\novolis\novolis-dogfooding
dotnet build Novolis.Dogfooding.slnx -c Release
dotnet run --project apps/BouncingBall -c Release --no-build
dotnet run --project apps/DoomLite3D -c Release --no-build
dotnet run --project apps/ArtillerySimulator -c Release --no-buildArtillery acceptance: flat terrain + vacuum ≈ textbook range; high-angle shot at map edge shows ground Y at edge (range-limit projection); preview arc matches flight endpoint; no regression in sweep hits on hills.
DoomLite acceptance: hitscan still kills enemies; F3 diagnostics; movement unchanged.
BouncingBall acceptance: spawn/stress (B / Ctrl+B) unchanged; F3 diagnostics.
Explicitly out of scope
- New NuGet repos / “Kit” packages
- DoomLite maze/enemy/boss systems (stay in app)
SpawnSphereLayout/ dual-BVH documentation-only helpers (unless time remains in P4)- BridgeCommander / WireFishViewer csproj changes
- Pushing
0.4.0-localto nuget.org (local feed only)