test(p0): retail find_cell_list trace parser + cdb value-capture tooling
P0 Task 5. RetailTrace parses the [fcl] golden format (seed/pos/picked, RetailCellPick); 4 TDD tests green. find-cell-list-capture.cdb targets CPhysicsObj::change_cell (commit-on-diff) to capture retail's accepted membership sequence at the doorway; README is the operator runbook (dt offset verification + decode_retail_hex float decode). The live run is P0's one user-gated step (Task 6 mines existing traces first). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ec78beb843
commit
b35e491f12
4 changed files with 225 additions and 0 deletions
75
tools/cdb/README-find-cell-list-capture.md
Normal file
75
tools/cdb/README-find-cell-list-capture.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# find-cell-list-capture — operator runbook
|
||||
|
||||
Captures retail's **accepted membership sequence** at the cottage doorway so P0 can pin
|
||||
acdream's `CellTransit.FindCellList` against the real client (P0 Task 6 — the P1 gate).
|
||||
This is the one user-gated step of P0. Everything else (parser, fixture loader, goldens) is
|
||||
already headless + green.
|
||||
|
||||
> **Try the autonomous path first.** Before running this, P0 Task 6 greps the *already-committed*
|
||||
> retail traces under `docs/research/2026-05-21-a6-captures/` for a usable membership pick. Only
|
||||
> run this capture if those traces don't yield one.
|
||||
|
||||
## What you get
|
||||
|
||||
A log of lines in the golden format the parser (`RetailTrace.ParseFindCellList`) reads:
|
||||
|
||||
```
|
||||
[fcl] seed=0xA9B40031 px=0x43200000 py=0x41200000 pz=0x42BC0000 picked=0xA9B40170
|
||||
```
|
||||
|
||||
`seed` = the cell the player was in; `picked` = the cell retail committed; `px/py/pz` = the player
|
||||
world origin as **raw IEEE-754 hex** (decode with `decode_retail_hex.py`). One line per accepted
|
||||
cell change, so a doorway walk yields a short clean sequence (e.g. `0031 → 0170 → 0171`).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **PDB matches the binary** (CLAUDE.md retail-debugger toolchain):
|
||||
```
|
||||
py tools/pdb-extract/check_exe_pdb.py "C:/Turbine/Asheron's Call/acclient.exe"
|
||||
```
|
||||
Expect `=== MATCH ===`.
|
||||
2. **Retail in-world at the Holtburg cottage doorway** (the building at world ≈ (161.9, 7.5, 94),
|
||||
outdoor landcell `0xA9B40031`, vestibule `0xA9B40170`, room `0xA9B40171` — see
|
||||
`docs/research/2026-06-03-p0-conformance-apparatus-notes.md`). Stand just outside the door.
|
||||
3. `cdb.exe` (x86) at `C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe`.
|
||||
|
||||
## One-time: verify the struct offsets (the script ships with placeholders)
|
||||
|
||||
Launch cdb attached, let it break once, then dump the layouts and edit `$t4/$t5/$t6` in the
|
||||
`.cdb`:
|
||||
|
||||
```
|
||||
dt acclient!CPhysicsObj @ecx # note m_position (origin x,y,z) offset -> POS_OFF ($t6)
|
||||
# note the current-cell pointer field -> CELLPTR_OFF ($t5)
|
||||
dt acclient!CObjCell poi(@esp+4) # note cell_id offset -> CELLID_OFF ($t4)
|
||||
```
|
||||
|
||||
`change_cell` is `thiscall`: `this` (CPhysicsObj*) is `@ecx`; the new cell arg is `poi(@esp+4)`.
|
||||
The defaults in the script (`0x18 / 0x08 / 0x1C`) are best-guesses from `acclient.h` — **confirm
|
||||
them** before trusting a capture.
|
||||
|
||||
## Run
|
||||
|
||||
```powershell
|
||||
& "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe" `
|
||||
-pn acclient.exe -cf tools\cdb\find-cell-list-capture.cdb *>&1 |
|
||||
Tee-Object -FilePath "find-cell-list-capture.console.log"
|
||||
```
|
||||
|
||||
Then, in retail, **walk slowly in and out of the doorway 5–10 times** (out → vestibule → room →
|
||||
back). The script auto-detaches (`qd`) after 400 cell changes; retail keeps running. Do **not**
|
||||
`Stop-Process` cdb — that kills retail (CLAUDE.md watchout).
|
||||
|
||||
## Fold into the golden
|
||||
|
||||
1. Decode the hex floats to decimals:
|
||||
```
|
||||
py tools/cdb/decode_retail_hex.py find-cell-list-capture.log > find-cell-list-threshold.log
|
||||
```
|
||||
(or hand-decode; each `0xHHHHHHHH` is a little-endian float). Result lines must read
|
||||
`[fcl] seed=0xA9B40031 px=160.0 py=10.0 pz=94.0 picked=0xA9B40170`.
|
||||
2. Place the decoded file at
|
||||
`tests/AcDream.Core.Tests/Conformance/Fixtures/find-cell-list-threshold.log`.
|
||||
3. Run `dotnet test … --filter FindCellList_DoorwayThreshold_MatchesRetailTrace`. GREEN = acdream
|
||||
already matches retail at the threshold. RED = a real divergence → that is the **P1** work,
|
||||
left as a documents-the-bug conformance test (no weakening the assertion — master plan §4).
|
||||
42
tools/cdb/find-cell-list-capture.cdb
Normal file
42
tools/cdb/find-cell-list-capture.cdb
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
$$ ============================================================================
|
||||
$$ find-cell-list-capture.cdb — P0 conformance retail golden capture
|
||||
$$ ----------------------------------------------------------------------------
|
||||
$$ Captures retail's ACCEPTED membership decision at the cottage doorway so we
|
||||
$$ can pin acdream's CellTransit.FindCellList against it (P0 Task 6, the P1 gate).
|
||||
$$
|
||||
$$ Emits one line per accepted cell change in the golden format the parser reads
|
||||
$$ (tests/AcDream.Core.Tests/Conformance/RetailTrace.cs):
|
||||
$$ [fcl] seed=0xHHHHHHHH px=0x<hex> py=0x<hex> pz=0x<hex> picked=0xHHHHHHHH
|
||||
$$ where seed = the cell the player was in (old), picked = the cell committed (new),
|
||||
$$ and px/py/pz = the player world origin (raw IEEE-754 hex; decode offline with
|
||||
$$ tools/cdb/decode_retail_hex.py, then write decimals into the golden fixture).
|
||||
$$
|
||||
$$ Target: CPhysicsObj::change_cell @ 0x00513390 (pc:281192) — commit-on-diff.
|
||||
$$ It fires ONLY on an accepted cell transition, so the doorway crossing yields a
|
||||
$$ short, clean sequence (e.g. 0031 -> 0170 -> 0171), not a per-tick flood.
|
||||
$$
|
||||
$$ thiscall: this (CPhysicsObj*) = @ecx ; arg new_cell (CObjCell*) = poi(@esp+4)
|
||||
$$
|
||||
$$ OFFSETS BELOW ARE PLACEHOLDERS — VERIFY LIVE (see README). At the first break:
|
||||
$$ dt acclient!CPhysicsObj @ecx $$ find m_position (Frame/Position) + cell
|
||||
$$ dt acclient!CObjCell poi(@esp+4) $$ find cell_id offset
|
||||
$$ dt acclient!Position <m_position> $$ find origin (x,y,z) float offsets
|
||||
$$ then edit CELLID_OFF / CELLPTR_OFF / POS_OFF and re-run.
|
||||
$$ ============================================================================
|
||||
|
||||
.logopen C:\Users\erikn\source\repos\acdream\find-cell-list-capture.log
|
||||
.sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
.symopt+ 0x40
|
||||
.reload /f acclient.exe
|
||||
|
||||
$$ ---- EDIT THESE after the dt dumps (hex byte offsets) ----------------------
|
||||
r $t4 = 0x18 $$ CELLID_OFF : CObjCell.cell_id (acclient.h:30938 — verify)
|
||||
r $t5 = 0x08 $$ CELLPTR_OFF : CPhysicsObj.cell (current cell ptr — verify)
|
||||
r $t6 = 0x1C $$ POS_OFF : CPhysicsObj.m_position.origin.x (verify; y=+4, z=+8)
|
||||
$$ ---------------------------------------------------------------------------
|
||||
|
||||
r $t0 = 0
|
||||
bp acclient!CPhysicsObj::change_cell "r $t0 = @$t0 + 1; r $t1 = poi(@ecx+@$t5); r $t2 = poi(@esp+4); .printf /D \"[fcl] seed=0x%08x px=0x%08x py=0x%08x pz=0x%08x picked=0x%08x\\n\", poi(@$t1+@$t4), poi(@ecx+@$t6), poi(@ecx+@$t6+4), poi(@ecx+@$t6+8), poi(@$t2+@$t4); .if (@$t0 >= 400) { .printf /D \"=== DETACH after %d cell changes ===\\n\", @$t0; qd } .else { gc }"
|
||||
|
||||
.printf \"find-cell-list capture armed (change_cell). Walk SLOWLY in/out of the cottage doorway now.\\n\"
|
||||
g
|
||||
Loading…
Add table
Add a link
Reference in a new issue