Recon script, three capture templates (walk / parts / alpha-depth), runner, and operator README under tools/walk-oracle/oh/. FW0 line formats are kept byte-identical; new EC/OC/PD/DM/AM/FL/PM/PC line kinds are documented. Auto-detach uses the fall-through + top-level qd recipe; qd never appears inside a breakpoint action. Lead-resolved: DBObj::m_DID is +0x28 (header layout, first base of CGfxObj and CObjCell, matches the proven FW0 cell reads); the three D3DPolyRender alpha functions are static cdecl with stack arguments, uint8 arguments read with by(). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
291 lines
16 KiB
Markdown
291 lines
16 KiB
Markdown
# OH1 walk-oracle capture kit (2026-09-02)
|
|
|
|
Draft cdb capture scripts for Campaign OVERHAUL's OH1 evidence gap: the four
|
|
OH1 contract docs
|
|
(`docs/research/2026-09-01-overhaul/oh1-retail-world-contract.md`,
|
|
`oh1-built-mesh-view-contract.md`, `oh1-alpha-list-contract.md`,
|
|
`oh1-depth-lifecycle.md`) establish retail's built-mesh/view/stamp, alpha
|
|
FIFO, and depth-lifecycle contracts from the named decomp plus live Ghidra
|
|
arbitration, but they explicitly say (`oh1-retail-world-contract.md` s10):
|
|
"Existing FW0 captures cover only frame/pose/DrawInside/DrawCells/
|
|
landscape/building entry. Full semantic equality requires a bounded richer
|
|
retail capture before the owning later slices close." This kit is that
|
|
richer capture: it extends the proven FW0 F/P/LS/BLD/DI/DC per-frame shape
|
|
with three new families (cell-shell/object-cell stamps, part/mesh
|
|
admission, and the alpha-FIFO/depth-counter state machine) without changing
|
|
any existing line format.
|
|
|
|
**These scripts are DRAFTS pending the recon pass below. Do not treat any
|
|
offset as final until `oh-recon.cdb`'s `dt` output confirms it against a
|
|
live attach.** See "Open questions" at the end before running a real
|
|
session.
|
|
|
|
## Files
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `oh-recon.cdb` | No breakpoints. `dt`/`x`/`uf`/`dd` dumps that confirm every offset the three capture scripts assume. Run this FIRST, once per session. |
|
|
| `oh-capture-walk.cdb.template` | FW0's F/P/LS/BLD/DI/DC lines, byte-identical, plus new `EC`/`OC` lines (cell-shell and object-cell draw-stamp events). 5 frames. |
|
|
| `oh-capture-parts.cdb.template` | F/P lines plus new `PD`/`DM` lines (per-part and per-mesh admission/GfxObj identity). The high-hit-rate script. 3 frames. |
|
|
| `oh-capture-alpha-depth.cdb.template` | F/P lines plus new `AM`/`FL`/`PM`/`PC` lines (the two alpha FIFO lists and the persistent portal-mask depth counter). 5 frames. |
|
|
| `oh-run-capture.ps1` | Runner: `-Script -Log -Frames`. Verifies the live acclient.exe pairs with `refs/acclient.pdb` (aborts on mismatch), substitutes `<LOG>`/`<FRAMES>`, launches `cdb -pn acclient.exe -cf <script>` in the background. |
|
|
|
|
## Prerequisites
|
|
|
|
1. `C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe` installed
|
|
(Microsoft Store WinDbg).
|
|
2. The retail client running the **2013 v11.4186 EoR build** (PDB GUID
|
|
`9e847e2f-777c-4bd9-886c-22256bb87f32`), in-world, connected to local ACE
|
|
on `127.0.0.1:9000`. The runner checks this for you and aborts on
|
|
mismatch, but confirm before starting — a mismatched attach silently
|
|
corrupts every offset in these scripts.
|
|
3. `docs/research/named-retail/acclient.h` and `acclient_2013_pseudo_c.txt`
|
|
available (already in the repo) if you need to cross-check a line
|
|
number cited in a script's comments.
|
|
|
|
## The four poses
|
|
|
|
Reused verbatim from `docs/research/2026-08-30-fw-walk-oracle/README.md`'s
|
|
trace table — these are the exact in-world spots already used for the FW0
|
|
oracle fixtures, so a human who captured those poses before can return to
|
|
the same spots from memory; there is no additional GPS/coordinate record
|
|
beyond these descriptions in this repo.
|
|
|
|
| Pose | Description | Root shape |
|
|
|---|---|---|
|
|
| `holtburg-doorway-still` | Standing in a cottage doorway, still | INTERIOR (`DI a9b4013f`) |
|
|
| `terrace-edge` | Waterfall ledge (the #456 acceptance pose), still | OUTDOOR |
|
|
| `cathedral-arrival` | Cathedral portal-in arrival, still | INTERIOR (`DI f4180106`) |
|
|
| `foundry-deep` | Bottom of the Holtburg foundry, still | INTERIOR (`DI a9b40176`) |
|
|
|
|
All four are **still** poses (no movement during capture) — this kit only
|
|
targets the still-fixture family, matching FW0's own "still fixtures gate
|
|
first" precedent (pose-application timing sensitivity in moving fixtures is
|
|
a separate, harder problem this kit does not attempt).
|
|
|
|
## Operator procedure
|
|
|
|
**Budget: one hour total** for all four poses. Each pose's three scripts
|
|
(walk, parts, alpha-depth) run back to back and take well under a minute of
|
|
actual capture time each; the time goes to repositioning, verifying detach,
|
|
and the occasional retry.
|
|
|
|
### 0. Once per session
|
|
|
|
Run recon first, before touching any pose:
|
|
|
|
```powershell
|
|
cd tools\walk-oracle\oh
|
|
.\oh-run-capture.ps1 -Script oh-recon.cdb -Log C:\path\to\oh-recon.log
|
|
```
|
|
|
|
Wait for it to finish (recon has no breakpoints, so it runs to completion
|
|
in well under a second once attached — watch for the `oh-recon.log.console`
|
|
file to stop growing, or just wait ~5 seconds). Read `oh-recon.log` and:
|
|
|
|
- confirm every `dt` offset matches what the capture scripts' comments
|
|
assume (see each template's own "OFFSETS (VERIFY-IN-RECON)" block);
|
|
- read the `dt acclient!DBObj` output and confirm `m_DID` is at `+0x28`
|
|
(the lead derived this from the header on 2026-09-02 and the parts
|
|
template is pre-filled with it; if `dt` disagrees, fix the two `0x28`
|
|
reads in `oh-capture-parts.cdb.template` before running it);
|
|
- glance at the three `uf` disassembly dumps
|
|
(`AddMeshToAlphaList`/`DrawPortalPolyInternal`/`FlushAlphaList`): the
|
|
named pseudo-C declares all three as plain static functions with no
|
|
receiver and no non-default convention (`acclient_2013_pseudo_c.txt`
|
|
424920, 424490, 425941), i.e. `__cdecl` with arguments at `[esp+4]`
|
|
upward; the templates read `uint8` arguments with `by()` (one byte) so
|
|
the untouched upper bytes of a pushed byte cannot pollute the value.
|
|
Only if the prologue shows `ecx`/`edx` being consumed as arguments does
|
|
the template need changing.
|
|
|
|
If either open question resolves DIFFERENTLY than assumed, fix the
|
|
template before capturing — do not capture first and "adjust the parser
|
|
later"; a wrong stack offset produces plausible-looking garbage, not an
|
|
obvious crash.
|
|
|
|
### 1. Per pose
|
|
|
|
For each of the four poses, in order (doorway-still, terrace-edge,
|
|
cathedral-arrival, foundry-deep — matches the FW0 capture order, no
|
|
particular reason to change it):
|
|
|
|
1. Position the retail character/camera at the pose (see table above) and
|
|
hold still.
|
|
2. Run the walk capture:
|
|
```powershell
|
|
.\oh-run-capture.ps1 -Script oh-capture-walk.cdb.template `
|
|
-Log C:\path\to\<pose>.walk.log -Frames 5
|
|
```
|
|
3. **Wait for detach** before moving or running the next script. The
|
|
runner returns immediately (it launches cdb in the background), so
|
|
watch for completion instead of assuming it's done:
|
|
```powershell
|
|
while (Get-Process cdb -ErrorAction SilentlyContinue) { Start-Sleep -Milliseconds 500 }
|
|
```
|
|
or simply tail the log and watch it stop growing at `F 5`:
|
|
```powershell
|
|
Get-Content C:\path\to\<pose>.walk.log -Tail 20 -Wait
|
|
```
|
|
4. Run the parts capture (stay in the same pose):
|
|
```powershell
|
|
.\oh-run-capture.ps1 -Script oh-capture-parts.cdb.template `
|
|
-Log C:\path\to\<pose>.parts.log -Frames 3
|
|
```
|
|
Wait for detach the same way.
|
|
5. Run the alpha-depth capture (stay in the same pose):
|
|
```powershell
|
|
.\oh-run-capture.ps1 -Script oh-capture-alpha-depth.cdb.template `
|
|
-Log C:\path\to\<pose>.alphadepth.log -Frames 5
|
|
```
|
|
Wait for detach.
|
|
6. Move to the next pose and repeat.
|
|
|
|
Do not run two of these scripts against the same live process
|
|
simultaneously — cdb's `-pn` attach is exclusive, and the second launch
|
|
will simply fail to attach rather than queue.
|
|
|
|
## Expected line counts
|
|
|
|
For `oh-capture-walk.cdb.template`'s F/P/LS/BLD/DI/DC lines, real per-frame
|
|
counts from the ORIGINAL FW0 40-frame captures of these exact four poses
|
|
give a concrete baseline (frame 40 in every FW0 log is truncated by the
|
|
auto-detach race, so these are per-COMPLETE-frame averages over frames
|
|
1-39; this kit's own 5-frame captures will end at a different, cleaner
|
|
boundary since they use the fall-through recipe instead of FW0's
|
|
non-firing `qd`, but the per-frame magnitude should still hold):
|
|
|
|
| Pose | DI/frame | DC/frame | LS/frame | BLD/frame |
|
|
|---|---:|---:|---:|---:|
|
|
| `holtburg-doorway-still` | 1 | ~6 | 1 | ~13 |
|
|
| `terrace-edge` | 0 (outdoor root) | ~2 | 1 | ~14 |
|
|
| `cathedral-arrival` | 1 | ~3 | 1 | ~10 |
|
|
| `foundry-deep` | 1 | 1 | 1 | ~12 |
|
|
|
|
`EC`/`OC` (new in the walk script) have **no prior baseline** — this is
|
|
their first capture. Expect a similar order of magnitude to `DC`/`BLD`
|
|
(shell/object-cell draws roughly track the cell/building roster each frame
|
|
walks); a flat **zero** `EC` or `OC` count for an interior pose is the real
|
|
red flag, not an exact mismatch against the table above.
|
|
|
|
`PD`/`DM` (parts script) and `AM`/`FL`/`PM`/`PC` (alpha-depth script) also
|
|
have **no prior baseline**:
|
|
|
|
- `PD`/`DM` are per-resident-part, not per-cell/building — expect
|
|
hundreds to low thousands of lines across 3 frames in a town pose
|
|
(doorway-still, terrace-edge, cathedral-arrival) and noticeably fewer in
|
|
the sparser foundry-deep interior. This is exactly why the parts script
|
|
defaults to 3 frames instead of 5.
|
|
- `FL` is bounded by known call sites
|
|
(`oh1-alpha-list-contract.md` s7): expect roughly
|
|
(buildings-in-view-this-frame) + 1 (top-level `SmartBox::RenderNormalMode`
|
|
flush) + (0 or 1, depending on whether that frame's `PView::DrawCells`
|
|
had an outside view) per frame — so order 10-16 for the town poses,
|
|
fewer for foundry-deep.
|
|
- `AM` and `PM` depend entirely on how much transparent/portal content is
|
|
in view at that specific pose (falls, torches, doorway apertures) and
|
|
could legitimately be zero for a pose with no transparent surfaces in
|
|
frame — a zero count here is not automatically a bug.
|
|
|
|
## If retail lags or ACE disconnects
|
|
|
|
The parts script is the risky one: it traps on every part draw (hundreds
|
|
per frame), so expect retail to freeze for a few seconds during its three
|
|
frames. Run it last for each pose, and if ACE drops the session once, use
|
|
`-Frames 2` on the retry rather than abandoning the pose.
|
|
|
|
Per `claude-memory/project_retail_debugger.md`'s "High hit rates lag the
|
|
game" watchout: `PD`/`DM` in the parts script are the highest-risk
|
|
breakpoints in this kit (fired once per resident part draw, potentially
|
|
thousands of times per frame in a dense town pose). If you observe visible
|
|
stutter, input lag, or an ACE disconnect during a capture:
|
|
|
|
1. Let the current capture finish or auto-detach if possible — do not
|
|
force-kill cdb (see CLAUDE.md: `Stop-Process -Force` on cdb takes the
|
|
attached retail client down with it via `TerminateProcess`).
|
|
2. Re-run with a SHORTER frame count for the offending script:
|
|
- `oh-capture-walk.cdb.template`: try `-Frames 3` instead of 5.
|
|
- `oh-capture-parts.cdb.template`: try `-Frames 1` instead of 3 — a
|
|
single frame is still enough to sanity-check the `PD`/`DM` line
|
|
format and the `<GFXOBJ_DID_OFF>` offset.
|
|
- `oh-capture-alpha-depth.cdb.template`: try `-Frames 3` instead of 5.
|
|
3. If ACE actually disconnected, follow CLAUDE.md's "Logout-before-
|
|
reconnect" guidance before relaunching retail — a hard-killed session
|
|
can stay logged in on ACE's side for several minutes.
|
|
4. If a specific pose keeps lagging even at the reduced frame count,
|
|
capture that pose's walk/alpha-depth scripts (cheap) and skip its parts
|
|
capture (expensive) rather than spending the whole hour on one pose.
|
|
|
|
## New line-kind prefixes (glossary)
|
|
|
|
All existing FW0 prefixes (`F`, `P`, `LS`, `BLD`, `DI`, `DC`) are
|
|
byte-identical to
|
|
`docs/research/2026-08-30-fw-walk-oracle/README.md`'s "Trace format"
|
|
section — not repeated here.
|
|
|
|
| Prefix | Script | Source breakpoint | Meaning |
|
|
|---|---|---|---|
|
|
| `EC` | walk | `RenderDeviceD3D::DrawEnvCell` @`0x0059F170` | A built EnvCell shell's draw-stamp fired this frame; `<cellid>` = the cell's `m_DID`. |
|
|
| `OC` | walk | `RenderDeviceD3D::DrawObjCellForDummies` @`0x005A0760` | An object-cell's shadow-part list was walked this frame; `<cellid>` = the cell's `m_DID`. |
|
|
| `PD` | parts | `CPhysicsPart::Draw` @`0x0050D7A0` | A physics part's draw call, with its degrade-resolved GfxObj identity and (best-effort) containing cell. |
|
|
| `DM` | parts | `RenderDeviceD3D::DrawMeshInternal` @`0x0059F360` | The actual admission/stamp/submit call for one GfxObj part, with its GfxObj identity directly from the argument (no degrade-level resolution needed). |
|
|
| `AM` | alpha-depth | `D3DPolyRender::AddMeshToAlphaList` @`0x0059C230` | One subset appended to the CLIP or ALPHA FIFO list. |
|
|
| `FL` | alpha-depth | `D3DPolyRender::FlushAlphaList` @`0x0059D2E0` | A flush-barrier call, with the return address to identify the caller site. |
|
|
| `PM` | alpha-depth | `D3DPolyRender::DrawPortalPolyInternal` @`0x0059BC90` | A far-Z building punch or true-depth exit-seal attempt, with the counter value before this call's own possible increment. |
|
|
| `PC` | alpha-depth | `PView::DrawCells` @`0x005A4840` | The persistent `portalsDrawnCount`/`forceClear` state sampled at this eligible call's entry. |
|
|
|
|
## Open questions a human must settle before a real session
|
|
|
|
1. **GfxObj DataID offset — RESOLVED 2026-09-02 (lead).** `DBObj : Interface`
|
|
lays out `vfptr@0`, `m_dataCategory@4`, `m_bLoaded@8`, the 8-byte
|
|
`m_timeStamp@0x10`, `m_pNext@0x18`, `m_pLast@0x1C`, `m_pMaintainer@0x20`,
|
|
`m_numLinks@0x24`, `m_DID@0x28`. `DBObj` is the first base of both
|
|
`CGfxObj` and `CObjCell` (via `SerializeUsingPackDBObj`), and `+0x28` is
|
|
exactly the `m_DID` offset every FW0 capture already read on cells. The
|
|
parts template is pre-filled with `0x28`; recon's `dt acclient!DBObj`
|
|
re-confirms it.
|
|
2. **Calling convention — RESOLVED 2026-09-02 (lead) from the named
|
|
pseudo-C signatures.** `AddMeshToAlphaList(MeshBuffer*, int32, CSurface*,
|
|
uint8, uint8, uint8)` (424920), `DrawPortalPolyInternal(CPolygon*, uint8)`
|
|
(424490), and `FlushAlphaList(float)` (425941) are static with the
|
|
default convention: no receiver, arguments at `[esp+4]` upward. The
|
|
`AM` line now also prints the `CSurface*` at `[esp+0xC]`, and every
|
|
`uint8` argument is read with `by()`. The recon `uf` dumps remain as a
|
|
sanity check.
|
|
3. **`CPhysicsPart+0x14` (`deg_level`).** Derived only from
|
|
`acclient.h:31151-31173`'s field order (confirmed consistent with its
|
|
neighbors `draw_state+0x1C` and `gfxobj+0x20`, both of which OH1
|
|
independently Ghidra-cited), but never independently Ghidra-checked
|
|
itself anywhere in the OH1 evidence docs. Low risk, still worth a glance
|
|
at `oh-recon.cdb`'s `dt acclient!CPhysicsPart` output.
|
|
4. **`DrawObjCell` vs. `DrawObjCellForDummies` cell-argument location.**
|
|
The task that produced this kit flagged this exact question as an
|
|
example of the class of ambiguity to watch for. It turned out NOT to be
|
|
ambiguous once the pseudo-C signatures were read directly
|
|
(`acclient_2013_pseudo_c.txt:429177,429198`): both take the cell
|
|
identity as a plain stack argument at `[esp+4]`, and
|
|
`oh-capture-walk.cdb.template` uses `DrawObjCellForDummies` (the
|
|
function `PView::DrawCells` step 10 actually calls, per
|
|
`oh1-depth-lifecycle.md`'s exact frame ordering) rather than the inner
|
|
`DrawObjCell`/`DrawPartCell` helpers. Recorded here as a "resolved, but
|
|
worth a second pair of eyes" item rather than a true open question,
|
|
since it was resolved by reading text rather than by a live `dt`/`uf`.
|
|
5. **The `PD`/`DM` lines' `cell=` field can be stale.** It is set by a
|
|
silent `RenderDeviceD3D::DrawPartCell` hook and simply carries forward
|
|
whatever cell it last saw — it can be wrong for the first part drawn
|
|
after a building's forced portal-only pass (`DrawBuilding`'s
|
|
`Draw(part, 1)` call) and before that frame's first ordinary object-cell
|
|
walk. Treat it as advisory, not authoritative; a real port needs the
|
|
actual call-stack correlation this draft does not attempt.
|
|
6. **`oh-capture-alpha-depth.cdb.template` does not instrument the exact
|
|
mid-`PView::DrawCells` PCs** (`0x5A4885` stamp increment, `0x5A488C`
|
|
counter read, `0x5A489E` reset, `0x5A48A9` conditional clear) that
|
|
`oh1-depth-lifecycle.md`'s own Ghidra arbitration table cites. It
|
|
samples the global `portalsDrawnCount`/`forceClear` memory cells at
|
|
`PView::DrawCells`' function ENTRY instead (the `PC` line), which is
|
|
sufficient to reconstruct before/after values across consecutive
|
|
eligible calls (per that same doc's "Consecutive-call example" table)
|
|
without guessing which register holds the value at an arbitrary
|
|
mid-function instruction — but it means this kit does not directly
|
|
observe the mid-frame stamp increment itself. If a later slice needs
|
|
that specific event, it needs its own recon pass over those four PCs.
|