Updates issue #98 with the sharp diagnosis from the retail cellar cdb
trace (commit 134c9b8):
The bug isn't cell-resolver, isn't walk_interp, isn't dat-fidelity.
It's BSP path-selection: our dispatcher picks Path 5 (Contact step_up)
for the cellar ramp polygon when retail picks Path 6 (find_walkable
land). The ramp is walkable (N.Z=0.695 > FloorZ=0.6642) so Path 6 is
the correct choice. Investigation continues in next session at
BSPQuery.FindCollisions path-selection logic.
Also documents failed fix attempts this session as informational so
next session doesn't re-attempt the same dead ends.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User walked retail UP the same Holtburg cottage cellar that acdream
gets stuck on. cdb captured retail's BSP behavior for paired
comparison against the acdream polydump trace (0b44996).
Retail (successful walk):
BP1 transitional_insert: 2,651
BP2 step_up: 29 (incl. 1 hit on the ramp slope, n.z=0.6950)
BP4 find_collisions: 4,032
BP5 adjust_sphere: 30 (ALL on FLAT planes; ZERO on the ramp)
BP6 check_walkable: 25
BP7 set_contact_plane: 18 (ALL set the SAME flat plane:
(0,0,1) d=-93.9998 = world Z=94 =
cottage main floor)
Acdream (stuck — from scen4_cottage_cellar_polydump):
cp-write: 229,300
push-back: ~1000 (270 on the RAMP slope poly 0x0008)
step_up_slide: 159
THE DIVERGENCE — pinpointed:
Retail's BSP path-selection for the cellar ramp picks Path 6 (find_walkable
land) — the ramp is treated as a walkable floor to LAND ON. Result:
BP7 sets the contact plane to the cottage main floor (Z=94). No push-back
needed on the ramp.
Our BSP picks Path 5 (Contact → step_up → adjust_sphere push-back) for the
SAME ramp polygon. Result: 270 push-backs against the ramp slope; step_up
keeps failing → step_up_slide loop → player stuck.
NEXT STEP (new session): trace why our BSP picks Path 5 instead of Path 6
for the ramp. Likely in BSPQuery.FindCollisions dispatcher's
path-selection logic. The ramp is walkable (N.Z=0.695 > FloorZ=0.6642) so
Path 6 should fire. Maybe a wrong ObjectInfo state flag, or a sub-step
order issue, or the ramp polygon's BSP-side classification is wrong.
This capture + the polydump capture give a complete picture for the next
investigation session. No more guess-fixes today — the data is now sharp.
Test suite: 1148 + 8 (unchanged this commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 3 v2 (3e140cf) added point-in cell-stickiness in
ResolveCellId's indoor branch. User verification + slice3v2 capture
confirms: cell-resolver ping-pong is FULLY CLOSED.
Data:
- scen2_v2 capture (pre-slice-3): 20+ cell-transit events with
rampant ping-pong (0xA9B4014B ↔ 0xA9B4014A ↔ 0xA9B4013F at the
cellar boundary, Z stable ~96.4 — same tick re-classification)
- slice3v2 capture (post-fix): 1 cell-transit event (login teleport
only) — ping-pong fully eliminated
Findings:
- A6.P2 Finding 3 (cell-resolver sling-out family) CLOSED.
- Issue #90 (sphere-overlap stickiness workaround in same function)
now redundant; can be removed in A6.P4 after broader visual
verification.
- Issue #97 (phantom collisions + fall-through on 2nd floor) hypothesis
pending: same instability family, likely closed as side-effect of
this fix. Re-test on next happy-test session.
- Issue #98 (cellar-up stuck) PERSISTS but with NEW DIAGNOSIS.
Originally filed as cell-resolver ping-pong (which was true and now
fixed), but user verification shows the cellar-up symptom remains
with a DIFFERENT root cause: BSP step-physics at the cellar stair
TOP. Push-back trace from slice3v2 capture:
n=(0, -0.719, 0.695) sloped face (walkable per FloorZ=0.664)
delta=(0, 0, 0.75) step-down probe lifts sphere by 0.75m
winterp=1.0->0.0 entire walk-interp consumed per tick
Player progresses up most of the stairs but blocks at top step
where the cellar transitions to the cottage main floor. #98 issue
updated with this re-diagnosis.
Includes:
- scen4_cottage_cellar_slice3 acdream.log (slice 3 v1 evidence;
ping-pong already closed by v1's sphere-overlap stickiness, but
v1 over-corrected by holding player in cellar during legitimate
transitions)
- scen4_cottage_cellar_slice3v2 acdream.log (slice 3 v2 evidence;
point-in stickiness fixes the over-correction; cellar-up reveals
the deeper BSP step-physics bug)
Docs updated:
- ISSUES.md — #98 re-diagnosed
- docs/plans/2026-04-11-roadmap.md — A6.P3 slice 3 marked SHIPPED;
slice 4 (or A6.P4) scoped for #98 step-physics investigation
- CLAUDE.md — Currently-working-toward block updated
Test suite: 1148 pass + 8 pre-existing fail (baseline maintained).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 2 v1 (`892019b`) attempted to close issue #96 by removing the
PhysicsEngine.cs L622 per-tick CP seed. v1 build/test green, CP-write
count dropped 91% in scen3 re-capture, BUT user happy-test surfaced
a regression: BSP step_up at the last step of stairs failed because
sub-step 1's AdjustOffset had no ContactPlane to compute the lift
direction.
Slice 2 v2 (`f8d669b`) reverted the seed removal + added a no-op-if-
unchanged guard inside CollisionInfo.SetContactPlane. The guard
early-returns when called with values matching current ci state.
Outcome:
- #96 PARTIALLY ADDRESSED, scope updated in ISSUES.md to "accepted as
documented retail divergence." The seed is load-bearing for step_up;
closing #96 fully would require deeper refactor (AdjustOffset
fallback to body.ContactPlane). Guard is benign improvement.
- Slice 2 v2 verification capture (scen3_inn_2nd_floor_slice2v2/
acdream.log) committed as evidence — 226,464 cp-writes from L624
seed confirms guard doesn't trigger for fresh-ci-per-tick pattern.
- Slice 2 v1 verification capture (scen3_inn_2nd_floor_slice2/
acdream.log) also committed — confirms v1 actually reduced cp-writes
(2,690 total) but the step_up regression made it unshippable.
NEW M1.5 BLOCKER FILED — issue #98: cellar ascent stuck at last step.
Evidence in slice2v2 capture's cell-transit chain:
0xA9B4014B → 0xA9B4014A → 0xA9B4013F → 0xA9B4014A → 0xA9B4014B → ...
(Z stable ~96.4; CellId ping-pongs every tick)
This is Finding 3 family (cell-resolver hysteresis missing) — same
root cause as #90 workaround + scen4 sling-out. Retail oracle:
CObjCell::find_cell_list Position-variant at
acclient_2013_pseudo_c.txt:308742-308783.
NEXT — A6.P3 slice 3:
- Port retail's cell-array hysteresis into ResolveCellId +
CheckBuildingTransit.
- Closes#98 (cellar-up), possibly #97 (phantom collisions same
instability family), enables #90 workaround removal.
Documents updated:
- ISSUES.md — #96 scope updated, #98 filed
- docs/plans/2026-04-11-roadmap.md — A6.P3 slice 2 marked SHIPPED,
slice 3 scope added
- CLAUDE.md — Currently-working-toward block updated to slice 3
Test suite: 1148 pass + 8 pre-existing fail (baseline maintained).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unexpected slice 1 win: the synthesis-strip + Mechanism B (LKCP
restore) fix didn't just close Finding 2 (CP-write blowup) — it also
unblocked stair-walking, which A6.P2 had categorized as Finding 1+3
territory expected to need separate fixes. User reports walking up
and down the inn stairs multiple times in acdream post-fix.
Shape shift in tag distribution:
Tag Pre-fix (FAIL) Post-fix (SUCCESS) Signal
---- ------------- ------------------ ------
indoor-walkable 859 0 synthesis gone
push-back-cell 1478 879 (-40%) multi-cell relaxed
push-back 51 345 (+577%) real step-up firing
push-back-disp 4156 6055 (+46%) real traversal
cp-write 33969 57846 L622 seed (slice 2)
Pre-fix: synthesis firing while physics hammers BSP trying to resolve
stair-step (failure mode). Post-fix: real BSP queries succeeding, real
step-up + step-down landing. Same shape as retail's stair-climb
(retail scen2: BP2 step_up=188, push-back-disp dominates).
A6.P2 Finding 1 (dispatcher entry frequency mismatch) hypothesis was
"likely secondary effect of Finding 2 — may close as side effect of
the fix." Confirmed empirically: dispatcher activity now matches
retail-like shape without explicit Finding 1 work.
Remaining (slice 2 territory):
- L622 per-tick PhysicsEngine.ResolveWithTransition seed fires 99.3%
of remaining cp-writes; retail's equivalent fires zero times on
flat-floor walks. Gate this seed to close the remaining CP-write
gap.
- Phantom collisions + occasional fall-through on 2nd floor reported
by user during happy-testing. New issue to file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review feedback on commit 6b4be7f:
- Section 1: strip stale [309NNN] inline annotations (off by 2-8
lines from actual file content; the 0052c1xx address comments
are the reliable anchor); address comments already present in the
decomp output are now used as inline anchors instead
- Section 2: validate_transition function header is at file line
272547 (was: 272538, inside the preceding check_collisions
function). Address 0050aa70 + LKCP-block range 272565-272583
were already correct. References section updated to match.
- Section 5: add note that SetContactPlane re-latches LKCP fields
(no-op when LKCP is the source, but non-obvious side effect)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-fix research note grounding the indoor CP-retention refactor in
retail's exact LKCP-restore pattern (acclient_2013_pseudo_c.txt:272565-272582)
and CEnvCell::find_env_collisions tiny shape (line 309573).
Key findings:
- find_env_collisions writes NO ContactPlane — only BSP Path 6 does (Mech A)
- validate_transition Collided/Slid/Adjusted branch calls set_contact_plane
from LKCP when proximity guard passes (global_curr_center, not global_sphere)
- Our ValidateTransition is missing the SetContactPlane call in that branch
(sets Contact/OnWalkable flags only) — this is the gap Task 4 closes
- Proximity sphere should be GlobalCurrCenter[0] not GlobalSphere[0]
- Exact insertion point: TransitionTypes.cs ~line 2849, inside the
'radius + EPSILON > |angle|' proximity-guard branch
Output of this note drives the per-transition Mechanism B insertion
point selection in Task 4 + the slice-1 acceptance shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A6.P1 (cdb probe spike) + A6.P2 (analysis report) both SHIPPED this
session. Updated:
docs/plans/2026-04-11-roadmap.md — M1.5 phase block now shows A6.P1
+ A6.P2 SHIPPED with commit refs; A6.P3 entry expanded with the
Finding-2-first sequencing recommendation from A6.P2; A6.P4 entry
notes the original "Holtburg Sewer end-to-end" acceptance walk is
unreachable (sewer doesn't exist).
docs/plans/2026-05-12-milestones.md — M1.5 demo scenario split into
building/cellar half (achievable post-A6.P3) + dungeon half (blocked
on issue #95 visibility blowup; promote to post-M1.5 if #95 isn't
fixed in scope). Issue list updated: added #95 + indoor sling-out
(new from scen4); marked stairs/2nd-floor/cellar as characterized by
A6.P2 Finding 2 family.
CLAUDE.md — Currently-working-toward block now points at A6.P3 as
the active phase. A6.P1 + A6.P2 ship noted with the findings doc
pointer. Demo-scenario note updated to reflect the sewer + #95
reality. Issues-in-scope updated.
Also includes a 1-line trailing-prompt addition to scen3 + scen4
retail.log files (cdb wrote one more `0:000>` after the kill that
landed after the original capture commits).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the A6.P1 stub with the analysis pass over 5 paired captures
(scen1-5). Scen6-9 (sewer-specific) cancelled because the Holtburg Sewer
doesn't exist on this ACE server and any substitute dungeon hits issue
#95 (portal-graph visibility blowup) on entry.
Four findings ready for A6.P3 sequencing:
Finding 1 — Dispatcher entry frequency mismatch (4x to 281x fewer in
acdream). Likely secondary effect; may close as side-effect
of Finding 2 fix.
Finding 2 — ContactPlane resynthesis blowup. 250x to INFINITE more CP
writes in acdream. Strongest single signal; scen3 shows
retail wrote CP zero times during a flat 2nd-floor walk
while acdream wrote 86,748 field updates. Primary M1.5
root cause. HIGH severity.
Finding 3 — Indoor cell-resolver sling-out (scen4). Resolver flings
+Acdream across landblock boundary; CheckBuildingTransit
fires 5,495 times during the sling while indoor BSP is
barely queried. Same family as the M1.5 cell-tracking
ping-pong hypothesis. HIGH severity.
Finding 4 — Portal-graph visibility blowup (scen5 incidental). Filed
as issue #95; not strictly A6 scope but documented here so
A6.P3 sequencing knows about it.
Tables 1+2 (per-site push-back delta + path-frequency diff) deferred to
A6.P1.5: the v4 cdb probe captures function entry only, not exit values.
Adding paired exit BPs is ~1 hour of cdb scripting work but not needed
unless A6.P3 fixes fail to close the symptoms.
Table 3 (CP lifecycle) fully populated — geometric mean CP-write ratio
across 4 finite scenarios is ~1,470x; median ~2,200x.
Table 4 (sub-step state mutations) partially populated with proxy
metrics (per-tag firing rates).
M1.5 symptom coverage matrix: every in-scope physics symptom maps to
at least one finding. Acceptance per spec §4.7 met.
A6.P3 sequencing recommendation: Finding 2 first (highest-confidence
single-cause; may close Finding 1 as side effect), re-run captures, then
Finding 3. Issue #95 handled separately outside A6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Filed during A6.P1 scen5 capture (Town Network as substitute for the
nonexistent Holtburg Sewer). After portal teleport, visibleCells per
cell explodes from ~4 to 135-145, with cells from multiple disconnected
landblocks loaded simultaneously — direct cause of the user-observed
"see through walls / other dungeons rendering" failure across all
portal-accessed dungeons.
This is what "dungeons are broken" means as a coherent failure mode.
Scen6-9 (sewer corridor/chamber/stair) as originally scripted couldn't
have produced clean physics-only captures because the dungeon would
have been visually unusable from the moment of portal entry. The A6.P1
scenario script was written before the visibility bug was characterized.
Evidence is in scen5's acdream.log (already committed at 35d5c58).
Scen6-9 are not captured — the visibility bug blocks the scenarios.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Substituted "Holtburg Sewer" portal with Town Network Portal — no
sewer entry exists in this world (user-verified). Town Network is
also an outdoor->indoor portal transition with the same physics
signature.
Both clients walked to the portal, entered, walked 2 m inside.
Retail: clean traversal. Acdream: also clean (no failure mode).
Retail (decoded, 23,890 raw / 9,769 BP lines):
BP1 transitional_insert: 13,863
BP4 find_collisions: 9,552
BP5 adjust_sphere: 97
BP6 check_walkable: 55
BP7 set_contact_plane: 65 (moderate, portal threshold + indoor)
BP2 step_up: 1
Acdream (31,914 lines, no failure):
[cp-write]: 20,956 (vs retail BP7 = 65 — ~322x ratio)
[cell-cache]: 9,642 (Holtburg landblock streaming)
[check-bldg]: 740
[push-back-disp]: 34 (flat-ground walking)
[push-back]: 1
[cell-transit]: 12 (CLEAN traversal, no thrashing)
cell-transit event chain — captures the portal entry signature:
0x00000000 -> 0xA9B30030 (login teleport)
0xA9B30030 -> 0xA9B40029 -> 0xA9B40021 -> 0xA9B40019 ->
0xA9B40011 -> 0xA9B40012 -> 0xA9B4000A -> 0xA9B4000B ->
0xA9B40003 (walked across Holtburg, all reason=resolver)
0xA9B40003 -> 0x00070143 reason=teleport (PORTAL ENTRY)
scen5 is the "control" — both clients reached their target, no
visible failure. The CP-write blowup persists as the only A6.P2
divergence. Useful baseline for separating "indoor physics broken
during walking" (scen2, scen3, scen4) from "indoor physics okay
when portal-delivered" (scen5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Asymmetric pair (scenario-level, not protocol-failure):
- Retail: user walked UP out of the cellar (ascent of 2 cellar
steps + exit through doorway) — captures ascent + indoor-to-
outdoor transition.
- Acdream: user teleported INTO the cellar, walked a few meters,
the resolver flung +Acdream OUTSIDE the cottage entirely
(landblock prefix changed A9B4 -> A9B3 mid-walk) — captures
a real indoor physics failure that's not a stair issue per se.
Both traces are valuable to A6.P2 even though they don't match
walk-for-walk.
Retail (decoded, 22,536 raw / 12,875 decoded BP lines):
BP1 transitional_insert: 9,402
BP4 find_collisions: 12,596 (ended in mem-access error
@ hit#12596 - cdb hit a null
transition arg, dropped to
interactive prompt; worth a
note for A6.P2 retail edge)
BP5 adjust_sphere: 136
BP6 check_walkable: 128
BP2 step_up: 13 (2-step cellar = 13 vs scen2
4-step inn = 188; non-linear)
BP7 set_contact_plane: 3 (Finding 2 holds)
Acdream (42,001 lines, ended with sling-out):
[cp-write]: 35,624
[check-bldg]: 5,495 (CheckBuildingTransit fired
constantly trying to re-resolve
which building +Acdream was in)
[cell-cache]: 540
[push-back-disp]: 82 (very few dispatcher hits)
[push-back]: 1 (almost no sphere-adjustment)
[indoor-bsp]: 2 (indoor BSP barely queried!)
[cell-transit]: 3 (3 transit events captured the sling:
0xA9B40148 -> 0xA9B40029 -> 0xA9B30030
all reason=resolver)
Sling-out signature: indoor BSP never engaged (only 2 indoor-bsp
hits), but the cell resolver fired 3 transit events crossing a
landblock boundary, with check-bldg thrashing in between. This is
distinct from scen2's stair-attempt pattern (which hammered the
BSP); scen4 shows the resolver pushing the character out of indoor
space entirely without triggering the indoor BSP collision path.
A6.P2 fix surface: investigate why ResolveCellId / CheckBuildingTransit
push a player from indoor cell 0xA9B40148 to outdoor cell 0xA9B30030
through routine walking. Likely the same family as the M1.5 hypothesis:
indoor cell membership isn't sticky (the ping-pong bug from the
2026-05-20 A4 handoff in a different guise).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User reached the 2nd floor in acdream via ACE @teleport (stair-physics
unblocked separately by scen2). Retail walked normally to 2nd floor.
Both clients performed the same walk: forward 3 m, sidestep 1 m,
walk back. Flat-floor scenario, no stairs, no transitions.
Retail (decoded, 21,337 lines):
BP1 transitional_insert: 10,217 hits
BP4 find_collisions: 10,636 hits
BP5 adjust_sphere: 113 hits
BP6 check_walkable: 113 hits threshold=0.6642
BP2 step_up: 0 hits (no stairs)
BP3 set_collide: 0 hits (no walls)
BP7 set_contact_plane: 0 hits (KEY: zero CP updates)
Acdream (93,558 lines):
[cp-write]: 86,748 (vs retail BP7 = 0 — INFINITE ratio)
[push-back-disp]: 2,752
[push-back]: 320
[push-back-cell]: 550
[other-cells]: 550
[indoor-bsp]: 1,061
[indoor-walkable]: 707
KEY FINDING for A6.P2: scen3 is the strongest CP-write blowup
evidence yet. On a flat 2nd-floor walk where retail's
set_contact_plane fires ZERO times across the entire scenario,
acdream rewrites the contact plane 86,748 times. This is the
exact pattern Finding 2 hypothesized (M1.5 design spec §1.2):
acdream resynthesizes CP every frame instead of retaining it
through the documented retention mechanisms (LKCP-restore,
Path-6 land write, post-OK step-down probe).
scen3 pair confirms CP-write blowup isn't stair-specific — it
fires equally for ordinary flat-floor walking inside any indoor
cell. A6.P3 fix surface: same as Finding 2 — stop resynthesizing
CP per frame.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Original acdream capture (a9a427f) was a doorway-walk because acdream's
indoor stair physics doesn't work. For A6.P2 to characterize the
divergence we need the FAILURE captured, not a substitute walk.
User re-attempted the inn stairs in acdream (whatever it produces:
bumping, sliding, stuck). Failure signature is dramatic vs door-walk:
Tag | door-walk | stair-attempt | ratio
----------------+-----------+---------------+------
push-back-disp | 1,141 | 4,156 | 3.6x
push-back-cell | 87 | 1,478 | 17x
other-cells | 87 | 1,478 | 17x
indoor-bsp | 343 | 1,286 | 3.7x
indoor-walkable | 227 | 859 | 3.8x
cp-write | 70,244 | 33,969 | 0.5x (!)
The 17x explosion on push-back-cell / other-cells says acdream's
CheckOtherCells loop fires constantly when physics can't resolve a
stair-step — the indoor BSP query fails, then the multi-cell
fallback fails, then the next tick repeats. The cp-write DROP
(half the door-walk volume) is the inverse signal: when no ground
plane resolves, no CP gets written. Both are A6.P2 fix-surface
indicators.
Now scen2 pair = retail successfully climbs (BP2 step_up=188) vs
acdream tries and fails (push-back-cell explosion).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Acdream-side capture for the Holtburg inn doorway walk, paired with
the v4 retail capture committed at 180b4a5. 84,130 lines total.
Probe line distribution (~30 sec session, ~2 sec actual walk):
[push-back] (adjust_sphere): 8 hits — vs retail BP5 12 hits
[push-back-disp] (dispatch): 295 — vs retail BP4 5818 (!)
[push-back-cell] (other_cells): 5 — vs retail's check_other_cells
[indoor-bsp]: 26
[cell-transit]: 30 (cell ID changes)
[cp-write]: 73,304 (per-field writes) — vs retail BP7 18 fn calls (!)
[cell-cache]: 540
Two major divergences already visible from this single scenario:
1. DISPATCH FREQUENCY: retail's BSPTREE::find_collisions fires 20×
more than acdream's BSPQuery.FindCollisions. Could reflect either
different physics tick rate, different sub-step cadence, or
different call paths into the dispatcher.
2. CONTACTPLANE LIFECYCLE: acdream writes CP fields 73,304 times
in 30 seconds (~2,400/sec). Retail calls set_contact_plane 18
times (~0.6/sec). Even with a 6× field-write multiplier per
set_contact_plane call, that's ~100 actual CP updates in retail
vs ~12K in acdream — 100-1000× more frequent in acdream. This
directly confirms the spec's hypothesis that FindEnvCollisions
indoor branch is rewriting CP every frame (sub-step?) instead
of retaining it across frames. Same family as the
TryFindIndoorWalkablePlane workaround.
Per-call shape comparison (BP5 hit#1):
Retail: plane=(0,0,1) d=-0.0, sphere=(0.0046,10.31,-0.27) r=0.48,
mvmt=(0,-0,-0.75), winterp=1.0
Acdream: plane=(0,0,1) d=-0.0, sphere=(-0.43,11.02,0.46) r=0.48,
mvmt=Z-down, winterp 1.0→0.96 (small adjust applied)
Identical operation SHAPE (ground plane + vertical step-down probe
+ same radius). XY positions differ because walks were independent.
Scenario 1 complete. Remaining 8 scenarios deferred per user
direction. Python hex→float decoder + A6.P1 handoff doc to follow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v4 cdb probe captured paired field data for the Holtburg inn
doorway walk. 13,552 BP hits in ~2 sec of walking. Distribution:
- BP1 transitional_insert: 7,686 (sub-step loop)
- BP4 find_collisions: 5,818 (per cell per sub-step)
- BP5 adjust_sphere_to_plane: 12 (the over-correction suspect)
- BP6 check_walkable: 12
- BP7 set_contact_plane: 18
Smoking-gun verification:
BP6 threshold_h=0x3F2A0751 ≈ 0.664 = PhysicsGlobals.FloorZ
BP5 plane normal = (0,0,1), movement = (0,-0,-0.75) — classic
step-down probe against the ground polygon
BP5 sphere radius = 0x3EF5C28F ≈ 0.480 m — player foot sphere
All hex-bits floats decode cleanly via Python struct.unpack('<f').
Decoder script TBD as part of the handoff.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
User swapped in the correct Sept 2013 EoR build acclient.exe.
GUID {9e847e2f-777c-4bd9-886c-22256bb87f32}, linker UTC
2013-09-06T00:17:56 — exact match for refs/acclient.pdb.
T15 captures are unblocked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Creates the 9 per-scenario capture directories (gitkeep stubs) and
the findings doc stub at docs/research/2026-05-21-a6-cdb-capture-findings.md.
A6.P1 fills the capture log slots (Task 15, user-driven); A6.P2
fills the analysis tables and findings section.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit trail for the A6.P1 capture session: the retail binary at
C:\Turbine\Asheron's Call\acclient.exe is the 2015-06-12 build
(GUID {08e25c14-e2a1-46d5-b056-92b2e43a7234}), not the Sept 2013
EoR build that pairs with refs/acclient.pdb
(expected GUID {9e847e2f-777c-4bd9-886c-22256bb87f32}).
BP-driven A6 captures cannot proceed until the matching binary is
installed. User needs acclient.exe v11.4186 (linker timestamp
2013-09-06) to match our PDB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15-task TDD plan covering the three pieces of A6.P1:
Phase A — Build the [push-back] acdream probe (Tasks 1-9):
toggle + 3 helpers + 3 emission sites in BSPQuery/Transition,
DebugVM mirror, CLAUDE.md env-var docs.
Phase B — Build the cdb infrastructure (Tasks 10-12):
7-BP cdb script, PowerShell runner, README.
Phase C — Execute 9 captures + findings stub (Tasks 13-15):
PDB-match verify, capture dir + findings stub, scenario captures.
API surface verified against current code: ResolvedPolygon has no
Id property (probe omits poly attribution; cross-ref via time-
adjacent [push-back-cell] line). CheckOtherCells locals are
sp.CheckCellId + cellId + result (verified at TransitionTypes.cs
lines 1418-1473). SpherePath has Collide/InsertType/WalkInterp,
ObjectInfo has State (verified).
Spec: docs/superpowers/specs/2026-05-21-phase-a6-indoor-physics-fidelity-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brainstormed + approved 2026-05-21 for M1.5 milestone work. Designs
the cdb probe spike methodology (7 retail breakpoints + new
[push-back] probe) to capture retail's per-tick BSP collision
response state at 9 indoor scenarios (4 buildings + 5 dungeon sites)
and compare against acdream. Working hypothesis: BSPQuery.AdjustSphereToPlane
or its callers over-correct vs retail, producing the family of
indoor symptoms (walls walk through, ping-pong, vibration, multi-Z
falling) plus driving the existing #90 + TryFindIndoorWalkablePlane
workarounds. A6 ships in 4 slices: P1 probe spike, P2 analysis,
P3 surgical fixes, P4 workaround removal + acceptance.
Phase O (DatPath Unification) pre-empted M1.5 and shipped 2026-05-21;
A6 resumes from Phase O state. Phase O only touched rendering/dat
code; indoor physics design is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ONE thing touches the DATs. WB code lives in our repo:
- src/AcDream.Core/Rendering/Wb/ — pure helpers (5 files, ~782 LOC)
- src/AcDream.App/Rendering/Wb/ — GL infra + mesh pipeline (~27 files, ~7K LOC)
Project references to WorldBuilder.Shared + Chorizite.OpenGLSDLBackend
dropped from AcDream.App.csproj and AcDream.Core.csproj.
references/WorldBuilder/ remains in-tree as read-reference only.
DefaultDatReaderWriter eliminated; DatCollection is the only dat reader.
WbMeshAdapter consumes our DatCollection via DatCollectionAdapter
(O-D7 fallback adapter; ObjectMeshManager has 26 _dats.X call sites,
exceeding the 20 refactor threshold).
Visual side-by-side passed: Holtburg town, inn interior, dungeon all
render identically to pre-O.
Doc updates:
- CLAUDE.md: rewrote WB integration cribs to point at extracted code.
Code Structure Rules rule 2 updated to remove stale seam names.
"Currently working toward" flipped from Phase O to M1.5 resumption.
- docs/architecture/worldbuilder-inventory.md: Phase O banner added.
Status/integration model updated to post-O ownership. Workflow
section updated to reference our extracted tree, not WB project ref.
- docs/plans/2026-04-11-roadmap.md: Phase O moved to shipped table.
Phase O "ahead" block collapsed to SHIPPED note. M1.5 block updated
to ACTIVE (Phase O shipped; resuming from 2026-05-20 baseline).
- docs/plans/2026-05-12-milestones.md: M1.5 heading updated to ACTIVE;
Phase O ship writeup prepended to the M1.5 block.
Phase O ship closes Tasks O-T1..O-T7 shipped across this session.
Specs + audit + plan: docs/superpowers/{specs,plans}/2026-05-21-phase-o-*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan: 7 tasks decomposing spec T2..T9 with bite-sized TDD-style steps,
exact file paths, commit-message templates, and a T4 safety-check
branch (refactor in place if ObjectMeshManager._dats call sites <=20;
fall back to thin adapter otherwise).
Spec fix: §4.1 mesh-pipeline files now correctly placed under
src/AcDream.App/Rendering/Wb/ instead of Core (ObjectMeshManager uses
Silk.NET.OpenGL types from Managed* wrappers, and CLAUDE.md forbids
Core depending on GL). §4.2's layer split (TextureHelpers in Core,
rest in App) was already correct.
Plan task order: T2 (setup) -> T5 (Core helpers, lowest risk) ->
T3 (App GL infra) -> T4 (App mesh pipeline + dat-shim) -> T7 (drop
refs + cleanup) -> T8 (visual verification) -> T9 (ship). T5 moved
earlier than spec order to validate the namespace migration flow on
small-blast-radius files before the load-bearing T4.
Self-review: all 12 spec decisions (O-D1..O-D12) mapped to plan tasks;
placeholders intentional + explained (MIT license body fetched at T2
step 4; commit-message parameters filled at task close).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
O-T1 audit (REPORT-ONLY) maps acdream's transitive closure on WorldBuilder:
33 files / ~7.7K LOC across Chorizite.OpenGLSDLBackend (28 files) and
WorldBuilder.Shared (5 files). Verdict on O-Q1 (thread-model): SAFE —
adapters run render-thread only; no worker-thread access to WB code.
Spec amendments incorporated via brainstorm:
- O-D7: Refactor ObjectMeshManager to take DatCollection directly (not
via adapter). T4 safety check — fall back to thin adapter if call-site
count >20.
- O-D8: Drop LandSurfaceManager, EnvCellRenderManager, PortalRenderManager,
TerrainRenderManager from the extract list — audit confirmed not reachable
(we have our own ports or never used them).
- O-D9: Promote 3 internal types in Chorizite to public on extraction
(EmbeddedResourceReader, TextureFormatExtensions, BufferUsageExtensions).
- O-D10: Strip [MemoryPackable] from TerrainEntry (we don't serialize).
- O-D11: Namespace AcDream.Core.Rendering.Wb.* for extracted code.
- O-D12: Drop ResolveId + [indoor-upload] NULL_RESULT diagnostic block.
Task breakdown: T6 (EnvCell/portal) eliminated; T5 (stateless helpers)
shrinks to 0.5d; T4 (mesh + refactor) grows to 2.5d. Net effort estimate
holds at ~7.75d.
All originally-open spec questions are now closed (Q1/Q2/Q3/Q4) or
deferred to T3 with an explicit verify step (Q5: SixLabors.ImageSharp
reachability).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-contained prompt for a fresh Claude Code session. The next session
reads it once, has all the context it needs, produces the WB-usage
closure audit at docs/research/2026-05-21-phase-o-t1-wb-audit.md,
and stops before any extraction. Investigation-only (the /investigate
skill applies). User reviews the audit before T2 begins.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase O extracts the WB pieces we actually use (mesh pipeline, texture
decode, GL state, scenery, terrain blending, EnvCell/portal decode —
roughly 3-5K LOC) into src/AcDream.Core/Rendering/Wb/, swaps their
dat dependency from DefaultDatReaderWriter to our DatCollection, and
drops the WorldBuilder.Shared + Chorizite.OpenGLSDLBackend project
references. WB stays in references/ as a read reference, not as a
project dependency. MIT attribution in NOTICE.md.
Tagline: ONE thing touches the DATs.
Discipline: verbatim copy first, no "improvements" while extracting.
Refactors land in follow-up phases. Out of scope: re-porting from
retail decomp; perf optimization; API cleanup.
User direction 2026-05-21: pre-empts M1.5. M1.5 paused at its
2026-05-20 baseline; A6/A7 don't touch dat infrastructure so no
rework needed when it resumes.
Files:
- docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md (new, full spec)
- docs/plans/2026-04-11-roadmap.md (Phase O block inserted before M1.5; M1.5 marked PAUSED)
- CLAUDE.md (Currently-working-toward line updated; M1.5 block marked paused)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Continued indoor testing through 2026-05-20 surfaced a deep family
of physics + lighting bugs that span buildings AND dungeons. Today's
session shipped 5 surgical fixes (A4 + #89 + #90 + #91 + #92) that
close the user-visible "walls walk through at Holtburg inn" symptom,
but #90 specifically is a CLAUDE.md-rules workaround (sphere-overlap
stickiness on top of point-only cell containment) added without prior
approval. The underlying issue (BSP push-back distance probably
diverges from retail) hasn't been measured. Plus the umbrella #83
(indoor multi-Z walking) has been open since 2026-05-19 with multiple
aborted fix attempts; plus indoor lighting (#80 + #81 + new #93 +
#94) has been deferred as "M7 polish" but is actually part of the
same indoor-experience problem.
Promoting to a milestone of its own forces the work to be central,
retail-anchored, and complete — not another whack-a-mole patch.
Milestone M1.5 — "Indoor world feels right":
Demo: enter Holtburg Sewer through the in-town portal, navigate
through 5-7 rooms with stairs + a multi-Z chamber, exit back to
town. Walls block. Stairs work. Items block. Lighting reads
correctly. Cell transitions smooth.
Phases:
A6 — Indoor physics fidelity (cdb-driven)
A7 — Indoor lighting fidelity (RenderDoc + retail-decomp driven)
Issues in scope: #80, #81, #83, #88, #90 (workaround removal),
#93 (new lighting umbrella), #94 (held-item spotlight),
+ TryFindIndoorWalkablePlane synthesis removal.
M2 ("Kill a drudge") deferred until M1.5 lands.
This commit updates:
- docs/plans/2026-05-12-milestones.md (M1.5 block inserted, M2 moved
to deferred status)
- docs/plans/2026-04-11-roadmap.md (A6 + A7 sub-pieces detailed)
- CLAUDE.md (Currently working toward updated to M1.5, M2 paragraph
marked deferred, M1.5 baseline shipped paragraph added)
- docs/ISSUES.md (#80, #81, #83, #88, #90 tagged M1.5 scope;
new #93 indoor lighting umbrella + #94 held-item spotlight filed)
- docs/research/2026-05-21-open-items-pickup-prompt.md (landscape
table reorganized around M1.5 phases)
A6 + A7 specs to be drafted in the next session(s).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A4 (multi-cell BSP iteration) ships in three commits (e6369e2,
493c5e5, 691493e — with revert 3add110 + reapply during visual
verification that proved A4 is not the cause of the issue surfaced).
1139 + 8 baseline maintained. 10 new unit tests pass. Wires retail's
CTransition::check_other_cells (acclient_2013_pseudo_c.txt:272717-272798)
into Transition.FindEnvCollisions.
Visual verification at the Holtburg inn vestibule surfaced a separate,
pre-existing M2 blocker (filed as #90): CellId ping-pongs between
outdoor 0xA9B40022 and indoor 0xA9B40164 on every wall push-back
because the push-back exits the indoor CellBSP volume, causing the
resolver to flip back to outdoor and bypass walls on outdoor ticks.
Indoor BSP results (Collided/Adjusted/Slid all firing) prove walls ARE
detected when the player is indoor; the aggregate "walls walk through"
appearance comes from CellId classification instability, not from
collision detection.
Bug reproduces fully with A4 reverted (launch-revert2.log captured 18
cell-id flips between 0xA9B40022 ↔ 0xA9B40164, 11 inside=True
building-transit events, 61 indoor-bsp queries firing the full
result distribution). A4 is correct and tested but dormant in
practice until #90 is fixed.
Updates:
- docs/research/2026-05-20-phase-a4-shipped-cell-pingpong-finding.md (new)
- docs/plans/2026-04-11-roadmap.md (A4 shipped row added)
- CLAUDE.md (Indoor walking Phase A4 paragraph + next-step pointer
to #90 with retail oracle anchor at acclient_2013_pseudo_c.txt:308742-308783)
- docs/ISSUES.md (#90 filed, HIGH severity, M2-blocker)
- docs/research/2026-05-21-open-items-pickup-prompt.md (landscape
table updated — A4 closed, #90 promoted to top blocker)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port retail's CTransition::check_other_cells (acclient_2013_pseudo_c.txt
:272717-272798) into Transition.FindEnvCollisions so the foot-sphere
sees walls in EVERY cell it overlaps, not just the one cell the player's
center is in. Closes the Holtburg inn vestibule wall walk-through
(cell 0xA9B40164 has only 4 polys; adjacent 0xA9B40157 has 23 walls
that are never queried today).
Architecture: new CellTransit.FindCellSet overload (preserves the
candidate HashSet that FindCellList currently discards), new private
Transition.CheckOtherCells method (direct port of the retail loop),
one wire-up in FindEnvCollisions between the existing primary-cell BSP
return and the synthesis fall-through. ~380 LOC total.
Out of scope: FindObjCollisions (already landblock-radius broadphased),
synthesis multi-cell search (A3's job), var_4c re-target (defer).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the 2026-05-21 session merged A1/A1.5/A1.6/A1.7 to main, six
discrete items remain. This doc maps them as a landscape rather than
single-phase:
- Collision (M2 critical path): A4 multi-cell BSP iteration → verify
stairs → A2 PHSP inversion → A3 synthesis removal
- Rendering (M7 polish): indoor lighting + spotlight-projection bugs
The recommended order is A4 first (biggest user payoff, unblocks A3),
then stairs verification, A2 + A3 paired, lighting in a separate
session. A3 must NOT ship before A4 — that's the Bug A regression
from 2026-05-20.
Includes a pasteable session-start prompt that the user can box into
a fresh Claude Code session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After merging the 2026-05-21 session into main (56d2b5e), update the
pickup prompt so the next session starts from main and creates its
own worktree for the A4 work — not the now-merged lucid-goldberg
branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures everything that shipped in the session — A1, A1.5, A1.6,
A1.7 plus the walk-miss probe spike — and what's still open:
- A4 (multi-cell BSP iteration) — the next big architectural fix,
closes the "walls walk-through-able in vestibule cells" gap
- A2 (PHSP inversion) — small fix, but only meaningful paired with A3
- A3 (synthesis removal) — needs A4 in place first to avoid
reverting back to Bug A's free-fall regression
- Lighting bugs (indoor lighting + spotlight projection) — M7 polish,
separate session
Includes per-fix commit SHAs, code anchors, retail decomp anchors,
probe + launch reference, anti-patterns, and a fresh-session pickup
prompt for boxing into Claude Code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ISSUES #83 Phase A1. Landblock stabs (entity.Id 0xC0XXYY00+n per
LandblockLoader.cs:55) were being registered with TWO collision
shadows: the correct per-part BSP at `entity.Id*256 + partIdx`, AND a
redundant mesh-AABB-fallback cylinder at `entity.Id`. The fallback
clamped to 1.5m radius, centered at the building's mesh origin,
producing user-reported "thin air" collisions inside cottages and
within 2m of building exteriors.
The fallback was originally designed for canopy-only-BSP procedural
scenery (0x80XXYY00+n) — trees whose BSP covers the canopy but not
the trunk. Landblock stabs have full BSP coverage and don't need it.
Probe evidence (launch-thinair capture):
- 0xC0A9B479 cylinder fallback (Holtburg cottage): 104 hits in a
short capture session, all inside the cottage main room
(cell=0xA9B4013F), ~2m from the building's mesh origin.
- 0xA9B47900 BSP (the actual cottage walls): 52 legitimate hits.
Fix: one new bool _isLandblockStab + one clause in the existing
mesh-AABB-fallback gate.
Spec: docs/superpowers/specs/2026-05-21-cylinder-fallback-dedup-design.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three docs from the indoor walk-miss probe spike landed in commits
27c7284..a2e7a87:
- Spec: design of the [walk-miss] + [floor-polys] diagnostic emissions
with the H1/H2/H3 disambiguation matrix.
- Plan: 3-task TDD implementation plan (flag, aggregator, emissions).
- Findings: live-capture analysis showing H3 (walkable_hits_sphere /
adjust_sphere_to_plane synthesis rejection) is the dominant defect.
817 of 876 ground-contact misses (93%) cluster at dz~0.48 m, while
the 7 HITs all sit at dz~0.46 m — a 2 cm boundary between working
and broken that points at the sphere-overlap math, not the probe
distance. H1 (multi-cell iteration missing) is real but only 3%
of misses, secondary. H2 (probe distance) ruled out.
Next step: line-by-line decomp comparison of FindWalkableInternal /
walkable_hits_sphere / adjust_sphere_to_plane against retail at
acclient_2013_pseudo_c.txt:322032 / :323006 / :326793, then design
the fix in a follow-up session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion to the Bug A wrong-scope handoff (35c266a). Provides the
boxed copy-paste prompt for a fresh session + quick reference for the
user and the helper:
- Branch state + KEEP/REMOVE recommendation
- Anti-patterns to avoid (don't repeat Bug A, validate risks with
probe data, stop at three failed verifications)
- Code anchors for Mechanisms A/B/C in our code
- Retail decomp anchors for the doorway investigation
- Probe + diagnostic env var menu
- 5-scenario visual verification list
- Launch command with UTF-8 conversion step
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug B (indoor BSP world-origin fix) shipped today at de8ffde.
Bug A (delete per-frame walkable-plane synthesis) attempted and
reverted at 0a7ce8f. Real bug is deeper than scoped:
Indoor cell floor polys don't cover the player's full XY range when
crossing thresholds (doorways). Step-down probes miss past the floor
edge, Mechanism C (post-OK step-down) can't catch the player,
ContactPlane invalidates, gravity pulls them through the void.
We have all three retail CP retention mechanisms (A, B, C). The
defect is geometry, not retention. Either dat-decoder missing some
floor polys, or cell-transition timing too late, or some retail
mechanism we haven't traced.
Handoff includes:
- State of every commit on this branch + KEEP/REMOVE recommendation
- Bug B evidence and recommendation to ship to main
- Bug A failure analysis with probe data
- Mechanisms A/B/C location in our code vs retail decomp anchors
- 5 prioritized investigation targets for fresh session
- Anti-patterns to avoid (don't repeat Bug A approach)
- Lessons learned (probe-first discipline, risk-as-falsification,
3-fails-in-a-session stop signal, Matrix4x4.Decompose idiom,
binary-timestamp paranoia)
Recommendation: merge Bug B alone, leave the rest for fresh session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six-task plan for Bug A slice (spec 2026-05-20):
1. Replace synthesis call site with return TransitionState.OK
2. Delete Transition.TryFindIndoorWalkablePlane method + constant
3. Delete IndoorWalkablePlaneTests.cs + TransitionTypesTests.cs
4. Run physics suite, confirm baseline holds
5. Single commit per spec
6. User visual verification (5 scenarios)
Net delta: ~-480 lines. BSPQuery.FindWalkableSphere + its 5 unit tests
retained as the underlying retail-faithful walkable-finder API.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 2 of 2 in the indoor ContactPlane retention phase. Deletes
Transition.TryFindIndoorWalkablePlane + the per-frame synthesis call
+ outdoor-terrain fallthrough + 9 tests. Replaces with bare
return TransitionState.OK; matching retail's BSPTREE::find_collisions
OK path (acclient_2013_pseudo_c.txt:323938). ContactPlane is retained
via the per-tick seed at PhysicsEngine.ResolveWithTransition:583
(init_contact_plane equivalent) or refreshed by BSP Path 3 / Path 4.
Predecessor: de8ffde (Bug B, BSP world-origin fix).
Evidence: launch-cp-probe-postfix-v2.log shows 3150 MISS / 3154
indoor-walkable calls (99.87% miss rate) after Bug B, with user-visible
"stuck falling when brushing upper floor edge" symptom unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five-task plan for Bug B slice (spec 2026-05-20). Tasks:
1. Regression test in BSPQueryTests.cs (BSPQuery API contract)
2. Apply Decompose + arg-pass at TransitionTypes.cs:1442
3. Run physics suite, confirm 8-failure baseline holds
4. Commit
5. User visual + probe-equivalence verification
Bug A explicitly deferred to a future slice.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>