diff --git a/docs/architecture/retail-divergence-register.md b/docs/architecture/retail-divergence-register.md index d375945c..18c2241f 100644 --- a/docs/architecture/retail-divergence-register.md +++ b/docs/architecture/retail-divergence-register.md @@ -197,7 +197,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps. | AP-19 | `PortalSideEpsilon` 0.01 (≈1 cm) instead of retail F_EPSILON ≈ 0.0002 — a documented render-root-lag tolerance, NOT a retail constant. DO-NOT-RETRY: T2 (BR-4) tried the retail value; CornerFloodReplay refuted it | `src/AcDream.App/Rendering/PortalVisibilityBuilder.cs:49` | Retail's tight epsilon only works with eye-exact swept curr_cell tracking; our viewer cell lags the eye by up to ~1 cm at pressed corners. Tighten after the #108-membership family + cdstW near-clip pin land | A 1 cm misclassification band at portal planes can flood or cull a portal the eye hasn't crossed — one-frame leaks / grey flashes at knife-edge doorway/corner positions | F_EPSILON @0x007c8c70; `PView::InitCell` 0x005a4b70 | | AP-20 | Sub-pixel view-polygon vertex merge fixed at 1080p-reference NDC units (2/1080); retail merges at ~1 actual screen pixel | `src/AcDream.App/Rendering/PortalProjection.cs:179` | Unit approximation whose coarseness only strengthens convergence — the merge is the flood's fixpoint floor (replaced MaxReprocessPerCell=16) | At 4K+ a legitimately visible 1–2 px sliver aperture collapses to degenerate and rejects — a thin/distant doorway stops admitting its flood slightly earlier than retail | `Render::copy_view` 0x0054dfc0 | | AP-21 | Entity translucency retains the invented α<0.05 fragment discard. World GfxObj/Setup instances now apply their DAT AlphaBlend/Additive/InvAlpha factors through the retail shared alpha queue, but sealed off-screen WbDrawDispatcher consumers (paperdoll/UI Studio) retain the old immediate normal-alpha pass for all three kinds | `src/AcDream.App/Rendering/Shaders/mesh_modern.frag`; `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs` (`DrawDeferredAlphaBatch` versus immediate Phase 8) | World presentation needed exact per-surface blend for spell/particle density and translucent-object intersections. Off-screen object previews are isolated render targets and have not shown an authored additive entity surface that justifies splitting their compact immediate pass | A faint world fringe below 5% alpha is discarded; a hypothetical additive/inverse-alpha paperdoll or UI Studio entity composites darker than retail inside that private viewport | `D3DPolyRender::SetSurface`; `D3DPolyRender::RenderMeshSubset`; SurfaceType.Additive → D3DBLEND_ONE | -| ~~AP-22~~ | **RETIRED 2026-08-06.** The invented cylinder is deleted, not re-derived: retail synthesizes NO shape for a shapeless object. `CPhysicsObj::FindObjCollisions` @0x0050f050 dispatches EXCLUSIVELY — BSP xor CylSphere xor Sphere xor nothing — and with zero cylspheres, zero spheres and no physics BSP it branches straight to the epilogue at `0x0050f22f je 0x50f31b`, returning the `OK_TS` seeded at `0x0050f13b mov edi,1`. Byte-verified against the PDB-paired binary (`9e847e2f-777c-4bd9-886c-22256bb87f32`), disassembled independently rather than read from the Binary Ninja text, whose `ebp_1` aliasing in this function is visibly corrupt. **`CPartArray::GetRadius` (0x005180a0) and `GetHeight` (0x005180b0) are absent from the function's entire call set** (which is exactly `GetNumCylsphere` 0x518080, `GetCylsphere` 0x518090, `GetNumSphere` 0x518060, `GetSphere` 0x518070, `CCylSphere::intersects_sphere` 0x53b8f0, `CSphere::intersects_sphere` 0x537fd0, `OBJECTINFO::missile_ignore` 0x50ceb0, `CPartArray::FindObjCollisions` 0x518180, `COLLISIONINFO::add_object` 0x6b4e20) — `Setup.Radius`/`Height` serve attack cones, `cylinder_distance` and MoveTo, never collision geometry; that consumer (`LiveEntityMotionRuntimeController.GetSetupCylinder`) is retail-faithful and untouched. **The row's site list was incomplete and partly wrong**: it named `ShadowShapeBuilder.cs`, which never reads `Setup.Radius` at all, and omitted TWO real copies — including `LandblockPhysicsContentBuilder`, the ONLY one the headless host executes. Fixing just the cited site would have left headless statics on the invented footprint. All three are deleted. **The row's risk statement was also stale**: it described a live approximation over "rare decorative props", but the branch was unreachable dead code. A sweep of all 5,935 Setups in the installed `client_portal.dat` — validated by byte accounting (5,935/5,935 records consumed with an exact `20 + 48*numLights` residual tail and zero unexplained bytes) and reproduced independently by the production `FlatCollisionAssetBuilder.FlattenSetup` path — finds **0** Setups satisfying the guard: every Setup with `Radius > 0.0001` carries at least one CylSphere or Sphere, and all 1,294 genuinely shapeless Setups have `Radius` exactly 0. Buckets: 678 with ≥1 CylSphere, 3,605 with 0 CylSpheres and ≥1 Sphere, 358 with no primitive but ≥1 physics-BSP part, 1,294 shapeless, 4,282 with `Radius > 0.0001`. Nothing loses collision, because nothing gained it; no visual gate is required. Pinned by `InstalledSetupCollisionReachabilityTests` (negative claim plus five external positive controls so a broken enumeration cannot pass it vacuously — sabotage-verified: inverting the claim reddens it, and an emptied enumeration fails on the controls at `0 != 5935` rather than passing) and by `ShapelessSetupWithRadius_ProducesNoRegistration`, whose sabotage (restoring the deleted block) reddens exactly that fact. The one prior test pinning the fallback built a DAT-impossible Setup; its live state/flag/seed-cell assertions were re-hosted onto a CylSphere fixture rather than deleted, and sabotage-verified in both directions. Evidence: `docs/research/2026-08-06-ap22-contract.md`. | `src/AcDream.App/Physics/LiveEntityCollisionBuilder.cs` (`Build`); `src/AcDream.App/Streaming/LandblockPhysicsPublisher.cs` (`PublishStaticEntity`); `src/AcDream.Content/LandblockPhysicsContentBuilder.cs` (`PublishStaticCollision`, headless-only); `tests/AcDream.Content.Tests/InstalledSetupCollisionReachabilityTests.cs`; `tests/AcDream.App.Tests/Physics/LiveEntityCollisionBuilderTests.cs` | — | — | `CPhysicsObj::FindObjCollisions` 0x0050f050 (OK_TS seed 0x0050f13b; BSP dispatch `test …,0x10000` 0x0050f165 / `je` 0x0050f16f; BSP-branch exit `jmp` 0x0050f19d; zero-spheres exit `je 0x50f31b` 0x0050f22f; epilogue 0x0050f31b); `CPartArray::GetRadius` 0x005180a0 and `GetHeight` 0x005180b0 (absent from that call set); consumers `CPhysicsObj::check_attack` 0x0050ec80, `get_distance_to_object` 0x0050f7a0 | +| ~~AP-22~~ | **RETIRED 2026-08-06.** The invented cylinder is deleted, not re-derived: retail synthesizes NO shape for a shapeless object. `CPhysicsObj::FindObjCollisions` @0x0050f050 dispatches EXCLUSIVELY — BSP xor CylSphere xor Sphere xor nothing — and with zero cylspheres, zero spheres and no physics BSP it branches straight to the epilogue at `0x0050f22f je 0x50f31b`, returning the `OK_TS` seeded at `0x0050f13b mov edi,1`. Byte-verified against the PDB-paired binary (`9e847e2f-777c-4bd9-886c-22256bb87f32`), disassembled independently rather than read from the Binary Ninja text, whose `ebp_1` aliasing in this function is visibly corrupt. **`CPartArray::GetRadius` (0x005180a0) and `GetHeight` (0x005180b0) are absent from the function's entire call set** (which is exactly `GetNumCylsphere` 0x518080, `GetCylsphere` 0x518090, `GetNumSphere` 0x518060, `GetSphere` 0x518070, `CCylSphere::intersects_sphere` 0x53b8f0, `CSphere::intersects_sphere` 0x537fd0, `OBJECTINFO::missile_ignore` 0x50ceb0, `CPartArray::FindObjCollisions` 0x518180, `COLLISIONINFO::add_object` 0x6b4e20) — `Setup.Radius`/`Height` serve attack cones, `cylinder_distance` and MoveTo, never collision geometry; that consumer (`LiveEntityMotionRuntimeController.GetSetupCylinder`) is retail-faithful and untouched. **The row's site list was incomplete and partly wrong**: it named `ShadowShapeBuilder.cs`, which never reads `Setup.Radius` at all, and omitted TWO real copies — including `LandblockPhysicsContentBuilder`, the ONLY one the headless host executes. Fixing just the cited site would have left headless statics on the invented footprint. All three are deleted. **The row's risk statement was also stale**: it described a live approximation over "rare decorative props", but the branch was unreachable dead code. A sweep of all 5,935 Setups in the installed `client_portal.dat` — validated by byte accounting (5,935/5,935 records consumed with an exact `20 + 48*numLights` residual tail and zero unexplained bytes) and reproduced independently by the production `FlatCollisionAssetBuilder.FlattenSetup` path — finds **0** Setups satisfying the guard: every Setup with `Radius > 0.0001` carries at least one CylSphere or Sphere, and all 1,294 genuinely shapeless Setups have `Radius` exactly 0. Buckets: 678 with ≥1 CylSphere, 3,605 with 0 CylSpheres and ≥1 Sphere, 358 with no primitive but ≥1 physics-BSP part, 1,294 shapeless, 4,282 with `Radius > 0.0001`. Nothing loses collision, because nothing gained it; no visual gate is required. Pinned by `InstalledSetupCollisionReachabilityTests` (negative claim plus five external positive controls so a broken enumeration cannot pass it vacuously — sabotage-verified: inverting the claim reddens it, and an emptied enumeration fails on the controls at `0 != 5935` rather than passing) and by `ShapelessSetupWithRadius_ProducesNoRegistration`, whose sabotage (restoring the deleted block) reddens exactly that fact. The one prior test pinning the fallback built a DAT-impossible Setup; its live state/flag/seed-cell assertions were re-hosted onto a CylSphere fixture rather than deleted, and sabotage-verified in both directions. Evidence: `docs/research/2026-08-06-ap22-contract.md`. | `src/AcDream.App/Physics/LiveEntityCollisionBuilder.cs` (`Build`); `src/AcDream.App/Streaming/LandblockPhysicsPublisher.cs` (`PublishStaticEntity`); `src/AcDream.Content/LandblockPhysicsContentBuilder.cs` (`PublishStaticCollision`, headless-only); `tests/AcDream.Content.Tests/InstalledSetupCollisionReachabilityTests.cs`; `tests/AcDream.App.Tests/Physics/LiveEntityCollisionBuilderTests.cs` | — | — | `CPhysicsObj::FindObjCollisions` 0x0050f050 (OK_TS seed 0x0050f13b; BSP dispatch `test …,0x10000` 0x0050f165 / `je` 0x0050f16f; BSP-branch exit `jmp` 0x0050f19d; zero-spheres exit `je 0x50f31b` 0x0050f22f; epilogue 0x0050f31b); `CPartArray::GetRadius` 0x005180a0 and `GetHeight` 0x005180b0 (absent from that call set); consumers `CPhysicsObj::check_attack` 0x0050ec80, `get_distance_to_object` 0x0050f7a0 | **EVIDENCE CORRECTED 2026-08-06 at the AP-22 architecture review.** The retirement commit claimed "Headless.Tests 89/89 exercises the site-3 copy"; that is FALSE, proven by sabotage - restoring the invented cylinder in BOTH static sites left the entire suite green. No test references `PublishStaticCollision`, and the headless suite's dummy DAT proxy makes `LandblockLoader.Load` fail so the code is never reached. Two of the three deletions, including the headless-only one, are pinned by the installed-DAT reachability proof ALONE. The behaviour is right; the coverage claim was not. Two further precisions: sites 2/3 guarded on the strictly wider `Radius > 0f` (not site 1's `> 0.0001f`), which differ over the DAT by exactly one Setup - `0x02001657`, denormal radius 1.3e-39 - and the reachability test now evaluates BOTH guards, each measured zero; and the stronger true fact is that all 1,652 no-primitive Setups carry `Radius` exactly 0, which is what actually makes sites 2/3 safe, rather than the 1,294 figure this row first cited. Retail DOES read `GetHeight` inside `report_object_collision` for the quadrant field - that is not a refutation of "never collision geometry", which is a claim about `FindObjCollisions`' shape dispatch only. Reachability independently reproduced by four decoders (contract sweep, implementer parser, both reviewers' from-scratch parsers) plus `tools/SetupInspect`. | AP-23 | Invented per-type pickup-radius heuristic (3 m creatures / 2 m doors-lifestones-portals-corpses / 0.6 m rest) for close-range gating plus the speculative local TurnToObject/MoveToObject install through the player's MoveToManager. **R5-V3 narrowed it:** the install threads the target's real Setup radius/height (`GetSetupCylinder`, same as wire mt-6) and the player's real radius; only the radius buckets remain invented. **Use retired from this seam 2026-07-25** and now sends immediately. | `src/AcDream.App/Interaction/WorldSelectionQuery.cs` (`TryGetApproach`/`GetUseRadius`); `src/AcDream.App/Interaction/PlayerInteractionMovementSink.cs` (`BeginApproach`) | The retained pickup presentation reserves a destination slot before the authoritative transfer; its close branch still needs an arrival boundary | A target whose real UseRadius differs from the bucket misjudges the pickup gate — pickup waits forever or fires early into a server "too far" | ACE Player_Move.cs:66; wire MoveToObject (type 6) carries the true radius; `CPhysicsObj::TurnToObject/MoveToObject` callers §9a/§9b | | ~~AP-24~~ | **RETIRED 2026-07-11** — matching v11.4186 x86 disassembly recovered `ATTACK_POWERUP_TIME=1.0` seconds and `DUAL_WIELD_POWERUP_TIME=0.8` seconds from the operands loaded by `GetPowerBarLevel`; jump and combat now share those constants. | `src/AcDream.Core/Combat/CombatModel.cs`; `src/AcDream.Runtime/Gameplay/PlayerMovementController.cs`; `src/AcDream.Runtime/Gameplay/RuntimeCombatAttackState.cs` | — | — | `ClientCombatSystem::GetPowerBarLevel @ 0x0056ADE0`; static data `0x007CEFC8/0x007CEFD0` | | AP-26 | DDD interrogation answered with an empty dat-version list (count=0); retail reports actual dat iteration state | `src/AcDream.Core.Net/Messages/DddInterrogationResponse.cs:18` | ACE is satisfied by the empty ack; pattern from holtburger | A dat-patching-enabled server could push a full patch or reject on version mismatch — the lie is harmless only while the server never acts on it | DDD flow 0xF7E5/0xF7E6 |