The wire local is LANDBLOCK-relative [0,192); the cell low word = floor(local/24), so a consistent (cell,local) pair must keep them in lockstep. Two retail-faithful corrections vs the first pass: - SnapToCell canonicalizes the OUTDOOR seed via AdjustToOutside (retail SetPositionInternal/adjust_to_outside @0x00504A40 — the #107 'never trust a server (cell,pos) pair' protection). Indoor seeds stay verbatim (BSP-validated). - SyncCellPositionDelta calls AdjustToOutside on EVERY delta, not just on 192 m crossings, so intra-landblock 24 m cell-index changes track (needed by Slice 3 membership). Idempotent within a cell. Tests rewritten to verify both (the earlier test paired an inconsistent cell+local). Core 1527 passed / 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
68 lines
3.3 KiB
C#
68 lines
3.3 KiB
C#
using System.Numerics;
|
|
using AcDream.Core.Physics;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Tests.Physics;
|
|
|
|
// #145 Slice 2a — PhysicsBody carries a cell-relative CellPosition that rides alongside
|
|
// the world Vector3 Position. The wire's local is LANDBLOCK-relative [0,192); the cell id
|
|
// low word (which 24 m cell) = floor(local/24), so a consistent pair has
|
|
// cell.lowword == cellIndex(local). Placement canonicalizes the outdoor seed (retail
|
|
// SetPositionInternal/adjust_to_outside); motion mirrors the world delta into the local
|
|
// and re-derives the cell every tick via AdjustToOutside.
|
|
public class PhysicsBodyCellSyncTests
|
|
{
|
|
[Fact]
|
|
public void SnapToCell_ConsistentOutdoorPair_SeedsAsGiven()
|
|
{
|
|
var body = new PhysicsBody();
|
|
// cell 0x23 = (lx=4, ly=2); local (100,50) is in that cell (floor(100/24)=4, floor(50/24)=2).
|
|
body.SnapToCell(0xC95B0023u, worldPos: new Vector3(1000f, 2000f, 12f), cellLocal: new Vector3(100f, 50f, 12f));
|
|
Assert.Equal(0xC95B0023u, body.CellPosition.ObjCellId);
|
|
Assert.Equal(new Vector3(100f, 50f, 12f), body.CellPosition.Frame.Origin);
|
|
Assert.Equal(new Vector3(1000f, 2000f, 12f), body.Position);
|
|
}
|
|
|
|
[Fact]
|
|
public void SnapToCell_InconsistentOutdoorPair_CanonicalizesCellFromLocal()
|
|
{
|
|
// #107 protection: retail re-derives the cell from the position on placement.
|
|
// (cell 0x01, local in cell 0x23) is fixed to 0x23.
|
|
var body = new PhysicsBody();
|
|
body.SnapToCell(0xC95B0001u, new Vector3(1000f, 2000f, 12f), new Vector3(100f, 50f, 12f));
|
|
Assert.Equal(0xC95B0023u, body.CellPosition.ObjCellId);
|
|
Assert.Equal(new Vector3(100f, 50f, 12f), body.CellPosition.Frame.Origin);
|
|
}
|
|
|
|
[Fact]
|
|
public void PositionDelta_WithinLandblock_UpdatesCellIndex()
|
|
{
|
|
var body = new PhysicsBody();
|
|
body.SnapToCell(0xC95B0023u, new Vector3(1000f, 2000f, 12f), new Vector3(100f, 50f, 12f));
|
|
body.Position += new Vector3(0f, 24f, 0f); // local (100,74) → cell (4,3)
|
|
Assert.Equal(new Vector3(100f, 74f, 12f), body.CellPosition.Frame.Origin);
|
|
// low word for (lx=4, ly=3): (ly&7) + ((lx&7)<<3) + 1 = 3 + 32 + 1 = 0x24
|
|
Assert.Equal(0xC95B0024u, body.CellPosition.ObjCellId);
|
|
Assert.Equal(new Vector3(1000f, 2024f, 12f), body.Position); // world still authoritative + moved
|
|
}
|
|
|
|
[Fact]
|
|
public void PositionDelta_AcrossSouthLandblockEdge_BumpsCellAndRewraps()
|
|
{
|
|
var body = new PhysicsBody();
|
|
body.SnapToCell(0xC95B0001u, new Vector3(1000f, 2000f, 12f), new Vector3(100f, 0.3f, 12f));
|
|
body.Position += new Vector3(0f, -1f, 0f); // local Y → -0.7 → south neighbour, ~191.3
|
|
int lbY = (int)((body.CellPosition.ObjCellId >> 16) & 0xFFu);
|
|
Assert.Equal(0x5A, lbY); // bumped to south neighbour 0xC95A
|
|
Assert.InRange(body.CellPosition.Frame.Origin.Y, 190f, 192f); // re-wrapped near top of southern block
|
|
}
|
|
|
|
[Fact]
|
|
public void PositionDelta_WithoutSeed_LeavesCellPositionDefault()
|
|
{
|
|
// A body never SnapToCell'd (e.g. a remote entity) gets no synthesized cell.
|
|
var body = new PhysicsBody();
|
|
body.Position += new Vector3(10f, 10f, 0f);
|
|
Assert.Equal(0u, body.CellPosition.ObjCellId);
|
|
}
|
|
}
|