fix(phys): #106 — outdoor membership crosses landblock boundaries (LandDefs global-lcoord port)
The player's outdoor cell froze at the last in-block cell the moment they
walked over a landblock boundary (10,449-frame playerCell freeze in the
2026-06-09 capture; whole neighbouring-block interiors unenterable, plus
the running-distortion from the stale render anchor). Root cause: the
add_all_outside_cells port clamped BOTH the candidate proposal and the
find_cell_list containing-cell pick to the current landblock's 8x8 grid,
in a frame that silently assumed the current block sits at world origin.
One step over the line -> zero candidates -> FindCellSet returns
currentCellId forever.
Retail has no such clamp. Its cell math runs in a GLOBAL landcell grid
(lcoord 0..2039 spanning the map): get_outside_lcoord = blockid_to_lcoord
+ floor(blockLocalPos/24) with no bounds besides the map edge, and
lcoord_to_gid re-derives the landblock id from the lcoord's upper bits —
crossings are inherent, never special-cased.
The fix, decomp-cited throughout:
- New AcDream.Core.Physics.LandDefs: in_bounds (pc:68509),
blockid_to_lcoord (pc:68520), inbound_valid_cellid (pc:163438),
gid_to_lcoord (pc:163500), lcoord_to_gid (pc:171859),
get_outside_lcoord (pc:438690), adjust_to_outside (pc:438719).
Cross-checked against ACE LandDefs.cs; three artifacts documented and
avoided: BN's int8_t mis-render of block_y, BN's dropped 192f
BlockLength constant, and ACE add_cell_block's "FIXME!" same-block
guard (an ACE divergence, not retail).
- CellTransit.AddAllOutsideCells rewritten as the faithful sphere
variant (pc:317499 @0x00533630): adjust_to_outside re-seats the
(cell, position) pair cross-block, check_add_cell_boundary (pc:317229)
adds up to 3 neighbours by global lcoord, add_outside_cell (pc:317056)
has no same-block filter. adjust_to_outside failure breaks the sphere
loop (pc:533699 verbatim).
- BuildCellSetAndPickContaining: the outdoor containing-cell pick is now
the global XY-column under the sphere centre (AdjustToOutside), not
the [0,8)-clamped current-prefix reconstruction. Interior-wins order
and current-cell-first hysteresis unchanged.
- World->block-local frame conversion via the landblock origin already
registered in CellGraph (new TryGetTerrainOrigin); Zero fallback
preserves the legacy anchor-block assumption for unregistered terrain.
- Cross-landblock building entry comes free: the candidate snapshot now
contains neighbour-block landcells, so GetBuilding/CheckBuildingTransit
fire for cottages across the line (the capture's one failing entry).
Investigated FIRST per the pickup brief: the b3ce505 #98 stopgap gate is
definitively exonerated — it is a collision-object query gate that fires
only for indoor primary cells; no membership path touches
ShadowObjectRegistry.
Tests: 31 new (25 LandDefs conformance incl. capture-geometry goldens
0xA9B40031 -> 0xA9B30038/0xA9B30034 and the northbound return; 4
AddAllOutsideCells cross-block; 3 FindCellSet membership goldens incl.
the non-anchor-frame origin conversion). Full suite: 294+218+420 green;
Core 1369 green + the 4 pre-existing door/#99-era failures + 1 skip
(unchanged from baseline).
Pseudocode + artifact notes:
docs/research/2026-06-09-landdefs-outside-cells-pseudocode.md.
Remaining acceptance: live boundary walk with ACDREAM_PROBE_CELL=1
(ISSUES.md #106).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
12fb408972
commit
7078264291
11 changed files with 813 additions and 88 deletions
|
|
@ -11,16 +11,18 @@ public class CellTransitAddAllOutsideCellsTests
|
|||
public void SphereWellInsideCell_AddsOneCell()
|
||||
{
|
||||
// A6.P4 (2026-05-24): coords are LANDBLOCK-LOCAL (X/Y in [0, 192]).
|
||||
// Player at landblock-local (12, 12, 0) → cell (0,0) in landblock 0xA9B40000.
|
||||
// Pre-fix this test passed world coords (32460, 34572) and the function
|
||||
// subtracted lbXf=32448 to get local 12. Post-fix the function expects
|
||||
// landblock-local directly.
|
||||
// #106 (2026-06-09): the convention is now explicit — coords are
|
||||
// world-frame and currentBlockOrigin converts them to block-local;
|
||||
// Zero origin == "current block is the anchor", the same inputs as
|
||||
// before. Player at landblock-local (12, 12, 0) → cell (0,0) in
|
||||
// landblock 0xA9B40000.
|
||||
var candidates = new HashSet<uint>();
|
||||
CellTransit.AddAllOutsideCells(
|
||||
worldSphereCenter: new Vector3(12f, 12f, 0f),
|
||||
sphereRadius: 0.5f,
|
||||
currentCellId: 0xA9B40001u,
|
||||
candidates);
|
||||
currentBlockOrigin: Vector3.Zero,
|
||||
candidates: candidates);
|
||||
|
||||
Assert.Single(candidates);
|
||||
Assert.Contains(0xA9B40001u, candidates);
|
||||
|
|
@ -29,19 +31,79 @@ public class CellTransitAddAllOutsideCellsTests
|
|||
[Fact]
|
||||
public void SphereAtCellEastBoundary_AddsTwoCells()
|
||||
{
|
||||
// A6.P4 (2026-05-24): landblock-local coords. Player at (23.6, 12, 0)
|
||||
// — near +X edge of cell (0,0). Sphere reaches to local X = 23.6 + 0.5
|
||||
// = 24.1 → cell (1,0) added.
|
||||
// Player at (23.6, 12, 0) — near +X edge of cell (0,0). Sphere
|
||||
// reaches to local X = 23.6 + 0.5 = 24.1 → cell (1,0) added.
|
||||
var candidates = new HashSet<uint>();
|
||||
CellTransit.AddAllOutsideCells(
|
||||
worldSphereCenter: new Vector3(23.6f, 12f, 0f),
|
||||
sphereRadius: 0.5f,
|
||||
currentCellId: 0xA9B40001u,
|
||||
candidates);
|
||||
currentBlockOrigin: Vector3.Zero,
|
||||
candidates: candidates);
|
||||
|
||||
Assert.Equal(2, candidates.Count);
|
||||
Assert.Contains(0xA9B40001u, candidates);
|
||||
// Cell (1,0): low-16 id = 1 * 8 + 0 + 1 = 9 → 0x0009.
|
||||
Assert.Contains(0xA9B40009u, candidates);
|
||||
}
|
||||
|
||||
// ── #106: landblock crossings (retail add_all_outside_cells has no
|
||||
// same-block clamp — LandDefs global lcoords) ─────────────────────
|
||||
|
||||
[Fact]
|
||||
public void SphereJustSouthOfBlockBoundary_AddsBothBlocks()
|
||||
{
|
||||
// 0.2 m south of A9B4's southern edge: adjust_to_outside re-seats to
|
||||
// A9B3 row 7 (cell 0xA9B30038 under x=150), and the boundary check
|
||||
// (point.Y = 23.8 > maxRad 23.5) adds the A9B4 cell back as the +Y
|
||||
// neighbour. Pre-#106 this returned ZERO candidates (the freeze).
|
||||
var candidates = new HashSet<uint>();
|
||||
CellTransit.AddAllOutsideCells(
|
||||
worldSphereCenter: new Vector3(150f, -0.2f, 0f),
|
||||
sphereRadius: 0.5f,
|
||||
currentCellId: 0xA9B40031u,
|
||||
currentBlockOrigin: Vector3.Zero,
|
||||
candidates: candidates);
|
||||
|
||||
Assert.Contains(0xA9B30038u, candidates); // containing (south block)
|
||||
Assert.Contains(0xA9B40031u, candidates); // +Y neighbour (home block)
|
||||
Assert.Equal(2, candidates.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SphereDeepInNeighbourBlock_AddsNeighbourCellOnly()
|
||||
{
|
||||
// The #106 capture geometry: ~109.65 m south of A9B4's origin =
|
||||
// ~82 m into A9B3 → cell row 3 (floor(-109.65/24) = -5 → ly 1435).
|
||||
var candidates = new HashSet<uint>();
|
||||
CellTransit.AddAllOutsideCells(
|
||||
worldSphereCenter: new Vector3(150f, -109.65f, 0f),
|
||||
sphereRadius: 0.5f,
|
||||
currentCellId: 0xA9B40031u,
|
||||
currentBlockOrigin: Vector3.Zero,
|
||||
candidates: candidates);
|
||||
|
||||
Assert.Contains(0xA9B30034u, candidates);
|
||||
Assert.DoesNotContain(0xA9B40031u, candidates);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonAnchorBlockOrigin_ConvertsWorldFrame()
|
||||
{
|
||||
// Player's current cell is in A9B3 (one block south of the anchor,
|
||||
// world origin (0, -192)). World y = -0.2 is block-local y = 191.8 —
|
||||
// still inside A9B3 row 7, with the boundary check adding the A9B4
|
||||
// cell to the north. Exercises the origin conversion that the
|
||||
// pre-#106 code silently skipped (world frame assumed block-local).
|
||||
var candidates = new HashSet<uint>();
|
||||
CellTransit.AddAllOutsideCells(
|
||||
worldSphereCenter: new Vector3(150f, -0.2f, 0f),
|
||||
sphereRadius: 0.5f,
|
||||
currentCellId: 0xA9B30038u,
|
||||
currentBlockOrigin: new Vector3(0f, -192f, 0f),
|
||||
candidates: candidates);
|
||||
|
||||
Assert.Contains(0xA9B30038u, candidates);
|
||||
Assert.Contains(0xA9B40031u, candidates);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,6 +159,75 @@ public class CellTransitFindCellSetTests
|
|||
Assert.Contains(0xA9B40001u, cellSet);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
// #106 — outdoor membership across landblock boundaries.
|
||||
// Retail's add_all_outside_cells + the find_cell_list pick run in the
|
||||
// GLOBAL landcell grid (LandDefs lcoords); crossing a landblock boundary
|
||||
// is inherent. The pre-#106 port clamped both to the current block's 8×8
|
||||
// grid → zero candidates one step over the line → membership frozen
|
||||
// (the 10,449-frame playerCell freeze in flap-105-capture.log).
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void OutdoorSeed_CrossesLandblockBoundary_South()
|
||||
{
|
||||
// The #106 acceptance golden: walking south out of A9B4, the outdoor
|
||||
// cell must advance to the southern neighbour block's cell. Anchor
|
||||
// frame (no registered terrain → origin Zero): world y = -0.2 is
|
||||
// 0.2 m into A9B3's row 7 under x=150 → cell 0xA9B30038.
|
||||
var cache = new PhysicsDataCache();
|
||||
|
||||
uint containing = CellTransit.FindCellSet(
|
||||
cache, new Vector3(150f, -0.2f, 0f), sphereRadius: 0.5f,
|
||||
currentCellId: 0xA9B40031u,
|
||||
out var cellSet);
|
||||
|
||||
Assert.Equal(0xA9B30038u, containing);
|
||||
Assert.Contains(0xA9B30038u, cellSet);
|
||||
Assert.Contains(0xA9B40031u, cellSet); // +Y neighbour still in the set
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OutdoorSeed_NearBoundaryButInside_StaysCurrent()
|
||||
{
|
||||
// 0.2 m NORTH of the boundary: the candidate set includes the A9B3
|
||||
// neighbour (sphere overlaps it) but the centre column is still the
|
||||
// current cell — membership must NOT flip early (single clean flip
|
||||
// at the line, matching the capture's 96/96 within-block behaviour).
|
||||
var cache = new PhysicsDataCache();
|
||||
|
||||
uint containing = CellTransit.FindCellSet(
|
||||
cache, new Vector3(150f, 0.2f, 0f), sphereRadius: 0.5f,
|
||||
currentCellId: 0xA9B40031u,
|
||||
out var cellSet);
|
||||
|
||||
Assert.Equal(0xA9B40031u, containing);
|
||||
Assert.Contains(0xA9B30038u, cellSet);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OutdoorSeed_NonAnchorBlock_UsesRegisteredTerrainOrigin()
|
||||
{
|
||||
// Northbound return: the player's current cell is in A9B3 (origin
|
||||
// (0, -192) registered via CellGraph terrain — the production path).
|
||||
// World y = +1 is 1 m back into the anchor block A9B4; the pick must
|
||||
// convert through A9B3's origin (block-local y = 193) and advance to
|
||||
// 0xA9B40031. Pre-#106 the world frame was silently assumed
|
||||
// block-local, which is wrong for every non-anchor block.
|
||||
var cache = new PhysicsDataCache();
|
||||
cache.CellGraph.RegisterTerrain(
|
||||
0xA9B30000u,
|
||||
new TerrainSurface(new byte[81], new float[256]),
|
||||
new Vector3(0f, -192f, 0f));
|
||||
|
||||
uint containing = CellTransit.FindCellSet(
|
||||
cache, new Vector3(150f, 1f, 0f), sphereRadius: 0.5f,
|
||||
currentCellId: 0xA9B30038u,
|
||||
out _);
|
||||
|
||||
Assert.Equal(0xA9B40031u, containing);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
// Membership hysteresis — the R1-flap root cause.
|
||||
// Retail CObjCell::find_cell_list adds the CURRENT cell at index 0
|
||||
|
|
|
|||
|
|
@ -931,8 +931,11 @@ public class DoorBugTrajectoryReplayTests
|
|||
const uint currentCellId = 0xA9B40150u;
|
||||
|
||||
var candidates = new HashSet<uint>();
|
||||
// #106 (2026-06-09): origin parameter added — Vector3.Zero is the
|
||||
// anchor-block frame this capture was taken in (same semantics as
|
||||
// the pre-#106 4-arg form).
|
||||
CellTransit.AddAllOutsideCells(
|
||||
sphereWorld, sphereRadius, currentCellId, candidates);
|
||||
sphereWorld, sphereRadius, currentCellId, Vector3.Zero, candidates);
|
||||
|
||||
const uint expectedDoorCell = 0xA9B40029u;
|
||||
Assert.True(candidates.Contains(expectedDoorCell),
|
||||
|
|
|
|||
200
tests/AcDream.Core.Tests/Physics/LandDefsTests.cs
Normal file
200
tests/AcDream.Core.Tests/Physics/LandDefsTests.cs
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Conformance tests for the retail LandDefs global-lcoord math (issue #106).
|
||||
/// Goldens derive from the decomp formulas (pseudocode doc:
|
||||
/// docs/research/2026-06-09-landdefs-outside-cells-pseudocode.md) using the
|
||||
/// Holtburg geometry from the #106 capture: landblock 0xA9B4 with its southern
|
||||
/// neighbour 0xA9B3 (block_x 0xA9 = 169, block_y 0xB4 = 180 / 0xB3 = 179).
|
||||
/// </summary>
|
||||
public class LandDefsTests
|
||||
{
|
||||
// ── blockid_to_lcoord (pc:68520) ────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void BlockIdToLcoord_A9B4_Is_1352_1440()
|
||||
{
|
||||
Assert.True(LandDefs.BlockIdToLcoord(0xA9B40031u, out int lx, out int ly));
|
||||
Assert.Equal(169 * 8, lx);
|
||||
Assert.Equal(180 * 8, ly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BlockIdToLcoord_HighBlockY_NoSignExtension()
|
||||
{
|
||||
// BN decomp renders block_y extraction with an int8_t cast that would
|
||||
// sign-extend 0xB4 → negative. ACE confirms zero-extension; this test
|
||||
// pins it for every block byte ≥ 0x80.
|
||||
Assert.True(LandDefs.BlockIdToLcoord(0xFEFE0001u, out int lx, out int ly));
|
||||
Assert.Equal(0xFE * 8, lx);
|
||||
Assert.Equal(0xFE * 8, ly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BlockIdToLcoord_ZeroCellId_Fails()
|
||||
{
|
||||
Assert.False(LandDefs.BlockIdToLcoord(0u, out _, out _));
|
||||
}
|
||||
|
||||
// ── gid_to_lcoord (pc:163500) ───────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void GidToLcoord_A9B40031_Is_1358_1440()
|
||||
{
|
||||
// low 0x31 = 49 → cell_x = (49-1)>>3 = 6, cell_y = (49-1)&7 = 0.
|
||||
Assert.True(LandDefs.GidToLcoord(0xA9B40031u, out int lx, out int ly));
|
||||
Assert.Equal(1358, lx);
|
||||
Assert.Equal(1440, ly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GidToLcoord_IndoorCell_Fails()
|
||||
{
|
||||
// Outdoor-only (decomp gates on low < 0x100).
|
||||
Assert.False(LandDefs.GidToLcoord(0xA9B40164u, out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GidToLcoord_InvalidLowRange_Fails()
|
||||
{
|
||||
Assert.False(LandDefs.GidToLcoord(0xA9B40000u, out _, out _)); // low 0
|
||||
Assert.False(LandDefs.GidToLcoord(0xA9B40041u, out _, out _)); // low 0x41 (> 0x40, < 0x100)
|
||||
}
|
||||
|
||||
// ── lcoord_to_gid (pc:171859) ───────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void LcoordToGid_RoundTrips_A9B40031()
|
||||
{
|
||||
Assert.Equal(0xA9B40031u, LandDefs.LcoordToGid(1358, 1440));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LcoordToGid_CrossesBlockSouth()
|
||||
{
|
||||
// One lcoord row south of A9B4's southern edge → block_y 179 (0xB3),
|
||||
// cell row 7: low = (1439&7) + (1358&7)*8 + 1 = 7 + 48 + 1 = 0x38.
|
||||
Assert.Equal(0xA9B30038u, LandDefs.LcoordToGid(1358, 1439));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LcoordToGid_OutOfMapBounds_ReturnsZero()
|
||||
{
|
||||
Assert.Equal(0u, LandDefs.LcoordToGid(-1, 5));
|
||||
Assert.Equal(0u, LandDefs.LcoordToGid(5, -1));
|
||||
Assert.Equal(0u, LandDefs.LcoordToGid(0x7F8, 5));
|
||||
Assert.Equal(0u, LandDefs.LcoordToGid(5, 0x7F8));
|
||||
}
|
||||
|
||||
// ── get_outside_lcoord (pc:438690) ──────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void GetOutsideLcoord_NegativeLocalY_CrossesSouth()
|
||||
{
|
||||
// floor(-1/24) = -1 (floor, not truncation — negative-safe).
|
||||
Assert.True(LandDefs.GetOutsideLcoord(
|
||||
0xA9B40031u, new Vector3(150f, -1f, 0f), out int lx, out int ly));
|
||||
Assert.Equal(1358, lx);
|
||||
Assert.Equal(1439, ly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOutsideLcoord_FromIndoorCellId_UsesBlockBits()
|
||||
{
|
||||
// adjust_to_outside accepts indoor low16 (0x100..0xFFFD) — the block
|
||||
// bits drive the lcoord; the position picks the landcell.
|
||||
Assert.True(LandDefs.GetOutsideLcoord(
|
||||
0xA9B40164u, new Vector3(12f, 12f, 0f), out int lx, out int ly));
|
||||
Assert.Equal(1352, lx);
|
||||
Assert.Equal(1440, ly);
|
||||
}
|
||||
|
||||
// ── adjust_to_outside (pc:438719) ───────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void AdjustToOutside_SouthCrossing_RewritesCellAndRebasesPos()
|
||||
{
|
||||
uint cellId = 0xA9B40031u;
|
||||
var pos = new Vector3(150f, -1f, 5f);
|
||||
Assert.True(LandDefs.AdjustToOutside(ref cellId, ref pos));
|
||||
Assert.Equal(0xA9B30038u, cellId);
|
||||
Assert.Equal(150f, pos.X, 4);
|
||||
Assert.Equal(191f, pos.Y, 4); // -1 − floor(-1/192)·192 = 191 (new block's frame)
|
||||
Assert.Equal(5f, pos.Z, 4); // Z untouched
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdjustToOutside_DeepSouth_CaptureGolden()
|
||||
{
|
||||
// The #106 capture geometry: player ~109.65 m south of A9B4's origin
|
||||
// (≈82 m into A9B3). floor(-109.65/24) = -5 → ly 1435 → row 3 of 0xB3.
|
||||
uint cellId = 0xA9B40031u;
|
||||
var pos = new Vector3(150f, -109.65f, 0f);
|
||||
Assert.True(LandDefs.AdjustToOutside(ref cellId, ref pos));
|
||||
Assert.Equal(0xA9B30034u, cellId);
|
||||
Assert.Equal(82.35f, pos.Y, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdjustToOutside_NorthboundReturn()
|
||||
{
|
||||
// From A9B3's frame, local y = 193 is 1 m into A9B4.
|
||||
uint cellId = 0xA9B30038u;
|
||||
var pos = new Vector3(150f, 193f, 0f);
|
||||
Assert.True(LandDefs.AdjustToOutside(ref cellId, ref pos));
|
||||
Assert.Equal(0xA9B40031u, cellId);
|
||||
Assert.Equal(1f, pos.Y, 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdjustToOutside_WithinBlock_KeepsBlockRewritesCell()
|
||||
{
|
||||
uint cellId = 0xA9B40031u; // cell (6,0)
|
||||
var pos = new Vector3(12f, 12f, 0f); // over cell (0,0)
|
||||
Assert.True(LandDefs.AdjustToOutside(ref cellId, ref pos));
|
||||
Assert.Equal(0xA9B40001u, cellId);
|
||||
Assert.Equal(12f, pos.X, 4);
|
||||
Assert.Equal(12f, pos.Y, 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdjustToOutside_EpsilonSnap_TreatsTinyNegativeAsZero()
|
||||
{
|
||||
// Retail snaps |coord| < 0.0002 to 0 BEFORE the floor — a hair-negative
|
||||
// y stays in the current block instead of flapping to the neighbour.
|
||||
uint cellId = 0xA9B40031u;
|
||||
var pos = new Vector3(150f, -0.0001f, 0f);
|
||||
Assert.True(LandDefs.AdjustToOutside(ref cellId, ref pos));
|
||||
Assert.Equal(0xA9B40031u, cellId);
|
||||
Assert.Equal(0f, pos.Y, 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdjustToOutside_InvalidLow_FailsAndZeroesCell()
|
||||
{
|
||||
uint cellId = 0xA9B40050u; // low 0x50: not landcell, not envcell
|
||||
var pos = new Vector3(12f, 12f, 0f);
|
||||
Assert.False(LandDefs.AdjustToOutside(ref cellId, ref pos));
|
||||
Assert.Equal(0u, cellId); // retail writes 0 on failure
|
||||
}
|
||||
|
||||
// ── inbound_valid_cellid (pc:163438) ────────────────────────────────
|
||||
|
||||
[Theory]
|
||||
[InlineData(0xA9B40001u, true)]
|
||||
[InlineData(0xA9B40040u, true)]
|
||||
[InlineData(0xA9B40100u, true)]
|
||||
[InlineData(0xA9B4FFFDu, true)]
|
||||
[InlineData(0xA9B4FFFFu, true)] // block sentinel
|
||||
[InlineData(0xA9B40000u, false)]
|
||||
[InlineData(0xA9B40041u, false)]
|
||||
[InlineData(0xA9B4FFFEu, false)]
|
||||
public void InboundValidCellId_LowRanges(uint cellId, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, LandDefs.InboundValidCellId(cellId));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue