fix(#171): sticky deep-overlap back-off sign pin — the runaway-to-center (gate-2 residual)
Gate 2: pack behavior good except "sometimes the monster is in the character / too close vs retail". The ACDREAM_PROBE_STICKY capture nailed it frame-by-frame: 1661 deep-overlap AdjustOffset ticks, ALL steering inward (dist -0.65 -> -0.78 -> ... -> -1.9 at +0.13/tick), monsters converging to centerDist~0 — while the suppressed-snap lines show ACE's authoritative positions stayed properly OUTSIDE (drift up to 7.7 m). The radii were correct (tgtR=0.68, ownR=0.59-0.98). Root cause: ACE's literal decode of StickyManager::adjust_offset (`if (delta >= |dist|) delta = dist;`) leaves delta POSITIVE when the overlap exceeds one tick's step — steering TOWARD the target center, a runaway whose equilibrium is centers-coincident. ACE servers virtually never reach that branch (quantum >=1/30 -> threshold ~1 m); at render-rate quanta the threshold is ~0.13 m and pack jostle trips it constantly. The BN mush (0x00555554-0x00555597) is unreadable on exactly this compare; the retail oracle (side-by-side on the same ACE: monsters separate) refutes the ACE-literal reading. Pin: sign-correct clamp — `else if (dist < 0) delta = -delta` (back off rate-limited). Identical to ACE-literal in every shallow/outside case. Register row AP-82 (same commit) with the cdb verification note. Conformance: StickyManagerTests.AdjustOffset_DeepOverlap_BacksOff_ RateLimited. Full suite 4039 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7a82317614
commit
699669502c
3 changed files with 40 additions and 1 deletions
|
|
@ -99,7 +99,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
|
||||
---
|
||||
|
||||
## 3. Documented approximation (AP) — 72 rows (AP-79 retired R5-V2 — the P4 TargetTracker adapter replaced by the ported TargetManager voyeur system)
|
||||
## 3. Documented approximation (AP) — 73 rows (AP-79 retired R5-V2 — the P4 TargetTracker adapter replaced by the ported TargetManager voyeur system; AP-82 added R5-V3 — sticky deep-overlap sign pin)
|
||||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
|
|
@ -179,6 +179,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| AP-77 | **`apply_current_movement`'s interpreted-branch tail (`ApplyCurrentMovementInterpreted`) writes body velocity DIRECTLY via `get_state_velocity`/`set_local_velocity` when grounded, instead of dispatching through an `IInterpretedMotionSink`** — retail's real `apply_interpreted_movement` (0x00528600) drives velocity indirectly through `DoInterpretedMotion`'s animation-table backend (the SAME function the funnel's `ApplyInterpretedMovement` already uses WITH a sink); this dual-dispatch call site (`HitGround`/`LeaveGround`/`ReportExhaustion`/hold-key toggles/`SetWeenieObject`/`SetPhysicsObject`) has no sink threaded through it | `src/AcDream.Core/Physics/MotionInterpreter.cs` (`ApplyCurrentMovementInterpreted`) | Direct continuation of the pre-R3-W4 `apply_current_movement` approximation (R3-W4 plan explicitly keeps this shape rather than relocating it — "the direct grounded-velocity write MOVES to the controller-side call site unchanged"); correct for the grounded, no-animation-table-yet state acdream is in today | A MotionTable whose locomotion cycle bakes a DIFFERENT velocity than `get_state_velocity`'s constants would silently diverge from what the animation actually plays, since this path never touches the animation backend at all | `CMotionInterp::apply_interpreted_movement` 0x00528600; retire when a sink is threaded through `apply_current_movement`'s callers (R6 root motion) |
|
||||
| AP-80 | **PlanFromVelocity survives for velocity-only NPC cycles** (M16): UpdatePosition-derived speed picks Ready/Walk/Run cycles for server-controlled creatures whose UMs never arrive (scripted-path NPCs); retail derives every cycle from motion messages through the motion tables (R4-V4 note; pre-existing mechanism, row added per the V4 plan) | `src/AcDream.Core/Physics/ServerControlledLocomotion.cs` (`PlanFromVelocity`); consumer `GameWindow.ApplyServerControlledVelocityCycle` | Some ACE entities move by position updates alone — without this, they slide in T-pose; constants (StopSpeed 0.2, RunThreshold 1.25) tuned against live ACE traffic | Cycle-pick thresholds are acdream inventions — a creature intended to walk fast may show run legs near the threshold | retire in R6 (root motion + full per-tick order) |
|
||||
| AP-81 | **Remote-DR gravity toggled via the Gravity STATE bit**: the jump handler sets `Body.State \|= Gravity` at VectorUpdate and both landing blocks clear it after `HitGround()`; retail keeps GRAVITY set for the object's whole life and gates gravity ACCELERATION on the Contact transient (`calc_acceleration`) (pre-existing K-fix9/K-fix15 mechanism, row added during #161 — which also fixed the ordering so `Motion.HitGround()`'s verbatim `state&0x400` gate runs BEFORE the clear) | `src/AcDream.App/Rendering/GameWindow.cs` (VectorUpdate jump handler + the two landing blocks) | The DR tick integrates gravity only for airborne remotes; the flag dance delivers exactly that without porting the full contact-gated `calc_acceleration` chain; the #161 ordering fix keeps the retail HitGround contract satisfied | Any NEW call into `Motion.HitGround`/`LeaveGround` placed after the clear silently no-ops on the gravity gate (the #161 leg-2 class); grounded remotes carry a non-retail state word (probes comparing state bits vs retail mislead) | `CPhysicsObj::calc_acceleration` (contact-gated); `set_on_walkable` 0x00511310; retire in R6 (contact-gated accel + persistent GRAVITY) |
|
||||
| AP-82 | **StickyManager deep-overlap back-off sign pin**: when the stick-gap overlap exceeds one tick's step (`speed×quantum < \|dist\|`, `dist < 0`), acdream applies `delta = −(speed×quantum)` (rate-limited back-off); ACE's literal port keeps `+delta` there — a runaway that steers INTO the target with equilibrium at centers-coincident. The BN mush (0x00555554-0x00555597) is unreadable on exactly this compare; the pin is refuted-by-evidence against ACE-literal: #171 gate-3 probe showed 1661 deep-overlap ticks all steering inward (monsters converged to centerDist≈0 — "monster inside the player") while retail side-by-side on the same ACE shows separation. ACE servers essentially never reach the branch (quantum ≥1/30 → threshold ~1 m; render-rate quanta → ~0.13 m) | `src/AcDream.Core/Physics/Motion/StickyManager.cs` (`AdjustOffset` delta clamp; conformance `StickyManagerTests.AdjustOffset_DeepOverlap_BacksOff_RateLimited`) | Minimal interpretation consistent with the mush structure AND observed retail; identical to ACE-literal in every shallow/outside case | If retail's true deep-overlap behavior differs (e.g. no movement at all), our back-off rate diverges in that rare state; verify via cdb `StickyManager::adjust_offset` trace with a forced overlap when convenient | `StickyManager::adjust_offset` 0x00555430 (x87 mush); ACE StickyManager.cs:117-121 (the literal branch this pin overrides) |
|
||||
|
||||
## 4. Temporary stopgap (TS) — 39 rows (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)
|
||||
|
||||
|
|
|
|||
|
|
@ -236,9 +236,27 @@ public sealed class StickyManager
|
|||
|
||||
// Don't overshoot: clamp the per-tick step to the remaining (signed)
|
||||
// distance — a negative dist inverts the direction (back off).
|
||||
//
|
||||
// DEEP-OVERLAP SIGN PIN (#171 gate-3, register AP row): ACE's literal
|
||||
// line is only `if (delta >= |dist|) delta = dist;` — when the
|
||||
// overlap is DEEPER than one tick's step, delta keeps its positive
|
||||
// sign and steers TOWARD the target center, a runaway whose
|
||||
// equilibrium is centers-coincident (gate-3 probe: 1661 deep-overlap
|
||||
// ticks, all inward, monsters converged to centerDist≈0 — the
|
||||
// "monster inside the player" report; retail side-by-side shows
|
||||
// separation). ACE servers essentially never reach that branch
|
||||
// (quantum ≥1/30 × speed ≈31 → threshold ~1 m); at render-rate
|
||||
// quanta the threshold is ~0.13 m and pack jostle trips it
|
||||
// constantly. The BN mush (0x00555554-0x00555597) is unreadable on
|
||||
// exactly this compare; the sign-correct clamp below is the minimal
|
||||
// interpretation consistent with the mush structure AND observed
|
||||
// retail — identical to ACE everywhere except deep-overlap, where it
|
||||
// backs off rate-limited instead of creeping inward.
|
||||
float delta = speed * (float)quantum;
|
||||
if (delta >= MathF.Abs(dist))
|
||||
delta = dist;
|
||||
else if (dist < 0f)
|
||||
delta = -delta;
|
||||
offset.Origin *= delta;
|
||||
|
||||
// Bounded turn to face the target (relative heading this tick).
|
||||
|
|
|
|||
|
|
@ -191,6 +191,26 @@ public sealed class StickyManagerTests
|
|||
Assert.Equal(-0.4f, frame.Origin.X, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdjustOffset_DeepOverlap_BacksOff_RateLimited()
|
||||
{
|
||||
var world = new Dictionary<uint, R5Host>();
|
||||
var self = new R5Host(10u, world) { Radius = 0.5f, MinterpMaxSpeed = 1.0f };
|
||||
var target = new R5Host(20u, world);
|
||||
// centerDist 0.1 → cyl = 0.1-0.5-0.5 = -0.9, minus 0.3 → dist = -1.2
|
||||
// (overlap DEEPER than one tick's step).
|
||||
var sticky = StuckAndInitialized(self, target, new Vector3(0.1f, 0f, 0f));
|
||||
|
||||
var frame = new MotionDeltaFrame();
|
||||
sticky.AdjustOffset(frame, quantum: 0.1);
|
||||
|
||||
// delta = 5*0.1 = 0.5 < |dist|=1.2 — ACE's literal branch kept +0.5
|
||||
// here (INWARD: the #171 gate-3 runaway-to-center; 1661 probe ticks,
|
||||
// all inward, equilibrium at centers-coincident). The sign pin backs
|
||||
// off rate-limited: dir +X × −0.5.
|
||||
Assert.Equal(-0.5f, frame.Origin.X, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdjustOffset_UsesCachedPosition_WhenTargetUnresolvable()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue