acdream/docs/superpowers/plans/2026-06-03-p0-conformance-apparatus.md
Erik a90f34368f test(p0): dat-backed conformance loader + characterized cottage-doorway topology
P0 (verbatim-spatial-pipeline-port) Tasks 1+2. ConformanceDats loads the
cottage-doorway cells from the real dats with their real ContainmentBsp;
CottageDoorwayCharacterizationTests maps the Holtburg 0140..017F indoor
neighborhood and pins the master-plan threshold building (origin
161.93,7.50,94.00): 0xA9B40170 vestibule (exit portal 0xFFFF + portal to
0171), 0xA9B40171 room. Grid math confirms the outdoor side is landcell
0xA9B40031 -> the 0031<->0170<->0171 ping-pong is verified real. Verified
interior points recorded for the point_in_cell/find_cell_list goldens.

Plan: docs/superpowers/plans/2026-06-03-p0-conformance-apparatus.md
Notes: docs/research/2026-06-03-p0-conformance-apparatus-notes.md

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

32 KiB
Raw Blame History

P0 — Conformance Apparatus Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans (inline) to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Build the headless, dat-backed conformance apparatus that lets every later phase (P1 membership, P2 door collision, P3 camera, P4 PView render) prove a port is verbatim retail — not "vibes" — by asserting real-retail outcomes (point_in_cell, find_cell_list, PVS visible-set) against fixtures loaded from the real client dats and against captured retail cdb traces.

Architecture: Three layers. (1) A dat-backed fixture loader that hydrates the Holtburg cottage-doorway cell neighborhood from the real dats with their real containment BSPs (no synthetic BSP, no JSON round-trip). (2) Golden conformance tests that pin retail-faithful outcomes — point_in_cell is geometric ground truth (the BSP is retail data, loaded from the same dats retail loads); find_cell_list pins the membership pick, with the subtle doorway-threshold case backed by a captured retail cdb trace. (3) cdb value-capture tooling — a new script that dumps find_cell_list's cell-id argument/return at the threshold (value, not just hit-count), plus a parser that turns its log into a golden fixture. The live capture run is the single user-gated step; everything else is autonomous + headless.

Tech Stack: C# / .NET 10, xUnit, DatReaderWriter (DatCollection.Get<T>), the existing PhysicsDataCache / CellTransit / BSPQuery engine, cdb (acclient.pdb) per the CLAUDE.md retail-debugger toolchain.

Scope note — what P0 is NOT. P0 does not change any production membership/collision/render code. It only adds tests/ + tools/cdb/ + docs/. The PVS visible-set golden is scaffolded (structure + retail anchor + a skipped placeholder) but not filled — a retail cell_draw_list trace is a P3/P4-coupled capture and the PVS code itself is replaced in P4. P0's load-bearing deliverable is the membership goldens (point_in_cell + find_cell_list) that P1 consumes immediately, plus ≥1 assertion backed by a real retail trace (the P1 gate).


File Structure

File Responsibility
tests/AcDream.Core.Tests/Conformance/ConformanceDats.cs (create) Shared dat-dir resolution + one-call headless load of an EnvCell (real ContainmentBsp) and a cached CellPhysics by cell id. The single seam tests use to reach the real dats.
tests/AcDream.Core.Tests/Conformance/CottageDoorwayCharacterizationTests.cs (create) Characterize-and-pin the real cottage-doorway cell topology (ids, worldOrigin, portals incl. the 0xFFFF exit portal, seenOutside, has-ContainmentBsp). Discovers the exact IDs the rest of P0 uses.
tests/AcDream.Core.Tests/Conformance/PointInCellConformanceTests.cs (create) Golden point_in_cell: assert containment for geometrically-known points against the real BSP. Retail-faithful by construction.
tests/AcDream.Core.Tests/Conformance/FindCellListConformanceTests.cs (create) Golden find_cell_list: unambiguous picks (clearly-inside / clearly-outside) from dats + the doorway-threshold pick pinned to a captured retail trace.
tests/AcDream.Core.Tests/Conformance/RetailTrace.cs (create) Parser: read a find-cell-list retail cdb log → strongly-typed RetailCellPick[] golden records. TDD'd against a checked-in sample log line.
tools/cdb/find-cell-list-capture.cdb (create) New cdb script: breakpoint CObjCell::find_cell_list, dump the input position + the returned containing cell id (value capture). Ready for the user to run against live retail at the cottage doorway.
tools/cdb/README-find-cell-list-capture.md (create) Operator runbook for the capture: prerequisites, exact launch command, what to walk, where the log lands, how to fold it into the golden.
docs/research/2026-06-03-p0-conformance-apparatus-notes.md (create) Living notes: the characterized cottage-doorway topology, the golden values + their provenance (geometric vs retail-trace), and the P1-entry checklist.

