using System.Collections.Generic; using System.Numerics; using AcDream.Core.Physics; using Xunit; namespace AcDream.Core.Tests.Physics; public class CellTransitAddAllOutsideCellsTests { [Fact] public void SphereWellInsideCell_AddsOneCell() { // A6.P4 (2026-05-24): coords are LANDBLOCK-LOCAL (X/Y in [0, 192]). // #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(); CellTransit.AddAllOutsideCells( worldSphereCenter: new Vector3(12f, 12f, 0f), sphereRadius: 0.5f, currentCellId: 0xA9B40001u, currentBlockOrigin: Vector3.Zero, candidates: candidates); Assert.Single(candidates); Assert.Contains(0xA9B40001u, candidates); } [Fact] public void SphereAtCellEastBoundary_AddsTwoCells() { // 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(); CellTransit.AddAllOutsideCells( worldSphereCenter: new Vector3(23.6f, 12f, 0f), sphereRadius: 0.5f, currentCellId: 0xA9B40001u, 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(); 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(); 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(); 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); } }