Commit graph

13 commits

Author SHA1 Message Date
Erik
9017107960 fix(p1): membership already matches retail — the 0/11 was a cdb capture artifact
The P1 "doorway membership lags retail" premise is FALSIFIED. acdream's swept
ResolveWithTransition already matches retail's true per-frame curr_cell: the
production gate ProductionPath_IndoorCrossings reads 9/9 on the indoor 0170<->0171
crossings with NO code change, once fed an aligned retail golden.

Root cause of the false 0/11: CPhysicsObj::SetPositionInternal calls change_cell
(acclient_2013_pseudo_c.txt:283456) BEFORE set_frame writes m_position (:283458),
so the original golden (find-cell-list-capture.cdb, read at the change_cell BP)
paired each frame's NEW cell with the PREVIOUS frame's position — a one-frame skew.
Verified 3 ways: the decomp ordering; golden_picked[i] == geom(golden_position[i+1])
for all 22 rows; acdream's static pick == golden_picked[i-1] for all rows. Both
retail and acdream pick with center-only point_in_cell on global_sphere[0] (no XY
lead; cache_global_sphere @ pc:274196). curr_cell commits via validate_transition
(@ pc:272608, curr_cell = check_cell) = the find_cell_list pick, structurally
identical to acdream's RunCheckOtherCellsAndAdvance -> FindCellSet -> SetCheckPos.
There was nothing to port; a swept advance would make membership LEAD by a frame.

- tools/cdb/find-cell-list-capture-aligned.cdb: re-capture reads the committed
  position from the set_frame that follows change_cell (cell+position same instant).
- Fixtures/find-cell-list-threshold.log: replaced with the aligned capture.
- ThresholdPortalCrossingReplayTests / FindCellListConformanceTests: rewritten from
  documents-the-bug to assert retail truth (per-segment / per-indoor-pick equality).
- handoff + notes + README + memory: banners correcting the disproven premise.

Still open (NOT indoor membership, which is DONE): outdoor->indoor 0031<->0170 entry
conformance (needs landcell + building stab in the gate cache); master-plan cleanups
(delete CheckBuildingTransit, unify find_env_collisions, demote ResolveCellId) refactor
working retail-faithful code -> need explicit user approval.

Conformance 60 pass / 1 skip / 0 fail; full Core 1309 pass / 5 fail (pre-existing
2 BSPStepUp + 3 door-collision = P2) / 1 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 18:54:27 +02:00
Erik
bb4dead0ae test(p0): retail-trace golden captured — membership criterion divergence pinned (P0 GATE MET)
P0 Task 6 complete. Captured live retail membership at the 0031<->0170<->0171
doorway via cdb on CPhysicsObj::change_cell (symbol-driven; offsets verified by
discover-types.cdb; PDB MATCH). 22 transitions, clean monotonic sequence, NO
ping-pong (retail is correct-by-construction). Golden:
Conformance/Fixtures/find-cell-list-threshold.log.

ROOT-CAUSE FINDING (the central P1 work): retail transitions membership at the
PORTAL CROSSING (CEnvCell::find_transit_cells @ 0x52c820 pc:309968 — sphere crosses
the doorway polygon plane), while acdream's FindCellList re-picks by POINT-IN-CELL
containment at the foot. Retail commits room 0171 while the foot is STILL inside
vestibule 0170's BSP (in_0171=0); acdream lags. ALL 22 transitions diverge for this
one criterion mismatch — not a per-cell hysteresis or a building-entry-only split.
This is master-plan §0 'hysteresis gap' confirmed against the real client.

FindCellList_DoorwayThreshold_DivergesFromRetail_PendingP1 (documents-the-bug, GREEN)
+ ThresholdDivergenceDiagnosticTests (per-transition containment print) pin it; both
flip when P1 ports the directed portal crossing. Conformance 59 pass / 1 skip / 0 fail;
full Core 1308 pass / 5 fail (baseline) / 1 skip — no new failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:04:51 +02:00
Erik
b35e491f12 test(p0): retail find_cell_list trace parser + cdb value-capture tooling
P0 Task 5. RetailTrace parses the [fcl] golden format (seed/pos/picked,
RetailCellPick); 4 TDD tests green. find-cell-list-capture.cdb targets
CPhysicsObj::change_cell (commit-on-diff) to capture retail's accepted
membership sequence at the doorway; README is the operator runbook
(dt offset verification + decode_retail_hex float decode). The live run
is P0's one user-gated step (Task 6 mines existing traces first).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:26:24 +02:00
Erik
fd1548af61 feat(phys): A6.P4 door — cdb-driven NegPolyHit dispatch (incomplete; needs BSP near-miss recording)
cdb attached to retail at a Holtburg cottage door while user walked the
inside-out off-center scenario. Three trace iterations identified that
retail's collision-recording happens via SPHEREPATH::set_neg_poly_hit
(fires hundreds of times during inside-out walk), NOT via the more
obvious-named COLLISIONINFO setters (which fire 0 times). Apparatus
scripts at tools/cdb/door-inside-out-v[1-3].cdb + symbol-probe.cdb.

