fix(physics): enforce retail step-down support radius (#273)

This commit is contained in:
Erik 2026-07-31 12:10:03 +02:00
parent 4dd40ad8fe
commit c24bc571cf
9 changed files with 2117 additions and 24 deletions

View file

@ -0,0 +1,95 @@
# Issue #273 — Holtburg tight-gap support validation
**Date:** 2026-07-31
**Status:** implementation, automated gates, and exact live gate pass
**Scope:** grounded player step-down support at a floor edge beside a static
cylinder
## Captured scene
The reproducible gap is in outdoor cell `0xA9B40032`, between:
- building shell GfxObj `0x01000F69`, placed at
`(158.178, 37.7055, 94.0)` with quaternion
`(w=.939319, x=0, y=0, z=-.343045)`;
- static post `0xCA9B4027`, placed at `(160.173, 34.487, 95.975)`,
represented by its Setup-authored cylinder (`radius=.282`,
`height=5.564`);
- the local player Setup's exact two spheres (`radius=.48`, origins
`z=.475` and `z=1.35`).
The building's supporting ledge terminates at local `x=4`. The first
post-side response moved the player's foot-sphere center to approximately
local `x=4.33`. The full `.48` movement sphere still overlapped the floor, so
the existing step-down path accepted the candidate. Repeated frames then
carried the player around the post and outside the building shell.
The fixture
`tests/AcDream.Core.Tests/Fixtures/issue273/0x01000F69.gfxobj.json` preserves
the installed DAT PhysicsBSP. The replay in
`Issue273HoltburgTightGapReplayTests` uses the captured object placement,
player spheres, static posts, and movement offsets.
## Retail mechanism
The missing rule is not extra collision padding and is not a larger player
sphere. It is retail's second-stage support validation:
1. `CTransition::step_down` (`0x0050B2A0`) performs the ordinary downward
collision probe.
2. After finding a walkable contact plane, an EdgeSlide mover that is not in
StepUp calls `CTransition::check_walkable` (`0x0050AFF0`). The binary
sequence is `test ah,2` at `0x0050B36A`, which is state bit `0x200`
(`EdgeSlide`), followed by the `step_up == 0` test and call at
`0x0050B380`.
3. `CTransition::check_walkable` first calls
`SPHEREPATH::check_walkables` (`0x0050C3E0`).
4. `SPHEREPATH::check_walkables` halves the saved foot-sphere radius and
calls `CPolygon::check_walkable` (`0x00538E60`).
5. If the remembered polygon does not support that smaller sphere,
`CTransition::check_walkable` performs a downward CheckWalkable insertion.
BSP leaves require both `walkable_hits_sphere` and
`CPolygon::check_small_walkable` (`BSPLEAF::hits_walkable`,
`0x0053D670`).
6. If neither check finds support, `CTransition::step_down` rejects the
candidate and the existing edge-response chain handles it.
ACDream already had the small-radius BSP-leaf test, but
`DoCheckWalkable` treated the mere presence of a remembered polygon as
success, and the ordinary `DoStepDown(..., runPlacement:false)` path never
called it. This let a full-radius overlap stand in for actual foot support.
## Port
- `BSPQuery.CheckWalkableSupport` is the shared resolved-polygon form of
retail `CPolygon::check_walkable`.
- `SpherePath.CheckWalkables` implements the retail half-radius remembered
polygon check without mutating canonical sphere state.
- `Transition.DoCheckWalkable` now tests the remembered polygon rather than
treating a non-null polygon as sufficient.
- `Transition.DoStepDown` restores the EdgeSlide/non-StepUp support gate
before the existing placement-policy seam.
There are no location checks, object IDs, guessed radii, widened collision
shapes, or gap-specific tolerances in the production fix.
## Regression impact
The existing #271 staircase-side replay begins with its center `.288 m`
outside a tread whose retail half-radius support boundary is `.24 m`.
Retail may therefore stop that exact candidate. The test now preserves the
original user-visible invariant—never reverse or accelerate downhill—without
requiring forward progress beyond retail's support boundary. The ordinary
continuous staircase replay still requires and achieves forward progress.
## Gates
- issue #273 fixture/replay: 3 passed;
- focused BSP, step-up, edge-slide, #185/#271 family: 42 passed / 1 skipped;
- complete Core tests: 4,111 passed / 2 skipped;
- Release solution build: passed;
- complete Release solution tests: 10,068 passed / 5 skipped.
The user accepted the exact in-client Holtburg gap gate on 2026-07-31: the
gap blocks from the tested approach, and the adjacent movement checks remain
healthy.