docs: A6.P3 #98 — record apparatus convergence + residual X-motion
The findings doc gets an evening-v2 follow-on documenting: - GfxObj dump infrastructure shipped (cc3afbc) - Harness reproduces cap-event collision normal (97fec19) - Residual +0.0266m X-motion divergence — the new investigation target - Pre-existing test suite flakiness (out of scope, tracked separately) CLAUDE.md's "Current A6 phase" block points at the residual divergence as the next concrete move with the test that gives <1s feedback per fix attempt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
97fec19dbb
commit
7729bdcf98
2 changed files with 150 additions and 59 deletions
40
CLAUDE.md
40
CLAUDE.md
|
|
@ -842,9 +842,43 @@ cause identified.** Four commits (`fb5fba6` → `44614ab` → `0f2db62` →
|
|||
- 13 new cell fixtures cover the full 0xA9B4014X neighborhood (272 KB).
|
||||
Findings doc (canonical pickup):
|
||||
[`docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md`](docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md).
|
||||
**Next-session move: extract cottage GfxObj polygons via focused
|
||||
`ACDREAM_PROBE_BUILDING=1` capture, register as ShadowEntry in harness,
|
||||
flip first-cap test to assertion form.**
|
||||
|
||||
**Evening v2 follow-on — apparatus convergence SHIPPED 2026-05-23 PM.**
|
||||
Two commits (`cc3afbc` → `97fec19`):
|
||||
- `cc3afbc` adds the GfxObj dump infrastructure (`ACDREAM_DUMP_GFXOBJS`)
|
||||
mirroring the existing `ACDREAM_DUMP_CELLS` pattern, with new
|
||||
`GfxObjDump`/`GfxObjDumpSerializer` parallel to `CellDump`. The new
|
||||
env var triggers `PhysicsDataCache.CacheGfxObj` to write the full
|
||||
resolved polygon table as JSON when a listed id caches. Closes the
|
||||
gap that the existing `[resolve-bldg]` probe couldn't fill (the BSP
|
||||
wire site that populates `LastBspHitPoly` was never wired, so the
|
||||
probe only emitted GfxObj-level metadata, not per-poly geometry).
|
||||
- `97fec19` lands the cottage GfxObj fixture (`0x01000A2B`, 74 polygons,
|
||||
BSP radius 13.989m matching live), the new `RegisterCottageGfxObj`
|
||||
harness helper, and a minimum-stub landblock so
|
||||
`TryGetLandblockContext` succeeds at the cellar XY. Harness now
|
||||
reproduces the live `cn=(0,0,-1)` cap bit-perfect. The full per-field
|
||||
round-trip uncovers ONE residual: live preserves +0.0266m of +X
|
||||
motion through the cap (edge-slide along the cottage floor); harness
|
||||
blocks all motion. Captured in
|
||||
`LiveCompare_FirstCap_ResidualXMotionDivergence_DocumentsNextInvestigation`
|
||||
in documents-the-bug form.
|
||||
- All 21 issue-#98-relevant tests (12 harness + 4 GfxObjDumpRoundTrip +
|
||||
1 new PhysicsDiagnosticsTests + 4 CellDumpRoundTripTests) pass
|
||||
deterministically in isolation.
|
||||
- Pre-existing test suite flakiness observed (8–19 failures across runs
|
||||
of the same code, from PhysicsResolveCapture / PhysicsDiagnostics
|
||||
statics leaking between test classes). INDEPENDENT of A6.P3 — verified
|
||||
by stashing the cottage helper and reproducing the same flaky range.
|
||||
Out of scope for this session; tracked as follow-up.
|
||||
|
||||
**Next-session move:** investigate the residual +X edge-slide divergence
|
||||
in `Transition.transitional_insert` / `AdjustOffset`'s handling of a
|
||||
`cn=(0,0,-1)` head-bump. Live treats it as a Z-only constraint and
|
||||
slides the remaining XY motion along the cottage floor; harness blocks
|
||||
the entire move vector instead. The harness's
|
||||
`LiveCompare_FirstCap_ResidualXMotionDivergence_DocumentsNextInvestigation`
|
||||
test gives <1s feedback per fix attempt. ~2 hours estimate.
|
||||
Original demo scenario (Holtburg Sewer end-to-end) is unreachable: sewer
|
||||
doesn't exist on this server, and **issue #95** (portal-graph visibility
|
||||
blowup) blocks any substitute dungeon. Revised M1.5 demo split into
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ documents the FIRST evidence-driven step in the saga.
|
|||
|
||||
## TL;DR
|
||||
|
||||
**Updated 2026-05-23 evening v2: apparatus convergence shipped.** The
|
||||
harness now reproduces the live cottage-floor cap event bit-perfect on
|
||||
the collision normal. The residual divergence is a single +X-motion
|
||||
edge-slide gap; everything else round-trips. The session below covers
|
||||
both arcs (root-cause identification THEN convergence).
|
||||
|
||||
- **Evidence-driven apparatus shipped.** `PhysicsResolveCapture` writes one
|
||||
JSON Lines record per player ResolveWithTransition call when
|
||||
`ACDREAM_CAPTURE_RESOLVE=<path>` is set. 41,228 records from a single
|
||||
|
|
@ -21,21 +27,26 @@ documents the FIRST evidence-driven step in the saga.
|
|||
new `LiveCompare_*` tests in `CellarUpTrajectoryReplayTests.cs` load three
|
||||
representative records (spawn, on-ramp, first-cap) and replay them
|
||||
through the harness engine. Spawn + on-ramp PASS bit-perfect; first-cap
|
||||
FAILS with a clear divergence — the right divergence.
|
||||
- **Root cause identified: the cottage GfxObj is missing from the harness.**
|
||||
FAILED with a clear divergence — the right divergence.
|
||||
- **Root cause identified: the cottage GfxObj was missing from the harness.**
|
||||
Live cap attributes the blocking entity to `obj=0xA9B47900` — a
|
||||
landblock-baked static building. The cottage's floor polygons live in
|
||||
this GfxObj's polygon table (registered as a ShadowEntry), NOT in any
|
||||
cottage CELL. The harness's `RegisterStairRampGfxObj` already models
|
||||
the cottage's RAMP polygon but is commented out and only covers one
|
||||
polygon. We need the full cottage GfxObj polygon set.
|
||||
cottage CELL.
|
||||
- **Apparatus convergence (v2 update).** With the cottage GfxObj
|
||||
`0x01000A2B` extracted via the new `ACDREAM_DUMP_GFXOBJS` infrastructure
|
||||
and registered as a ShadowEntry in `BuildEngineWithCellarFixtures`, the
|
||||
harness now reproduces the live `cn=(0,0,-1)` cap exactly. The
|
||||
full per-field round-trip reveals one residual: live preserves
|
||||
+0.0266 m of +X motion through the cap; harness blocks all motion.
|
||||
That's the next investigation target — see the "Residual divergence"
|
||||
section below.
|
||||
- **Not a step-up / AdjustOffset bug.** The head sphere (top at Z=foot+1.2)
|
||||
hits the cottage floor at Z=94.0 from BELOW. Math: cap at foot Z=92.74
|
||||
matches 94.0 − 1.2 = 92.80. Confirmed by user reporting same cap when
|
||||
JUMPING in the cellar (purely vertical motion). The retail comparison
|
||||
question is now "why doesn't retail block the head sphere there?" —
|
||||
likely cottage GfxObj polygon side-mode + retail's BSP query
|
||||
treats single-sided polygons correctly.
|
||||
question is now sharpened to "how does live's post-cap edge-slide
|
||||
preserve the +X component that the harness drops?"
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -220,43 +231,73 @@ polygon facing DOWN.
|
|||
|
||||
## Next-session pickup
|
||||
|
||||
### Recommended move: extract cottage GfxObj polygons + register in harness
|
||||
### What shipped 2026-05-23 evening v2 (post-prior-section)
|
||||
|
||||
1. **One more focused live capture** with `ACDREAM_PROBE_BUILDING=1` (the
|
||||
`[resolve-bldg]` probe). The cap event will dump the cottage GfxObj's
|
||||
full BSP root + hit polygon vertices in both object-local and world
|
||||
space, plus the cottage GfxObj id, scale, rotation, and world origin.
|
||||
Three commits land apparatus convergence on the cap event:
|
||||
|
||||
2. **Add `RegisterCottageGfxObj(engine, cache)` helper** in the harness
|
||||
(rename or extend `RegisterStairRampGfxObj`). Construct a synthetic
|
||||
GfxObj with the full polygon table extracted from the capture. The
|
||||
BSP needs all polygons (ramp + cottage floor + walls); a one-leaf
|
||||
wrapper suffices for the comparison-test scope.
|
||||
| Commit | What |
|
||||
|---|---|
|
||||
| `cc3afbc` | **GfxObj dump infrastructure.** Mirrors `ACDREAM_DUMP_CELLS`: new env var `ACDREAM_DUMP_GFXOBJS` triggers `PhysicsDataCache.CacheGfxObj` to write the full resolved polygon table as JSON, suffix `.gfxobj.json` so dumps don't collide with cell dumps in the same dir. New `GfxObjDump` DTO + `GfxObjDumpSerializer` parallel to `CellDump`; round-trip tests cover Capture / Write / Read / Hydrate; the Hydrate path constructs a synthetic single-leaf BSP for query coverage. |
|
||||
| `97fec19` | **Harness reproduces the cottage-floor cap event.** `BuildEngineWithCellarFixtures` now registers a stub landblock 0xA9B40000 (TerrainSurface at z=-1000) so `TryGetLandblockContext` succeeds at the cellar XY, plus a new `RegisterCottageGfxObj` helper that loads the dumped cottage GfxObj fixture, hydrates it with synthetic BSP, and registers as a ShadowEntry at world (130.5, 11.5, 94.0) with 180° Z rotation — matching production's `GameWindow.cs:5893` registration shape for landblock-baked statics. The cottage fixture (74 polys, 6 downward-facing floor triangles, BSP radius 13.989 m) lives at `tests/.../Fixtures/issue98/0x01000A2B.gfxobj.json`; capture launch script is `launch-a6-issue98-cottage-gfxobj-dump.ps1`. |
|
||||
|
||||
3. **Uncomment the registration call** in
|
||||
`BuildEngineWithCellarFixtures`. Re-run
|
||||
`LiveCompare_FirstCap_HarnessMissesCottageFloorBecauseCottageGfxObjNotRegistered`
|
||||
— it should now FAIL because the harness DOES reproduce the cap.
|
||||
Flip the test body to `AssertCallMatchesCapture(engine, captured)` to
|
||||
enforce the round-trip.
|
||||
Test outcome at apparatus convergence:
|
||||
|
||||
4. **At this point the harness is FAITHFUL to live.** Now the question
|
||||
becomes "how does retail differ?" — which is a retail cdb investigation,
|
||||
not an apparatus investigation.
|
||||
| Test | Outcome | Meaning |
|
||||
|---|---|---|
|
||||
| `LiveCompare_Tick0_Spawn` | PASS | Spawn round-trip preserved by the new landblock + cottage state |
|
||||
| `LiveCompare_Tick376_OnRamp` | PASS | On-ramp round-trip preserved |
|
||||
| `LiveCompare_FirstCap_HarnessReproducesCottageFloorCapNormal` | PASS (NEW) | Harness reproduces the live cn=(0,0,-1) cap-event normal exactly |
|
||||
| `LiveCompare_FirstCap_ResidualXMotionDivergence_DocumentsNextInvestigation` | PASS (documents-the-bug) | Captures the ONE remaining post-cap divergence: live preserves +0.0266 m of +X motion through the cap (edge-slide along the cottage floor in XY); harness blocks ALL motion. Y and Z agree. |
|
||||
|
||||
### Alternative move: skip ahead to the retail cdb question
|
||||
### The residual divergence (next investigation target)
|
||||
|
||||
If the user is impatient with apparatus polish, skip step 3 and go
|
||||
directly to a retail trace of the cellar-up scenario. Attach cdb to a
|
||||
running retail acclient, set breakpoints on `BSPTREE::find_collisions`
|
||||
and `CGfxObj::shadow_find_obj_collisions`, walk up the cottage ramp,
|
||||
log every BSP query against the cottage GfxObj. Compare which polygons
|
||||
retail finds vs which polygons our acdream engine finds.
|
||||
After registering the cottage GfxObj:
|
||||
|
||||
The retail trace is the ultimate oracle — but it requires reproducible
|
||||
retail-server state (the user has a working `+Acdream` on local ACE,
|
||||
but the retail client also needs to connect there, which works per the
|
||||
CLAUDE.md "Retail debugger toolchain" section).
|
||||
```
|
||||
Live: cn=(0,0,-1), position=(141.3865, 7.2243, 92.7390) ← +X motion preserved
|
||||
Harness: cn=(0,0,-1), position=(141.3599, 7.2243, 92.7390) ← X stuck at input
|
||||
Input: currentPos=(141.3599, 7.2243, 92.7390)
|
||||
targetPos =(141.3865, 6.8221, 92.7390)
|
||||
requestedDelta=(+0.0266, -0.4022, 0)
|
||||
```
|
||||
|
||||
The cap-event collision normal matches bit-perfect. Position diverges
|
||||
in X only. Working hypothesis: live's response to a `cn=(0,0,-1)`
|
||||
head-bump treats it as a Z-only constraint and edge-slides the
|
||||
remaining XY component along the cottage floor; harness's BSP path is
|
||||
rejecting the entire move vector instead of computing a slid offset.
|
||||
|
||||
That hypothesis is the next-session investigation target — work the
|
||||
slide path in `Transition.transitional_insert` / `AdjustOffset` against
|
||||
the production cap-event call. The new
|
||||
`LiveCompare_FirstCap_ResidualXMotionDivergence_DocumentsNextInvestigation`
|
||||
test PASSES today (asserting the current residual) and FAILS when the
|
||||
divergence closes — that's the signal to flip it into
|
||||
`AssertCallMatchesCapture` form.
|
||||
|
||||
### Alternative pickup move: retail cdb trace at the cottage ramp-top
|
||||
|
||||
If apparatus polish is enough and the user wants to widen the question
|
||||
to "how does retail differ?", attach cdb to a running retail acclient
|
||||
(see CLAUDE.md "Retail debugger toolchain"), set breakpoints on
|
||||
`BSPTREE::find_collisions` and `CGfxObj::shadow_find_obj_collisions`,
|
||||
walk up the cottage ramp, and log every BSP query against the cottage
|
||||
GfxObj. Compare which polygons retail finds vs which polygons our
|
||||
acdream engine finds. Retail's trace is the ultimate oracle for the
|
||||
"how does retail differ?" question — but the apparatus-side X residual
|
||||
investigation is the more focused, faster-feedback next step.
|
||||
|
||||
### Pre-existing test flakiness (out of scope but documented)
|
||||
|
||||
While verifying the cottage helper, the full `dotnet test` serial run
|
||||
produced 8–19 failures across 1192 tests depending on order — the
|
||||
suite has static-state leakage between test classes (likely from
|
||||
`PhysicsResolveCapture.CapturePath`, `PhysicsDiagnostics.Probe*Enabled`,
|
||||
and similar global mutators). The flakiness is **independent of A6.P3**:
|
||||
stashing the cottage helper out and rerunning produces the same flaky
|
||||
range. All 21 issue-#98-relevant tests (12 harness + 4
|
||||
`GfxObjDumpRoundTripTests` + 1 new `PhysicsDiagnosticsTests` + 4
|
||||
`CellDumpRoundTripTests`) pass deterministically in isolation.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -279,40 +320,56 @@ CLAUDE.md "Retail debugger toolchain" section).
|
|||
## Pickup prompt for next session
|
||||
|
||||
```
|
||||
A6.P3 #98 comparison harness — session paused 2026-05-23 evening.
|
||||
A6.P3 #98 — apparatus convergence landed, residual X-motion divergence
|
||||
is next.
|
||||
|
||||
Read FIRST:
|
||||
1. docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md
|
||||
(this doc — TL;DR, what was shipped, the root cause finding)
|
||||
(this doc — particularly the "What shipped 2026-05-23 evening v2"
|
||||
and "The residual divergence" sections)
|
||||
2. tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs
|
||||
(especially the LiveCompare_* tests + LiveCompare_FirstCap_DiagnosticDump)
|
||||
3. CLAUDE.md "Current A6 phase" block (look for the 2026-05-23
|
||||
evening apparatus + finding paragraph)
|
||||
(especially the two LiveCompare_FirstCap_* tests and the
|
||||
RegisterCottageGfxObj helper)
|
||||
3. CLAUDE.md "Current A6 phase" block
|
||||
|
||||
State both altitudes:
|
||||
Currently working toward: M1.5 — Indoor world feels right
|
||||
Current phase: A6.P3 — comparison harness shipped + root cause
|
||||
identified. Cottage GfxObj 0xA9B47900 missing from harness; needs
|
||||
full polygon list extracted from a focused capture.
|
||||
Current phase: A6.P3 — apparatus convergence shipped. Harness now
|
||||
reproduces the live cottage-floor cap event (cn=(0,0,-1) round-trips
|
||||
bit-perfect). Residual: +0.0266 m of +X motion lost in the harness's
|
||||
post-cap slide where live preserves it.
|
||||
|
||||
The doc has TWO concrete options for what to do next:
|
||||
Two concrete next moves:
|
||||
|
||||
(A) Extract cottage GfxObj polygons via focused ACDREAM_PROBE_BUILDING
|
||||
capture, register as ShadowEntry in harness, flip
|
||||
LiveCompare_FirstCap_* test to assertion form. ~2 hours.
|
||||
Apparatus convergence. RECOMMENDED.
|
||||
(A) Investigate the +X edge-slide divergence in the harness. The
|
||||
LiveCompare_FirstCap_ResidualXMotionDivergence_DocumentsNextInvestigation
|
||||
test currently passes asserting the divergence; flipping it should
|
||||
drive the investigation. Likely target: Transition.transitional_insert
|
||||
/ AdjustOffset's handling of a cn=(0,0,-1) head-bump — live treats
|
||||
it as Z-only constraint and edge-slides the remaining XY motion;
|
||||
harness blocks all motion. Decomp anchor: acclient_2013_pseudo_c.txt
|
||||
in the find_obj_collisions → adjust_sphere_to_plane chain. ~2 hours
|
||||
estimate.
|
||||
|
||||
(B) Skip ahead to retail cdb trace of cottage ramp-top BSP queries to
|
||||
answer "what does retail actually do differently?". Larger scope;
|
||||
more direct fidelity question.
|
||||
(B) Attach cdb to retail at the cottage ramp-top, trace the BSP queries,
|
||||
compare polygon-by-polygon what retail finds vs what acdream finds.
|
||||
Authoritative for the "how does retail differ?" question but
|
||||
larger scope (~half day setup + capture).
|
||||
|
||||
Pick A or B. If A: the doc has a step-by-step plan in the
|
||||
"Recommended next-session move" section.
|
||||
(A) is recommended — the harness now isolates this divergence to a
|
||||
specific known XY slide path; the test gives <1s feedback per fix
|
||||
attempt. (B) becomes valuable if (A) hypothesis chase stalls.
|
||||
|
||||
CLAUDE.md rules apply throughout. NO speculative fixes — the saga
|
||||
already converted from speculation to evidence-driven; keep it that
|
||||
way.
|
||||
|
||||
Out-of-scope but observed: pre-existing test suite has 8–19 failures
|
||||
across runs of the same code due to static-state leakage between test
|
||||
classes (PhysicsResolveCapture, PhysicsDiagnostics statics). Targeted
|
||||
issue-#98 tests pass deterministically in isolation. Don't touch the
|
||||
flakiness this session; it's a separate investigation.
|
||||
|
||||
Test baseline: 1178 + 8 pre-existing failures (serial run).
|
||||
Maintain throughout. The previously-failing
|
||||
LiveCompare_FirstCap_HarnessMissesCottageFloorBecauseCottageGfxObjNotRegistered
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue