fix(phys): #107 indoor-login spawn wedge — validate the server (cell,pos) pair at the player snap (retail AdjustPosition) + unfreeze same-landblock teleport arrivals + self-consistent wire pairs
Root cause (capture resolve-107-login1.jsonl + dat conformance scan): ACE restored the player with a POISONED (cell, position) pair — cell 0xA9B40162 (one building) with a position inside 0xA9B40171 (a different building 55 m away). Our entry snap trusted the claim verbatim: the player stood fake-grounded for minutes (isOnGround passthrough, no contact plane, no walkable polygon — zero-move resolves short-circuit), the FIRST movement input ran a real transition, the pick demoted the indoor claim to outdoor mid-building, and the player fell 2.4 m through the cottage floor onto the terrain underneath — wedged inside the building shell. The second wedge shape (flood-fix-gate2.log) was the PortalSpace freeze: the teleport-arrival detection gated on `differentLandblock || farAway>100m`, an invented heuristic — ACE's same-landblock short-hop corrections matched neither, so PortalSpace never exited and movement input stayed frozen all session. Four legs, all retail-anchored: 1. PhysicsEngine.Resolve (the player snap path: login entry + teleport arrival) now runs AdjustPosition first — retail SetPositionInternal step 1 (acclient :283892, AdjustPosition :280009): validate/correct the claimed cell from the foot-sphere center BEFORE any physics. Corrections log one [spawn-adjust] line. 2. AdjustPosition's previously-deferred indoor seen_outside → adjust_to_outside sub-fallback (:280037-280046) is completed; CellPhysics gains the SeenOutside flag (dat EnvCellFlags.SeenOutside) cached in CacheCellStruct. The camera path does not reach this sub-branch in the gated scenarios (CameraCornerSealReplayTests green). 3. PortalSpace arrival = ANY player position update (holtburger PlayerTeleport handler conformant; recenter still only on landblock change). Verified live: ACE sent a same-lb dist=69.8 correction that the old gate would have frozen on — it now completes. 4. Outbound wire (cell, position) pairs are now SELF-CONSISTENT: derive the landblock frame from the resolver's full cell id instead of welding a position-derived landblock onto its low word — the old composition could write exactly the poisoned pair shape into ACE's character save. Plus the #106-gate-2 hold extension: an indoor spawn claim waits for the claimed cell's hydration (IsSpawnCellReady) so the validation can act — the async equivalent of retail's synchronous cell load. Live verification (wedge-107-verify1.log): entry clean; ACE's same-lb teleport correction completed (old code: permanent freeze); the teleport destination itself carried ANOTHER poisoned claim (0xA9B40150) which [spawn-adjust] corrected to 0xA9B40019; player fully controllable, walking across landcells. 3 new dat-backed conformance tests pin the poisoned-pair facts (Issue107SpawnDiagnosticTests). Baseline: 1380+4 pre-existing #99-era failures+1 skip / 223 / 420 / 294. Pending user gate: park indoors, log out gracefully, relaunch — expect a clean indoor spawn standing on the interior floor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
fb360ab3cc
commit
1090189d39
4 changed files with 287 additions and 19 deletions
|
|
@ -160,6 +160,21 @@ public sealed class PhysicsDataCache
|
|||
/// (indoor room geometry). No-ops if the id is already cached or the
|
||||
/// CellStruct has no physics BSP.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// #107: true when ANY cell struct for the landblock (high-16 prefix of
|
||||
/// <paramref name="cellId"/>) is cached. Cell structs for a landblock are
|
||||
/// registered as a batch by the streaming completion, so "some cells present
|
||||
/// but the claimed one absent" means the claimed id is bogus (poisoned save)
|
||||
/// rather than still-streaming.
|
||||
/// </summary>
|
||||
public bool HasAnyCellStructInLandblock(uint cellId)
|
||||
{
|
||||
uint prefix = cellId & 0xFFFF0000u;
|
||||
foreach (var key in _cellStruct.Keys)
|
||||
if ((key & 0xFFFF0000u) == prefix) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CacheCellStruct(uint envCellId, DatReaderWriter.DBObjs.EnvCell envCell,
|
||||
CellStruct cellStruct, Matrix4x4 worldTransform)
|
||||
{
|
||||
|
|
@ -211,6 +226,9 @@ public sealed class PhysicsDataCache
|
|||
Portals = portals,
|
||||
PortalPolygons = portalPolygons,
|
||||
VisibleCellIds = visibleCellIds,
|
||||
// #107: retail CEnvCell.seen_outside — consumed by AdjustPosition's
|
||||
// indoor not-found fallback (acclient :280037).
|
||||
SeenOutside = envCell.Flags.HasFlag(DatReaderWriter.Enums.EnvCellFlags.SeenOutside),
|
||||
};
|
||||
_cellStruct[envCellId] = cellPhysics;
|
||||
|
||||
|
|
@ -574,4 +592,13 @@ public sealed class CellPhysics
|
|||
/// visibility filter.
|
||||
/// </summary>
|
||||
public IReadOnlySet<uint> VisibleCellIds { get; init; } = new System.Collections.Generic.HashSet<uint>();
|
||||
|
||||
/// <summary>
|
||||
/// #107: retail <c>CEnvCell.seen_outside</c> (dat <c>EnvCellFlags.SeenOutside</c>).
|
||||
/// True for interiors with outdoor-visible portals (ground-floor cottage rooms).
|
||||
/// <c>PhysicsEngine.AdjustPosition</c>'s indoor branch falls back to the outdoor
|
||||
/// landcell under the point when the claimed cell's visible graph does not
|
||||
/// contain it AND this flag is set (retail acclient :280037-280046).
|
||||
/// </summary>
|
||||
public bool SeenOutside { get; init; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue