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>
108 lines
7 KiB
Text
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
|