acdream/tools/walk-oracle/oh
2026-09-03 06:40:35 +02:00
..
oh-capture-alpha-depth.cdb.template tools(render): fix printf newline escapes in the OH alpha-depth template 2026-09-02 18:19:35 +02:00
oh-capture-parts.cdb.template tools(walk-oracle): parts capture guards null part-array slots (cdb aborted the PD/DM action on a null gfxobj) 2026-09-03 06:40:35 +02:00
oh-capture-walk.cdb.template tools(walk-oracle): capture the per-land-cell draw order (LC/SC lines) for S3 chunk 3 2026-09-03 06:31:54 +02:00
oh-run-capture.ps1 tools(render): OH retail capture scripts for the S3/S4 answer key 2026-09-02 18:09:11 +02:00
README.md tools(walk-oracle): capture the per-land-cell draw order (LC/SC lines) for S3 chunk 3 2026-09-03 06:31:54 +02:00

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) and, added 2026-09-03 for S3 chunk 3, LC/SC lines: one per RenderDeviceD3D::DrawLandCell (0x0059f120) / DrawSortCell (0x0059f140) call with the land cell's m_DID — retail's far-to-near landscape draw order within LS. 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:

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:
    .\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:
    while (Get-Process cdb -ErrorAction SilentlyContinue) { Start-Sleep -Milliseconds 500 }
    
    or simply tail the log and watch it stop growing at F 5:
    Get-Content C:\path\to\<pose>.walk.log -Tail 20 -Wait
    
  4. Run the parts capture (stay in the same pose):
    .\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):
    .\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.