diff --git a/tests/AcDream.Core.Tests/Conformance/FindCellListConformanceTests.cs b/tests/AcDream.Core.Tests/Conformance/FindCellListConformanceTests.cs
new file mode 100644
index 0000000..8d09c0c
--- /dev/null
+++ b/tests/AcDream.Core.Tests/Conformance/FindCellListConformanceTests.cs
@@ -0,0 +1,87 @@
+using System.Numerics;
+using AcDream.Core.Physics;
+using DatReaderWriter;
+using DatReaderWriter.Options;
+using Xunit;
+
+namespace AcDream.Core.Tests.Conformance;
+
+///
+/// P0 Task 4 — golden conformance for the membership pick
+/// (retail CObjCell::find_cell_list
+/// @ 0x52b4e0 pc:308742). The unambiguous cases are retail-faithful by
+/// construction: the candidate cells are loaded from the same dats retail
+/// loads, and a sphere clearly inside one room can only resolve to that
+/// room. The subtle doorway-threshold pick (the 0031↔0170↔0171 ping-pong)
+/// is the trace-backed golden in FindCellListConformanceTests (Task 6).
+///
+public class FindCellListConformanceTests
+{
+ private const float FootRadius = 0.4f; // player foot-sphere radius
+
+ /// Cache the whole threshold building (016F..0175) so portal expansion is faithful.
+ private static PhysicsDataCache LoadThresholdBuilding(DatCollection dats)
+ {
+ var cache = new PhysicsDataCache();
+ for (uint low = 0x016Fu; low <= 0x0175u; low++)
+ ConformanceDats.LoadEnvCell(dats, cache, ConformanceDats.HoltburgLandblock | low);
+ return cache;
+ }
+
+ [Fact]
+ public void FindCellList_DeepInsideRoom0171_Returns0171()
+ {
+ var datDir = ConformanceDats.ResolveDatDir();
+ if (datDir is null) return;
+ using var dats = new DatCollection(datDir, DatAccessType.Read);
+ var cache = LoadThresholdBuilding(dats);
+
+ var world = Vector3.Transform(
+ CottageDoorwayCharacterizationTests.Interior0171Local,
+ cache.GetCellStruct(CottageDoorwayCharacterizationTests.Room0171)!.WorldTransform);
+
+ uint picked = CellTransit.FindCellList(cache, world, FootRadius,
+ CottageDoorwayCharacterizationTests.Room0171);
+ Assert.Equal(CottageDoorwayCharacterizationTests.Room0171, picked);
+ }
+
+ [Fact]
+ public void FindCellList_DeepInsideVestibule0170_Returns0170()
+ {
+ var datDir = ConformanceDats.ResolveDatDir();
+ if (datDir is null) return;
+ using var dats = new DatCollection(datDir, DatAccessType.Read);
+ var cache = LoadThresholdBuilding(dats);
+
+ var world = Vector3.Transform(
+ CottageDoorwayCharacterizationTests.Interior0170Local,
+ cache.GetCellStruct(CottageDoorwayCharacterizationTests.Vestibule0170)!.WorldTransform);
+
+ uint picked = CellTransit.FindCellList(cache, world, FootRadius,
+ CottageDoorwayCharacterizationTests.Vestibule0170);
+ Assert.Equal(CottageDoorwayCharacterizationTests.Vestibule0170, picked);
+ }
+
+ ///
+ /// Seeding from the WRONG current cell (room 0171) while standing deep inside
+ /// the vestibule (0170) must still resolve to 0170 — find_cell_list re-picks by
+ /// containment, it does not trust the stale seed. This is the membership-stability
+ /// property P1 must preserve.
+ ///
+ [Fact]
+ public void FindCellList_InVestibule_SeededFromRoom_Returns0170()
+ {
+ var datDir = ConformanceDats.ResolveDatDir();
+ if (datDir is null) return;
+ using var dats = new DatCollection(datDir, DatAccessType.Read);
+ var cache = LoadThresholdBuilding(dats);
+
+ var world = Vector3.Transform(
+ CottageDoorwayCharacterizationTests.Interior0170Local,
+ cache.GetCellStruct(CottageDoorwayCharacterizationTests.Vestibule0170)!.WorldTransform);
+
+ uint picked = CellTransit.FindCellList(cache, world, FootRadius,
+ CottageDoorwayCharacterizationTests.Room0171); // stale/wrong seed
+ Assert.Equal(CottageDoorwayCharacterizationTests.Vestibule0170, picked);
+ }
+}