test(p0): retail find_cell_list trace parser + cdb value-capture tooling
P0 Task 5. RetailTrace parses the [fcl] golden format (seed/pos/picked, RetailCellPick); 4 TDD tests green. find-cell-list-capture.cdb targets CPhysicsObj::change_cell (commit-on-diff) to capture retail's accepted membership sequence at the doorway; README is the operator runbook (dt offset verification + decode_retail_hex float decode). The live run is P0's one user-gated step (Task 6 mines existing traces first). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ec78beb843
commit
b35e491f12
4 changed files with 225 additions and 0 deletions
52
tests/AcDream.Core.Tests/Conformance/RetailTrace.cs
Normal file
52
tests/AcDream.Core.Tests/Conformance/RetailTrace.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
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).
|
||||
/// <paramref name="SeedCellId"/> is the seed/current cell at find_cell_list
|
||||
/// entry; <paramref name="PickedCellId"/> is the chosen containing cell
|
||||
/// (retail <c>*arg5</c> in CObjCell::find_cell_list @ 0x52b4e0 pc:308742).
|
||||
/// </summary>
|
||||
public sealed record RetailCellPick(uint SeedCellId, Vector3 Position, uint PickedCellId);
|
||||
|
||||
/// <summary>Parser for the <c>find-cell-list-capture.cdb</c> 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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(line)) return null;
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>Parse a log, skipping every non-matching line (noise/banner/other BPs).</summary>
|
||||
public static IReadOnlyList<RetailCellPick> ParseAll(IEnumerable<string> lines)
|
||||
{
|
||||
var list = new List<RetailCellPick>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var rec = ParseFindCellList(line);
|
||||
if (rec is not null) list.Add(rec);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
56
tests/AcDream.Core.Tests/Conformance/RetailTraceTests.cs
Normal file
56
tests/AcDream.Core.Tests/Conformance/RetailTraceTests.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using System.Numerics;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Conformance;
|
||||
|
||||
/// <summary>P0 Task 5 — TDD for the retail find_cell_list trace parser.</summary>
|
||||
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_NegativeCoordinatesAndLowercaseHex_Ok()
|
||||
{
|
||||
const string line =
|
||||
"[fcl] seed=0xa9b40031 px=-12.5 py=0 pz=-3.25 picked=0xa9b40170";
|
||||
var rec = RetailTrace.ParseFindCellList(line);
|
||||
Assert.NotNull(rec);
|
||||
Assert.Equal(0xA9B40031u, rec!.SeedCellId);
|
||||
Assert.Equal(new Vector3(-12.5f, 0f, -3.25f), rec.Position);
|
||||
Assert.Equal(0xA9B40170u, rec.PickedCellId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_NonMatchingLine_ReturnsNull()
|
||||
{
|
||||
Assert.Null(RetailTrace.ParseFindCellList("[BP4] find_collisions hit#10170 collide=0"));
|
||||
Assert.Null(RetailTrace.ParseFindCellList(""));
|
||||
Assert.Null(RetailTrace.ParseFindCellList("[fcl] seed=0xA9B40170 px=1 py=2")); // truncated
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseFile_SkipsNoiseAndYieldsOnlyPicks()
|
||||
{
|
||||
var lines = new[]
|
||||
{
|
||||
"armed; walk now",
|
||||
"[fcl] seed=0xA9B40031 px=160.0 py=10.0 pz=94.0 picked=0xA9B40170",
|
||||
"[BP] noise",
|
||||
"[fcl] seed=0xA9B40170 px=158.0 py=12.0 pz=95.0 picked=0xA9B40171",
|
||||
};
|
||||
var picks = RetailTrace.ParseAll(lines);
|
||||
Assert.Equal(2, picks.Count);
|
||||
Assert.Equal(0xA9B40170u, picks[0].PickedCellId);
|
||||
Assert.Equal(0xA9B40171u, picks[1].PickedCellId);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue