acdream/tools/walk-oracle/oh/oh-capture-alpha-depth.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

108 lines
7 KiB
Text

* 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