Task 1 — Shared dat-backed fixture loader

Files:

  • Create: tests/AcDream.Core.Tests/Conformance/ConformanceDats.cs

The loader mirrors the proven dat-read pattern in tests/AcDream.Core.Tests/Physics/DoorBugTrajectoryReplayTests.cs:184-219 (DatCollection.Get<EnvCell>Get<Environment>CellStruct → physics-verbatim worldTransform) and the EnvCell.FromDat derivation (src/AcDream.Core/World/Cells/EnvCell.cs:42-76). It returns BOTH a EnvCell (for PointInCell) and a cached CellPhysics (for CellTransit), so membership tests have one seam.

  • Step 1: Write the loader
using System;
using System.IO;
using System.Numerics;
using AcDream.Core.Physics;
using AcDream.Core.World.Cells;
using DatReaderWriter;
using DatReaderWriter.Options;
using DatEnvCell = DatReaderWriter.DBObjs.EnvCell;
using DatEnvironment = DatReaderWriter.DBObjs.Environment;
using Env = System.Environment;

namespace AcDream.Core.Tests.Conformance;

/// <summary>
/// P0 conformance apparatus — headless load of the real Holtburg dats.
/// Tests that need real cell geometry (the retail containment BSP) resolve
/// the dat dir here and load cells via <see cref="LoadEnvCell"/>. Returns
/// null dat dir when the dats are absent (CI) so callers can skip cleanly,
/// matching DoorBugTrajectoryReplayTests.ResolveDatDir.
/// </summary>
public static class ConformanceDats
{
    private const uint EnvironmentFilePrefix = 0x0D000000u; // dat namespace for Environment files

    /// <summary>The Holtburg landblock these fixtures live in.</summary>
    public const uint HoltburgLandblock = 0xA9B40000u;

    /// <summary>Resolve the client dat directory, or null if unavailable (skip the test).</summary>
    public static string? ResolveDatDir()
    {
        var fromEnv = Env.GetEnvironmentVariable("ACDREAM_DAT_DIR");
        if (!string.IsNullOrWhiteSpace(fromEnv) && Directory.Exists(fromEnv))
            return fromEnv;
        var def = Path.Combine(
            Env.GetFolderPath(Env.SpecialFolder.UserProfile),
            "Documents", "Asheron's Call");
        return Directory.Exists(def) ? def : null;
    }

    /// <summary>The physics-verbatim cell→world transform (no +2cm render lift).</summary>
    public static Matrix4x4 WorldTransform(DatEnvCell datCell) =>
        Matrix4x4.CreateFromQuaternion(datCell.Position.Orientation) *
        Matrix4x4.CreateTranslation(datCell.Position.Origin);

    /// <summary>
    /// Load one EnvCell from the dats with its REAL containment BSP, and register
    /// it into <paramref name="cache"/> as a CellPhysics. Returns the high-level
    /// EnvCell (PointInCell) so a single load serves both membership predicates.
    /// </summary>
    public static EnvCell LoadEnvCell(DatCollection dats, PhysicsDataCache cache, uint cellId)
    {
        var datCell = dats.Get<DatEnvCell>(cellId)
            ?? throw new InvalidOperationException($"EnvCell 0x{cellId:X8} not found in dats");
        var environment = dats.Get<DatEnvironment>(EnvironmentFilePrefix | datCell.EnvironmentId)
            ?? throw new InvalidOperationException($"Environment 0x{datCell.EnvironmentId:X8} not found");
        if (!environment.Cells.TryGetValue(datCell.CellStructure, out var cellStruct) || cellStruct is null)
            throw new InvalidOperationException($"CellStruct {datCell.CellStructure} missing from environment");

        var world = WorldTransform(datCell);
        cache.CacheCellStruct(cellId, datCell, cellStruct, world);   // physics CellPhysics (real CellBSP)
        return EnvCell.FromDat(cellId, datCell, cellStruct, world);  // render/containment EnvCell (real ContainmentBsp)
    }
}
  • Step 2: Verify it compiles

Run: dotnet build tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj -c Debug Expected: build succeeds (no test yet — Task 2 exercises it).

  • Step 3: Commit
git add tests/AcDream.Core.Tests/Conformance/ConformanceDats.cs
git commit -m "test(p0): dat-backed cottage-doorway fixture loader (ConformanceDats)"

Task 2 — Characterize and pin the cottage-doorway topology

Files:

  • Create: tests/AcDream.Core.Tests/Conformance/CottageDoorwayCharacterizationTests.cs
  • Create: docs/research/2026-06-03-p0-conformance-apparatus-notes.md

The master plan names the neighborhood loosely (0xA9B4003x + 0xA9B4017x), and the 0170/0171 ids are reused across buildings in landblock 0xA9B4. So this task discovers the real topology and pins it. The candidate indoor ids are 0xA9B40170 and 0xA9B40171 (the ping-pong pair named in the master plan §0). The test prints each cell's structure on first run; fill the asserts from the printout, then they become the pinned characterization.

  • Step 1: Write the characterization test (observe form)
using System;
using System.Linq;
using AcDream.Core.Physics;
using DatReaderWriter;
using DatReaderWriter.Options;
using Xunit;
using Xunit.Abstractions;

namespace AcDream.Core.Tests.Conformance;

public class CottageDoorwayCharacterizationTests
{
    private readonly ITestOutputHelper _out;
    public CottageDoorwayCharacterizationTests(ITestOutputHelper output) => _out = output;

    // Candidate indoor cells of the cottage-doorway ping-pong (master plan §0: 0031↔0170↔0171).
    public static readonly uint[] CandidateIndoor = { 0xA9B40170u, 0xA9B40171u };

    [Fact]
    public void Characterize_CottageDoorwayCells_PrintStructure()
    {
        var datDir = ConformanceDats.ResolveDatDir();
        if (datDir is null) { _out.WriteLine("SKIP: dats unavailable"); return; }

        using var dats = new DatCollection(datDir, DatAccessType.Read);
        var cache = new PhysicsDataCache();

        foreach (var id in CandidateIndoor)
        {
            var cell = ConformanceDats.LoadEnvCell(dats, cache, id);
            var phys = cache.GetCellStruct(id)!;
            var origin = System.Numerics.Vector3.Transform(
                System.Numerics.Vector3.Zero, phys.WorldTransform);
            bool hasExitPortal = phys.Portals!.Any(p => p.OtherCellId == 0xFFFFu);
            _out.WriteLine(
                $"0x{id:X8}: worldOrigin=({origin.X:F2},{origin.Y:F2},{origin.Z:F2}) " +
                $"seenOutside={cell.SeenOutside} hasContainmentBsp={cell.ContainmentBsp?.Root is not null} " +
                $"portals={phys.Portals!.Count} exitPortal={hasExitPortal} " +
                $"stab={cell.StabList.Count} " +
                $"portalDests=[{string.Join(",", phys.Portals!.Select(p => $"0x{p.OtherCellId:X4}"))}]");
        }
    }
}
  • Step 2: Run it and READ the printed structure

Run: dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj -c Debug --filter "FullyQualifiedName~Characterize_CottageDoorwayCells" -l "console;verbosity=detailed" Expected: PASS, with two 0xA9B401XX: worldOrigin=... portalDests=[...] lines in the output. Record those lines in docs/research/2026-06-03-p0-conformance-apparatus-notes.md under a "Characterized topology" heading. They are the source of truth for Tasks 2b/3.

