test(render) Campaign FW1: pose-aware oracle trace parser

WalkOracleTrace decodes the P lines of the pose-stamped capture round
(raw IEEE-754 dwords from Render::viewer_pos @0x0081ef00: camera cell,
world origin, the four Frame quaternion components in storage order)
into WalkOraclePose on each frame. Poseless FW0 fixtures parse as
before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 10:19:45 +02:00
parent 17c57bbd1d
commit f289366700

View file

@ -22,6 +22,7 @@ public static class WalkOracleTrace
var frames = new List<WalkOracleFrame>();
List<WalkOracleEvent>? current = null;
int currentNumber = 0;
WalkOraclePose? currentPose = null;
foreach (string line in lines)
{
@ -29,15 +30,39 @@ public static class WalkOracleTrace
if (frameMatch.Success)
{
if (current is not null)
frames.Add(new WalkOracleFrame(currentNumber, current));
frames.Add(new WalkOracleFrame(currentNumber, current, currentPose));
currentNumber = int.Parse(
frameMatch.Groups[1].Value, CultureInfo.InvariantCulture);
current = new List<WalkOracleEvent>();
currentPose = null;
continue;
}
if (current is null)
continue;
Match poseMatch = PosePattern.Match(line);
if (poseMatch.Success)
{
// P <objcell_id> <origin xyz> <quat q0..q3> — raw IEEE-754 dwords
// dumped from Render::viewer_pos @0x0081ef00 at the frame marker.
uint[] raw = new uint[8];
for (int i = 0; i < 8; i++)
raw[i] = uint.Parse(
poseMatch.Groups[i + 1].Value,
NumberStyles.HexNumber, CultureInfo.InvariantCulture);
currentPose = new WalkOraclePose(
raw[0],
new System.Numerics.Vector3(
BitConverter.Int32BitsToSingle((int)raw[1]),
BitConverter.Int32BitsToSingle((int)raw[2]),
BitConverter.Int32BitsToSingle((int)raw[3])),
BitConverter.Int32BitsToSingle((int)raw[4]),
BitConverter.Int32BitsToSingle((int)raw[5]),
BitConverter.Int32BitsToSingle((int)raw[6]),
BitConverter.Int32BitsToSingle((int)raw[7]));
continue;
}
if (line == "LS")
{
current.Add(WalkOracleEvent.Landscape());
@ -102,13 +127,32 @@ public static class WalkOracleTrace
=> uint.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
private static readonly Regex FramePattern = new(@"^F (\d+)\s*$", RegexOptions.Compiled);
private static readonly Regex PosePattern = new(
@"^P ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8}) ([0-9a-f]{8})\s*$",
RegexOptions.Compiled);
private static readonly Regex BuildingPattern = new(@"^BLD ([0-9a-f]{8})\s*$", RegexOptions.Compiled);
private static readonly Regex DrawInsidePattern = new(@"^DI ([0-9a-f]{8})\s*$", RegexOptions.Compiled);
private static readonly Regex DrawCellsPattern = new(
@"^DC pv=[0-9a-f]{8} ov=(\d+) n=(\d+):((?: [0-9a-f]{8})*)\s*$", RegexOptions.Compiled);
}
public sealed record WalkOracleFrame(int Number, IReadOnlyList<WalkOracleEvent> Events)
/// <summary>The camera pose dumped at the frame marker (raw dwords from
/// <c>Render::viewer_pos</c> @0x0081ef00): the camera's cell id, world
/// origin, and the Frame quaternion's four raw components (q0..q3 in
/// storage order — axis convention resolved by the replay runner).
/// Present only in the pose-stamped capture round.</summary>
public sealed record WalkOraclePose(
uint CellId,
System.Numerics.Vector3 Origin,
float Q0,
float Q1,
float Q2,
float Q3);
public sealed record WalkOracleFrame(
int Number,
IReadOnlyList<WalkOracleEvent> Events,
WalkOraclePose? Pose = null)
{
/// <summary>The frame's root: the first DrawInside cell, or null when outdoor-rooted.</summary>
public uint? InteriorRootCell