acdream/tests/AcDream.Core.Tests/Physics/PhysicsBodyCellSyncTests.cs
Erik 7605439efa test(physics #145): continuous-tracking test — refutes the review staleness claim
Adversarial review flagged a possible CellPosition staleness on indoor->outdoor
transition. Verified against source: false positive (SnapToCell isn't called on
building entry; the body is world-space so the delta is frame-invariant; the anchor
disengages indoors). Added a test proving CellPosition tracks the outdoor cell under
the world position across a multi-cell, cross-landblock walk and stays canonical.
Core cell-sync 6/6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:38:47 +02:00

102 lines
5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
[Fact]
public void ContinuousTracking_LongWalkAcrossCellsAndBlock_NeverGoesStale()
{
// Refutes the "CellPosition goes stale" concern (incl. the indoor-transit case):
// because the Position setter re-derives the cell via AdjustToOutside on EVERY
// write, CellPosition continuously tracks the outdoor cell under the body's WORLD
// position. The body is always world-space, so the world delta is frame-invariant —
// there is no separate "interior" frame to desync. On any exit it is the correct
// cell, never stale.
var body = new PhysicsBody();
body.SnapToCell(0xC95B0001u, new Vector3(1000f, 2000f, 12f), new Vector3(10f, 10f, 12f));
// A winding walk: east within the block, then north across the 192 m boundary.
foreach (var step in new[]
{
new Vector3(50f, 0f, 0f),
new Vector3(50f, 0f, 0f),
new Vector3(0f, 100f, 0f),
new Vector3(0f, 100f, 0f), // local Y 110 → 210, crosses north into 0xC95C
})
body.Position += step;
// local accum (10,10)+(100,200) = (110,210); Y 210 wraps to the north neighbour
// 0xC95C (lbY 0x5B → 0x5C), local Y = 210192 = 18; cell (lx=4, ly=0) → low 0x21.
Assert.Equal(0xC95C0021u, body.CellPosition.ObjCellId);
Assert.Equal(new Vector3(110f, 18f, 12f), body.CellPosition.Frame.Origin);
// And the carried (cell, local) is already canonical — re-deriving does not march.
uint cell = body.CellPosition.ObjCellId;
var local = body.CellPosition.Frame.Origin;
Assert.True(LandDefs.AdjustToOutside(ref cell, ref local));
Assert.Equal(body.CellPosition.ObjCellId, cell);
}
}