fix(physics): #146 — re-base building collision per landblock apply (lost after portal-in)
Static building/house-wall collision worked on a fresh login to a town but was lost after logging in elsewhere and PORTALING in: the foot-sphere clipped straight through walls while server-spawned doors (own collision) still blocked. Root (capture-confirmed, ACDREAM_PROBE_BUILDING bldOrigin): the building shell BSP's WorldTransform is computed from the streaming-relative origin (origin = (lb − _liveCenter)·192) AT CACHE TIME, and CacheBuilding is idempotent (first-wins per cell). A teleport recenters _liveCenter, but the idempotent guard never re-bases the cached transform — so the shell sat at a stale world offset (login Arwic → portal Holtburg: bldOrigin=(-5488,2149), ~5.5 km from the player at (83,24)), and FindBuildingCollisions never penetrated → result=OK everywhere → no block. Terrain doesn't suffer this because AddLandblock overwrites its WorldOffset on every apply. Fix: clear a landblock's cached buildings at the start of each ApplyLoadedTerrain (PhysicsDataCache.RemoveBuildingsForLandblock), then let the existing loop re-populate fresh with the CURRENT origin — the per-apply re-base terrain already gets, while keeping CacheBuilding's per-cell first-wins within each fresh pass (retail CSortCell::add_building). Also adds bldOrigin to the [bldg-channel] probe. Verified on the exact repro (Arwic login → Holtburg portal → walk into wall): bldOrigin now (107.5,36.0) at the wall (was -5488,2149); channel returns Collided → Slid (block + wall-slide). Suites green: Core 1568(+2 skip), App 468(+2 skip), UI 425, Net 317. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5b3e946b8b
commit
49d743f88a
3 changed files with 34 additions and 1 deletions
|
|
@ -6943,6 +6943,17 @@ public sealed class GameWindow : IDisposable
|
||||||
// formula (gridX * 8 + gridY + 1) within the 192m × 192m landblock.
|
// formula (gridX * 8 + gridY + 1) within the 192m × 192m landblock.
|
||||||
if (lbInfo is not null && lbInfo.Buildings.Count > 0)
|
if (lbInfo is not null && lbInfo.Buildings.Count > 0)
|
||||||
{
|
{
|
||||||
|
// #146 (2026-06-24): clear this landblock's prior building cache so
|
||||||
|
// the loop re-bases each building's STREAMING-RELATIVE WorldTransform
|
||||||
|
// with the CURRENT _liveCenter (origin, recomputed above per apply).
|
||||||
|
// CacheBuilding's per-cell first-wins then applies fresh within this
|
||||||
|
// pass — mirrors terrain's per-apply WorldOffset re-base (AddLandblock).
|
||||||
|
// Without it, CacheBuilding's idempotent guard locks the transform at
|
||||||
|
// the frame it was first cached with, so a teleport recenter strands
|
||||||
|
// the shell BSP at a stale world offset and house walls stop colliding
|
||||||
|
// (login at Arwic → portal to Holtburg → walls clip; bldOrigin ~5.5km off).
|
||||||
|
_physicsDataCache.RemoveBuildingsForLandblock(lb.LandblockId);
|
||||||
|
|
||||||
uint lbPrefix = lb.LandblockId & 0xFFFF0000u;
|
uint lbPrefix = lb.LandblockId & 0xFFFF0000u;
|
||||||
foreach (var building in lbInfo.Buildings)
|
foreach (var building in lbInfo.Buildings)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -455,6 +455,28 @@ public sealed class PhysicsDataCache
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// #146 (2026-06-24): drop every cached building belonging to a landblock so
|
||||||
|
/// the next <see cref="CacheBuilding"/> pass re-bases their STREAMING-RELATIVE
|
||||||
|
/// <c>WorldTransform</c> against the CURRENT <c>_liveCenter</c>. Without this,
|
||||||
|
/// CacheBuilding's per-cell first-wins guard LOCKS the transform at whatever
|
||||||
|
/// frame it was first cached with; a teleport recenter then leaves the shell
|
||||||
|
/// BSP at a stale world offset (~the source↔dest landblock distance), so the
|
||||||
|
/// foot-sphere walks through where the wall visually is and collision is lost
|
||||||
|
/// (login at Arwic → portal to Holtburg → house walls clip; bldOrigin probed
|
||||||
|
/// ~5.5 km off). Terrain avoids this because <c>AddLandblock</c> overwrites its
|
||||||
|
/// WorldOffset every apply; buildings get the equivalent per-apply re-base via
|
||||||
|
/// the caller clearing-then-repopulating (which keeps first-wins within each
|
||||||
|
/// fresh pass — retail <c>CSortCell::add_building</c> semantics preserved).
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveBuildingsForLandblock(uint landblockId)
|
||||||
|
{
|
||||||
|
uint prefix = landblockId & 0xFFFF0000u;
|
||||||
|
foreach (var key in _buildings.Keys)
|
||||||
|
if ((key & 0xFFFF0000u) == prefix)
|
||||||
|
_buildings.TryRemove(key, out _);
|
||||||
|
}
|
||||||
|
|
||||||
public BuildingPhysics? GetBuilding(uint landcellId)
|
public BuildingPhysics? GetBuilding(uint landcellId)
|
||||||
=> _buildings.TryGetValue(landcellId, out var b) ? b : null;
|
=> _buildings.TryGetValue(landcellId, out var b) ? b : null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2874,7 +2874,7 @@ public sealed class Transition
|
||||||
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
||||||
{
|
{
|
||||||
Console.WriteLine(System.FormattableString.Invariant(
|
Console.WriteLine(System.FormattableString.Invariant(
|
||||||
$"[bldg-channel] cell=0x{cellId:X8} model=0x{building.ModelId:X8} wpos=({sp.GlobalSphere[0].Origin.X:F3},{sp.GlobalSphere[0].Origin.Y:F3},{sp.GlobalSphere[0].Origin.Z:F3}) hitsInterior={sp.HitsInteriorCell} result={result}"));
|
$"[bldg-channel] cell=0x{cellId:X8} model=0x{building.ModelId:X8} wpos=({sp.GlobalSphere[0].Origin.X:F3},{sp.GlobalSphere[0].Origin.Y:F3},{sp.GlobalSphere[0].Origin.Z:F3}) bldOrigin=({bldOrigin.X:F3},{bldOrigin.Y:F3},{bldOrigin.Z:F3}) hitsInterior={sp.HitsInteriorCell} result={result}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0x006b5338: non-OK + non-Contact mover → environment attribution.
|
// 0x006b5338: non-OK + non-Contact mover → environment attribution.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue