handoff: M1.5 dungeon support (G.3) grounded — design research + the terrain-less-premise refutation
Adds the 2026-06-13 dungeon-G.3 handoff doc + a dat-probe test that RESOLVED the pivotal design ambiguity. A research agent assumed dungeon landblocks are terrain-less (LandblockLoader.Load returns null -> "rewrite the pipeline for terrain-less landblocks", 13 seams). The dat probe refutes it: dungeon landblock 0x0125 has a flat (all-zero-height) LandBlock record PLUS 71 EnvCells and no buildings/objects -> it streams fine via the existing pipeline as a flat-terrain landblock. The real blocker (#133) is narrow: the teleport-arrival handler (GameWindow.cs:4928) snaps the player via physics.Resolve BEFORE the dungeon landblock streams in -> Resolve falls back to the resident Holtburg landblocks -> places the player at A9B3 ocean. Fix shape: hold-until-hydration (reuse the #107 IsSpawnCellReady gate for the teleport-arrival path) + place into the EnvCell + the retail TeleportAnimState portal-space FSM for the full-G.3 loading screen. ACE confirms dungeons are single-landblock, so "multi-landblock LOD" is moot. The handoff captures: this session's closes (#108-residual/#127/#125 gated, #116 partial), the M1.5 re-open decision, the corrected root cause, the 5-way reference grounding (holtburger/ACE/retail decomp + the dat probe), the design direction, and the open brainstorm questions. Next session: resume the brainstorm at "propose approaches" -> spec -> writing-plans -> implement. Suites green: App 264+1skip / Core 1445+2skip / UI 420 / Net 294. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9c2ceb2336
commit
90786c19e2
2 changed files with 281 additions and 0 deletions
205
docs/research/2026-06-13-dungeon-g3-handoff.md
Normal file
205
docs/research/2026-06-13-dungeon-g3-handoff.md
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
# Handoff (2026-06-13): M1.5 EXTENDED — dungeon support (full Phase G.3). Design grounded; ready to brainstorm → spec → implement.
|
||||
|
||||
**Branch:** `claude/thirsty-goldberg-51bb9b`, pushed to BOTH remotes at the
|
||||
HEAD this doc commits with. Suites green: App 264+1skip / Core 1445+2skip /
|
||||
UI 420 / Net 294 (the dungeon dat-probe test added this session is
|
||||
output-only).
|
||||
|
||||
This session closed a batch of M1.5 render/physics issues, then — at the
|
||||
dungeon-demo gate — discovered dungeons don't work and the user **extended
|
||||
M1.5 to include full dungeon support (Phase G.3)**. M2 is re-deferred. The
|
||||
design is grounded (5-way reference research + a decisive dat probe); the
|
||||
next session brainstorms approaches → writes the spec → implements.
|
||||
|
||||
---
|
||||
|
||||
## 1. What this session shipped (all on branch, pushed, most user-gated)
|
||||
|
||||
| Item | Outcome | Commits |
|
||||
|---|---|---|
|
||||
| **#108-residual** (cellar grass window) | CLOSED, user-gated "Yes it is fixed." Terrain was drawn DOUBLE-SIDED; the grass was the grade sheet's underside seen from a below-grade cellar eye. Ported retail `landPolysDraw` eye-side gate as terrain backface cull. Membership/viewer EXONERATED by a vertical cellar-ascent harness. | `007af13`, `96a425a`, `bf80067` |
|
||||
| **#127** (distant-building flood flap) | CLOSED, user-gated "Seems to have been fixed." Died with the W=0 clip port (`987313a`); confirmed by a run-past churn detector (0 churn, 21 buildings × 5 distances × 100 mm-steps). | `4ad6fb9` |
|
||||
| **#125** (sticky-drop debt) | CLOSED. Bounded upload retry — a failed `UploadMeshData` re-stages for the next frame up to `MaxUploadRetries` (counter on the `ObjectMeshData`); the CPU-cache short-circuit no longer permanently strands a failed upload. | `8682a8d` |
|
||||
| **#116** (slide-response) | PARTIAL. Ghidra (the user pointed me to the running Ghidra MCP) resolved the BN `test ah,5` branch-sign ambiguity: `slide_sphere` compares squared magnitudes against `F_EPSILON` (0.0002), not `EpsilonSq` (4e-8) — fixed `TransitionTypes.cs:3098,3105`, full physics suite green. The two reported shapes still need a cdb trace (shape-1 = upstream collision-normal recording; shape-2 = D4 first-frame dispatch). | `35961f2`, `bf18a54` |
|
||||
|
||||
---
|
||||
|
||||
## 2. The milestone churn (read this — the docs were corrected)
|
||||
|
||||
- I briefly marked **M1.5 LANDED** on the building/cellar demo and started M2
|
||||
(`1bf037a`). **The user reverted that:** the indoor world isn't done while
|
||||
dungeons are broken, so M1.5 is EXTENDED to include dungeon support, and the
|
||||
user chose the **FULL Phase G.3 scope** (streaming + portal-space loading
|
||||
screen + `PlayerTeleport` handling). Correction committed `9c2ceb2`.
|
||||
- **Current truth:** M1.5 ACTIVE; building/cellar demo DONE + user-gated;
|
||||
dungeon support (G.3) is the remaining M1.5 exit-gate. M2 (CombatMath first
|
||||
port) DEFERRED. Docs reflect this (milestones doc, CLAUDE.md current-state,
|
||||
ISSUES.md #133).
|
||||
|
||||
---
|
||||
|
||||
## 3. The dungeon bug — CORRECTED root cause (issue #133)
|
||||
|
||||
User attempted the dungeon demo via the **meeting-hall portal** → "no dungeon,
|
||||
just ocean." ACE logged a flood of `failed transition for +Acdream from
|
||||
0x01250126 [30 -60 6.0] to 0xA9B0000E [-32227 -26748 5.9]` … marching south at
|
||||
Z≈−0.9 (underwater).
|
||||
|
||||
**Diagnostic capture (`launch-dungeon-diag.log`, probes
|
||||
`ACDREAM_PROBE_CELL`/`ACDREAM_PROBE_VIEWER`/`ACDREAM_WB_DIAG`):**
|
||||
```
|
||||
live: teleport arrival — old lb=(169,180) new lb=(1,37) dist=42524.0
|
||||
[snap] claim=0xA9B3000E pos=(30,-60,6.005) cells=17 bestCell=0xA9B30103 ... indoor=False -> targetCell=0xA9B3000E
|
||||
live: teleport complete — snapped to <30,-60,6.005> cell=0xA9B3000E
|
||||
[cell-transit] A9B3000E -> A9B2000E -> A9B1000E -> ... (sliding south into ocean)
|
||||
```
|
||||
ACE correctly placed the player in dungeon cell **0x01250126** (landblock
|
||||
`0x0125` = (1,37)). acdream's arrival handler (`GameWindow.cs:4908-4931`)
|
||||
recenters streaming to (1,37) but then **immediately** calls
|
||||
`_physicsEngine.Resolve(pos=(30,-60,6.005), cell=0x01250126)` to snap the
|
||||
player — **before the dungeon landblock has streamed in**. Resolve can't find
|
||||
the dungeon cell, falls back to an outdoor scan against the **still-resident
|
||||
Holtburg landblocks**, and snaps to `0xA9B3000E` (Holtburg's south edge, local
|
||||
(30,−60) maps into the block south of the A9B4 spawn). Streaming then shifts
|
||||
the frame out from under the player → slides south into ocean.
|
||||
|
||||
### ⚠️ The "terrain-less landblock" framing is WRONG (verified by dat probe)
|
||||
|
||||
A pipeline-seam research agent *assumed* dungeon landblocks have no `LandBlock`
|
||||
record (so `LandblockLoader.Load` returns null) and produced a 13-seam
|
||||
"rewrite the pipeline for terrain-less landblocks" plan. **A direct dat probe
|
||||
(`DungeonLandblockDatProbeTests`, committed) refutes that:**
|
||||
```
|
||||
0x0125 (dungeon): LandBlock 0x0125FFFF PRESENT, Height[81] allZero=True (flat)
|
||||
LandBlockInfo: NumCells=71, Buildings=0, Objects=0
|
||||
EnvCells 0x0100.. present (the 71 dungeon rooms)
|
||||
0xA9B4 (Holtburg): LandBlock PRESENT, heights non-zero; NumCells=123, Buildings=12, Objects=114
|
||||
```
|
||||
A dungeon landblock is a **flat-terrain landblock** (all-zero height index =
|
||||
the lowest/"ocean" terrain) **plus its EnvCells, no buildings/objects**. So
|
||||
`LandblockLoader.Load(0x0125…)` returns a valid flat landblock, the terrain
|
||||
mesh builds a flat plane, and `PhysicsEngine.AddLandblock` gets a valid flat
|
||||
`TerrainSurface`. **The existing pipeline can already stream a dungeon
|
||||
landblock.** The 13 terrain-dependency seams are NOT the blocker.
|
||||
|
||||
**The real blocker is narrow: teleport TIMING + PLACEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Reference grounding (5-way research; dat agent failed, replaced by the probe above)
|
||||
|
||||
**holtburger (client-behavior oracle):**
|
||||
- PlayerTeleport (0xF751) → enter `EnteringWorld` (portal space), **suspend
|
||||
physics bodies**, send **LoginComplete immediately** (no waiting for assets).
|
||||
- Exit portal space → `InWorld` when the server sends ObjectCreate (entities) +
|
||||
UpdatePosition (player) + the **StartGame** event → resume bodies.
|
||||
- holtburger does NOT stream landblocks (entity-centric); not our model — we
|
||||
DO stream from our own dats. Take the **FSM shape** (EnteringWorld/InWorld +
|
||||
suspend/resume) not the no-streaming part.
|
||||
- DDD is NOT part of the teleport flow (responds empty). (`messages.rs:480-486`,
|
||||
`:190-195`, `player.rs:71-79`, `types.rs:169-175`.)
|
||||
|
||||
**ACE (server):** `Player_Location.cs:654-707` Teleport() sends PlayerTeleport
|
||||
(sequence) → a **fake UpdatePosition** to trigger client load → the real
|
||||
UpdatePosition with PositionPack (CellID = dungeonID<<16 | cellIndex, e.g.
|
||||
`0x01250126`, xyz, rotation). **Server sends NO geometry — client loads cells
|
||||
from its own dats by cellID** (matches our dat-driven model). Portal:
|
||||
`Portal.cs:269-292` ActOnUse → AdjustDungeon (corrects cell id) →
|
||||
ThreadSafeTeleport. **Dungeons are SINGLE-landblock** (`Player_Tick.cs:548-560`
|
||||
forbids moving between dungeon landblocks without teleport) → "multi-landblock
|
||||
LOD" in the full-G.3 scope is MOOT for AC dungeons. IsDungeon = all heights 0 +
|
||||
NumCells>0 + no buildings (`Landblock.cs:575-631`).
|
||||
|
||||
**Retail decomp (client):** terrain (`CLandBlock::grab_visible_cells`) and
|
||||
dungeon cells (`CEnvCell::grab_visible_cells`, :311878) load on **separate
|
||||
paths**; a cell with `seen_outside==0` loads ZERO terrain and walks only its
|
||||
`stab_list` (adjacent EnvCells). **Portal-space = a 6-state `TeleportAnimState`
|
||||
FSM** (:219682-219774): WORLD_FADE_OUT → TUNNEL_FADE_IN → TUNNEL (hold while
|
||||
loading) → TUNNEL_FADE_OUT → WORLD_FADE_IN → OFF; `m_pPortalSpace` is the
|
||||
tunnel viewport (the "loading"/black screen). Retail gates cell-ready on DDD
|
||||
(server cell push) — **we don't need DDD** (we have the dats); we gate on our
|
||||
own streaming hydration. Open: no distinct "pink screen" asset found — retail's
|
||||
loading visual is the portal tunnel.
|
||||
|
||||
**acdream pipeline seams (corrected by the dat probe):** the dungeon landblock
|
||||
streams fine as flat-terrain. Real seams that matter:
|
||||
- `GameWindow.cs:4908-4931` — teleport arrival: recenter then **Resolve
|
||||
immediately** (the bug). No hold-until-hydration.
|
||||
- `PhysicsEngine.IsSpawnCellReady` (`:468`) — the EXISTING #107 gate; already
|
||||
handles indoor cells (checks DataCache for 0x0100+). **Reuse it for the
|
||||
teleport-arrival path.**
|
||||
- EnvCell hydration (render `_cellVisibility`/`EnvCellRenderer`; physics
|
||||
`CacheCellStruct`) is iterated from `LandBlockInfo.NumCells` and is
|
||||
**orthogonal to terrain** — should fire for a dungeon landblock once it
|
||||
streams (`GameWindow.cs:5564-5576`, `6015-6028`). VERIFY it does.
|
||||
- Placement: the player is at cell `0x01250126`, pos (30,−60,6.005); must be
|
||||
placed in the **EnvCell** (the #107/#111 validated-claim path,
|
||||
`WalkableFloorZNearest`), not on the flat terrain.
|
||||
|
||||
---
|
||||
|
||||
## 5. Design direction (to confirm in the brainstorm)
|
||||
|
||||
A retail-faithful, much-narrower-than-feared shape:
|
||||
|
||||
1. **Teleport state machine (portal space).** On PlayerTeleport: enter a
|
||||
PortalSpace/EnteringWorld state, suspend player physics, (optionally) start
|
||||
the retail `TeleportAnimState` tunnel FSM for the loading visual. On arrival
|
||||
UpdatePosition: recenter streaming on the destination landblock, then **HOLD**
|
||||
— do not snap — until the destination landblock + the claimed EnvCell hydrate
|
||||
(reuse `IsSpawnCellReady`). Then place into the EnvCell (validated-claim
|
||||
path), exit PortalSpace → InWorld, resume physics, send LoginComplete.
|
||||
(acdream already has `OnTeleportStarted`/portal-space + the #107 hold for
|
||||
LOGIN — extend that machinery to the teleport-arrival path rather than
|
||||
snapping at `:4928`.)
|
||||
2. **Streaming a far teleport.** Confirm the recenter actually drives the
|
||||
streamer to load the destination dungeon landblock (the Chebyshev window
|
||||
around the new center) and unloads the old neighborhood without stranding the
|
||||
player. The dungeon streams as a flat-terrain landblock — no new loader path
|
||||
needed, but verify the apply path + EnvCell hydration fire.
|
||||
3. **Render/physics in the dungeon.** Once the EnvCells hydrate, the existing
|
||||
PView indoor render + per-cell collision should work (same as buildings).
|
||||
The flat terrain renders below; PView roots at the viewer EnvCell. VERIFY the
|
||||
3-5-room navigation, walls block, stairs, lighting (A7 not done — expect
|
||||
lighting findings), transitions.
|
||||
4. **Portal-space loading screen (full-G.3 polish).** The retail 6-state tunnel
|
||||
FSM (`TeleportAnimState`) — implement after the core teleport+place works, or
|
||||
a simpler fade-to-black first.
|
||||
|
||||
**Open design questions for the brainstorm:**
|
||||
- Do we implement the retail `TeleportAnimState` tunnel FSM faithfully now, or a
|
||||
simpler fade-to-black for M1.5 and the full tunnel later?
|
||||
- How long to HOLD before giving up (the dungeon may need several frames to
|
||||
stream); what's the failure/timeout behavior?
|
||||
- Does the existing streaming controller already load a landblock 42 km away on
|
||||
recenter, or does it assume incremental movement? (Confirm the recenter→load
|
||||
path for a big jump.)
|
||||
- Placement: the cell-local pos (30,−60,6.005) vs the EnvCell origins (~(0,−30,0))
|
||||
— confirm the EnvCell BSP contains the point and the #107/#111 walkable-floor
|
||||
placement lands the player on the dungeon floor.
|
||||
|
||||
---
|
||||
|
||||
## 6. Apparatus added this session (kept)
|
||||
|
||||
| Tool | How | For |
|
||||
|---|---|---|
|
||||
| `DungeonLandblockDatProbeTests` | `dotnet test --filter DungeonLandblockDatProbe` | Dumps the dat structure of a dungeon (0x0125) vs outdoor (A9B4) landblock — the terrain-less-vs-flat resolution |
|
||||
| `launch-dungeon-diag.log` | `ACDREAM_PROBE_CELL=1 ACDREAM_PROBE_VIEWER=1 ACDREAM_WB_DIAG=1` | The teleport→snap→slide capture; `[snap]`/`[cell-transit]`/`live: teleport` lines are the chain |
|
||||
| `Issue108CellarAscentViewerReplayTests` | App.Tests filter | Vertical cellar-ascent viewer harness (membership EXONERATED for #108) |
|
||||
| `Issue127FloodFlipReplayTests.DistantBuildingStrafe_NoAdmissionChurn` | App.Tests filter | #127 run-past churn-detector regression pin |
|
||||
|
||||
Decomp grounding: holtburger teleport flow, ACE Teleport/Portal/AdjustDungeon,
|
||||
retail `CEnvCell::grab_visible_cells` (:311878) + `TeleportAnimState` FSM
|
||||
(:219682-219774). Full raw research in the workflow output (this session).
|
||||
|
||||
---
|
||||
|
||||
## 7. Next session: brainstorm → spec → implement
|
||||
|
||||
The brainstorming skill was invoked and scope was set (full G.3). The next
|
||||
session resumes the brainstorm at "propose 2-3 approaches" with the grounding
|
||||
above, settles the design, writes the spec to
|
||||
`docs/superpowers/specs/2026-06-13-dungeon-support-design.md`, then →
|
||||
writing-plans → implement. The paste-ready pickup prompt is in the session
|
||||
message that produced this doc.
|
||||
Loading…
Add table
Add a link
Reference in a new issue