fix(cdb): A6.P1 — a6-probe.cdb v3 with C++ float reads

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>
This commit is contained in:
Erik 2026-05-21 19:50:11 +02:00
parent 7b9b26f647
commit 1b6d49ea57
2 changed files with 51463 additions and 37 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,23 +1,24 @@
$$
$$ Phase A6.P1 cdb probe spike v2 — 2026-05-21
$$ Phase A6.P1 cdb probe spike v3 — 2026-05-21
$$
$$ v2 changes (from v1 dry-run lessons + type dumper offsets):
$$ - All struct offsets verified against PDB via dt acclient!TYPENAME
$$ (see tools/cdb/a6-types-dump.txt for the source).
$$ - BP6 symbol fixed: CTransition::check_walkable (v1 had
$$ validate_walkable which doesn't exist in the PDB).
$$ - Stack-arg reads use [esp+N] with double-indirect (poi(...)+offset)
$$ for pointer args. v1 used arbitrary registers (@edx, @edi) and
$$ produced all-zero data fields.
$$ v3 changes (from v2 dry-run lesson):
$$ - All float reads now use @@c++(*(float*)addr) instead of dwo(addr).
$$ cdb's MASM evaluator treats dwo() as a 32-bit integer, which then
$$ prints as 0.000000 when passed to .printf %f (which expects double).
$$ The @@c++ expression forces C++ interpretation, dereferencing the
$$ address as a float pointer, producing a proper float that printf %f
$$ can format. v2 hit counts were correct but all %f values were 0.
$$ - Integer reads (%d) still use dwo() — that works.
$$
$$ v2 changes (from v1 dry-run):
$$ - All struct offsets verified against PDB via dt acclient!TYPENAME.
$$ - BP6 symbol fixed: CTransition::check_walkable.
$$ - Stack-arg reads use [esp+N] with proper double-indirect.
$$
$$ Calling convention: all functions __thiscall. ecx = this; non-this
$$ args at [esp+4], [esp+8], [esp+0xC] in order, after the return
$$ address at [esp+0].
$$
$$ 7 breakpoints, each with non-blocking action (printf + gc).
$$ Auto-detach via qd after 50,000 total hits (CLAUDE.md gotcha:
$$ high BP rates lag retail enough to trigger ACE timeout).
$$
$$ Field offsets used (from PDB dt dump):
$$ Plane: N.x +0x00, N.y +0x04, N.z +0x08, d +0x0c
$$ CSphere: center.x +0x00, .y +0x04, .z +0x08, radius +0x0c
@ -47,48 +48,43 @@ r $t7 = 0
$$ BP1: CTransition::transitional_insert(this, sub_step_count)
$$ ecx = CTransition*; [esp+4] = sub_step_count (int)
$$ read: sub_step_count, sphere_path.insert_type (trans+0x174)
$$ read: sub_step_count (int), sphere_path.insert_type (int) at trans+0x174
$$ All integer fields; no @@c++ needed.
bp acclient!CTransition::transitional_insert "r $t1 = @$t1 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP1] transitional_insert hit#%d substeps=%d insertType=%d\\n\", @$t1, dwo(@esp+4), dwo(@ecx+0x174); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ BP2: CTransition::step_up(this, step_up_normal)
$$ ecx = CTransition*; [esp+4] = Vector3* step_up_normal
$$ read: walkable_allowance (trans+0x1d8), step_up_normal.z (arg+8)
bp acclient!CTransition::step_up "r $t2 = @$t2 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP2] step_up hit#%d walkAllow=%f normalZ=%f\\n\", @$t2, dwo(@ecx+0x1d8), dwo(poi(@esp+4)+8); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ read: walkable_allowance (float at trans+0x1d8), step_up_normal.z (float via arg)
bp acclient!CTransition::step_up "r $t2 = @$t2 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP2] step_up hit#%d walkAllow=%f normalZ=%f\\n\", @$t2, @@c++(*(float*)(@ecx+0x1d8)), @@c++(*(float*)(*(unsigned int*)(@esp+4)+8)); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ BP3: SPHEREPATH::set_collide(this, collision_normal)
$$ ecx = SPHEREPATH*; [esp+4] = Vector3* collision_normal
$$ read: normal.x, .y, .z via *arg
bp acclient!SPHEREPATH::set_collide "r $t3 = @$t3 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP3] set_collide hit#%d nx=%f ny=%f nz=%f\\n\", @$t3, dwo(poi(@esp+4)+0), dwo(poi(@esp+4)+4), dwo(poi(@esp+4)+8); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ read: normal.x, .y, .z (floats via arg)
bp acclient!SPHEREPATH::set_collide "r $t3 = @$t3 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP3] set_collide hit#%d nx=%f ny=%f nz=%f\\n\", @$t3, @@c++(*(float*)(*(unsigned int*)(@esp+4)+0)), @@c++(*(float*)(*(unsigned int*)(@esp+4)+4)), @@c++(*(float*)(*(unsigned int*)(@esp+4)+8)); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ BP4: BSPTREE::find_collisions(this, transition, walkable_allowance)
$$ ecx = BSPTREE*; [esp+4] = CTransition*; [esp+8] = float walkable_allowance
$$ read: trans.sphere_path.collide (trans+0x124),
$$ trans.sphere_path.insert_type (trans+0x174),
$$ walkable_allowance from arg3 (float-by-value at esp+8)
bp acclient!BSPTREE::find_collisions "r $t4 = @$t4 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP4] find_collisions hit#%d collide=%d insertType=%d walkAllow=%f\\n\", @$t4, dwo(poi(@esp+4)+0x124), dwo(poi(@esp+4)+0x174), dwo(@esp+8); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ ecx = BSPTREE*; [esp+4] = CTransition*; [esp+8] = float walkable_allowance (by-value!)
$$ read: trans.sphere_path.collide (int via arg2+0x124),
$$ trans.sphere_path.insert_type (int via arg2+0x174),
$$ walkable_allowance (float by-value at esp+8)
bp acclient!BSPTREE::find_collisions "r $t4 = @$t4 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP4] find_collisions hit#%d collide=%d insertType=%d walkAllow=%f\\n\", @$t4, dwo(poi(@esp+4)+0x124), dwo(poi(@esp+4)+0x174), @@c++(*(float*)(@esp+8)); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ BP5: CPolygon::adjust_sphere_to_plane(this, sphere_path, sphere, movement)
$$ ecx = CPolygon*; [esp+4] = SPHEREPATH*; [esp+8] = CSphere*; [esp+0xC] = Vector3* movement
$$ THE OVER-CORRECTION SUSPECT. Captures EVERY arg field for paired delta analysis.
$$ read: plane.N.{x,y,z} + plane.d (poly+0x20..+0x2c),
$$ sphere.center.{x,y,z} + sphere.radius (via arg2),
$$ sphere_path.walk_interp (sphere_path+0x1bc),
$$ movement.{x,y,z} (via arg4)
bp acclient!CPolygon::adjust_sphere_to_plane "r $t5 = @$t5 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP5] adjust_sphere hit#%d Nx=%f Ny=%f Nz=%f d=%f cx=%f cy=%f cz=%f r=%f winterp=%f mvx=%f mvy=%f mvz=%f\\n\", @$t5, dwo(@ecx+0x20), dwo(@ecx+0x24), dwo(@ecx+0x28), dwo(@ecx+0x2c), dwo(poi(@esp+8)+0), dwo(poi(@esp+8)+4), dwo(poi(@esp+8)+8), dwo(poi(@esp+8)+0xc), dwo(poi(@esp+4)+0x1bc), dwo(poi(@esp+0xc)+0), dwo(poi(@esp+0xc)+4), dwo(poi(@esp+0xc)+8); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ THE OVER-CORRECTION SUSPECT. Captures EVERY arg field as float for paired delta analysis.
bp acclient!CPolygon::adjust_sphere_to_plane "r $t5 = @$t5 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP5] adjust_sphere hit#%d Nx=%f Ny=%f Nz=%f d=%f cx=%f cy=%f cz=%f r=%f winterp=%f mvx=%f mvy=%f mvz=%f\\n\", @$t5, @@c++(*(float*)(@ecx+0x20)), @@c++(*(float*)(@ecx+0x24)), @@c++(*(float*)(@ecx+0x28)), @@c++(*(float*)(@ecx+0x2c)), @@c++(*(float*)(*(unsigned int*)(@esp+8)+0)), @@c++(*(float*)(*(unsigned int*)(@esp+8)+4)), @@c++(*(float*)(*(unsigned int*)(@esp+8)+8)), @@c++(*(float*)(*(unsigned int*)(@esp+8)+0xc)), @@c++(*(float*)(*(unsigned int*)(@esp+4)+0x1bc)), @@c++(*(float*)(*(unsigned int*)(@esp+0xc)+0)), @@c++(*(float*)(*(unsigned int*)(@esp+0xc)+4)), @@c++(*(float*)(*(unsigned int*)(@esp+0xc)+8)); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ BP6: CTransition::check_walkable(this, threshold)
$$ ecx = CTransition*; [esp+4] = float threshold
$$ v1 used "validate_walkable" — that symbol doesn't exist in the PDB.
$$ The actual function is check_walkable (verified against symbols.json
$$ and decomp at acclient_2013_pseudo_c.txt:272811).
bp acclient!CTransition::check_walkable "r $t6 = @$t6 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP6] check_walkable hit#%d threshold=%f\\n\", @$t6, dwo(@esp+4); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ ecx = CTransition*; [esp+4] = float threshold (by-value)
$$ Expected: ~0.0871556997 (cos 85°) per decomp at line 273202.
bp acclient!CTransition::check_walkable "r $t6 = @$t6 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP6] check_walkable hit#%d threshold=%f\\n\", @$t6, @@c++(*(float*)(@esp+4)); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ BP7: COLLISIONINFO::set_contact_plane(this, plane, is_water)
$$ ecx = COLLISIONINFO*; [esp+4] = Plane*; [esp+8] = int is_water
$$ read: plane.N + plane.d (via arg2), is_water (via arg3)
bp acclient!COLLISIONINFO::set_contact_plane "r $t7 = @$t7 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP7] set_contact_plane hit#%d Nx=%f Ny=%f Nz=%f d=%f isWater=%d\\n\", @$t7, dwo(poi(@esp+4)+0), dwo(poi(@esp+4)+4), dwo(poi(@esp+4)+8), dwo(poi(@esp+4)+0xc), dwo(@esp+8); .if (@$t0 >= 50000) { qd } .else { gc }"
$$ read: plane.N + plane.d (floats via arg2), is_water (int via arg3)
bp acclient!COLLISIONINFO::set_contact_plane "r $t7 = @$t7 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP7] set_contact_plane hit#%d Nx=%f Ny=%f Nz=%f d=%f isWater=%d\\n\", @$t7, @@c++(*(float*)(*(unsigned int*)(@esp+4)+0)), @@c++(*(float*)(*(unsigned int*)(@esp+4)+4)), @@c++(*(float*)(*(unsigned int*)(@esp+4)+8)), @@c++(*(float*)(*(unsigned int*)(@esp+4)+0xc)), dwo(@esp+8); .if (@$t0 >= 50000) { qd } .else { gc }"
.printf "a6-probe v2 armed: BPs 1-7 set with PDB-verified offsets, threshold=50000 total hits, qd on threshold\n"
.printf "a6-probe v3 armed: BPs 1-7 set with PDB-verified offsets + C++ float reads, threshold=50000 total hits, qd on threshold\n"
$$ Continue execution
g