v3 with @@c++(*(float*)..) STILL produced 0.000000 across the board.
Conclusion: cdb's .printf %f is unreliable for our use case — possibly
doesn't handle the float-to-double promotion in varargs the way C
printf does, or has a deeper limitation we don't have time to debug.
Pivoting to: print all floats as 32-bit hex bits via %08X, reinterpret
in the Python analysis pipeline via struct.unpack('<f', bytes.fromhex(...))
to recover IEEE 754 single-precision values.
This bypasses cdb's float formatting entirely. Integer reads (which
work — substeps, insertType, collide flag, isWater) stay as %d.
The smoking gun: BP6's check_walkable threshold should be 0.0871556997
(cos 85°) per the decomp call site at acclient_2013_pseudo_c.txt:273202.
v4's BP6 should output threshold_h=0x3DB283D7. If it does, the
infrastructure is sound and we can proceed to all 9 scenarios.
v3 capture preserved as retail-v3-cpp-zero-floats.log audit trail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v2 dry-run produced correct hit counts but all %f field values
printed as 0.000000 — including BP6 threshold which the decomp says
must be 0.0871556997f (cos 85°). Root cause: cdb's MASM evaluator
returns dwo(addr) as a 32-bit integer; .printf %f expects a 64-bit
double; passing the integer to %f produces formatted-zero garbage.
Fix: switch all float-reading expressions to @@c++(*(float*)addr).
The C++ evaluator dereferences memory as a float pointer, returning
a proper float that .printf %f formats correctly. Integer reads (%d)
still use MASM dwo() — that works.
For double-indirect (pointer args), the form is
@@c++(*(float*)(*(unsigned int*)(@esp+N)+offset))
which reads the pointer at [esp+N], adds the offset, and treats the
result as a float pointer.
v2 capture preserved as retail-v2-zero-floats.log audit trail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces v1's broken-offset BP actions with PDB-authoritative field
reads. All offsets extracted from `dt acclient!TYPENAME` against the
loaded PDB (output preserved at tools/cdb/a6-types-dump.txt).
Key offsets:
Plane.N at +0x00, .d at +0x0c
CSphere.center at +0x00, .radius at +0x0c
CPolygon.plane at +0x20
SPHEREPATH.collide +0x104, .walkable_allowance +0x1b8, .walk_interp +0x1bc
CTransition.sphere_path +0x020 (so e.g. CTransition+0x174 = insert_type)
Per-BP arg-read fixes (all use __thiscall: ecx=this, args at [esp+N]):
BP1: substeps from [esp+4], insertType from this+0x174
BP2: walkable_allowance from this+0x1d8, normal.z from *(arg+8)
BP3: normal.x/y/z from *arg
BP4: collide+insertType via *(arg2+0x124/0x174), walkAllow from arg3
BP5 (the over-correction suspect): full plane + sphere + walk_interp +
movement vector. 12 fields, all double-indirect for pointer args.
BP6 SYMBOL FIXED: CTransition::check_walkable (v1 had
validate_walkable which doesn't exist; check_walkable confirmed
in symbols.json and at decomp line 272811).
BP7: plane + isWater from *arg.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sets non-blocking breakpoints on transitional_insert, step_up,
set_collide, find_collisions, adjust_sphere_to_plane,
validate_walkable, set_contact_plane. Each BP increments a counter
and emits a single printf line. Auto-detach via qd at 50K total
hits to avoid retail lag (CLAUDE.md gotcha — high BP rates trigger
ACE timeout).
Also adds !tools/cdb/*.cdb negation to .gitignore so committed
reference scripts in tools/cdb/ are tracked despite the blanket
*.cdb scratch-file rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>