feat(ui): debug overlay + refined input controls

Adds the first on-screen HUD for the dev client plus today's mouse-control
refinements. Also lands yesterday's scenery-alignment changes that were
left uncommitted in the working tree.

Overlay:
- BitmapFont rasterizes a system TTF via StbTrueTypeSharp into a 512x512
  R8 atlas at startup (Consolas on Windows, DejaVu/Menlo fallbacks)
- TextRenderer batches 2D quads in screen-space with ortho projection;
  one shader + two draw calls (rect then text) for panel backgrounds
  under glyphs
- DebugOverlay composes info / stats / compass / help panels on top of
  the 3D scene; toggles via F1/F4/F5/F6; transient toasts for key events
- DebugLineRenderer and its shaders (carried over from the scenery work)
  are properly committed in this commit

Controls:
- Per-mode mouse sensitivity (Chase 0.15, Fly 1.0, Orbit 1.0); F8/F9 to
  adjust the active mode multiplicatively (x1.2)
- Hold RMB to free-orbit the chase camera around the player; release
  stays at the new angle (no snap-back)
- Mouse-wheel zooms chase distance between 2m and 40m
- Chase pitch widened to [-0.7, 1.4] so mouse-Y tilts both ways from
  the default neutral angle

Scenery alignment (carried from yesterday's session):
- ShadowObjectRegistry AllEntriesForDebug + Scale field
- SceneryGenerator uses ACViewer's OnRoad polygon test + baseLoc +
  set_heading rotation
- BSPQuery dispatchers accept localToWorld so normals/offsets transform
  correctly per part
- TransitionTypes.CylinderCollision rewritten with wall-slide + push-out
- PhysicsDataCache caches visual-mesh AABB for scenery that lacks
  physics Setup bounds
This commit is contained in:
Erik 2026-04-17 18:45:38 +02:00
parent 6b4e7569a3
commit ff325abd7b
20 changed files with 2734 additions and 268 deletions

View file

@ -0,0 +1,169 @@
# Session 2026-04-16 — Scenery Alignment + Collision Registration
## Headline result
**Scenery alignment + collision finally in a working state.**
- All outdoor entities get collision cylinders
- Trees in roads mostly fixed
- Trees snapped to terrain (triangle-aware Z sample matching player physics)
- Visual-to-collision position alignment correct
## Critical root causes found this session
### 1. Scenery entity ID collision across landblocks
**File:** `src/AcDream.App/Rendering/GameWindow.cs` `BuildSceneryEntitiesForStreaming`
Old: `sceneryIdBase = 0x80000000 | (landblockId & 0x00FFFF00)` — the mask only
captured bits 8-23 which always has `0xFF` in the low byte (from the lb id
suffix). Result: every landblock with the same Y coord produced scenery with
**identical IDs**. When streaming loaded the next landblock, `ShadowObjects
.Register` called `Deregister(entityId)` first, wiping out the previous
landblock's collision. Only the LAST-loaded landblock had collision for any
given ID.
Fix: `0x80000000 | (lbXByte << 16) | (lbYByte << 8)` — each landblock gets a
unique ID namespace. Every scenery entity now uniquely identified.
### 2. Scenery generator using wrong terrain-Z formula
**File:** `src/AcDream.App/Rendering/GameWindow.cs` `SampleTerrainZ`
My earlier "triangle-aware" terrain sample had wrong barycentric math.
Physics's `TerrainSurface.SampleZ` used different (correct) math derived
from the WorldBuilder mesh index buffer. So player walked at one Z,
scenery placed at another → trees hovered above ground.
Fix: Ported WorldBuilder `TerrainUtils.GetHeight` exactly — same split
direction formula, same barycentric triangle pair, same interpolation.
Both scenery and player now use identical height sampling.
### 3. Collision cylinder position using AABB center, not entity root
Multi-part scenery meshes can have parts offset from the mesh origin. The
visual mesh AABB center is NOT the entity's rendered position. Using
AABB center for the collision cylinder put collision meters away from the
visible mesh.
Fix: Collision cylinder center = `entity.Position` (the rendered origin).
Radius from the retail Setup CylSphere when available, clamped to
[0.3, 1.5] m.
### 4. Scenery iterated 81 vertices, retail iterates 64 cells
**File:** `src/AcDream.Core/World/SceneryGenerator.cs`
Decompiled FUN_005311a0 iterates `local_8c < 8 && local_94 < 8` — 64 cells.
We iterated 0..8 on both axes (81 vertices). Extra scenery at landblock
seams.
Fix: `for (int x = 0; x < CellsPerSide; x++)` where `CellsPerSide = 8`.
Note: WorldBuilder iterates 81, ACViewer iterates 81. Retail iterates 64.
Currently matching retail decompiled — may need revisiting if we see seam
gaps.
### 5. Road check was per-vertex nearest-neighbor, should be 4-corner polygonal
**File:** `src/AcDream.Core/World/SceneryGenerator.cs` `IsOnRoad`
ACE-server's simplified road test uses single-vertex `(terrain & 0x3)` check.
That's wrong — retail uses a 4-corner polygonal test with 5m road ribbon
(`FUN_00530d30`).
Fix: Direct port of ACViewer's `Landblock.OnRoad` (lines 300-398) with
16-case corner-configuration dispatch and 5m `RoadHalfWidth`. Also kept an
extra "origin-cell has road vertex" guard since retail's exact threshold
constants (`_DAT_007c97cc/...`) weren't in dumped chunks.
### 6. Pre-displacement road check was wrong
Retail doesn't skip a vertex based on its own road bit — it rolls displacement
then tests the final position against OnRoad. My code had `if (IsRoadVertex(raw))
continue;` which silently dropped scenery retail would have kept.
Fix: Removed. The post-displacement OnRoad test is the only road check.
### 7. Rotation missing baseLoc + (450-heading)%360 flip
**File:** `src/AcDream.Core/World/SceneryGenerator.cs`
Retail's `AFrame::set_heading(degrees)` transforms `yaw = -(450 - heading) % 360`
before building the quaternion. It also composes with `BaseLoc.Orientation`.
Fix: Applied both. Rotations now match retail for asymmetric scenery.
### 8. Stabs in buildingCells
**File:** `src/AcDream.App/Rendering/GameWindow.cs`
Was treating `lbInfo.Objects` (stabs) as buildings, over-suppressing scenery
in town landblocks. Retail only suppresses scenery in cells with actual
`lbInfo.Buildings`.
Fix: Only `Buildings` contribute to `buildingCells`.
## Still imperfect (for tomorrow)
- Some scenery still hovers slightly above ground in certain cells.
Probably a minor split-direction edge case or BaseLoc.Z handling quirk.
- User wants a real debug overlay (on-screen text/markers with positions,
directions, distances, etc.). Currently has F3 console dump + cyan debug
wireframes from the DebugLineRenderer.
- Collision is now consistent but "feel" needs tuning against retail.
- Large trees with BSP on canopy still rely on our CylSphere fallback
rather than a retail-faithful dual-test (BSP + CylSphere).
## Pickup for next session
User explicitly said next session:
1. **Debug overlay UI** — on-screen text for player pos, direction, heading
in degrees, nearest object distances, "am I colliding" indicator. The
current approach (F3 console dump, wireframes in 3D world) is insufficient
because the user doesn't know "what 40m looks like" visually.
2. **Controls** — make working with the client easier. Presumably: keybind
list, camera controls, debug toggles, maybe a tool palette.
## Files changed this session
- `src/AcDream.App/Rendering/GameWindow.cs` — scenery ID namespace, OnRoad,
visual-mesh-AABB collision, triangle-aware SampleTerrainZ, debug wireframes,
F3 dump, building/stab separation, DebugLineRenderer wiring.
- `src/AcDream.App/Rendering/DebugLineRenderer.cs` (new) — minimal GL line
renderer for wireframe debug.
- `src/AcDream.App/Rendering/Shaders/debug_line.{vert,frag}` (new) — debug
line shader pair.
- `src/AcDream.Core/World/SceneryGenerator.cs` — 64-cell iteration, ACViewer
OnRoad, baseLoc+heading rotation, BaseLoc.Z passthrough, removed
pre-displacement road check.
- `src/AcDream.Core/World/WorldEntity.cs` — added `Scale` field for scenery.
- `src/AcDream.Core/Physics/PhysicsDataCache.cs``GfxObjVisualBounds` +
`GetVisualBounds` for mesh AABB fallback.
- `src/AcDream.Core/Physics/ShadowObjectRegistry.cs``AllEntriesForDebug()`
for the debug overlay, `Scale` on ShadowEntry.
- `src/AcDream.Core/Physics/BSPQuery.cs``localToWorld` rotation parameter
on all dispatcher methods so collision normals/offsets transform correctly.
- `src/AcDream.Core/Physics/TransitionTypes.cs``CylinderCollision` rewritten
with wall-slide + push-out, `FindObjCollisions` rewritten per-object with
retail 6-path dispatcher, TransitionalInsert retry loop.
## Key reference pointers (for tomorrow)
- WorldBuilder scenery: `references/WorldBuilder/Chorizite.OpenGLSDLBackend/
Lib/SceneryRenderManager.cs` + `SceneryHelpers.cs`.
- WorldBuilder terrain: `references/WorldBuilder/WorldBuilder.Shared/
Modules/Landscape/Lib/TerrainUtils.cs` (GetHeight, GetNormal, OnRoad).
- Retail decompiled collision: `docs/research/decompiled/chunk_00530000.c`
FUN_005311a0 (scenery loop) and FUN_00530d30 (OnRoad).
- Setup field layout: `docs/research/decompiled/chunk_00510000.c` getter
thunks ~line 7563-7662 (CylSpheres at +0x48, Radius +0x64, etc.).
## Collision pipeline overview (current state)
1. **Scenery generation** (`SceneryGenerator.Generate`) — 64 cells per lb,
LCG-deterministic displacement, OnRoad + building cell filter, slope
filter, returns `ScenerySpawn{ObjectId, LocalPosition, Rotation, Scale}`.
2. **Scenery hydration** (`BuildSceneryEntitiesForStreaming`) — samples
terrain Z via `_physicsEngine.SampleTerrainZ`, builds `WorldEntity`.
3. **Collision registration** (end of `ApplyLoadedTerrain`) — for every
outdoor mesh entity: pick radius from Setup.CylSphere → Setup.Radius →
mesh AABB fallback. Height from Setup.Height or mesh. Register cylinder
at `entity.Position` in `ShadowObjectRegistry`.
4. **Collision query** (`Transition.FindObjCollisions`) — player sphere
sweeps via `GetNearbyObjects` which searches player's lb + 8 neighbors.
Per-object dispatch: BSP → `BSPQuery.FindCollisions` (retail 6-path),
Cylinder → `CylinderCollision` (wall-slide + push-out).
Good night.