docs+test(physics): retire stale TS-1 row; file AD-53/AD-54 for its two acdream-only branches
Campaign P Slice P2 step 1 (docs/research/2026-07-30-response-layer-edge-family-pseudocode.md §2, §6 Step 1/2). The TS-1 register row (retail-divergence-register.md:238) described work that is already done: SpherePath.PrecipiceSlide, Transition.CliffSlide, and Transition.EdgeSlideAfterStepDownFailed are real, tested ports of retail's edge_slide -> precipice_slide/cliff_slide chain (pc:274316, pc:272397, pc:273001-273090). Its cited :1254 line was stale stepping-loop code the file moved past. The one real remaining gap (the back-probe fallback skipping retail's walkable_check_pos/localspace_sphere recache before its second precipice_slide call, pc:274318-274326 / 0050b4e0-0050b507) needed no production code change: a fresh read of SPHEREPATH::get_walkable_pos (0050a8f0), cache_localspace_sphere (0050c9d0), and set_walkable_check_pos (00509ce0) shows that machinery exists to re-project a sphere across retail's PER-CELL local coordinate frames. acdream's SpherePath.WalkableVertices and GlobalSphere are populated in UNIFIED WORLD SPACE at assignment time (SetWalkable/SetWalkableTransformed, SetCheckPos/RestoreCheckPos), so both operands BSPQuery.FindCrossedEdge compares are already commensurable -- retail's recache is a no-op correction under this architecture, and FindCrossedEdge never reads a sphere radius, so retail's walkable_scale radius correction has no acdream counterpart either. Documented in-code at the back-probe site with full citations, and pinned with EdgeSlideBackProbePrecipiceSlideTests: a walkable polygon rediscovered near GlobalCurrCenter, tested against GlobalSphere[0] restored to the original failed target, crosses the edge and slides -- it does not wedge into Collided (and the inverse case, standing inside the polygon with no edge crossed, correctly still returns Collided matching retail's own precipice_slide on a false find_crossed_edge). TS-1's other two flagged gaps are real acdream-only compensating branches, not retail reads, and get their own rows rather than being silently retired alongside it: - AD-53: CliffSlide's three-source reference-normal fallback chain (LastWalkablePlane -> LastKnownContactPlane -> world-up) vs retail's direct last_known_contact_plane.N use. A fresh read of last_known_contact_plane's maintenance (pc:272659-272668) confirms retail overwrites it unconditionally every validate_transition pass, including with a steep plane -- so the fallback chain compensates for AP-4's incomplete OnWalkable bookkeeping, not a retail-matching read. - AD-54: the walkable-steepness reroute to CliffSlide before PrecipiceSlide when the stored walkable polygon itself is steeper than FloorZ. Retail's raw edge_slide has no such branch; the permissive LandingZ acceptance that makes this state reachable IS retail-faithful (TS-4's own BSPTREE::find_collisions citation), but whether retail's outer transitional_insert retry loop absorbs the resulting COLLIDED_TS some other way is not yet independently verified -- flagged open in the row. Physics test suite: 1836 passed, 1 skipped (D4, unrelated to this change). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3a4782048e
commit
325fee7cbb
3 changed files with 223 additions and 9 deletions
|
|
@ -62,10 +62,12 @@ accepted-divergence entries (#96, #49, #50).
|
|||
|
||||
---
|
||||
|
||||
## 2. Adaptation (AD) — 44 rows (AD-52 filed 2026-07-29 at Campaign N slice N6 — the fragment-assembler 60 s partial TTL + completed-sequence ring; AD-51 filed 2026-07-29 at Campaign N slice N4 — the reclaimed-word pool for ACE's fresh-sequence cleartext RejectRetransmit; AD-50 filed 2026-07-29 at Campaign N slice N2 — the inbound-watermark ACE init; AD-49 stays reserved for Campaign N §5's blob-layer ordering deferral, filed when its slice lands; AD-47 and AD-48 filed 2026-07-29 at Campaign V slice V11 — the MSAA sample-position and present-pacing rows the campaign's risk register scheduled for the GL deletion; AD-11 retired 2026-07-23 — exact low-bit ItemUses predicate; AD-31 retired 2026-07-15 — the DAT-authored portal-space viewport replaces the black transit cover)
|
||||
## 2. Adaptation (AD) — 46 rows (AD-53/AD-54 filed 2026-07-30 at Campaign P Slice P2, split out of the retired TS-1 row — CliffSlide's three-source reference-normal fallback chain and the walkable-steepness reroute to CliffSlide; AD-52 filed 2026-07-29 at Campaign N slice N6 — the fragment-assembler 60 s partial TTL + completed-sequence ring; AD-51 filed 2026-07-29 at Campaign N slice N4 — the reclaimed-word pool for ACE's fresh-sequence cleartext RejectRetransmit; AD-50 filed 2026-07-29 at Campaign N slice N2 — the inbound-watermark ACE init; AD-49 stays reserved for Campaign N §5's blob-layer ordering deferral, filed when its slice lands; AD-47 and AD-48 filed 2026-07-29 at Campaign V slice V11 — the MSAA sample-position and present-pacing rows the campaign's risk register scheduled for the GL deletion; AD-11 retired 2026-07-23 — exact low-bit ItemUses predicate; AD-31 retired 2026-07-15 — the DAT-authored portal-space viewport replaces the black transit cover)
|
||||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
| AD-53 | `Transition.CliffSlide`'s reference-normal cross-product operand tries THREE sources in priority order (`LastWalkablePlane` if `Normal.Z >= FloorZ`, then `LastKnownContactPlane` at the same threshold, then world-up `UnitZ`); retail's `CTransition::cliff_slide` uses `this->collision_info.last_known_contact_plane.N` directly, with no fallback chain | `src/AcDream.Core/Physics/TransitionTypes.cs` (`CliffSlide`, the `referenceNormal`/`refSource` selection above the cross-product) | Filed 2026-07-30 splitting TS-1's retirement (Campaign P Slice P2). A fresh read of `last_known_contact_plane`'s own maintenance (pc:272659-272668) confirms retail overwrites it unconditionally from `contact_plane` every `validate_transition` pass — including with a steep plane — so retail keeps NO separately-preserved flat-ground history there either; this fallback chain is a genuine acdream invention, not a retail-matching read. Kept because it compensates for AP-4's incomplete `OnWalkable` bookkeeping (L.4-cliffslide-fallback, 2026-04-30): without it, `cross(currentSteep, lastKnownSteep)` degenerates to zero after >1 frame on a continuous steep slope, and CliffSlide returns `OK` (no deflection) instead of downhill drift — the "stay on the roof" wedge the L.4 session fought | If AP-4's `OnWalkable` reordering is ever completed/removed, `last_known_contact_plane` should carry the same information retail's does and this fallback chain becomes unneeded ballast (or, worse, silently picks a stale `LastWalkablePlane` over the now-correct current one) — re-audit together with AP-4 | `CTransition::cliff_slide` pc:272397 (0050a6d0); `last_known_contact_plane` maintenance pc:272659-272668 (~0050ad07); `docs/research/2026-07-30-response-layer-edge-family-pseudocode.md` §2 gap #2 |
|
||||
| AD-54 | `Transition.EdgeSlideAfterStepDownFailed` reroutes to `CliffSlide` instead of `PrecipiceSlide` when the stored walkable polygon itself is steeper than `FloorZ` (`sp.WalkablePlane.Normal.Z < PhysicsGlobals.FloorZ`); retail's raw `SPHEREPATH::edge_slide` has no steepness branch here — `if (walkable != null) { ... precipice_slide(...) }` unconditionally | `src/AcDream.Core/Physics/TransitionTypes.cs` (`EdgeSlideAfterStepDownFailed`, the `L.4-walkable-steep` block) | Filed 2026-07-30 splitting TS-1's retirement (Campaign P Slice P2). The permissive `LandingZ` walkable-acceptance threshold that lets a steep roof become "walkable" in the first place IS confirmed retail-faithful (`BSPTREE::find_collisions` unconditional `walkable_allowance = LandingZ`, pc:323740-323783, TS-4's own citation) — so a steep-roof walkable polygon is a real state retail also reaches. What is NOT independently verified is whether retail's outer `transitional_insert` caller absorbs a same-polygon-standing `COLLIDED_TS` from `precipice_slide` (its raw `find_crossed_edge` returning false while standing on, not crossing, the polygon) some other way that avoids the acdream "stuck in a Collided revert loop" this reroute prevents | If retail's outer retry loop turns out to already handle the no-crossed-edge-while-standing-on-a-steep-poly case without a reroute, this branch is an unnecessary compensating layer that could route a genuinely PrecipiceSlide-bound case (a shallow polygon edge that happens to sit at exactly `FloorZ`) into CliffSlide instead | `SPHEREPATH::edge_slide` pc:273001-273090 (0050b3d0, direct walkable branch quoted at pc:364-370 in the P2 research doc); `BSPTREE::find_collisions` pc:323740-323783 (0053a730, unconditional `LandingZ`); `docs/research/2026-07-30-response-layer-edge-family-pseudocode.md` §2 gap #3 |
|
||||
| AD-46 | **LIVE. Reframed at Campaign V slice V11 (2026-07-29), when GL was deleted and the comparison that discovered this row ceased to exist.** Dense alpha-blended distant scenery (the treeline) may read slightly denser than retail's, because the anisotropic TAP PATTERN is implementation-defined and acdream's Vulkan driver does not tap identically to retail's D3D9 one. Both request the same sampler state — trilinear, clamp-and-repeat, the device's maximum anisotropy. **What changed at V11 is only the left-hand side of the comparison**: this was measured GL-vs-Vulkan (~15% of the pixels in the band), and it is now a Vulkan-vs-retail question against the D3D oracle in the last column. The measurement below is retained as the evidence that the residual is a tap pattern and not a bug, even though one of its two arms no longer exists. | `src/AcDream.App/Rendering/Wb/WorldTextureArray.cs` (`RhiWorldTextureArray.WorldArrayAnisotropy`); measured in plan §5.5.19, reframed §5.5.24 | Not assumed — narrowed by measurement while both backends still existed, on an offline capture with no session, no entities and both clocks pinned. Anisotropy 1 → 41,509 differing pixels in the tree band; anisotropy 16 (GL's value, and retail's `m_D3DCaps.MaxAnisotropy`) → 22,266, and the rest of the frame fell to 497 px of 563,200, i.e. 8.8e-04, inside the campaign's 0.001 threshold. The residual was not a sub-pixel shift (an integer shift search found none), not a sharpness change (high-frequency energy matched within 5%), and not depth precision (forcing Vulkan's window-depth range to GL's compressed [0.5, 1] moved it by 3%). Monotone improvement toward GL's own anisotropy with no knob left is what made it a driver property rather than a bug. | Distant foliage shimmers or reads denser than retail's. The class is confined to alpha-blended dense overlap: opaque terrain, roofs, walls, water, statics, the character and the whole retained UI are inside threshold. **Now unfalsifiable by self-differential** — with GL gone, the only way to retire this row is a side-by-side against the retail client, not against another acdream backend. | `RenderDeviceD3D::SetDefaultD3DStates @ 0x005a3800`, whose `SetSamplerState(stage, 0xA /* D3DSAMP_MAXANISOTROPY */, m_D3DCaps.MaxAnisotropy)` at `0x005a4230` is the value acdream requests |
|
||||
| AD-47 | **Filed at Campaign V slice V11 (2026-07-29); the campaign's risk register scheduled this row here.** Multisample resolve sample POSITIONS are unspecified by both the Vulkan and D3D9 specifications, so acdream's MSAA-on silhouette edges do not match retail's pixel-for-pixel even at the same sample count. acdream's strict pixel gates therefore run with MSAA forced OFF on every arm, and MSAA-on gets only a relaxed visual smoke. | `src/AcDream.App/RuntimeOptions.cs` (`ACDREAM_MSAA_SAMPLES`); forced to 0 in `tools/run-offline-pixel-gate.ps1` | Measured, not assumed: plan §5.5.16 compared two backends at 4x and found **8.83% of the frame differing — 81,359 px of 921,600 — essentially all of it hugging foliage and silhouette edges**, which is ninety-fold over the 0.001 gate threshold. That is two implementations' sample patterns, not a renderer divergence, which is why forcing MSAA off is what makes the remaining difference attributable rather than a threshold relaxation. | Edge quality on thin geometry (fence rails, foliage, distant railings) differs from retail at the sub-pixel level whenever MSAA is on, which is the ordinary player configuration. Because the gates run MSAA off, **a real regression confined to the multisample path would not be caught by them** — that is the actual exposure this row records. | D3D9 `D3DRS_MULTISAMPLEANTIALIAS` / `D3DMULTISAMPLE_TYPE` as set by `RenderDeviceD3D::SetDefaultD3DStates @ 0x005a3800`; retail's sample pattern is the driver's, exactly as ours is |
|
||||
| AD-48 | **Filed at Campaign V slice V11 (2026-07-29).** Presentation is paced by the Vulkan swapchain present mode (FIFO, i.e. VSync) or by a refresh-rate software pacer when uncapped, rather than by retail's D3D9 `Present` with its own frame-rate limiter. Frame delivery cadence, and therefore input-to-photon latency, is a property of our present path rather than a port of retail's. | `src/AcDream.App/RuntimeOptions.cs:98-100`; `src/AcDream.App/Rendering/Gpu/Vk/VulkanSwapchain.cs` | Retail's limiter and ours both bound the frame rate to the display; the simulation is fixed-step and clock-driven, so gameplay timing does not ride on presentation cadence. The uncapped path exists for measurement and is not the shipping default. | A pacing mismatch shows up as judder or input latency that differs from retail's feel without any visual difference in a captured frame — invisible to every pixel gate by construction. Issue **#235** (the capped/RDP jump-presentation cadence alias) is the known live instance of this class. | D3D9 `IDirect3DDevice9::Present`; retail's frame limiter in `RenderDeviceD3D` |
|
||||
|
|
@ -231,11 +233,11 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| AP-125 | Transport control packets (the 2.0 s cumulative AckSequence and the 0.6 s RequestRetransmit) are emitted STANDALONE; retail piggybacks optional headers onto queued outbound packets first-fit (`FlowQueue::CoalesceData @ 0x00547740`, invoked at `TransmitNewPackets @ 0x00547A6E`), and `EnqueueNaks` hands the NAK to `PacketController::EnqueueOptionalHeader @ 0x00543C84` rather than emitting directly. | `src/AcDream.Core.Net/Transport/AckNakScheduler.cs` (`EmitCumulativeAck`, `EmitNakRequest`) | ACE honours a RequestRetransmit ONLY when EncryptedChecksum is absent (NetworkSession.cs:283-284) — a retail-style piggyback onto a sequenced packet encrypts the NAK and ACE silently ignores it, making S2C loss unrecoverable; ACE likewise advances its client-sequence watermark on any packet whose flags are not exactly AckSequence (:474-476), so coalesced control content on a borrowed sequence risks skipping a real packet. Standalone exact-flag emission is the only ACE-safe shape; it also keeps reliable packets free of optional headers, making the resend cache strip provably a no-op. | Slightly higher C2S datagram count than retail (one extra small packet per 2.0 s / per NAK window); marginally more loss exposure for the control packets themselves on a metered path. | `FlowQueue::CoalesceData @ 0x00547740`; `SharedNet::EnqueuePak @ 0x00543B10`; `SharedNet::EnqueueNaks @ 0x00543BD0`; ACE `NetworkSession.cs:283-284,:342-343,:474-476` |
|
||||
| AP-126 | One monotonic Stopwatch-backed clock (`TransportClock`) drives every transport gate (2.0 s ack, 0.6 s NAK, 0.333 s handshake retry, 0.5 s interval, 5 s assembler sweep); retail splits gates between `Timer::cur_time` (server-adjusted) and `Timer::local_time`. | `src/AcDream.Core.Net/Transport/TransportClock.cs` | The cur/local split only matters for gates that must track server clock adjustments; none of the ported gates semantically depend on server time — they are local cadences. A single injectable source also gives the virtual-clock test seam every conformance suite relies on. | A future port of a genuinely server-clock-relative gate could silently use the wrong clock if it reuses TransportClock without checking this row. | `SharedNet::EnqueuePak @ 0x00543B10` (cur_time); `ClientNet::ProcessConnection @ 0x00545450` (local_time for the 140 s check) |
|
||||
|
||||
## 4. Temporary stopgap (TS) — 43 active rows (TS-57..TS-61 filed 2026-07-29 during Campaign N — no outbound RejectRetransmit; TS-27 narrowed same slice to the inbound direction) + TS-37 historical note (TS-20 retired 2026-07-16 — the later named-retail audit disproved the proposed DrawingBSP polygon filter; TS-37 is a retired-row historical note, not an active count; TS-39 retired R5-V3 — sticky seams bound to the ported PositionManager/StickyManager, radii threaded; TS-45 retired 2026-07-07 — hand-rolled `SphereCollision` replaced by the faithful CSphere family port, fixing the player-vs-monster crowd wedge; TS-3 retired 2026-07-07 — `frames_stationary_fall` accounting ported in the #182 verbatim UpdateObjectInternal rebuild, fixing the airborne falling-animation wedge; TS-41 retired 2026-07-07 — SERVERVEL synth-velocity remote body-drive replaced by the retail interp catch-up + unconditional MovementManager::UseTime, the remote-creature de-overlap #184; TS-42 retired 2026-07-19 — semantic animation completion now precedes the ordered Target/Movement/PartArray/Position tail; TS-44 narrowed again 2026-07-19 — complete orientation joined interpolation, only during-stick enqueue suppression remains)
|
||||
## 4. Temporary stopgap (TS) — 42 active rows (TS-1 retired 2026-07-30 at Campaign P Slice P2 — the row was stale; the EdgeSlide → PrecipiceSlide/CliffSlide chain is already a real, tested port, see the AD-53/AD-54 rows for the two compensating branches it left registered; TS-57..TS-61 filed 2026-07-29 during Campaign N — no outbound RejectRetransmit; TS-27 narrowed same slice to the inbound direction) + TS-37 historical note (TS-20 retired 2026-07-16 — the later named-retail audit disproved the proposed DrawingBSP polygon filter; TS-37 is a retired-row historical note, not an active count; TS-39 retired R5-V3 — sticky seams bound to the ported PositionManager/StickyManager, radii threaded; TS-45 retired 2026-07-07 — hand-rolled `SphereCollision` replaced by the faithful CSphere family port, fixing the player-vs-monster crowd wedge; TS-3 retired 2026-07-07 — `frames_stationary_fall` accounting ported in the #182 verbatim UpdateObjectInternal rebuild, fixing the airborne falling-animation wedge; TS-41 retired 2026-07-07 — SERVERVEL synth-velocity remote body-drive replaced by the retail interp catch-up + unconditional MovementManager::UseTime, the remote-creature de-overlap #184; TS-42 retired 2026-07-19 — semantic animation completion now precedes the ordered Target/Movement/PartArray/Position tail; TS-44 narrowed again 2026-07-19 — complete orientation joined interpolation, only during-stick enqueue suppression remains)
|
||||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
| TS-1 | PrecipiceSlide context missing — conservative stop-at-edge instead of retail's EdgeSlide → PrecipiceSlide / CliffSlide | `src/AcDream.Core/Physics/TransitionTypes.cs:1254` | Awaiting the next L.2c slice; a diagnostic records which ingredient (precipice context / steep plane / EdgeSlide flag) is missing | Player stops dead at precipice edges where retail slides along/over — visible mismatch at cliff and roof edges | retail EdgeSlide → PrecipiceSlide chain |
|
||||
| ~~TS-1~~ | **RETIRED 2026-07-30 (Campaign P Slice P2) — the row was stale, not the code.** The cited `:1254` line is unrelated stepping-loop code; the file moved substantially since the row was written. Retail's `EdgeSlide → PrecipiceSlide / CliffSlide` chain is already a real, tested port: `SpherePath.PrecipiceSlide` (`TransitionTypes.cs:943-970`, retail `SPHEREPATH::precipice_slide` pc:274316), `Transition.CliffSlide` (`:2080-2164`, retail `CTransition::cliff_slide` pc:272397, return-value mapping verified against `acclient.h:6100-6108`), and `Transition.EdgeSlideAfterStepDownFailed` (`:1907-2078`, mirrors `CTransition::edge_slide` pc:273001-273090). The one real gap (back-probe fallback skipping retail's `walkable_check_pos`/`localspace_sphere` recache, pc:274318-274326) needed no code change: acdream's `WalkableVertices`/`GlobalSphere` are populated in unified world space at assignment time (`SetWalkable`/`SetWalkableTransformed`, `SetCheckPos`/`RestoreCheckPos`), so both operands `BSPQuery.FindCrossedEdge` compares are already commensurable — retail's per-cell local-frame reprojection is a no-op correction here. Documented in-code at the back-probe site and pinned by `EdgeSlideBackProbePrecipiceSlideTests`. The chain's two acdream-only compensating branches (CliffSlide's three-source reference-normal fallback; the walkable-steepness reroute to CliffSlide before PrecipiceSlide) are real, non-retail additions — filed as AD-53 / AD-54 rather than folded into this row. | `src/AcDream.Core/Physics/TransitionTypes.cs` (`SpherePath.PrecipiceSlide`, `Transition.CliffSlide`, `Transition.EdgeSlideAfterStepDownFailed`); `tests/AcDream.Core.Tests/Physics/EdgeSlideBackProbePrecipiceSlideTests.cs` | — | — | `SPHEREPATH::precipice_slide` pc:274316 (0050cc80); `CTransition::cliff_slide` pc:272397 (0050a6d0); `CTransition::edge_slide` pc:273001-273090 (0050b3d0); `SPHEREPATH::get_walkable_pos`/`cache_localspace_sphere`/`set_walkable_check_pos` pc:274318-274326 (0050a8f0/0050c9d0/00509ce0); `docs/research/2026-07-30-response-layer-edge-family-pseudocode.md` §2, §6 Step 1 |
|
||||
| TS-4 | Path-6 steep-poly slide-tangent shortcut: airborne hits on >FloorZ polys skip retail's SetCollide → Path-4 → ContactPlane landing chain, returning Slid in place. **Includes a `SetSlidingNormal` write at both sites** — retail's BSP layer never writes `collision_info.sliding_normal` (only `validate_transition` 0x0050ac21 does; the #137 mechanism-2 class), so on transition success the steep-face normal persists to the body and seeds the next frame | `src/AcDream.Core/Physics/BSPQuery.cs` (Path-6 steep branches, `worldNormal.Z < FloorZ`) | Deliberate deviation: our faithful port DID wedge (missing step_up_slide / cliff_slide details on grounded-steep); validated against the 2026-04-30 retail cdb trace (retail body didn't wedge). Filed L.5+ for retail-strict | Airborne steep contact never commits Contact / lands as retail — roof-bounce trajectories, landing events, grounded-steep transitions diverge; a persisted steep-face normal can absorb an exactly-anti-parallel next-frame push (#137 wedge class) until an oblique input clears it | `BSPTREE::find_collisions` SetCollide pc:323783-323821 |
|
||||
| TS-5 | `CanJump` always true — burden/stamina gating deferred (stat plumbing incomplete pre-M2). R3-W3 extends this row: `IWeenieObject.JumpStaminaCost`/`PlayerWeenie.JumpStaminaCost` are new (feeding `jump_is_allowed`'s verbatim stamina-refusal branch) and are ALSO always-affordable/cost-0 stubs for the same reason | `src/AcDream.Core/Physics/PlayerWeenie.cs:44` (`CanJump`), `:52` (`JumpStaminaCost`, R3-W3) | Marked deferred; harmless until stats matter | Client launches jumps retail refuses (exhausted/overburdened) — server rejection / rubber-band; divergent jump availability vs retail muscle memory | CMotionInterp jump path stamina/burden inquiry; `jump_is_allowed` 0x005282b0 `JumpStaminaCost` vtable +0x44 |
|
||||
| TS-6 | Weather particle emission suppressed — all weathery DayGroups map to Overcast (correct fog/cloud tone, no precipitation); retail's camera-attached weather subsystem not yet located in the decomp | `src/AcDream.Core/World/WeatherState.cs:200` | Decomp research verified the sky loop never reads `DefaultPesObjectId`; an earlier name-based rain spawn regressed (rained where retail didn't, 2026-04-23) — inventing a name→rain path is forbidden until the real subsystem is found | Rainy/snowy/stormy days never show retail's precipitation effects (permanent missing visuals until the subsystem is found and ported) | FUN_00508010 / FUN_0051bed0→FUN_0051bfb0 (negative findings) |
|
||||
|
|
@ -310,12 +312,11 @@ WITH that phase, not before.
|
|||
1. **TS-27 — INBOUND retransmit handling** — the outbound sent-packet cache + resend landed with Campaign N Slice N1 (2026-07-29, class-doc gap list fixed same commit); the inbound sequence-aligned ISAAC + client NAK emission (N2/N4) remain the hard blocker for non-loopback play — one lost S2C packet still deafens the session permanently.
|
||||
2. **TS-4 — Path-6 steep slide-tangent shortcut** — landing/contact state diverges on every airborne-steep hit; the L.5+ retail-strict followup is already filed with the missing-ingredient analysis.
|
||||
3. **UN-1 — CheckOtherCells iteration order** — behavior-bearing halt order with a log-cosmetics justification; trivial to fix (iterate CELLARRAY build order, sort only in probe output).
|
||||
4. **TS-1 — PrecipiceSlide stop-at-edge** — visible movement mismatch at every cliff/roof edge; diagnostic already records which ingredient is missing.
|
||||
5. **UN-6 — 200 ms ConnectResponse sleep** — unexplained constant on every login with an intermittent-failure shape; either find the ACE race and cite it, or replace with an acknowledged-ready check.
|
||||
6. **UN-4 — GfxObj sides/negative-surface logic** — diagnose against the retail-cited CellStruct interpretation on a known double-sided GfxObj; promote to AP with a citation or align it.
|
||||
7. **TS-8 — MagicUpdateEnchantment StatMod parse (#7/#12)** — vitals wrong for the whole session after any buff; parser shape is known from holtburger.
|
||||
8. **TS-55 — AdminEnvirons fog/radar presentation** — exact retail mechanism is known; port the authored ambient/fog fields, radar blanking, Clear, and `0x270F` together.
|
||||
9. **TS-19 — Legacy ChaseCamera deletion** — already marked "pending the follow-up deletion commit"; its continued existence can mask or manufacture flap symptoms during debugging.
|
||||
4. **UN-6 — 200 ms ConnectResponse sleep** — unexplained constant on every login with an intermittent-failure shape; either find the ACE race and cite it, or replace with an acknowledged-ready check.
|
||||
5. **UN-4 — GfxObj sides/negative-surface logic** — diagnose against the retail-cited CellStruct interpretation on a known double-sided GfxObj; promote to AP with a citation or align it.
|
||||
6. **TS-8 — MagicUpdateEnchantment StatMod parse (#7/#12)** — vitals wrong for the whole session after any buff; parser shape is known from holtburger.
|
||||
7. **TS-55 — AdminEnvirons fog/radar presentation** — exact retail mechanism is known; port the authored ambient/fog fields, radar blanking, Clear, and `0x270F` together.
|
||||
8. **TS-19 — Legacy ChaseCamera deletion** — already marked "pending the follow-up deletion commit"; its continued existence can mask or manufacture flap symptoms during debugging.
|
||||
|
||||
**Phase-gated (do WITH the phase, flagged here so they aren't forgotten):**
|
||||
M2 combat must land TS-5 (CanJump gating), TS-23 (PK bits), TS-25
|
||||
|
|
|
|||
|
|
@ -1985,6 +1985,23 @@ public sealed class Transition
|
|||
// PrecipiceSlide. CliffSlide deflects motion along the ridge
|
||||
// between current-steep and last-known-walkable; gravity then
|
||||
// produces visible downhill drift.
|
||||
//
|
||||
// TS-1 gap #3 (register AD-54, Campaign P Slice P2 2026-07-30):
|
||||
// retail's raw SPHEREPATH::edge_slide has NO steepness branch here
|
||||
// — `if (walkable != null) { ... precipice_slide(...) }` unconditionally
|
||||
// (acclient_2013_pseudo_c.txt:364-370 per the P2 research quote). The
|
||||
// LandingZ permissive acceptance itself IS retail-faithful — confirmed
|
||||
// by TransitionalInsert's own Path-4 Collide branch
|
||||
// (TransitionTypes.cs, `DoCheckWalkable(PhysicsGlobals.LandingZ, engine)`
|
||||
// above) and TS-4's BSPTREE::find_collisions read
|
||||
// (pc:323740-323783: `sphere_path.walkable_allowance = LandingZ`
|
||||
// unconditionally, no slope test) — so a steep roof really is
|
||||
// "walkable" in retail too. What is NOT independently verified from
|
||||
// the raw decomp is whether retail's OUTER caller (transitional_insert)
|
||||
// absorbs a same-polygon-standing Collided from precipice_slide via
|
||||
// its own retry loop rather than needing this reroute; see
|
||||
// docs/research/2026-07-30-response-layer-edge-family-pseudocode.md §2
|
||||
// gap #3.
|
||||
if (sp.WalkablePlane.Normal.Z < PhysicsGlobals.FloorZ)
|
||||
{
|
||||
var cliffPlane = sp.WalkablePlane;
|
||||
|
|
@ -2015,6 +2032,32 @@ public sealed class Transition
|
|||
// Retail back-probes from the current sphere center to rediscover the
|
||||
// walkable polygon we just left, then restores the failed candidate and
|
||||
// runs precipice_slide against that polygon.
|
||||
//
|
||||
// TS-1 gap #1 research (Campaign P Slice P2, 2026-07-30,
|
||||
// docs/research/2026-07-30-response-layer-edge-family-pseudocode.md §2):
|
||||
// retail's SPHEREPATH::edge_slide back-probe branch re-caches
|
||||
// walkable_check_pos/localspace_sphere here — SPHEREPATH::get_walkable_pos
|
||||
// (0050a8f0) -> SPHEREPATH::cache_localspace_sphere (0050c9d0) ->
|
||||
// SPHEREPATH::set_walkable_check_pos (00509ce0), acclient_2013_pseudo_c.txt
|
||||
// :274318-274326 (0050b4e0-0050b507) — before its second precipice_slide
|
||||
// call. That machinery re-projects the restored check_pos sphere into the
|
||||
// walkable polygon's OWN per-cell local frame via Position::localtolocal,
|
||||
// and precipice_slide itself then applies a LandDefs::get_block_offset
|
||||
// landblock correction (pc:274341) before testing find_crossed_edge.
|
||||
// Deliberately NOT ported here: acdream's SpherePath.WalkableVertices/
|
||||
// WalkablePlane are populated in UNIFIED WORLD SPACE at assignment time
|
||||
// (SetWalkable/SetWalkableTransformed above bake worldOrigin+scale in
|
||||
// immediately), and GlobalSphere is likewise always world-space
|
||||
// (SetCheckPos/RestoreCheckPos). Both operands BSPQuery.FindCrossedEdge
|
||||
// compares are therefore already commensurable with no landblock/cell
|
||||
// reprojection needed — retail's local-frame recache is a no-op
|
||||
// correction under this architecture, and FindCrossedEdge never reads a
|
||||
// sphere radius, so retail's walkable_scale radius correction has no
|
||||
// acdream counterpart either. Pinned by
|
||||
// EdgeSlideBackProbePrecipiceSlideTests (direct SpherePath.PrecipiceSlide
|
||||
// state test): a walkable polygon rediscovered near GlobalCurrCenter,
|
||||
// tested against GlobalSphere[0] restored to the original failed target,
|
||||
// crosses the edge and slides — it does not wedge into Collided.
|
||||
Vector3 backToCurrent = sp.GlobalCurrCenter[0].Origin - sp.GlobalSphere[0].Origin;
|
||||
sp.AddOffsetToCheckPos(backToCurrent);
|
||||
|
||||
|
|
@ -2048,6 +2091,25 @@ public sealed class Transition
|
|||
// deflection. Using LastWalkable preserves the prior flat-ground
|
||||
// plane across continuous-slope frames; world-up gives a guaranteed
|
||||
// non-zero deflection when no walkable history exists at all.
|
||||
//
|
||||
// TS-1 gap #2 (register AD-53, Campaign P Slice P2 2026-07-30): retail's
|
||||
// raw CTransition::cliff_slide (pc:272397, 0050a6d0) uses
|
||||
// this->collision_info.last_known_contact_plane.N DIRECTLY as the second
|
||||
// cross-product operand — no fallback chain. Confirmed by a fresh read of
|
||||
// last_known_contact_plane's own maintenance
|
||||
// (acclient_2013_pseudo_c.txt:272659-272668, pc ~0050ad07): retail
|
||||
// overwrites last_known_contact_plane from contact_plane UNCONDITIONALLY
|
||||
// on every validate_transition pass, the same "gets overwritten by
|
||||
// whatever's current, including a steep plane" behavior this file's
|
||||
// ContactPlane/LastKnownContactPlane tracking already has — retail does
|
||||
// NOT maintain a separately-preserved flat-ground history there either.
|
||||
// This three-source chain (LastWalkablePlane -> LastKnownContactPlane ->
|
||||
// UnitZ) is therefore a genuine acdream invention, not a retail-matching
|
||||
// read — kept because it compensates for AP-4's incomplete OnWalkable
|
||||
// bookkeeping (see DO-NOT-RETRY item 9 in
|
||||
// docs/research/2026-07-30-response-layer-edge-family-pseudocode.md §0)
|
||||
// and removing it reintroduces the degenerate-cross "stay on the roof"
|
||||
// wedge the L.4 session (2026-04-30) fought. See that doc's §2 gap #2.
|
||||
Vector3 referenceNormal;
|
||||
string refSource;
|
||||
if (sp.HasLastWalkablePolygon && sp.LastWalkablePlane.Normal.Z >= PhysicsGlobals.FloorZ)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using Xunit;
|
||||
using Plane = System.Numerics.Plane;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign P Slice P2, TS-1 gap #1 (2026-07-30 research pass,
|
||||
/// <c>docs/research/2026-07-30-response-layer-edge-family-pseudocode.md</c>
|
||||
/// §2). Pins the specific state <c>Transition.EdgeSlideAfterStepDownFailed</c>'s
|
||||
/// back-probe fallback (<c>TransitionTypes.cs:2015-2031</c>) hands to
|
||||
/// <see cref="SpherePath.PrecipiceSlide"/>: a walkable polygon rediscovered
|
||||
/// near <c>GlobalCurrCenter</c> (the last-known-good grounded position),
|
||||
/// tested against <c>GlobalSphere[0]</c> restored to the ORIGINAL failed
|
||||
/// move target (off the polygon's edge) after <c>RestoreCheckPos()</c>.
|
||||
///
|
||||
/// <para>
|
||||
/// Retail's <c>SPHEREPATH::edge_slide</c> back-probe branch
|
||||
/// (<c>acclient_2013_pseudo_c.txt:274316-274326</c>, <c>0050b4e0-0050b507</c>)
|
||||
/// re-caches <c>walkable_check_pos</c>/<c>localspace_sphere</c> from
|
||||
/// <c>SPHEREPATH::get_walkable_pos</c> (<c>0050a8f0</c>) via
|
||||
/// <c>SPHEREPATH::cache_localspace_sphere</c> (<c>0050c9d0</c>) and
|
||||
/// <c>SPHEREPATH::set_walkable_check_pos</c> (<c>00509ce0</c>) before calling
|
||||
/// <c>precipice_slide</c> a second time. That machinery exists to solve a
|
||||
/// coordinate-FRAME problem: retail's <c>walkable</c> polygon and
|
||||
/// <c>check_pos</c> are each expressed relative to a PER-CELL local frame
|
||||
/// (<c>cache_localspace_sphere</c> re-projects one into the other's frame via
|
||||
/// <c>Position::localtolocal</c>, and <c>precipice_slide</c> itself applies a
|
||||
/// <c>LandDefs::get_block_offset</c> landblock correction, pc:274341).
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// acdream's <see cref="SpherePath.WalkableVertices"/>/<see cref="SpherePath.WalkablePlane"/>
|
||||
/// are populated in UNIFIED WORLD SPACE at assignment time (see
|
||||
/// <c>SpherePath.SetWalkable</c>/<c>SetWalkableTransformed</c>,
|
||||
/// <c>TransitionTypes.cs:667-739</c>, which bake <c>worldOrigin</c> and
|
||||
/// <c>scale</c> in immediately), and <see cref="SpherePath.GlobalSphere"/> is
|
||||
/// likewise always world-space (<c>SpherePath.SetCheckPos</c>/
|
||||
/// <c>RestoreCheckPos</c>, <c>TransitionTypes.cs:621-650</c>). Both operands
|
||||
/// <see cref="BSPQuery.FindCrossedEdge"/> compares are therefore ALREADY
|
||||
/// commensurable without any recache step — retail's local-frame
|
||||
/// re-projection is a no-op correction in acdream's flat-world-space design.
|
||||
/// <see cref="BSPQuery.FindCrossedEdge"/> also never reads a sphere radius
|
||||
/// (only <c>sphereCenter</c>), so retail's radius/<c>walkable_scale</c>
|
||||
/// correction has no acdream counterpart to begin with.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// This test does not add a recache step (there is nothing for it to
|
||||
/// correct in this architecture); it instead PINS the claim: given exactly
|
||||
/// the field values the back-probe fallback produces (a world-space walkable
|
||||
/// polygon near <c>GlobalCurrCenter</c>, a restored <c>GlobalSphere[0]</c> at
|
||||
/// the original off-edge target), <c>PrecipiceSlide</c> must find the
|
||||
/// crossed edge and slide — not wedge into <c>Collided</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class EdgeSlideBackProbePrecipiceSlideTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Back-probe re-discovers a flat platform's edge polygon near the last
|
||||
/// known good center; the restored (failed) target sphere sits just past
|
||||
/// the +X edge. PrecipiceSlide must cross that edge and slide, matching
|
||||
/// retail's post-recache precipice_slide result (ADJUSTED_TS/SLID_TS,
|
||||
/// never a stuck COLLIDED_TS).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PrecipiceSlide_BackProbeState_CrossesEdge_DoesNotWedge()
|
||||
{
|
||||
var transition = new Transition();
|
||||
var sp = transition.SpherePath;
|
||||
|
||||
// Last known good grounded center — where the back-probe offset
|
||||
// points back toward (retail: global_curr_center).
|
||||
sp.GlobalCurrCenter[0].Origin = new Vector3(0f, 0f, 1f);
|
||||
sp.GlobalCurrCenter[0].Radius = 0.5f;
|
||||
|
||||
// The walkable polygon the back-probe's DoStepDown rediscovered near
|
||||
// that center: a flat 2x2 platform, top face at Z=1.
|
||||
var plane = new Plane(Vector3.UnitZ, -1f);
|
||||
sp.SetWalkable(
|
||||
plane,
|
||||
new[]
|
||||
{
|
||||
new Vector3(-1f, -1f, 1f),
|
||||
new Vector3(1f, -1f, 1f),
|
||||
new Vector3(1f, 1f, 1f),
|
||||
new Vector3(-1f, 1f, 1f),
|
||||
},
|
||||
Vector3.UnitZ);
|
||||
|
||||
// GlobalSphere[0] after RestoreCheckPos(): the ORIGINAL failed move
|
||||
// target, just past the platform's +X edge (off the polygon, over
|
||||
// open air) — exactly what the back-probe fallback hands to
|
||||
// PrecipiceSlide once retail's edge_slide restores check_pos.
|
||||
sp.CheckPos = new Vector3(1.2f, 0f, 1f);
|
||||
sp.GlobalSphere[0].Origin = sp.CheckPos;
|
||||
sp.GlobalSphere[0].Radius = 0.5f;
|
||||
|
||||
var result = sp.PrecipiceSlide(transition);
|
||||
|
||||
Assert.NotEqual(TransitionState.Collided, result);
|
||||
Assert.True(
|
||||
result is TransitionState.Slid or TransitionState.Adjusted or TransitionState.OK,
|
||||
$"Back-probe PrecipiceSlide must slide/adjust across the found edge, not wedge; got {result}.");
|
||||
|
||||
// The walkable context is consumed (retail: this->walkable = nullptr
|
||||
// inside precipice_slide before slide_sphere runs).
|
||||
Assert.False(sp.WalkableValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanity inverse: when the restored target sphere is still WELL INSIDE
|
||||
/// the rediscovered polygon (no edge crossed — e.g. the back-probe found
|
||||
/// the same ground the mover is standing on), retail's raw
|
||||
/// <c>precipice_slide</c> returns COLLIDED_TS
|
||||
/// (<c>acclient_2013_pseudo_c.txt:274322-274326</c>: <c>eax==0 → walkable
|
||||
/// = nullptr; return 2</c>). Confirms the "no wedge" claim above is about
|
||||
/// the edge-crossing case specifically, not a blanket "never Collided."
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PrecipiceSlide_NoEdgeCrossed_ReturnsCollided_MatchingRetail()
|
||||
{
|
||||
var transition = new Transition();
|
||||
var sp = transition.SpherePath;
|
||||
|
||||
sp.GlobalCurrCenter[0].Origin = new Vector3(0f, 0f, 1f);
|
||||
sp.GlobalCurrCenter[0].Radius = 0.5f;
|
||||
|
||||
var plane = new Plane(Vector3.UnitZ, -1f);
|
||||
sp.SetWalkable(
|
||||
plane,
|
||||
new[]
|
||||
{
|
||||
new Vector3(-1f, -1f, 1f),
|
||||
new Vector3(1f, -1f, 1f),
|
||||
new Vector3(1f, 1f, 1f),
|
||||
new Vector3(-1f, 1f, 1f),
|
||||
},
|
||||
Vector3.UnitZ);
|
||||
|
||||
// Restored target well inside the polygon — no edge crossed.
|
||||
sp.CheckPos = new Vector3(0.1f, 0f, 1f);
|
||||
sp.GlobalSphere[0].Origin = sp.CheckPos;
|
||||
sp.GlobalSphere[0].Radius = 0.5f;
|
||||
|
||||
var result = sp.PrecipiceSlide(transition);
|
||||
|
||||
Assert.Equal(TransitionState.Collided, result);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue