fix(phys): #107 gate-run regression — auto-entry hold opened on a mid-population cache read; wait for the claimed cell itself
Gate-run finding (wedge-107-gate-indoor-login.log + dat scan): ACE saved the cell one room off (claim 0xA9B40172, position inside 0xA9B40171 — adjacent rooms). FindVisibleChildCell corrects this fine when hydrated (proven by the new AdjacentRoomClaim regression test), but the live entry committed the raw claim: IsSpawnCellReady's "any cell struct in the landblock => claim is bogus, proceed" disambiguator observed the MID-POPULATION state (interiors hydrate in id order on the background worker; the render-thread predicate read the cache mid-loop) and opened the gate before the claim and its stab neighbors were cached. AdjustPosition then saw a null cell struct and silently passed the claim through; the first movement demoted the player to outdoor inside the house — the user-visible "transparent interior, see straight through walls" (render is downstream of membership: an outdoor-classified viewer only sees the interior through the doorway flood). Fix: the hold now waits for THE CLAIMED CELL's struct, full stop (IsSpawnCellReady simplification; HasAnyCellStructInLandblock removed). Claims that can never hydrate are filtered by GameWindow against the dat's LandBlockInfo.NumCells range (memoized IsSpawnClaimUnhydratable), and PhysicsEngine.Resolve carries a loud lost-cell-equivalent safety net: an indoor claim with NO cell struct AND NO CellSurface floor data demotes to the outdoor landcell with a [spawn-adjust] line instead of committing raw (retail GotoLostCell :283418; documented divergence). Partial hydration (CellSurface present, struct pending) keeps the legacy floor-snap behavior — HasCellSurface uses the file's masked-low-word norm so bare-id fixtures and full-id production both resolve. Baseline restored: Core 1381 (+4 new #107 conformance tests) + 4 pre-existing #99-era failures + 1 skip; App 223 / UI 420 / Net 294. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
34fcbc3806
commit
e4f6750e09
4 changed files with 130 additions and 24 deletions
|
|
@ -1012,13 +1012,15 @@ public sealed class GameWindow : IDisposable
|
|||
// additionally waits for the claimed cell's hydration so the
|
||||
// entry snap's AdjustPosition validation can act (retail loads
|
||||
// the cell synchronously before SetPosition; this is the
|
||||
// async-streaming equivalent). A claim whose landblock's
|
||||
// interior batch hydrated WITHOUT it is poisoned — proceed
|
||||
// and let AdjustPosition demote it.
|
||||
// async-streaming equivalent). Claims that can never hydrate
|
||||
// (id outside the landblock's NumCells range per the dat)
|
||||
// don't hold the gate — the Resolve-head safety net demotes
|
||||
// them loudly.
|
||||
&& (!_lastSpawnByGuid.TryGetValue(_playerServerGuid, out var sp)
|
||||
|| sp.Position is not { } spawnClaim
|
||||
|| spawnClaim.LandblockId == 0
|
||||
|| _physicsEngine.IsSpawnCellReady(spawnClaim.LandblockId)),
|
||||
|| _physicsEngine.IsSpawnCellReady(spawnClaim.LandblockId)
|
||||
|| IsSpawnClaimUnhydratable(spawnClaim.LandblockId)),
|
||||
enterPlayerMode: EnterPlayerModeFromAutoEntry);
|
||||
}
|
||||
|
||||
|
|
@ -11305,6 +11307,34 @@ public sealed class GameWindow : IDisposable
|
|||
/// the guard already guarantee <c>_entitiesByServerGuid</c> contains
|
||||
/// the player guid, so the inner TryGetValue is a fast-path success.
|
||||
/// </summary>
|
||||
// #107 (2026-06-10): memoized "this indoor spawn claim can never hydrate"
|
||||
// check against the dat's LandBlockInfo.NumCells. Used by the auto-entry
|
||||
// hold so a garbage claim doesn't stall login forever; the Resolve-head
|
||||
// safety net demotes it loudly once entry proceeds.
|
||||
private (uint Claim, bool Unhydratable)? _spawnClaimRangeMemo;
|
||||
|
||||
private bool IsSpawnClaimUnhydratable(uint claim)
|
||||
{
|
||||
if ((claim & 0xFFFFu) < 0x0100u) return false;
|
||||
if (_spawnClaimRangeMemo is { } m && m.Claim == claim) return m.Unhydratable;
|
||||
|
||||
bool unhydratable = false;
|
||||
if (_dats is not null)
|
||||
{
|
||||
DatReaderWriter.DBObjs.LandBlockInfo? lbInfo;
|
||||
lock (_datLock)
|
||||
{
|
||||
lbInfo = _dats.Get<DatReaderWriter.DBObjs.LandBlockInfo>(
|
||||
(claim & 0xFFFF0000u) | 0xFFFEu);
|
||||
}
|
||||
uint low = claim & 0xFFFFu;
|
||||
unhydratable = lbInfo is null || lbInfo.NumCells == 0
|
||||
|| low >= 0x0100u + lbInfo.NumCells;
|
||||
}
|
||||
_spawnClaimRangeMemo = (claim, unhydratable);
|
||||
return unhydratable;
|
||||
}
|
||||
|
||||
private void EnterPlayerModeFromAutoEntry()
|
||||
{
|
||||
_playerMode = true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue