fix(physics): #92 — seed resolver with server cell id at player-mode entry

EnterPlayerModeNow computed the initial cellId from landblock prefix
+ hardcoded low byte 0x0001 (outdoor sentinel) and passed only the
low 16 bits to PhysicsEngine.Resolve. When the server places the
player INSIDE a building (spawn cell id e.g. 0xA9B4015A indoor), the
sentinel forced the outdoor seed branch — for the first several ticks
CheckBuildingTransit hadn't yet picked up the interior cell (it
depends on the sphere overlapping the destination cell's BSP), the
player was classified outdoor, indoor BSP queries didn't run, and
exterior walls were passable until enough inward motion finally
promoted them.

User-visible symptom: "logged in inside the inn, ran out through the
exterior wall; ran back in and the walls now block."

Fix: use spawn.Position.LandblockId (the server's authoritative full
cell id with landblock prefix) when available; fall back to the old
sentinel only if the spawn record is missing (defensive — shouldn't
fire in live play since OnLiveEntitySpawnedLocked writes _lastSpawnByGuid
before EnterPlayerModeNow can possibly run).

1147 + 8 baseline maintained.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-21 09:17:37 +02:00
parent 7ac8f544a7
commit 23ab17362a

View file

@ -10106,11 +10106,38 @@ public sealed class GameWindow : IDisposable
_playerController.StepDownHeight = 0.4f;
Console.WriteLine($"physics: player step heights — defaulting to 0.4 m (no setup dat)");
}
int plbX = _liveCenterX + (int)MathF.Floor(playerEntity.Position.X / 192f);
int plbY = _liveCenterY + (int)MathF.Floor(playerEntity.Position.Y / 192f);
uint pinitCellId = ((uint)plbX << 24) | ((uint)plbY << 16) | 0x0001u;
// Issue #92 (2026-05-20): seed the resolver with the SERVER's
// authoritative cell id from the spawn message instead of a
// hardcoded outdoor sentinel (`landblockPrefix | 0x0001`). When
// the player logs in INSIDE a building (server reports an indoor
// cell like `0xA9B4015A`), the old sentinel forced the resolver
// into the outdoor seed branch — for the first several ticks
// CheckBuildingTransit hadn't yet picked up the interior cell,
// so the player was classified outdoor, indoor BSP queries
// didn't run, and exterior walls were passable until the player
// moved far enough INWARD that the sphere overlap eventually
// promoted them. Visible symptom: "logged in inside the inn,
// ran out through the exterior wall."
//
// Fall back to the old sentinel when no spawn record is cached
// (defensive — should never fire in live play because the
// _lastSpawnByGuid entry was written by OnLiveEntitySpawnedLocked
// before EnterPlayerModeNow could possibly be reached).
uint pinitCellId;
if (_lastSpawnByGuid.TryGetValue(_playerServerGuid, out var playerSpawn)
&& playerSpawn.Position is { } spawnPos
&& spawnPos.LandblockId != 0)
{
pinitCellId = spawnPos.LandblockId;
}
else
{
int plbX = _liveCenterX + (int)MathF.Floor(playerEntity.Position.X / 192f);
int plbY = _liveCenterY + (int)MathF.Floor(playerEntity.Position.Y / 192f);
pinitCellId = ((uint)plbX << 24) | ((uint)plbY << 16) | 0x0001u;
}
var initResult = _physicsEngine.Resolve(
playerEntity.Position, pinitCellId & 0xFFFFu,
playerEntity.Position, pinitCellId,
System.Numerics.Vector3.Zero, 100f);
_playerController.SetPosition(initResult.Position, initResult.CellId);