Our codebase has NegPolyHitDispatch defined but never called. The
downstream TransitionalInsert NegPolyHit handler was a stub. Two-part
fix landed:

1. BSPQuery.FindCollisions Path 5 (Contact branch) restructured —
   distinguishes full hit (hit0 == true → StepSphereUp) from near-miss
   (hit0 == false but hitPoly0 != null → NegPolyHitDispatch). Mirrors
   retail BSPTREE::find_collisions at
   acclient_2013_pseudo_c.txt:0053a630-0053a6fb.

2. Transition.TransitionalInsert NegPolyHit handler — dispatches to
   step_up + step_up_slide (NegStepUp=true) or records collision
   normal + returns Collided (NegStepUp=false). Mirrors retail
   CTransition::transitional_insert at
   acclient_2013_pseudo_c.txt:0050b7af-0050b7e6.

Tests: all 11 fix-relevant + regression tests pass including issue #98.

VISUAL VERIFICATION (user-driven inside-out off-center): still squeezes
through. Diagnostic [neg-poly-dispatch] probe shows ZERO hits in
production. The Path 5 restructuring doesn't surface NegPolyHit
because our SphereIntersectsPolyInternal only sets hitPoly on FULL
hits — retail's sphere_intersects_poly sets var_5c (closest polygon)
even on near-misses via BSP-traversal side effect.

Remaining fix (next session): add near-miss polygon recording to
SphereIntersectsPolyInternal. Once it sets hitPoly on near-miss BSP
traversal, the Path 5 NegPolyHit dispatch (this commit) will fire
and the TransitionalInsert handler (this commit) will block.

Full handoff with cdb trace table + next-step plan:
docs/research/2026-05-25-door-bug-cdb-retail-trace-findings.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:36:22 +02:00
Erik
6f666c14da tools(cdb): A6.P3 #98 Step 4 — retail find_walkable capture script
Step 4 of the apparatus plan. Adds the cdb script + runner that pairs
with Issue98CellarUpReplayTests to compare retail's walkable-query
behavior against acdream's during the Holtburg cottage cellar ascent.

Breakpoints (all symbols verified against refs/acclient.pdb via grep
docs/research/named-retail/symbols.json):
- BPA: BSPLEAF::find_walkable          — leaf-level walkable query
- BPB: CPolygon::walkable_hits_sphere  — per-polygon overlap test
- BPC: CPolygon::find_crossed_edge     — per-polygon edge containment
- BPD: CTransition::check_other_cells  — outer dispatcher
- BPE: COLLISIONINFO::set_contact_plane — GOLD signal: retail accepted
                                          this plane
- BPF: CPolygon::adjust_sphere_to_plane — per-polygon projection

Output format: 32-bit hex bits for all floats via dwo() + %08X (cdb's
%f handling is broken for dwo reads; see a6-probe.cdb v3→v4 history).
Decoder: tools/cdb/decode_retail_hex.py already handles _h=0x... fields.

Auto-detach threshold: 50000 hits across BPA/B/C/D/F. BPE is unbounded
(contact plane writes are rare, ~18 per ascent per slice 5 capture).

Runner: tools/cdb/issue98-runner.ps1
  .\tools\cdb\issue98-runner.ps1 -ScenarioTag "cellar_up_attempt_1"

Prereqs (per CLAUDE.md retail debugger toolchain section):
- Retail acclient.exe v11.4186 running and in-world
- ACE running on 127.0.0.1:9000
- Character at the BOTTOM of a Holtburg cottage cellar stair
- cdb.exe present at the Windows Kits 10 path

Output:
  docs\research\2026-05-23-a6-captures\<ScenarioTag>\retail.log

Reading the log:
- [BPE] lines tell you which plane retail accepted (the answer we need).
- Cross-reference [BPE]'s normal/d against the cell fixtures in
  tests/AcDream.Core.Tests/Fixtures/issue98/*.json to identify which
  cell + polyId retail picked.
- The divergence between retail's accepted polygon and our replay test's
  "no walkable accepted" result IS the fix target.

The capture itself is a user action (cdb requires a live retail
process); this commit only ships the protocol. Step 5 (comparison doc)
follows after the capture lands.
2026-05-23 15:29:02 +02:00
Erik
194ed3ef21 feat(cdb): A6.P1 — decode_retail_hex.py hex→float decoder
Python tool that decodes the retail.log hex-bits float fields produced
by a6-probe.cdb v4 into IEEE 754 single-precision values. Required
because cdb's .printf %f doesn't reliably format floats from dwo()
reads — v4 works around this by emitting 32-bit hex, this script
reinterprets via struct.unpack('<f', struct.pack('<I', value)).

Verified against scen1 retail.log:
  BP6 threshold_h=0x3F2A0751 → threshold=0.6642 (= FloorZ exactly)
  BP5 hit#1 Nz_h=0x3F800000 → Nz=1.0 (ground normal)
  9,517 float fields decoded across 9,331 lines.

Output written next to input as .decoded.log. Format matches
acdream-side [push-back] probe (4-decimal floats), so A6.P2
analysis can compare line-for-line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:03:03 +02:00
Erik
2d841cb615 fix(cdb): A6.P1 — a6-probe.cdb v4 hex-bits floats
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>
2026-05-21 19:55:48 +02:00
Erik
1b6d49ea57 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>
2026-05-21 19:50:11 +02:00
Erik
7b9b26f647 fix(cdb): A6.P1 — a6-probe.cdb v2 with PDB-verified offsets
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>
2026-05-21 19:43:50 +02:00
Erik
d0c8c54d96 fix(cdb): A6.P1 — v1 dry-run lessons + v2 prep tooling
Dry-run of scenario 1 (retail-v1-broken-offsets.log preserved as
audit trail) surfaced three issues with the v1 cdb script:

1. STACK-ARG OFFSETS WRONG: BP actions used arbitrary registers
   (@edx, @edi) to read function args, but __thiscall puts non-this
   args on the stack ([esp+N] after the return address). All 12 BP5
   "adjust_sphere" hits printed Nx=0.0 Ny=0.0 ... — fields not read.
   Fixed by writing a type dumper (a6-types-dump.cdb + runner) that
   uses cdb's `dt` command against the loaded PDB to get authoritative
   struct offsets. v2 probe script (to be written next) will use
   double-indirect reads (dwo(poi(@esp+N)+offset)) with correct
   offsets from the dump.

2. TEE-OBJECT UTF-16 ENCODING: PowerShell's default Tee-Object writes
   UTF-16 LE with BOM, making logs unparseable by grep without
   conversion. Runner now uses Out-File -Encoding ASCII. Sacrifices
   live console echo; use `Get-Content -Tail 50 -Wait` in a separate
   shell if live monitoring is needed.

3. BP6 SYMBOL NOT FOUND: `acclient!CTransition::validate_walkable`
   doesn't exist in the PDB. Decomp at line 272811 has
   `CTransition::check_walkable` — likely the actual name. To be
   verified + fixed in v2.

The BP hit-count distribution from v1 is still meaningful diagnostic
data (14,318 transitional_insert + 16,558 find_collisions + 40
set_contact_plane + 12 adjust_sphere + 1 step_up + 1 set_collide in
a 2-second walk through the inn doorway). Preserved as a baseline
sanity-check the v2 distribution can be diffed against.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:38:31 +02:00
Erik
df315a9654 docs(cdb): A6.P1 — README for the cdb probe + runner
Documents prerequisites (PDB match, cdb install, retail+ACE
running), per-scenario invocation, the 9-scenario tag table, and
the parallel acdream capture command. Includes the CLAUDE.md cdb
watchouts inline so probe operators don't have to chase them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:44:42 +02:00
Erik
1c640ebefa feat(cdb): A6.P1 — PowerShell runner for a6-probe.cdb
Wrapper that attaches cdb to a live retail acclient.exe with a
scenario-tagged log path. Per-scenario invocation:
  .\tools\cdb\a6-probe-runner.ps1 -ScenarioTag "scen1_inn_doorway"
Output: docs\research\2026-05-21-a6-captures\<ScenarioTag>\retail.log

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:43:34 +02:00
Erik
7bb799b02c feat(cdb): A6.P1 — 7-BP probe script for retail BSP collision response
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>
2026-05-21 18:41:29 +02:00