tools(render): OH retail capture scripts for the S3/S4 answer key
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>
This commit is contained in:
parent
13fc7d8349
commit
ebaa41df1a
5 changed files with 716 additions and 0 deletions
291
tools/walk-oracle/oh/README.md
Normal file
291
tools/walk-oracle/oh/README.md
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
# 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.
|
||||||
108
tools/walk-oracle/oh/oh-capture-alpha-depth.cdb.template
Normal file
108
tools/walk-oracle/oh/oh-capture-alpha-depth.cdb.template
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
* OH1 walk-oracle capture: ALPHA + DEPTH LIFECYCLE (2026-09-02). Captures
|
||||||
|
* the two retail FIFO alpha lists (oh1-alpha-list-contract.md) and the
|
||||||
|
* persistent portalsDrawnCount depth state machine (oh1-depth-lifecycle.md)
|
||||||
|
* at call-entry granularity. Frame count default 5 (substitute <FRAMES>).
|
||||||
|
*
|
||||||
|
* SCOPE NOTE: oh1-depth-lifecycle.md's exact state machine touches specific
|
||||||
|
* PCs INSIDE PView::DrawCells (0x5A4885 mid-frame stamp increment, 0x5A488C
|
||||||
|
* counter read, 0x5A489E counter reset, 0x5A48A9 conditional clear -- see
|
||||||
|
* that doc's "Ghidra branch arbitration table"). This script does NOT set
|
||||||
|
* breakpoints at those interior addresses: OH1's own report supplies the PC
|
||||||
|
* ranges but this task did not re-disassemble them, so the exact register
|
||||||
|
* holding the counter value at each of those specific mid-function PCs is
|
||||||
|
* unconfirmed here -- guessing a register at an arbitrary mid-function
|
||||||
|
* instruction (as opposed to a function ENTRY, where the calling convention
|
||||||
|
* fixes argument locations) is a materially different and riskier kind of
|
||||||
|
* guess than anything else in this directory. Instead this script samples
|
||||||
|
* the GLOBAL portalsDrawnCount memory cell (0x008719B4, confirmed by OH1's
|
||||||
|
* Ghidra xref set as the ONLY three accesses to that address in the whole
|
||||||
|
* binary) at two safe, function-ENTRY breakpoints: PView::DrawCells itself
|
||||||
|
* (the "PC" line, giving counterBefore for that eligible call -- which,
|
||||||
|
* per oh1-depth-lifecycle.md's own "Consecutive-call example" table, IS the
|
||||||
|
* same value as counterAfter for the previous eligible call, so sampling
|
||||||
|
* only at entry is sufficient to reconstruct the sequence offline) and
|
||||||
|
* every DrawPortalPolyInternal attempt (the "PM" line, giving the
|
||||||
|
* pre-this-call's-own-increment counter value for each individual mask
|
||||||
|
* attempt). This is a deliberate, documented scope reduction, not an
|
||||||
|
* oversight -- see the README's open questions.
|
||||||
|
*
|
||||||
|
* AUTO-DETACH: same fall-through + top-level qd recipe as the other two
|
||||||
|
* scripts in this directory. Never qd inside a bp action.
|
||||||
|
*
|
||||||
|
* LINE FORMATS:
|
||||||
|
* F <n> -- unchanged from FW0
|
||||||
|
* P <cellid> <ox> <oy> <oz> <qw> <qx> <qy> <qz> -- unchanged from FW0
|
||||||
|
* AM mesh=<ptr> surf=<n> new=<0|1> clip=<0|1> listSel=<n> -- NEW.
|
||||||
|
* D3DPolyRender::AddMeshToAlphaList @0x0059C230 entry. mesh/surf are
|
||||||
|
* arg1 (MeshBuffer*) / arg2 (surfaceNum); new = arg4 (bNewObject);
|
||||||
|
* clip = arg5 (overrideClipmapSetting); listSel = arg6 (0 selects the
|
||||||
|
* ALPHA list, nonzero selects the CLIP list -- confirmed directly from
|
||||||
|
* the decompiled body at acclient_2013_pseudo_c.txt:424925-424982, which
|
||||||
|
* branches on "arg6 == 0" to the alphaedMeshCountAlpha/List* globals vs.
|
||||||
|
* the alphaedMeshCountClip/List* globals). See the calling-convention
|
||||||
|
* note below before trusting these stack offsets.
|
||||||
|
* FL thresh=<hex> ret=<hex> -- NEW.
|
||||||
|
* D3DPolyRender::FlushAlphaList @0x0059D2E0 entry. thresh = arg1's raw
|
||||||
|
* float bits (decode offline; oh1-alpha-list-contract.md ss7 lists the
|
||||||
|
* five call sites and their literal threshold constants -- 0f/0f/0f/0f
|
||||||
|
* from DrawBuilding/PView::DrawCells/SmartBox::RenderNormalMode/
|
||||||
|
* CreatureMode::Render, 0.75f from DrawBlock's emergency-capacity
|
||||||
|
* drain); ret = the return address on the stack at function entry
|
||||||
|
* (poi(@esp), i.e. [esp] itself before the callee's own push ebp),
|
||||||
|
* which a human can map back to one of those five sites offline via
|
||||||
|
* `ln` or the oh1-alpha-list-contract.md ss7 table.
|
||||||
|
* PM poly=<ptr> mode=<0|1> counterBefore=<hex> -- NEW.
|
||||||
|
* D3DPolyRender::DrawPortalPolyInternal @0x0059BC90 entry. poly = arg1
|
||||||
|
* (CPolygon*); mode = arg2 (0 = true-depth / exit-seal or mode-3
|
||||||
|
* fallback, nonzero = far-Z / building punch -- oh1-depth-lifecycle.md
|
||||||
|
* "Far-Z punches and true-depth exit seals" table); counterBefore is
|
||||||
|
* the GLOBAL portalsDrawnCount sampled BEFORE this specific call's own
|
||||||
|
* local-boundary-reject-then-maybe-increment logic runs. Far-mode
|
||||||
|
* (mode!=0) attempts NEVER change the counter; true-mode (mode==0)
|
||||||
|
* attempts increment it by 1 UNLESS the whole-polygon local x/y==+/-12
|
||||||
|
* boundary reject fires first (which this script cannot observe --
|
||||||
|
* only the audited CPU clip in a real port could).
|
||||||
|
* PC ov=<n> counter=<hex> fc=<0|1> -- NEW.
|
||||||
|
* PView::DrawCells @0x005A4840 entry (SAME breakpoint address as FW0's
|
||||||
|
* DC line, but this script does NOT also emit a DC line -- the task
|
||||||
|
* spec for this script only asked for the alpha/depth family, not the
|
||||||
|
* cell-shell roster; see oh-capture-walk.cdb.template for DC). ov =
|
||||||
|
* outside_view.view_count (PView+0x38); counter = portalsDrawnCount
|
||||||
|
* sampled at this call's entry (the "previous eligible call's" final
|
||||||
|
* value, per oh1-depth-lifecycle.md); fc = the forceClear global
|
||||||
|
* (0x008ED824, expected 0 -- OH1 found no write site for it in this
|
||||||
|
* binary, so a nonzero read here would itself be a notable finding).
|
||||||
|
*
|
||||||
|
* CALLING-CONVENTION NOTE (OPEN QUESTION -- the second big one, see README):
|
||||||
|
* AddMeshToAlphaList, FlushAlphaList, and DrawPortalPolyInternal all show
|
||||||
|
* NO "this" receiver in the named pseudo-C signatures (unlike
|
||||||
|
* DrawBuilding/DrawEnvCell/DrawPartCell/DrawMeshInternal, which explicitly
|
||||||
|
* annotate "this @ ecx"). This script ASSUMES that absence means a plain
|
||||||
|
* stack-argument layout starting immediately at esp+4 for arg1 (i.e.
|
||||||
|
* either true __cdecl, or __thiscall with an entirely UNUSED ecx receiver
|
||||||
|
* that does not consume a stack slot either way) -- NOT that some visible
|
||||||
|
* argument is silently arg2 of an unshown receiver. oh-recon.cdb includes
|
||||||
|
* `uf` disassembly dumps of all three functions specifically so a human
|
||||||
|
* can read the prologue and confirm (or refute) this before trusting the
|
||||||
|
* stack offsets below. If `uf` shows the first instructions touching
|
||||||
|
* [esp+4] before referencing ecx for anything other than a dead/unused
|
||||||
|
* load, the assumption holds.
|
||||||
|
*
|
||||||
|
* BEFORE USE: replace <FRAMES> (plain decimal digits) and <LOG> (output
|
||||||
|
* path). <VPOS> is pre-resolved to Render::viewer_pos @0x0081ef00, same as
|
||||||
|
* the other two scripts in this directory.
|
||||||
|
|
||||||
|
.logopen <LOG>
|
||||||
|
.sympath C:\Users\erikn\source\repos\acdream\refs
|
||||||
|
.symopt+ 0x40
|
||||||
|
.reload /f acclient.exe
|
||||||
|
r $t0 = 0
|
||||||
|
bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .printf \"P %08x %08x %08x %08x %08x %08x %08x %08x\\n\", poi(0081ef00+4), poi(0081ef00+0x3c), poi(0081ef00+0x40), poi(0081ef00+0x44), poi(0081ef00+8), poi(0081ef00+0xc), poi(0081ef00+0x10), poi(0081ef00+0x14); .if (@$t0 < 0n<FRAMES>) { gc }"
|
||||||
|
bp 005a4840 ".printf \"PC ov=%d counter=%04x fc=%d\\n\", poi(@ecx+0x38), wo(008719b4), poi(008ed824); gc"
|
||||||
|
bp 0059c230 ".printf \"AM mesh=%08x surf=%d csurf=%08x new=%d clip=%d listSel=%d\n\", poi(@esp+4), poi(@esp+8), poi(@esp+0xc), by(@esp+0x10), by(@esp+0x14), by(@esp+0x18); gc"
|
||||||
|
bp 0059d2e0 ".printf \"FL thresh=%08x ret=%08x\\n\", poi(@esp+4), poi(@esp); gc"
|
||||||
|
bp 0059bc90 ".printf \"PM poly=%08x mode=%d counterBefore=%04x\n\", poi(@esp+4), by(@esp+8), wo(008719b4); gc"
|
||||||
|
g
|
||||||
|
.echo ===DETACHING===
|
||||||
|
qd
|
||||||
|
.logclose
|
||||||
111
tools/walk-oracle/oh/oh-capture-parts.cdb.template
Normal file
111
tools/walk-oracle/oh/oh-capture-parts.cdb.template
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
* OH1 walk-oracle capture: PARTS (2026-09-02). The high-hit-rate script --
|
||||||
|
* CPhysicsPart::Draw and RenderDeviceD3D::DrawMeshInternal fire far more
|
||||||
|
* often per frame than DrawCells/DrawEnvCell/DrawBuilding (once per resident
|
||||||
|
* part vs. once per cell/building). Frame count default 3 (substitute
|
||||||
|
* <FRAMES>) to keep the trap cost bounded -- see
|
||||||
|
* claude-memory/project_retail_debugger.md's "High hit rates lag the game"
|
||||||
|
* watchout. Every bp action here is ONE .printf plus a handful of poi reads,
|
||||||
|
* per the task's own instruction; no .while loops, no nested nested calls.
|
||||||
|
*
|
||||||
|
* AUTO-DETACH: same fall-through + top-level qd recipe as
|
||||||
|
* oh-capture-walk.cdb.template / tools/cdb/pview-spiral2.cdb. Never qd
|
||||||
|
* inside a bp action.
|
||||||
|
*
|
||||||
|
* LINE FORMATS:
|
||||||
|
* F <n> -- unchanged from FW0
|
||||||
|
* P <cellid> <ox> <oy> <oz> <qw> <qx> <qy> <qz> -- unchanged from FW0
|
||||||
|
* PD gfx=<ptr> did=<hex> force=<0|1> cell=<hex> -- NEW.
|
||||||
|
* CPhysicsPart::Draw @0x0050D7A0 entry. gfx = the resolved
|
||||||
|
* CGfxObj* for the part's current degrade level (this->gfxobj[deg_level]);
|
||||||
|
* did = that GfxObj's DataID (see OPEN QUESTION below -- offset
|
||||||
|
* unconfirmed, placeholder token); force = the arg2 force flag (0 =
|
||||||
|
* ordinary CShadowPart::draw call, 1 = DrawBuilding's forced
|
||||||
|
* portal-only pass); cell = the LAST cell DID a DrawPartCell breakpoint
|
||||||
|
* saw (best-effort call-context correlation -- see note below, this can
|
||||||
|
* be STALE for the first part drawn after a building's force pass and
|
||||||
|
* before the frame's first ordinary object cell).
|
||||||
|
* DM gfx=<ptr> did=<hex> force=<0|1> bound=<0|1|2> cell=<hex> -- NEW.
|
||||||
|
* RenderDeviceD3D::DrawMeshInternal @0x0059F360 entry. gfx/did are read
|
||||||
|
* DIRECTLY from arg2 (no degrade-level resolution needed -- this is the
|
||||||
|
* already-selected CGfxObj*, cheaper/more reliable than the PD line's
|
||||||
|
* read); force = arg3; bound = arg4 (BoundingType: 0=OUTSIDE,
|
||||||
|
* 1=PARTIALLY_INSIDE, 2=ENTIRELY_INSIDE per
|
||||||
|
* oh1-built-mesh-view-contract.md's viewconeCheck table); cell = same
|
||||||
|
* best-effort DrawPartCell correlation as PD.
|
||||||
|
* There is no separate printed line for RenderDeviceD3D::DrawPartCell -- it
|
||||||
|
* is instrumented SILENTLY (register store only, no .printf) purely to
|
||||||
|
* supply the "cell" field above. DrawPartCell's own arg2 IS the cell DID
|
||||||
|
* directly (acclient_2013_pseudo_c.txt:429177 -- DrawObjCell calls
|
||||||
|
* "DrawPartCell(this, arg2->m_DID.id, &arg2->vtable, 0)"), so this is a
|
||||||
|
* clean 1-instruction, zero-.printf hook cheap enough to run every part
|
||||||
|
* draw's containing cell-walk turn.
|
||||||
|
*
|
||||||
|
* OFFSETS (VERIFY-IN-RECON -- see oh-recon.cdb's dt list):
|
||||||
|
* CPhysicsPart+0x14 = deg_level, +0x1C = draw_state, +0x20 = gfxobj,
|
||||||
|
* +0x24 = gfxobj_scale, +0x78 = draw_pos, +0xC0 = material, +0xC4 =
|
||||||
|
* surfaces, +0xDC = m_current_render_frame_num, +0xE0 = physobj.
|
||||||
|
* +0x1C/+0x20/+0xC0/+0xC4/+0xDC/+0xE0 are TRIPLE-confirmed: OH1's own
|
||||||
|
* Ghidra table (oh1-built-mesh-view-contract.md "View state"/"Built
|
||||||
|
* content and stamps") cites them independently, AND they fall out of
|
||||||
|
* acclient.h:31151-31173's field order (CYpt float @0x00, viewer_heading
|
||||||
|
* Vector3 @0x04, degrades ptr @0x10, deg_level uint @0x14, deg_mode int
|
||||||
|
* @0x18, draw_state int @0x1C, gfxobj ptr @0x20, ...). +0x14 (deg_level)
|
||||||
|
* is ONLY the acclient.h-order derivation -- OH1 never independently
|
||||||
|
* Ghidra-cited that specific field, though its immediate neighbors
|
||||||
|
* (+0x1C, +0x20) are both confirmed, which is reassuring but not proof.
|
||||||
|
* Confirm with `dt acclient!CPhysicsPart` in oh-recon.cdb before trusting
|
||||||
|
* the PD line's degrade-level GfxObj resolution.
|
||||||
|
* CGfxObj+0x6C = constructed_mesh, +0x70 = use_built_mesh, +0x90 =
|
||||||
|
* drawing_sphere, +0x94 = drawing_bsp -- same triple-confirmation
|
||||||
|
* (OH1 table + acclient.h:31712-31731 field order, matched exactly).
|
||||||
|
* Not directly read by this script, listed for completeness/cross-check.
|
||||||
|
* RenderDeviceD3D::DrawMeshInternal(this@ecx, CGfxObj* arg2, uint8 arg3,
|
||||||
|
* BoundingType arg4) -- arg2 @ esp+4, arg3 @ esp+8, arg4 @ esp+0xC.
|
||||||
|
* Signature: acclient_2013_pseudo_c.txt:427965 ("this @ ecx" annotation,
|
||||||
|
* same shape as the already-proven DrawBuilding/DrawEnvCell hooks).
|
||||||
|
* CPhysicsPart::Draw(this, int32 arg2) -- plain __thiscall, this=ecx,
|
||||||
|
* arg2 (force) @ esp+4. Signature: acclient_2013_pseudo_c.txt:274964.
|
||||||
|
* RenderDeviceD3D::DrawPartCell(this@ecx, uint32 arg2, CPartCell* arg3,
|
||||||
|
* uint32 arg4) -- arg2 (cell DID) @ esp+4. Signature:
|
||||||
|
* acclient_2013_pseudo_c.txt:429198.
|
||||||
|
*
|
||||||
|
* RESOLVED 2026-09-02 (lead): DBObj::m_DID is at +0x28. DBObj : Interface lays out vfptr@0, m_dataCategory@4, m_bLoaded@8, m_timeStamp(8-byte)@0x10, m_pNext@0x18, m_pLast@0x1C, m_pMaintainer@0x20, m_numLinks@0x24, m_DID@0x28; DBObj is the FIRST base of CGfxObj and of CObjCell (via SerializeUsingPackDBObj), and CEnvCell+0x28 is the offset every FW0 capture already proved. oh-recon.cdb's dt acclient!DBObj re-confirms. Original note kept below for provenance:
|
||||||
|
* GfxObj DataID offset. CGfxObj : DBObj (acclient.h:31712), and DBObj's
|
||||||
|
* own m_DID field (acclient.h:27579) sits behind an "Interface" vtable
|
||||||
|
* base, a `long double` timestamp, and several pointers whose EXACT
|
||||||
|
* compiler-inserted padding this research could not hand-derive with
|
||||||
|
* confidence (x86 MSVC alignment of the `long double`/double member under
|
||||||
|
* DBObj's own __declspec(align(4)) is genuinely ambiguous from the header
|
||||||
|
* text alone). DO NOT trust the placeholder below. Replace
|
||||||
|
* 0x28 with the real hex offset oh-recon.cdb's
|
||||||
|
* `dt acclient!CGfxObj` / `dt acclient!DBObj` output reports for m_DID
|
||||||
|
* before running this script for real. There is no runner-substituted
|
||||||
|
* default for this token (oh-run-capture.ps1 only fills <LOG>/<FRAMES>) --
|
||||||
|
* hand-edit the two `0x28` occurrences below.
|
||||||
|
*
|
||||||
|
* CALLING-CONVENTION NOTE: CPhysicsPart::Draw and DrawMeshInternal both use
|
||||||
|
* the proven "this@ecx (or plain thiscall), remaining args on the stack
|
||||||
|
* starting at esp+4" shape already exercised by the FW0 BLD/DI hooks, so
|
||||||
|
* this script's stack reads carry the SAME confidence as those. This is
|
||||||
|
* NOT true of the alpha-list functions in oh-capture-alpha-depth.cdb.template
|
||||||
|
* -- see that file's own calling-convention note.
|
||||||
|
*
|
||||||
|
* BEFORE USE: replace <FRAMES> (plain decimal digits), <LOG> (output path),
|
||||||
|
* and BOTH occurrences of 0x28 (see OPEN QUESTION above).
|
||||||
|
* <VPOS> is pre-resolved to Render::viewer_pos @0x0081ef00, same as
|
||||||
|
* oh-capture-walk.cdb.template.
|
||||||
|
|
||||||
|
.logopen <LOG>
|
||||||
|
.sympath C:\Users\erikn\source\repos\acdream\refs
|
||||||
|
.symopt+ 0x40
|
||||||
|
.reload /f acclient.exe
|
||||||
|
r $t0 = 0
|
||||||
|
r $t5 = 0
|
||||||
|
bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .printf \"P %08x %08x %08x %08x %08x %08x %08x %08x\\n\", poi(0081ef00+4), poi(0081ef00+0x3c), poi(0081ef00+0x40), poi(0081ef00+0x44), poi(0081ef00+8), poi(0081ef00+0xc), poi(0081ef00+0x10), poi(0081ef00+0x14); .if (@$t0 < 0n<FRAMES>) { gc }"
|
||||||
|
bp 005a07a0 "r $t5 = poi(@esp+4); gc"
|
||||||
|
bp 0050d7a0 "r $t6 = poi(poi(@ecx+0x20) + poi(@ecx+0x14)*4); .printf \"PD gfx=%08x did=%08x force=%d cell=%08x\\n\", @$t6, poi(@$t6+0x28), poi(@esp+4), @$t5; gc"
|
||||||
|
bp 0059f360 "r $t7 = poi(@esp+4); .printf \"DM gfx=%08x did=%08x force=%d bound=%d cell=%08x\\n\", @$t7, poi(@$t7+0x28), by(@esp+8), poi(@esp+0xc), @$t5; gc"
|
||||||
|
g
|
||||||
|
.echo ===DETACHING===
|
||||||
|
qd
|
||||||
|
.logclose
|
||||||
75
tools/walk-oracle/oh/oh-capture-walk.cdb.template
Normal file
75
tools/walk-oracle/oh/oh-capture-walk.cdb.template
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
* OH1 walk-oracle capture: WALK (2026-09-02). Adds the cell-shell/object-cell
|
||||||
|
* stamp events (EC/OC) to the exact proven FW0 F/P/LS/BLD/DI/DC per-frame
|
||||||
|
* shape. Frame count default 5 (substitute <FRAMES> via
|
||||||
|
* oh-run-capture.ps1 -Frames, or by hand).
|
||||||
|
*
|
||||||
|
* AUTO-DETACH: this file deliberately does NOT reuse FW0's own
|
||||||
|
* ".if (@$t0 >= 0nN) { qd } .else { gc }" shape -- that puts `qd` inside a
|
||||||
|
* CONDITIONAL bp action, which the project's own retail-debugger toolchain
|
||||||
|
* memory documents as NEVER FIRING (qd/q/qq inside a bp action is silently
|
||||||
|
* ignored; re-confirmed the hard way on issue #177, see
|
||||||
|
* claude-memory/project_retail_debugger.md). Instead this uses the proven
|
||||||
|
* tools/cdb/pview-spiral2.cdb recipe: the frame-marker bp's action calls
|
||||||
|
* `gc` while below threshold and simply omits `gc` on the terminal hit (so
|
||||||
|
* the action "falls through" and the whole process genuinely stops); control
|
||||||
|
* then returns to the script's single top-level `g`, and a TOP-LEVEL `qd`
|
||||||
|
* (not inside any bp action) detaches cleanly. Every other bp in this file
|
||||||
|
* always `gc`s unconditionally -- only the frame marker decides when to
|
||||||
|
* stop, exactly like every other capture script in this directory.
|
||||||
|
*
|
||||||
|
* LINE FORMATS:
|
||||||
|
* F <n> -- unchanged from FW0
|
||||||
|
* P <cellid> <ox> <oy> <oz> <qw> <qx> <qy> <qz> -- unchanged from FW0
|
||||||
|
* LS -- unchanged from FW0
|
||||||
|
* BLD <cellid> -- unchanged from FW0
|
||||||
|
* DI <cellid> -- unchanged from FW0
|
||||||
|
* DC pv=<PView*> ov=<n> n=<n>: <ids...> -- unchanged from FW0
|
||||||
|
* EC <cellid> -- NEW. RenderDeviceD3D::DrawEnvCell
|
||||||
|
* @0x0059F170 entry; id is the
|
||||||
|
* CEnvCell's m_DID (arg2+0x28).
|
||||||
|
* OC <cellid> -- NEW. RenderDeviceD3D::
|
||||||
|
* DrawObjCellForDummies
|
||||||
|
* @0x005A0760 entry; id is the
|
||||||
|
* CObjCell's m_DID (arg2+0x28).
|
||||||
|
* EC/OC together let a human line up "which cell shells/object lists did
|
||||||
|
* PView::DrawCells' reverse loops (steps 9-10 in oh1-depth-lifecycle.md)
|
||||||
|
* actually stamp-draw this frame" against the DC line's flood-cell roster.
|
||||||
|
*
|
||||||
|
* OFFSETS (VERIFY-IN-RECON -- see oh-recon.cdb, all in this file's dt list):
|
||||||
|
* CEnvCell+0x28 / CObjCell+0x28 = m_DID -- PROVEN, not a guess: this is
|
||||||
|
* the exact offset the existing FW0 DI/DC/BLD lines already read in
|
||||||
|
* production captures (docs/research/2026-08-30-fw-walk-oracle/*.log),
|
||||||
|
* and CEnvCell : CObjCell (acclient.h:32072) shares the base-class
|
||||||
|
* layout, so DrawObjCellForDummies's CObjCell* argument reads the same
|
||||||
|
* way. Still listed in oh-recon.cdb's dt for a fresh re-confirmation.
|
||||||
|
* RenderDeviceD3D::DrawEnvCell(this@ecx, CEnvCell* arg2) -- arg2 @ esp+4.
|
||||||
|
* Signature: acclient_2013_pseudo_c.txt:427885. Same "this@ecx, one
|
||||||
|
* stack arg" shape as the already-proven DrawBuilding/DrawInside hooks.
|
||||||
|
* RenderDeviceD3D::DrawObjCellForDummies(this, CObjCell* arg2) -- plain
|
||||||
|
* __thiscall, arg2 @ esp+4. Signature: acclient_2013_pseudo_c.txt:429177.
|
||||||
|
*
|
||||||
|
* BEFORE USE: replace <FRAMES> (plain decimal digits, e.g. "5" -- the
|
||||||
|
* template already supplies the "0n" decimal-radix prefix) and <LOG>
|
||||||
|
* (output path). oh-run-capture.ps1 -Frames does this substitution.
|
||||||
|
* <VPOS> is pre-resolved to Render::viewer_pos @0x0081ef00 (the address the
|
||||||
|
* FW0 posed captures already used successfully -- see
|
||||||
|
* claude-memory/project_frame_walk_campaign.md 2026-08-30 evening entry and
|
||||||
|
* tools/walk-oracle/fw0-capture-pose.cdb.template's own <VPOS> note). Re-run
|
||||||
|
* `x acclient!*viewer_pos*` in oh-recon.cdb if this session's binary differs.
|
||||||
|
|
||||||
|
.logopen <LOG>
|
||||||
|
.sympath C:\Users\erikn\source\repos\acdream\refs
|
||||||
|
.symopt+ 0x40
|
||||||
|
.reload /f acclient.exe
|
||||||
|
r $t0 = 0
|
||||||
|
bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .printf \"P %08x %08x %08x %08x %08x %08x %08x %08x\\n\", poi(0081ef00+4), poi(0081ef00+0x3c), poi(0081ef00+0x40), poi(0081ef00+0x44), poi(0081ef00+8), poi(0081ef00+0xc), poi(0081ef00+0x10), poi(0081ef00+0x14); .if (@$t0 < 0n<FRAMES>) { gc }"
|
||||||
|
bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||||
|
bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||||
|
bp 00506330 ".printf \"LS\\n\"; gc"
|
||||||
|
bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||||
|
bp 0059f170 ".printf \"EC %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||||
|
bp 005a0760 ".printf \"OC %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||||
|
g
|
||||||
|
.echo ===DETACHING===
|
||||||
|
qd
|
||||||
|
.logclose
|
||||||
131
tools/walk-oracle/oh/oh-run-capture.ps1
Normal file
131
tools/walk-oracle/oh/oh-run-capture.ps1
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
# OH1 walk-oracle capture runner (2026-09-02).
|
||||||
|
#
|
||||||
|
# Substitutes <LOG> and the <FRAMES> frame-count token into one of this
|
||||||
|
# directory's oh-capture-*.cdb.template files (or runs oh-recon.cdb
|
||||||
|
# unmodified, since it has no <FRAMES> token to fill), verifies the LIVE
|
||||||
|
# retail acclient.exe process actually pairs with our named-retail PDB
|
||||||
|
# (aborting on mismatch -- see claude-memory/project_retail_debugger.md and
|
||||||
|
# CLAUDE.md's "Retail debugger toolchain" section for why this matters: a
|
||||||
|
# 2015 C:\Turbine\Asheron's Call\acclient.exe build does NOT pair with
|
||||||
|
# refs/acclient.pdb, only the 2013 v11.4186 build does), then launches cdb
|
||||||
|
# in the BACKGROUND (non-blocking -- the operator needs the foreground to
|
||||||
|
# drive the retail client during the capture) with output redirected to a
|
||||||
|
# companion ".console" file next to -Log.
|
||||||
|
#
|
||||||
|
# NOTE: this script does NOT resolve every token some templates need.
|
||||||
|
# oh-capture-parts.cdb.template has two <GFXOBJ_DID_OFF> occurrences that
|
||||||
|
# are NOT substituted here (see that file's own OPEN QUESTION comment and
|
||||||
|
# the README) -- hand-edit those before pointing -Script at that template
|
||||||
|
# for a real session.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# .\oh-run-capture.ps1 -Script oh-recon.cdb -Log C:\path\to\oh-recon.log
|
||||||
|
# .\oh-run-capture.ps1 -Script oh-capture-walk.cdb.template `
|
||||||
|
# -Log C:\path\to\holtburg-doorway-still.walk.log -Frames 5
|
||||||
|
#
|
||||||
|
# Then, in another shell (this script returns immediately):
|
||||||
|
# Get-Content C:\path\to\holtburg-doorway-still.walk.log -Tail 50 -Wait
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Script,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Log,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateRange(1, 200)]
|
||||||
|
[int]$Frames = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$cdbExe = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe"
|
||||||
|
if (-not (Test-Path -LiteralPath $cdbExe)) {
|
||||||
|
throw "cdb.exe was not found at '$cdbExe'. Install Microsoft Store WinDbg (~50 MB)."
|
||||||
|
}
|
||||||
|
|
||||||
|
$symbolPath = "C:\Users\erikn\source\repos\acdream\refs"
|
||||||
|
if (-not (Test-Path -LiteralPath $symbolPath)) {
|
||||||
|
throw "Symbol directory '$symbolPath' does not exist. Every template in this " +
|
||||||
|
"directory hardcodes '.sympath $symbolPath' -- fix that path or create the directory."
|
||||||
|
}
|
||||||
|
|
||||||
|
$scriptDir = $PSScriptRoot
|
||||||
|
$scriptPath = if ([System.IO.Path]::IsPathRooted($Script)) { $Script } else { Join-Path $scriptDir $Script }
|
||||||
|
if (-not (Test-Path -LiteralPath $scriptPath)) {
|
||||||
|
throw "Capture script '$scriptPath' does not exist."
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- verify the LIVE acclient.exe pairs with refs/acclient.pdb ---
|
||||||
|
$retail = Get-CimInstance Win32_Process -Filter "Name = 'acclient.exe'" |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($null -eq $retail) {
|
||||||
|
throw "No live retail acclient.exe process was found. Launch retail and get " +
|
||||||
|
"in-world before running a capture (see CLAUDE.md's Retail debugger " +
|
||||||
|
"toolchain section step 2)."
|
||||||
|
}
|
||||||
|
|
||||||
|
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..")).Path
|
||||||
|
$checkScript = Join-Path $repoRoot "tools\pdb-extract\check_exe_pdb.py"
|
||||||
|
if (-not (Test-Path -LiteralPath $checkScript)) {
|
||||||
|
throw "PDB pairing checker not found at '$checkScript'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$pairing = & py $checkScript $retail.ExecutablePath 2>&1 | Out-String
|
||||||
|
if ($pairing -notmatch "=== MATCH:") {
|
||||||
|
throw @"
|
||||||
|
The live retail executable at '$($retail.ExecutablePath)' does NOT pair with
|
||||||
|
refs/acclient.pdb. Do not attach -- symbol offsets will be silently wrong.
|
||||||
|
The matching binary is documented in claude-memory/project_retail_debugger.md
|
||||||
|
as v11.4186 (Sept 2013 EoR build, PDB GUID
|
||||||
|
9e847e2f-777c-4bd9-886c-22256bb87f32); a 2015 C:\Turbine\Asheron's
|
||||||
|
Call\acclient.exe build will MISMATCH.
|
||||||
|
|
||||||
|
$pairing
|
||||||
|
"@
|
||||||
|
}
|
||||||
|
Write-Host "PDB pairing OK: $($retail.ExecutablePath) (PID $($retail.ProcessId))"
|
||||||
|
|
||||||
|
# --- substitute <LOG> and <FRAMES>, write a temp copy ---
|
||||||
|
$logPath = if ([System.IO.Path]::IsPathRooted($Log)) { $Log } else { Join-Path (Get-Location) $Log }
|
||||||
|
$logDir = Split-Path -Parent $logPath
|
||||||
|
if ($logDir -and -not (Test-Path -LiteralPath $logDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = Get-Content -LiteralPath $scriptPath -Raw
|
||||||
|
$content = $content.Replace('<LOG>', $logPath)
|
||||||
|
$content = $content.Replace('<FRAMES>', $Frames.ToString([System.Globalization.CultureInfo]::InvariantCulture))
|
||||||
|
|
||||||
|
if ($content -match '<GFXOBJ_DID_OFF>') {
|
||||||
|
Write-Warning ("'$scriptPath' still contains an unresolved <GFXOBJ_DID_OFF> " +
|
||||||
|
"token -- this runner does not fill it in. Hand-edit the template with " +
|
||||||
|
"the offset oh-recon.cdb's 'dt acclient!CGfxObj' reports for the " +
|
||||||
|
"inherited DBObj::m_DID field before proceeding, or the capture will " +
|
||||||
|
"produce a garbage 'did=' field on every PD/DM line.")
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||||
|
$tempScript = Join-Path $env:TEMP "oh-capture-$timestamp.cdb"
|
||||||
|
Set-Content -LiteralPath $tempScript -Value $content -Encoding ASCII
|
||||||
|
|
||||||
|
# --- launch cdb in the background ---
|
||||||
|
$consoleLog = "$logPath.console"
|
||||||
|
Write-Host "Attaching cdb to acclient.exe PID $($retail.ProcessId)."
|
||||||
|
Write-Host "Trace log: $logPath"
|
||||||
|
Write-Host "Console log: $consoleLog"
|
||||||
|
Write-Host "Temp script: $tempScript (not auto-deleted -- cdb needs it while running)"
|
||||||
|
Write-Host "Frame threshold: $Frames"
|
||||||
|
Write-Host "Tail the trace live with: Get-Content '$logPath' -Tail 50 -Wait"
|
||||||
|
|
||||||
|
$proc = Start-Process -FilePath $cdbExe `
|
||||||
|
-ArgumentList @("-pn", "acclient.exe", "-cf", $tempScript) `
|
||||||
|
-RedirectStandardOutput $consoleLog `
|
||||||
|
-RedirectStandardError "$consoleLog.err" `
|
||||||
|
-WindowStyle Hidden `
|
||||||
|
-PassThru
|
||||||
|
|
||||||
|
Write-Host "cdb launched in the background, PID $($proc.Id). This script returns now --"
|
||||||
|
Write-Host "drive the retail client into the target pose and hold still until the"
|
||||||
|
Write-Host "capture auto-detaches (frame marker hits the $Frames-frame threshold)."
|
||||||
Loading…
Add table
Add a link
Reference in a new issue