If a candidate id throws "not found in dats", it is the wrong id — widen the candidate scan to 0xA9B40170..0xA9B40179 in the loop, re-run, and identify the two cells whose worldOrigin matches the cottage (low Y, near the 014x cellar) and that form a portal pair (one's portalDests contains the other). Pin those two ids.

  • Step 3: Pin the discovered values (assert form)

Replace the print-only body with explicit asserts using the recorded values, e.g. (substitute the REAL numbers you recorded — these are illustrative):

[Fact]
public void CottageDoorway_Cell0170_IsVestibuleWithExitPortal()
{
    var datDir = ConformanceDats.ResolveDatDir();
    if (datDir is null) return;
    using var dats = new DatCollection(datDir, DatAccessType.Read);
    var cache = new PhysicsDataCache();

    var cell = ConformanceDats.LoadEnvCell(dats, cache, 0xA9B40170u);
    var phys = cache.GetCellStruct(0xA9B40170u)!;

    Assert.True(cell.ContainmentBsp?.Root is not null, "real cell BSP must load");
    Assert.NotEmpty(phys.Portals!);
    // Pin whatever the characterization printed — exit portal presence is the load-bearing fact:
    Assert.Contains(phys.Portals!, p => p.OtherCellId == 0xFFFFu || p.OtherCellId == 0x0171u);
}

Keep the Characterize_..._PrintStructure test (it documents the data and re-runs cheaply).

  • Step 4: Run to verify the pinned asserts pass

Run: dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj -c Debug --filter "FullyQualifiedName~CottageDoorwayCharacterizationTests" Expected: PASS (both tests).

  • Step 5: Commit
git add tests/AcDream.Core.Tests/Conformance/CottageDoorwayCharacterizationTests.cs docs/research/2026-06-03-p0-conformance-apparatus-notes.md
git commit -m "test(p0): characterize + pin cottage-doorway cell topology from dats"

Task 3 — point_in_cell golden conformance

Files:

  • Create: tests/AcDream.Core.Tests/Conformance/PointInCellConformanceTests.cs
  • Modify: docs/research/2026-06-03-p0-conformance-apparatus-notes.md

Retail's point_in_cell for an EnvCell is BSPTREE::point_in_cell_bsp against cell_bsp (master plan A6; reference doc §6.3). Our EnvCell.PointInCellBSPQuery.PointInsideCellBsp is the port. The BSP is loaded from the same dats retail loads, so a geometrically-correct containment answer is the retail answer. Goldens: a point at the cell's world origin-ish interior must be inside; a point far outside the landblock must be outside; the matching point must be inside exactly one of the doorway pair.

  • Step 1: Write the failing test
using System.Numerics;
using AcDream.Core.Physics;
using DatReaderWriter;
using DatReaderWriter.Options;
using Xunit;

namespace AcDream.Core.Tests.Conformance;

public class PointInCellConformanceTests
{
    [Fact]
    public void PointInCell_InteriorPoint_IsInsideItsOwnCell()
    {
        var datDir = ConformanceDats.ResolveDatDir();
        if (datDir is null) return;
        using var dats = new DatCollection(datDir, DatAccessType.Read);
        var cache = new PhysicsDataCache();

        var cell = ConformanceDats.LoadEnvCell(dats, cache, 0xA9B40171u);
        var phys = cache.GetCellStruct(0xA9B40171u)!;

        // A point just above the cell's covering-sphere centre, in world space, is interior.
        var localCentre = phys.CellBSP is not null
            ? cache.GetCellStruct(0xA9B40171u)!.WorldTransform // placeholder; replaced below
            : default;
        // Use the cell's bounding-sphere origin (cell-local) lifted to world:
        var worldCentre = Vector3.Transform(CharacterizedInteriorLocal_0171, phys.WorldTransform);

        Assert.True(cell.PointInCell(worldCentre),
            $"interior point {worldCentre} must be inside cell 0x0171's BSP");
    }

    [Fact]
    public void PointInCell_FarAwayPoint_IsOutside()
    {
        var datDir = ConformanceDats.ResolveDatDir();
        if (datDir is null) return;
        using var dats = new DatCollection(datDir, DatAccessType.Read);
        var cache = new PhysicsDataCache();

        var cell = ConformanceDats.LoadEnvCell(dats, cache, 0xA9B40171u);
        var faraway = new Vector3(10000f, 10000f, 10000f);
        Assert.False(cell.PointInCell(faraway), "a point 10 km away cannot be inside");
    }

    // Pin this from Task 2's characterization: a cell-LOCAL point clearly inside 0x0171's volume
    // (e.g. its bounding-sphere origin, or (0,0,1) if the cell is centred on its local origin).
    private static readonly Vector3 CharacterizedInteriorLocal_0171 = new(0f, 0f, 1.0f);
}
  • Step 2: Run to verify it fails (or reveals the right interior point)

Run: dotnet test ... --filter "FullyQualifiedName~PointInCellConformanceTests" -l "console;verbosity=detailed" Expected: PointInCell_FarAwayPoint_IsOutside PASSES immediately. ..._IsInsideItsOwnCell may FAIL if CharacterizedInteriorLocal_0171 isn't actually inside — that's the signal to use the real interior point from Task 2 (the cell's bounding-sphere origin, printed by adding it to the characterization output).

  • Step 3: Fix the interior point from characterization data

Augment the Task-2 print to also emit the cell's bounding-sphere origin (cell-local), then set CharacterizedInteriorLocal_0171 to a point you've confirmed is inside (the sphere origin, or a point nudged toward the floor). Remove the dead localCentre placeholder line.

  • Step 4: Run to verify both pass

Run: dotnet test ... --filter "FullyQualifiedName~PointInCellConformanceTests" Expected: PASS (both).

  • Step 5: Commit
git add tests/AcDream.Core.Tests/Conformance/PointInCellConformanceTests.cs docs/research/2026-06-03-p0-conformance-apparatus-notes.md
git commit -m "test(p0): point_in_cell golden conformance vs real dat BSP"

Task 4 — find_cell_list golden conformance (unambiguous cases)

Files:

  • Create: tests/AcDream.Core.Tests/Conformance/FindCellListConformanceTests.cs

CellTransit.FindCellList(cache, sphereCentre, radius, currentCellId) is the membership pick. Unambiguous goldens come straight from the real dats: a sphere clearly inside room 0x0171 returns 0x0171; a sphere clearly inside vestibule 0x0170 returns 0x0170. These pin the pick without needing a retail trace (the geometry is retail data). The threshold case (ping-pong) is Task 6, backed by a retail trace.

  • Step 1: Write the failing test
using System.Collections.Generic;
using System.Numerics;
using AcDream.Core.Physics;
using DatReaderWriter;
using DatReaderWriter.Options;
using Xunit;

namespace AcDream.Core.Tests.Conformance;

public class FindCellListConformanceTests
{
    private const float FootRadius = 0.4f; // retail player foot-sphere radius (PhysicsBody default)

    [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 = new PhysicsDataCache();

        ConformanceDats.LoadEnvCell(dats, cache, 0xA9B40170u);
        var room = ConformanceDats.LoadEnvCell(dats, cache, 0xA9B40171u);
        var phys = cache.GetCellStruct(0xA9B40171u)!;

        // A world point confirmed interior to 0x0171 in Task 3:
        var inside0171 = Vector3.Transform(DeepInsideLocal_0171, phys.WorldTransform);
        Assert.True(room.PointInCell(inside0171), "fixture sanity: point must be inside 0x0171");

        // find_cell_list seeded from the room itself must return the room.
        uint picked = CellTransit.FindCellList(cache, inside0171, FootRadius, 0xA9B40171u);
        Assert.Equal(0xA9B40171u, picked);
    }

    // Pin from Task 3 (a cell-local point well inside 0x0171, away from any portal plane):
    private static readonly Vector3 DeepInsideLocal_0171 = new(0f, 0f, 1.0f);
}
  • Step 2: Run to verify

Run: dotnet test ... --filter "FullyQualifiedName~FindCellListConformanceTests" -l "console;verbosity=detailed" Expected: PASS. If it returns a different cell, record the actual return in the notes doc and investigate whether the seed point is too near a portal plane (move it deeper). A genuine divergence here is itself a finding — document it; do NOT change production code in P0.

  • Step 3: Commit
git add tests/AcDream.Core.Tests/Conformance/FindCellListConformanceTests.cs
git commit -m "test(p0): find_cell_list golden — unambiguous interior picks from dats"

Task 5 — Retail-trace parser + cdb value-capture tooling

Files:

  • Create: tests/AcDream.Core.Tests/Conformance/RetailTrace.cs
  • Create: tools/cdb/find-cell-list-capture.cdb
  • Create: tools/cdb/README-find-cell-list-capture.md

The autonomous half of the retail-trace golden: the parser (TDD'd against a sample line) and the capture script (ready to run). The capture run is the user gate in Task 6.

The cdb script breakpoints CObjCell::find_cell_list (acclient!CObjCell::find_cell_list, 0x52b4e0). this (the seed cell, thiscallecx) carries objcell_id at a known offset; the position is an argument. We log the seed cell id and the position, and (via a return breakpoint) the picked cell id. The exact field offsets are confirmed at capture time with dt acclient!CObjCell @ecx (CLAUDE.md retail-debugger watchouts). The script emits lines of the form [fcl] seed=0xHHHHHHHH px=<f> py=<f> pz=<f> picked=0xHHHHHHHH.

  • Step 1: Write the failing parser test
using System.Numerics;
using Xunit;

namespace AcDream.Core.Tests.Conformance;

public class RetailTraceTests
{
    [Fact]
    public void Parse_FindCellListLine_YieldsSeedPosAndPicked()
    {
        const string line =
            "[fcl] seed=0xA9B40170 px=141.5000 py=7.2200 pz=92.7400 picked=0xA9B40171";
        var rec = RetailTrace.ParseFindCellList(line);
        Assert.NotNull(rec);
        Assert.Equal(0xA9B40170u, rec!.SeedCellId);
        Assert.Equal(new Vector3(141.5f, 7.22f, 92.74f), rec.Position);
        Assert.Equal(0xA9B40171u, rec.PickedCellId);
    }

    [Fact]
    public void Parse_NonMatchingLine_ReturnsNull()
    {
        Assert.Null(RetailTrace.ParseFindCellList("[BP4] find_collisions hit#10170 collide=0"));
    }
}
  • Step 2: Run to verify it fails

Run: dotnet test ... --filter "FullyQualifiedName~RetailTraceTests" Expected: FAIL — RetailTrace does not exist.

  • Step 3: Write the parser
using System;
using System.Globalization;
using System.Numerics;
using System.Text.RegularExpressions;

namespace AcDream.Core.Tests.Conformance;

/// <summary>A single retail find_cell_list pick captured via cdb (golden oracle).</summary>
public sealed record RetailCellPick(uint SeedCellId, Vector3 Position, uint PickedCellId);

/// <summary>Parser for the find-cell-list-capture.cdb log format.</summary>
public static class RetailTrace
{
    private static readonly Regex Fcl = new(
        @"^\[fcl\]\s+seed=0x(?<seed>[0-9A-Fa-f]{1,8})\s+" +
        @"px=(?<px>-?\d+(\.\d+)?)\s+py=(?<py>-?\d+(\.\d+)?)\s+pz=(?<pz>-?\d+(\.\d+)?)\s+" +
        @"picked=0x(?<picked>[0-9A-Fa-f]{1,8})\s*$",
        RegexOptions.Compiled);

    public static RetailCellPick? ParseFindCellList(string line)
    {
        var m = Fcl.Match(line);
        if (!m.Success) return null;
        var ci = CultureInfo.InvariantCulture;
        return new RetailCellPick(
            SeedCellId:  Convert.ToUInt32(m.Groups["seed"].Value, 16),
            Position:    new Vector3(
                float.Parse(m.Groups["px"].Value, ci),
                float.Parse(m.Groups["py"].Value, ci),
                float.Parse(m.Groups["pz"].Value, ci)),
            PickedCellId: Convert.ToUInt32(m.Groups["picked"].Value, 16));
    }
}
  • Step 4: Run to verify it passes

Run: dotnet test ... --filter "FullyQualifiedName~RetailTraceTests" Expected: PASS (both).

  • Step 5: Write the cdb capture script
$$ find-cell-list-capture.cdb — value-capture of CObjCell::find_cell_list at the cottage doorway.
$$ Logs the seed cell id + input position + picked cell id (NOT just a hit count).
$$ Prereqs: retail in-world at the Holtburg cottage doorway; PDB matches (check_exe_pdb.py = MATCH).
$$ Offsets for objcell_id + the position arg are CONFIRMED at runtime with `dt acclient!CObjCell @ecx`
$$ and `dv` — edit the @ecx+OFFSET below if the dt dump shows a different layout.

.logopen C:\Users\erikn\source\repos\acdream\find-cell-list-capture.log
.sympath C:\Users\erikn\source\repos\acdream\refs
.symopt+ 0x40
.reload /f acclient.exe

r $t0 = 0
$$ Entry: dump seed cell id (this->objcell_id @ ecx+0x20 — VERIFY with dt) + the position arg.
bp acclient!CObjCell::find_cell_list "r $t0 = @$t0 + 1; .printf /D \"[fcl-entry#%d] seed=0x%08x\\n\", @$t0, poi(@ecx+0x20); .if (@$t0 >= 4000) { qd } .else { gc }"

.printf \"find_cell_list capture armed. Walk SLOWLY in/out of the cottage doorway now.\\n\"
g
  • Step 6: Write the operator runbook

Create tools/cdb/README-find-cell-list-capture.md with: the check_exe_pdb.py MATCH precondition; the exact PowerShell launch (cdb.exe -pn acclient.exe -cf find-cell-list-capture.cdb); the instruction to first run dt acclient!CObjCell @ecx once to confirm the objcell_id offset and dv to find the position arg, then edit the script; the walk to perform (stand in the doorway, step in and out across the threshold 510×); where the log lands; and how to hand the log to Task 6.

  • Step 7: Commit
git add tests/AcDream.Core.Tests/Conformance/RetailTrace.cs tools/cdb/find-cell-list-capture.cdb tools/cdb/README-find-cell-list-capture.md
git commit -m "test(p0): retail find_cell_list trace parser + value-capture cdb script"

Task 6 — The retail-trace golden (USER GATE) + P1-entry checklist

Files:

  • Modify: tests/AcDream.Core.Tests/Conformance/FindCellListConformanceTests.cs
  • Create: tests/AcDream.Core.Tests/Conformance/Fixtures/find-cell-list-threshold.log (from the capture)
  • Modify: docs/research/2026-06-03-p0-conformance-apparatus-notes.md

This task has the one irreducible user-gated step: a live retail cdb capture. Everything is prepped (Task 5). After the capture, encode ≥1 retail pick as a golden assertion — satisfying the kickoff's "do NOT start P1 before ≥1 golden retail-trace assertion exists."

  • Step 1: USER GATE — capture the retail trace

Mining the existing committed traces first (docs/research/2026-05-21-a6-captures/scen1_inn_doorway/retail.decoded.log and scen4_cottage_cellar/retail.decoded.log): grep for any line carrying a find_cell_list cell-id pick. If one exists in usable form, use it (no new capture needed). Otherwise, ask the user to run tools/cdb/find-cell-list-capture.cdb per its README against live retail at the cottage doorway, and place the resulting log at tests/AcDream.Core.Tests/Conformance/Fixtures/find-cell-list-threshold.log.

  • Step 2: Add the trace-backed golden

For each captured RetailCellPick, assert acdream's CellTransit.FindCellList returns the same picked cell for the same (SeedCellId, Position):

[Fact]
public void FindCellList_DoorwayThreshold_MatchesRetailTrace()
{
    var datDir = ConformanceDats.ResolveDatDir();
    if (datDir is null) return;
    var fixturePath = System.IO.Path.Combine(
        System.AppContext.BaseDirectory, "Conformance", "Fixtures", "find-cell-list-threshold.log");
    if (!System.IO.File.Exists(fixturePath)) return; // gate not yet satisfied — skip until captured

    using var dats = new DatCollection(datDir, DatAccessType.Read);
    var cache = new PhysicsDataCache();
    foreach (var id in new[] { 0xA9B40170u, 0xA9B40171u })
        ConformanceDats.LoadEnvCell(dats, cache, id);

    foreach (var raw in System.IO.File.ReadAllLines(fixturePath))
    {
        var pick = RetailTrace.ParseFindCellList(raw);
        if (pick is null) continue;
        uint ours = CellTransit.FindCellList(cache, pick.Position, 0.4f, pick.SeedCellId);
        Assert.Equal(pick.PickedCellId, ours);
    }
}

Per master-plan no-shortcuts rule §4: if this assertion fails, it means acdream diverges from retail at the threshold — that is the P1 work, captured as a RED conformance test. Leave it RED (documents-the-bug) and let P1 turn it GREEN. Do NOT weaken the assertion.

  • Step 3: Wire the fixture into the test project so it copies to output

Modify tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj to copy Conformance/Fixtures/** to the output dir (mirror how existing Fixtures/issue98/** is copied — find that <ItemGroup> and add the Conformance/Fixtures glob, or confirm a wildcard already covers it).

  • Step 4: Run the full conformance suite

Run: dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj -c Debug --filter "FullyQualifiedName~Conformance" Expected: all GREEN, except possibly the documents-the-bug threshold test if acdream diverges (that RED is a finding for P1, not a P0 failure).

  • Step 5: Write the P1-entry checklist into the notes doc

In docs/research/2026-06-03-p0-conformance-apparatus-notes.md, record: the characterized topology; every golden + its provenance (geometric vs retail-trace); whether the threshold golden is GREEN (acdream already matches) or RED (P1 must fix); and the explicit statement "P0 gate met: ≥1 retail-trace-backed assertion exists" so P1 can begin.

  • Step 6: Commit
git add tests/AcDream.Core.Tests/Conformance/ docs/research/2026-06-03-p0-conformance-apparatus-notes.md tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj
git commit -m "test(p0): retail-trace-backed find_cell_list threshold golden (P1 gate met)"

Task 7 — PVS-golden scaffold (structure only, fill deferred to P3/P4)

Files:

  • Create: tests/AcDream.Core.Tests/Conformance/PvsConformanceTests.cs

The master plan lists "PVS visible-set for a given (cell, eye) matches" under P0, but a retail cell_draw_list trace is a P3/P4-coupled capture and the PVS code is replaced in P4. So P0 scaffolds the test (intent + retail anchor + a skipped placeholder) so the structure exists when P4 captures the retail visible-set.

  • Step 1: Write the scaffold (skipped)
using Xunit;

namespace AcDream.Core.Tests.Conformance;

/// <summary>
/// PVS (portal-visibility) conformance — scaffold. Retail oracle:
/// PView::ConstructView @ 0x005a57b0 (pc:433750) produces cell_draw_list; the golden is a
/// captured retail cell_draw_list for a given (viewer_cell, eye). Filled in P3/P4 when the
/// camera viewer-cell + ConstructView ports land and a retail cell_draw_list trace is captured
/// (new cdb script breakpointing PView::DrawCells / cell_draw_list, sibling to
/// find-cell-list-capture.cdb). See docs/research/2026-06-02-retail-render-pipeline-full-reference.md §3.
/// </summary>
public class PvsConformanceTests
{
    [Fact(Skip = "P0 scaffold — filled in P4 with a captured retail cell_draw_list trace")]
    public void Pvs_CottageInterior_MatchesRetailCellDrawList() { }
}
  • Step 2: Verify it builds + reports as skipped

Run: dotnet test ... --filter "FullyQualifiedName~PvsConformanceTests" Expected: 1 skipped, 0 failed.

  • Step 3: Commit
git add tests/AcDream.Core.Tests/Conformance/PvsConformanceTests.cs
git commit -m "test(p0): PVS-golden conformance scaffold (filled in P4)"

Self-Review

Spec coverage (master-plan P0 + reference §G4):

  • "Headless fixtures of the cottage neighborhood loaded from real dats" → Task 1 (ConformanceDats.LoadEnvCell) + Task 2 (characterize).
  • "point_in_cell matches" → Task 3 (geometric goldens vs real BSP).
  • "find_cell_list returns the same cell as a captured retail trace at the threshold" → Task 4 (unambiguous) + Task 6 (threshold, retail-trace-backed).
  • "the PVS visible-set ... matches" → Task 7 (scaffold; fill in P4, documented).
  • "Use the existing ACDREAM_CAPTURE_RESOLVE + cdb retail traces" → Task 5 (parser + capture script) + Task 6 (mine existing traces first, else live capture).
  • "≥1 golden retail-trace assertion before P1" → Task 6 (the P1 gate).

Placeholder scan: Interior-point constants (*_Local_0171) and pinned cell asserts are explicitly characterization-derived (Task 2/3 print-then-pin) — concrete process, not hand-waving. The cdb field offset (ecx+0x20) is flagged as runtime-verified (dt acclient!CObjCell @ecx).

Type consistency: ConformanceDats.LoadEnvCell returns EnvCell; cache.GetCellStruct(id) returns CellPhysics (matches DoorBugTrajectoryReplayTests); CellTransit.FindCellList(cache, Vector3, float, uint) → uint (matches the membership map); RetailTrace.ParseFindCellList(string) → RetailCellPick?. Consistent across Tasks 1/3/4/6.

Risks / open verifications (resolve during execution, not blocking):

  • PhysicsDataCache.CacheCellStruct + GetCellStruct + CellPhysics.WorldTransform/Portals/CellBSP member names are from the door-test read at DoorBugTrajectoryReplayTests.cs:213-225; verify exact casing on first compile.
  • The two cottage-doorway ids may not be 0170/0171 — Task 2 discovers the truth and the later tasks consume whatever Task 2 pins.
  • Whether the existing committed retail traces already contain a usable find_cell_list pick is checked first in Task 6 Step 1 before asking the user for a live capture.