acdream/tools/walk-oracle/oh/oh-capture-parts.cdb.template
Erik ebaa41df1a 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>
2026-09-02 18:09:11 +02:00

111 lines
7.1 KiB
Text

* 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