diff --git a/docs/superpowers/specs/2026-05-19-indoor-cell-rendering-fix-design.md b/docs/superpowers/specs/2026-05-19-indoor-cell-rendering-fix-design.md index 3b1c018..f433375 100644 --- a/docs/superpowers/specs/2026-05-19-indoor-cell-rendering-fix-design.md +++ b/docs/superpowers/specs/2026-05-19-indoor-cell-rendering-fix-design.md @@ -98,23 +98,74 @@ Six untested hypotheses, in rough order of probability: ## 3. Solution -**Phase 1: Diagnostics.** Add a runtime-toggleable `ACDREAM_PROBE_INDOOR=1` -env-var (mirrored as a DebugPanel checkbox) that prints one line per frame -with: +### Phase 1 — Diagnostics (this phase's work) -- Number of cell entities walked by the dispatcher. -- Per-cell-entity: `TryGetRenderData(envCellId)` hit/miss. -- On hit: `renderData.IsSetup`, `renderData.SetupParts.Count`. -- For each SetupPart: `TryGetRenderData(partGfxObjId)` hit/miss. -- The composed world matrix for the cell-geometry part (so we can see - where the floor actually ends up in world space). -- Whether the entity was culled by `visibleCellIds` (and why). +Five probes, each individually toggleable via env-var + DebugPanel +checkbox. The probes live in a new +`AcDream.Core.Rendering.RenderingDiagnostics` static class (mirroring +the `AcDream.Core.Physics.PhysicsDiagnostics` pattern shipped in L.2a) +so they're discoverable from one place and survive across the +Core / App seam. -Run the client, walk into Holtburg Inn, capture probe output. The log -tells us exactly which step in the chain is breaking. +Each probe is **rate-limited**: by default, one line per (envCellId, +frame-modulo-30) — i.e., once per second per cell at 30 Hz — to avoid +log spam. When `ACDREAM_PROBE_INDOOR_VERBOSE=1` is also set, the +rate-limit drops and every frame logs. -**Phase 2: Fix the specific break.** Once the probe identifies the -failure point, implement the surgical fix. Likely shapes per hypothesis: +| Env var (and DebugPanel mirror) | Probe | Code location | Line format | +|---|---|---|---| +| `ACDREAM_PROBE_INDOOR_WALK` | Cell-entity dispatcher walk | `WbDrawDispatcher.WalkVisibleEntities` (rate-limited per cellId) | `[indoor-walk] cellEnt=0xID pos=(x,y,z) parentCell=0xID landblockVisible=B aabbVisible=B cellInVis=B drawn=B` | +| `ACDREAM_PROBE_INDOOR_LOOKUP` | Render-data lookup for cell entities | `WbDrawDispatcher.DrawAccumulated` per cell entity | `[indoor-lookup] cellId=0xID hit=B isSetup=B partCount=N hasEnvCellGeom=B partsHit=N partsMiss=N` | +| `ACDREAM_PROBE_INDOOR_UPLOAD` | WB upload result for envCellId | `WbMeshAdapter.IncrementRefCount` (on first call per id) + a callback hooked into `_meshManager.Tick()` for completion | `[indoor-upload] cellId=0xID requested=true completed=B partsCount=N cellGeomVerts=N error="..."` | +| `ACDREAM_PROBE_INDOOR_XFORM` | Composed world transform for cell-geometry SetupPart | `WbDrawDispatcher` inside the `IsSetup` branch at line 607-621, for partGfxObjId matching `(envCellId | 0x1_00000000UL)` | `[indoor-xform] cellId=0xID cellOrigin=(x,y,z) entityWorld=(...) partTransform=(...) composed=(x,y,z y-axis,z-axis) detExpected≈1 detActual=F` | +| `ACDREAM_PROBE_INDOOR_CULL` | Visibility / cull decision per cell entity | `WbDrawDispatcher.WalkVisibleEntities` (the two filter sites at lines 304-305 and 317-319) | `[indoor-cull] cellEnt=0xID reason="visibleCellIds-miss" or "frustum" or "served" details="..."` | + +The five probes can be enabled independently or together. The user's +common case is `ACDREAM_PROBE_INDOOR_ALL=1` which sets all five at +once. + +#### Implementation outline + +1. **New file** `src/AcDream.Core/Rendering/RenderingDiagnostics.cs` — + five static `bool` properties, each backed by an env-var read at + startup, each runtime-settable from the DebugPanel. +2. **DebugPanel section** — new "Indoor rendering diagnostics" block + in the existing DebugPanel "Diagnostics" group, with one checkbox + per probe + a master "all" toggle. +3. **WbDrawDispatcher edits** — instrument the walk and the IsSetup + draw branch. The walk probe needs to know whether the entity passed + the cell-visibility filter; the cull probe needs the same data. + Cleanest: emit BOTH lines in one place when either probe is on. +4. **WbMeshAdapter edits** — `IncrementRefCount` logs an `[indoor-upload] + requested=true` line when the id is recognized as an EnvCell + (high-bit check `(id & 0xFFFF) >= 0x0100`). On Tick(), when a + completion drains for an envCellId, log the result line with the + actual ObjectMeshData/ObjectRenderData fields. +5. **No GameWindow changes** beyond passing the diagnostics class + into the dispatcher (if not already accessible). + +#### Capture procedure + +1. Build with the probe instrumentation. `dotnet build` green. +2. Launch with `ACDREAM_PROBE_INDOOR_ALL=1`. Walk to Holtburg Inn, + stand at the doorway, then step inside, then walk around the room. +3. Stop the client, grep `launch.log` for `[indoor-*]` lines. +4. The captured log identifies WHICH hypothesis matches: + - **H1 (null upload)** → `[indoor-upload] completed=false` + - **H2 (empty batches)** → `[indoor-upload] cellGeomVerts=0` + - **H3 (cull bug)** → `[indoor-cull] reason="visibleCellIds-miss"` + - **H4 (double-spawn)** → `[indoor-lookup] partCount` includes + static-object IDs that ALSO appear in `landblock.Entities` + - **H5 (transform double-apply)** → `[indoor-xform] composed` + world position lands at `2 × cellOrigin` instead of `cellOrigin` + - **H6 (MeshRefs structure)** → ruled out; probe data would still + surface it as `hit=true isSetup=true partCount=N` followed by + all `partsHit=0` + +### Phase 2 — Fix the specific break (next phase) + +Once the probe identifies the failure point, implement the surgical +fix. Likely shapes per hypothesis: | Hypothesis | Fix shape | |---|---| @@ -170,16 +221,28 @@ bug), tests verify visibility BFS for indoor entities. **Phase 1 (this phase):** -- [ ] `ACDREAM_PROBE_INDOOR=1` env var + DebugPanel mirror. -- [ ] One log line per frame, per cell entity, showing render-data lookup - results, SetupParts traversal, and composed transforms. +- [ ] `AcDream.Core.Rendering.RenderingDiagnostics` static class created + with five `bool` properties + master `IndoorAll` toggle, each backed + by an env-var read at startup and runtime-settable. +- [ ] DebugPanel "Diagnostics" group has a new "Indoor rendering" + subsection with six checkboxes (five probes + master). +- [ ] `WbDrawDispatcher` emits `[indoor-walk]`, `[indoor-lookup]`, + `[indoor-xform]`, `[indoor-cull]` lines when the respective probe + is on. Rate-limited to ~1/sec per cell unless verbose mode active. +- [ ] `WbMeshAdapter` emits `[indoor-upload]` lines for EnvCell IDs: + one `requested` line on first `IncrementRefCount`, one `completed` + line when WB's Tick drains the result (success or failure). +- [ ] `dotnet build` clean. `dotnet test` clean (the diagnostics-only + change should not affect any test). - [ ] Probe captured at Holtburg Inn confirms which hypothesis matches. -- [ ] Phase 2 design (amended spec or new spec) documents the surgical fix. + Capture procedure documented in §3 above. +- [ ] Phase 2 design (amended spec or new spec) documents the surgical + fix matched to the identified hypothesis. **Phase 2 (next phase, driven by Phase 1 output):** - [ ] `dotnet build` clean, `dotnet test` clean. -- [ ] Visual verification: walking into Holtburg Inn renders interior floor + - walls correctly. +- [ ] Visual verification: walking into Holtburg Inn renders interior + floor + walls correctly. - [ ] Roadmap updated. -- [ ] Probe left in place for future regressions but defaulted off. +- [ ] Probes left in place for future regressions but defaulted off.