fix(physics #145): Slice 3 — carried-anchor membership; closes the far-town cascade
The outdoor membership pick derived the landblock origin from the terrain registry, which returns (0,0) for an UNSTREAMED neighbour — so a fresh far-town teleport at a landblock edge marched the cell id one block per physics tick (the cascade; the 17410 ACE rejects is its wire artifact). Fix: thread the CARRIED cell-relative frame anchor (body.Position - body.CellPosition.Frame.Origin) into the pick via SpherePath.CarriedBlockOrigin. That anchor IS the true landblock world origin, correct even for an unstreamed neighbour, so the pick re-derives the SAME (consistent) cell and never marches. - CellTransit.FindCellSet/BuildCellSetAndPickContaining: Vector3? carriedBlockOrigin (null default = legacy TryGetTerrainOrigin → every existing caller/test untouched). - PhysicsEngine.ResolveWithTransition: set the anchor from a SEEDED OUTDOOR body whose carried landblock matches the resolve cell (else null → legacy). - PlayerMovementController.SetPosition: 3-arg overload seeds CellPosition from the wire's (cell, local) via SnapToCell; 2-arg delegates with cellLocal=pos (anchor (0,0,0) == legacy → zero test churn). - GameWindow.CellLocalForSeed: the placement seam (_liveCenter used ONCE here to derive the cell-local; physics carries it forward without _liveCenter). Regression: TeleportFarTownRunawayTests (south + east edge, unstreamed neighbour). Core 1529 / App 480, zero regressions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
865aae8876
commit
6349ba49aa
6 changed files with 147 additions and 13 deletions
|
|
@ -792,8 +792,22 @@ public sealed class PlayerMovementController
|
|||
}
|
||||
|
||||
public void SetPosition(Vector3 pos, uint cellId)
|
||||
// #145: tests + legacy callers run in the world==block-local frame (no
|
||||
// streaming center), so the cell-local seed IS the world position. This
|
||||
// makes the carried anchor (body.Position − CellPosition.Origin) == (0,0,0),
|
||||
// identical to the legacy Zero terrain-origin fallback → behaviour unchanged.
|
||||
=> SetPosition(pos, cellId, pos);
|
||||
|
||||
/// <summary>
|
||||
/// Server-snap / teleport placement. <paramref name="cellLocal"/> is the
|
||||
/// LANDBLOCK-relative position (the wire's local, or world − landblock origin)
|
||||
/// which seeds the body's cell-relative <c>CellPosition</c> WITHOUT any streaming
|
||||
/// center (#145). A teleport is a large jump, so this snaps the cell frame
|
||||
/// directly via <c>SnapToCell</c> rather than delta-syncing through the setter.
|
||||
/// </summary>
|
||||
public void SetPosition(Vector3 pos, uint cellId, Vector3 cellLocal)
|
||||
{
|
||||
_body.Position = pos;
|
||||
_body.SnapToCell(cellId, pos, cellLocal);
|
||||
_prevPhysicsPos = pos;
|
||||
_currPhysicsPos = pos;
|
||||
UpdateCellId(cellId, "teleport");
|
||||
|
|
|
|||
|
|
@ -5482,6 +5482,22 @@ public sealed class GameWindow : IDisposable
|
|||
private AcDream.App.World.TeleportArrivalController? _teleportArrival;
|
||||
private System.Numerics.Quaternion _pendingTeleportRot = System.Numerics.Quaternion.Identity;
|
||||
|
||||
// #145: the LANDBLOCK-relative (cell-local) position used to SEED the player
|
||||
// body's cell-relative CellPosition. This is the ONE place the streaming center
|
||||
// (_liveCenter) is allowed to touch the physics frame — at the placement seam,
|
||||
// converting the render-frame world position into the wire's (cell, local). After
|
||||
// seeding, physics carries (cell, local) forward without ever reading _liveCenter.
|
||||
private System.Numerics.Vector3 CellLocalForSeed(System.Numerics.Vector3 worldPos, uint cellId)
|
||||
{
|
||||
int lbX = (int)((cellId >> 24) & 0xFFu);
|
||||
int lbY = (int)((cellId >> 16) & 0xFFu);
|
||||
var origin = new System.Numerics.Vector3(
|
||||
(lbX - _liveCenterX) * 192f,
|
||||
(lbY - _liveCenterY) * 192f,
|
||||
0f);
|
||||
return worldPos - origin;
|
||||
}
|
||||
|
||||
private void EnsureTeleportArrivalController()
|
||||
{
|
||||
if (_teleportArrival is not null) return;
|
||||
|
|
@ -5534,7 +5550,8 @@ public sealed class GameWindow : IDisposable
|
|||
pe.ParentCellId = resolved.CellId;
|
||||
pe.Rotation = _pendingTeleportRot;
|
||||
}
|
||||
_playerController.SetPosition(snappedPos, resolved.CellId);
|
||||
_playerController.SetPosition(snappedPos, resolved.CellId,
|
||||
CellLocalForSeed(snappedPos, resolved.CellId));
|
||||
|
||||
_chaseCamera?.Update(snappedPos, _playerController.Yaw);
|
||||
_retailChaseCamera?.Update(snappedPos, _playerController.Yaw,
|
||||
|
|
@ -12750,7 +12767,8 @@ public sealed class GameWindow : IDisposable
|
|||
var initResult = _physicsEngine.Resolve(
|
||||
playerEntity.Position, pinitCellId,
|
||||
System.Numerics.Vector3.Zero, 100f);
|
||||
_playerController.SetPosition(initResult.Position, initResult.CellId);
|
||||
_playerController.SetPosition(initResult.Position, initResult.CellId,
|
||||
CellLocalForSeed(initResult.Position, initResult.CellId));
|
||||
// #111 (2026-06-10): snap the ENTITY too — parity with the
|
||||
// teleport-arrival path (entity.SetPosition + ParentCellId at
|
||||
// GameWindow.cs:4914). Without this, the renderer keeps drawing the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue