fix(vendor): the range watcher measures retail's cylinder-gap — the acceptance-band self-close is dead (Fable, from the live trace)
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
The vendor-verify gate's trace proved the entire walk-to-use chain succeeding — arrival natural, Use dispatched, UseDone, the full 117-item ApproachVendor — and the panel still never appeared: the range watcher's plain center-distance shortcut (AP-160) closed the session the same frame it opened. The walk stops where the server accepts (cylinder-gap: center minus both radii), which lands ~4.3 m center against the vendor's authored 3 m — inside the acceptance band, outside the watcher's bare-center check. EnforceRange now measures cylinder-gap with both radii resolved through the SAME ResolveObjectTableHost seam the movement arrival uses — the seam whose absence was AP-160's original justification, created by the previous commit's fix. The watcher and the walk agree by construction. Unresolvable hosts degrade an operand to center distance (close-early only, never holding a session ACE ended); heights pass 0 (the host surface exposes radius only). AP-160 narrowed; #352 files the deferred cylinder-vs-center discriminating unit test (needs a 38-member host fake; the live gate covered the behavior today). Clean-room complete solution: 11,536 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
02b735ba4a
commit
1688863366
3 changed files with 43 additions and 16 deletions
|
|
@ -24,6 +24,16 @@ What does NOT go here:
|
||||||
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
|
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
|
||||||
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
|
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
|
||||||
|
|
||||||
|
## #352 — Vendor range-watcher cylinder metric: discriminating unit test deferred
|
||||||
|
|
||||||
|
**Status:** OPEN (filed 2026-08-08). The EnforceRange cylinder-gap fix
|
||||||
|
(acceptance-band self-close, vendor-verify-gate.log evidence) landed with
|
||||||
|
the existing 17-range/lifecycle tests green but WITHOUT a unit test that
|
||||||
|
discriminates cylinder-vs-center (needs an IPhysicsObjHost fake — 38
|
||||||
|
members — bound via BindObjectTableHostResolver; center>radius while
|
||||||
|
cylinder<=radius stays open, radii-sabotage closes). Write it next
|
||||||
|
session; the live gate covered the behavior.
|
||||||
|
|
||||||
## #351 — LandblockBuildOriginTests.FarLoad_StripsEnvCellsAndPhysics flake (Debug, load-sensitive)
|
## #351 — LandblockBuildOriginTests.FarLoad_StripsEnvCellsAndPhysics flake (Debug, load-sensitive)
|
||||||
|
|
||||||
**Status:** OPEN (filed 2026-08-08). Seen three times today in Debug runs
|
**Status:** OPEN (filed 2026-08-08). Seen three times today in Debug runs
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -87,18 +87,15 @@ public static class RuntimeVendorRangeQuery
|
||||||
/// </para>
|
/// </para>
|
||||||
///
|
///
|
||||||
/// <para>
|
/// <para>
|
||||||
/// <b>Distance metric divergence (register AP-160):</b> retail/ACE close
|
/// <b>Distance metric (AP-160 residual narrowed 2026-08-08):</b> the
|
||||||
/// on CYLINDER-GAP distance — both objects' own collision radius and
|
/// watcher measures retail's CYLINDER-GAP distance
|
||||||
/// height subtracted from the center distance
|
/// (<c>Position::cylinder_distance</c>) with both radii resolved through
|
||||||
/// (<c>Position::cylinder_distance</c>/ACE's <c>GetCylinderDistance</c>).
|
/// the same <c>ResolveObjectTableHost</c> seam the movement arrival
|
||||||
/// Runtime does not resolve a live per-NPC collision radius/height
|
/// uses — the plain-center-distance shortcut self-closed sessions the
|
||||||
/// outside the App-layer's Setup-cylinder resolver
|
/// server had just opened (the walk-to-use acceptance band; see the
|
||||||
/// (<c>WorldSelectionQuery</c>, App-only — out of reach per the
|
/// inline comment at the range check). Heights pass 0 (the host surface
|
||||||
/// Core-structure rules), so this uses plain 3D center-to-center
|
/// exposes radius only); an unresolvable host degrades that operand to
|
||||||
/// distance via <see cref="ObjectRangeMath.ObjectsInRange"/>'s
|
/// center distance, which only ever closes EARLY.
|
||||||
/// <c>useRadii: false</c> branch instead. Effect: the panel can close up
|
|
||||||
/// to (player radius + vendor radius) sooner than exact retail —
|
|
||||||
/// typically well under a meter for a two-legged NPC.
|
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void EnforceRange(GameRuntime runtime)
|
public static void EnforceRange(GameRuntime runtime)
|
||||||
|
|
@ -148,15 +145,35 @@ public static class RuntimeVendorRangeQuery
|
||||||
// an EXACT position match. That is retail's own behavior for a
|
// an EXACT position match. That is retail's own behavior for a
|
||||||
// radius-0 (or unauthored) handler, not a bug to paper over.
|
// radius-0 (or unauthored) handler, not a bug to paper over.
|
||||||
float useRadius = vendorRecord.Snapshot.UseRadius ?? 0f;
|
float useRadius = vendorRecord.Snapshot.UseRadius ?? 0f;
|
||||||
|
|
||||||
|
// #vendor-verify-gate 2026-08-08: the watcher MUST measure with the
|
||||||
|
// same metric the walk-to-use arrival and ACE's acceptance use —
|
||||||
|
// cylinder-gap (center distance minus both bodies' radii), retail's
|
||||||
|
// Position::cylinder_distance. With plain center distance there is
|
||||||
|
// an acceptance band (bare UseRadius .. UseRadius + both radii,
|
||||||
|
// observed live: open accepted at 4.29 m center vs radius 3) where
|
||||||
|
// the server OPENS the session and this watcher instantly
|
||||||
|
// self-closed it — the panel flashed shut within one frame. The
|
||||||
|
// radii come from the same GetObjectA seam the movement arrival
|
||||||
|
// resolves through (ResolveObjectTableHost, lazy-minimal hosts
|
||||||
|
// included); an unresolvable host contributes 0 and degrades to
|
||||||
|
// center distance, which can only close EARLY, never hold a session
|
||||||
|
// ACE has ended.
|
||||||
|
float playerRadius =
|
||||||
|
runtime.EntityObjects.Physics.ResolveObjectTableHost(playerGuid)
|
||||||
|
?.Radius ?? 0f;
|
||||||
|
float vendorRadius =
|
||||||
|
runtime.EntityObjects.Physics.ResolveObjectTableHost(vendorId)
|
||||||
|
?.Radius ?? 0f;
|
||||||
bool inRange = ObjectRangeMath.ObjectsInRange(
|
bool inRange = ObjectRangeMath.ObjectsInRange(
|
||||||
AbsolutePosition(playerPosition),
|
AbsolutePosition(playerPosition),
|
||||||
0f,
|
playerRadius,
|
||||||
0f,
|
0f,
|
||||||
AbsolutePosition(vendorPosition),
|
AbsolutePosition(vendorPosition),
|
||||||
0f,
|
vendorRadius,
|
||||||
0f,
|
0f,
|
||||||
useRadius,
|
useRadius,
|
||||||
useRadii: false,
|
useRadii: true,
|
||||||
ignoreZDelta: false);
|
ignoreZDelta: false);
|
||||||
|
|
||||||
if (!inRange)
|
if (!inRange)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue