Novolis Docs
novolis-governance / completed-plans/3d_doom_dogfood_c43061bc.plan.md

3D Doom-lite dogfood plan

dotnetgovernancenovolis

name: 3D Doom dogfood overview: Build a single-level, first-person 3D Doom-style demo in novolis-dogfooding that exercises Novolis.Raylib (3D + billboards + textures), Novolis.Math (grid map + FPS camera + 2D collision), and optionally Novolis.Physics (room mesh from grid). Fill library gaps discovered during implementation and attribute all external assets in CREDITS.md. todos:

  • id: gov-brief

content: Add wave-9-doom-dogfood extraction brief under novolis-governance status: completed

  • id: math-fps-collision

content: Implement FirstPersonCamera + GridCollision2D in Novolis.Math.Geometry with TUnit tests status: completed

  • id: raylib-billboard-plane

content: Add DrawBillboard/DrawBillboardPro/DrawPlane to raylib6 manifest, regenerate, extend RayGameContext (LoadTexture, DrawBillboard) status: completed

  • id: assets-credits

content: Vendor Kenney CC0 sprite subset into DoomLite3D/Assets and write CREDITS.md status: completed

  • id: dogfood-app

content: Create apps/DoomLite3D with level, player, enemies, HUD; register in Novolis.Dogfooding.slnx status: completed

  • id: physics-optional

content: "Phase 2: RoomMeshBuilder.FromWallGrid + BvhStaticWorld player sweep (optional after MVP runs)" status: completed isProject: false


You chose simplified 3D (Raylib BeginMode3D), not a classic 2.5D raycaster. That aligns with existing pieces: `XFighter` (RayGame.Run + BeginWorld/EndWorld), `DenseGrid`, and `RayGameContext`.

flowchart TB
  subgraph dogfood [novolis-dogfooding/apps/DoomLite3D]
    Game[DoomLiteGame]
    Map[LevelMap from DenseGrid]
    Player[PlayerController]
    Enemies[EnemyBillboards]
    Hud[WeaponHud]
  end
  subgraph math [novolis-math]
    Grid[DenseGrid wall map]
    FPC[FirstPersonCamera]
    Col[GridCollision2D]
  end
  subgraph raylib [novolis-raylib]
    RG[RayGame.Run]
    World[World.Begin + cubes/planes]
    Bill[DrawBillboardPro - new]
    Tex[Textures.Load]
  end
  subgraph physics [novolis-physics optional]
    Mesh[RoomMesh.FromWallGrid]
    BVH[BvhStaticWorld]
  end
  Game --> RG
  Map --> Grid
  Player --> FPC
  Player --> Col
  Game --> World
  Enemies --> Bill
  Hud --> Tex
  Map -.->|phase 2| Mesh
  Mesh -.-> BVH

Game scope (single level MVP)

FeatureImplementation
One dungeon levelHand-authored `DenseGrid<byte>` (0 = walkable, 1 = wall); ~20×20 cells
World geometryEach wall cell → `World.DrawCubeV` (1×2×1 m); floor → `DrawPlane` (new binding)
PlayerWASD on XZ plane, mouse look, `DisableCursor`; height ~1.7 m eye offset
Enemies3–5 positions; `DrawBillboardPro` with Kenney sprite textures; simple chase or idle
CombatLeft-click / Space: ray or distance check; enemy “dies” (hide billboard)
HUDCrosshair + optional weapon sprite (`DrawHudTexture`, same pattern as [`CockpitHud`](d:\novolis\novolis-raylib\samples\XFighter\Game\CockpitHud.cs))
ControlsEsc = exit, R = reset level (mirror XFighter)

Out of scope for v1: multiplayer, WAD loader, doors, audio (raylib sound not in generated bindings yet), full OBJ modular kit (depends on wave 7b).


Where code lives

LayerLocation
Dogfood app[`novolis-dogfooding/apps/DoomLite3D/`](d:\novolis\novolis-dogfooding\apps\DoomLite3D\) — new project + entry in [`Novolis.Dogfooding.slnx`](d:\novolis\novolis-dogfooding\Novolis.Dogfooding.slnx)
Reusable math[`novolis-math/src/Novolis.Math.Geometry/`](d:\novolis\novolis-math\src\Novolis.Math.Geometry\)
Reusable raylib[`novolis-raylib/pipeline/raylib6/`](d:\novolis\novolis-raylib\pipeline\raylib6\) manifests + codegen → `World` / `RayGameContext`
Optional physics helper[`novolis-physics/src/Novolis.Physics.Collision.Simple/`](d:\novolis\novolis-physics\src\Novolis.Physics.Collision.Simple\)
Governance brief[`novolis-governance/docs/extraction-briefs/wave-9-doom-dogfood.md`](d:\novolis\novolis-governance\docs\extraction-briefs\wave-9-doom-dogfood.md) (new)

References (dogfood csproj): ProjectReference to submodules novolis-raylib, novolis-math (Arrays + Geometry), and optionally novolis-physics — same pattern as `RaylibHello.csproj`.

Run:

cd d:\novolis\novolis-dogfooding
dotnet run --project apps/DoomLite3D/DoomLite3D.csproj

Library gaps to close

1. `novolis-math` — FPS camera + grid collision

Frank’s `FpsCameraState` is listed as do not migrate as-is in gameengine-reference-policy, but the idea is approved via selective mining with a Novolis redesign.

Add to Novolis.Math.Geometry:

  • `FirstPersonCamera`Vector3 Position, yaw/pitch radians, GetForward() / GetRight() on XZ + Y pitch, AddLookDelta(mouseDx, mouseDy, sensitivity), clamp pitch.
  • `GridCollision2D`TryMove(DenseGrid<byte> map, Vector2 pos, Vector2 delta, float radius) → slide against blocked cells (Doom-style grid blocking, no GameObject graph).

Tests: Novolis.Math.Geometry.Tests — known map, blocked corner slide, forward vector at yaw=0/π/2.

2. `novolis-raylib` — 3D sprites + jam API

Raylib 6 already exposes DrawBillboard* and DrawPlane in `vendor/raylib-6/include/raylib.h`, but they are not in generated `World.g.cs`.

API to add (codegen manifest)Why
`DrawBillboard` / `DrawBillboardPro`Enemy pickups, items
`DrawPlane`Floor/ceiling
(optional) `DrawCubeTexture`Textured walls later

Extend `RayGameContext` (`RayGame.cs`):

  • LoadTexture(path) / UnloadTexture — thin wrappers over Textures.Load (XFighter calls Textures directly; jam API should match DrawHudTexture ergonomics).
  • DrawBillboard(camera, texture, position, scale, tint) — requires active BeginWorld pass (camera parameter matches raylib).

Regenerate bindings: dotnet run pipeline/raylib6/run.cs all per novolis-raylib README.

Tests: extend Novolis.Raylib.Game.Unit or Runtime tests if headless billboard draw is feasible; at minimum compile + manual dogfood run.

3. `novolis-physics` — optional phase 2 dogfood

v1: use GridCollision2D only (fast, correct for axis-aligned cube walls).

v2 (validates physics package): add RoomMeshBuilder.FromWallGrid(DenseGrid<byte>, cellSize, wallHeight)StaticTriangleMeshBvhStaticWorld; player movement via SweepSphere on a capsule. This dogfoods `BvhStaticWorld` without pulling Frank’s scene graph.

4. Not needed for this demo

PackageReason
Classic 2D raycaster libYou chose 3D
`novolis-physics` in v1Grid collision suffices
OBJ / wave 7bGrid cubes are enough for one level; modular Kenney OBJ can follow wave 7b
Audio bindingsDefer unless you explicitly want gunshot in v1

Assets (GitHub / Kenney, CC0)

Vendor a small subset into apps/DoomLite3D/Assets/ (committed or downloaded via script — prefer committed PNGs for reproducible CI).

AssetSourceLicenseUse
Enemy / item sprites[Kenney — Tiny Dungeon](https://kenney.nl/assets/tiny-dungeon)[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/)Billboards
Weapon / UISame pack or [Roguelike Caves & Dungeons](https://kenney.nl/assets/roguelike-caves-dungeons)CC0HUD overlay
(Optional later) Modular pieces[Modular Dungeon Kit](https://kenney.nl/assets/modular-dungeon-kit)CC0After OBJ helper ships

Attribution file: apps/DoomLite3D/Assets/CREDITS.md (mirror `novolis-security CREDIT.md` style):

## Kenney — Tiny Dungeon
- Source: https://kenney.nl/assets/tiny-dungeon
- License: CC0 1.0 Universal (https://creativecommons.org/publicdomain/zero/1.0/)
- Files used: <list filenames vendored in Assets/>

Do not use id Software Doom/Wolfenstein copyrighted WAD art.

Optional script: apps/DoomLite3D/scripts/fetch-kenney-assets.ps1 documents download URL + which files to copy (developer convenience, not required at runtime if assets are committed).


App structure (dogfood)

apps/DoomLite3D/
  Program.cs              # RayGame.Run("Doom Lite", 1280, 720, init, update)
  DoomLite3D.csproj
  Assets/
    CREDITS.md
    enemies/*.png
    weapon.png
  Game/
    DoomLiteGame.cs       # loop orchestration
    LevelMap.cs           # grid → wall positions, spawn points
    LevelRenderer.cs      # cubes + plane + billboards
    PlayerController.cs   # input + FirstPersonCamera + GridCollision2D
    EnemySystem.cs
    WeaponHud.cs

Camera bridge: PlayerController.BuildRaylibCamera() maps FirstPersonCameraNovolis.Raylib.Rendering.Camera.Perspective (XFighter’s `PlayerFlight.BuildCamera` pattern, with yaw/pitch).


Governance

Add brief `wave-9-doom-dogfood.md`:

  • Documents what was extracted vs what stays in the app
  • Confirms no Frank.GameEngine.Rendering.* migration
  • Links dogfood app as acceptance test for math + raylib billboard APIs

Acceptance criteria

  • dotnet build / dotnet run from novolis-dogfooding on Windows (desktop; native raylib DLL)
  • Walk one level, look with mouse, shoot enemies, reset with R
  • CREDITS.md lists every external PNG and source URL
  • New math APIs have TUnit tests
  • New raylib APIs appear in generated World and RayGameContext after codegen