feat(render) Campaign FW1: SIX OF TEN FIXTURES FULLY CONFORMANT
Three final pins complete the still-fixture set: (1) the CELL portal side decode is the INVERSE of the 0x2 bit (uniform with the building convention; the doorway-still flood proved it - ov=2 n=3 exact, and foundry-deep stays green); (2) interior frames key the landscape order off the OUTSIDE-projected landcell (get_outside_cell_id - derived from the camera origin); (3) the outdoor pview has draw_landscape=FALSE so look-in floods discard exit portals - the ov=0 pattern of every traced look-in. CONFORMANT: foundry-deep (every frame), doorway-still, street-outdoor, terrace-center, terrace-edge (the #456 acceptance pose), cathedral-arrival - full frames identical to retail. The moving four diverge only at punch-edge frames (walkabout F9, foundry-entry F67) - pose-timing sensitivity parked in the driver Skip note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
28475d0544
commit
66f9e0d459
6 changed files with 158 additions and 14 deletions
|
|
@ -44,7 +44,11 @@ public interface IRetailFrameWalkContext : IWalkBuildingFrameContext
|
|||
public sealed class RetailFrameWalk
|
||||
{
|
||||
private readonly WalkPView _interiorPView = new();
|
||||
private readonly WalkPView _outdoorPView = new();
|
||||
// The outdoor pview never draws the landscape through its look-in
|
||||
// floods (the landscape is already drawn when buildings punch), so its
|
||||
// draw_landscape is FALSE and look-in DCs always read ov=0 — exactly
|
||||
// the pv=…/ov=0 pattern of every look-in in the oracle traces.
|
||||
private readonly WalkPView _outdoorPView = new() { DrawLandscape = false };
|
||||
private readonly WalkPortalView _defaultView = new();
|
||||
|
||||
public WalkPView InteriorPView => _interiorPView;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,25 @@ public static class WalkLandscapeDatBuilder
|
|||
private static int SideCellCountForRing(int ring)
|
||||
=> ring <= 1 ? 8 : ring == 2 ? 4 : ring <= 4 ? 2 : 1;
|
||||
|
||||
public static BuiltWorld Build(DatCollection dats, uint cameraCellId)
|
||||
/// <summary>Per-frame viewer placement for moving replays (the camera
|
||||
/// stays within the anchor block in every moving fixture).</summary>
|
||||
public static void SetViewer(WalkLandscape landscape, uint cameraCellId, Vector3 cameraOrigin)
|
||||
{
|
||||
uint low = cameraCellId & 0xFFFFu;
|
||||
if (low >= 1 && low <= 0x40)
|
||||
{
|
||||
int cellIndex = (int)low - 1;
|
||||
landscape.ViewerCellX = cellIndex / 8;
|
||||
landscape.ViewerCellY = cellIndex % 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
landscape.ViewerCellX = Math.Clamp((int)MathF.Floor(cameraOrigin.X / 24f), 0, 7);
|
||||
landscape.ViewerCellY = Math.Clamp((int)MathF.Floor(cameraOrigin.Y / 24f), 0, 7);
|
||||
}
|
||||
}
|
||||
|
||||
public static BuiltWorld Build(DatCollection dats, uint cameraCellId, Vector3 cameraOrigin = default)
|
||||
{
|
||||
Region region = (Region)dats.Get<Region>(0x13000000u)!;
|
||||
float[] heightTable = region.LandDefs.LandHeightTable;
|
||||
|
|
@ -57,6 +75,15 @@ public static class WalkLandscapeDatBuilder
|
|||
landscape.ViewerCellX = cellIndex / 8;
|
||||
landscape.ViewerCellY = cellIndex % 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interior camera: retail keys the landscape order off the
|
||||
// OUTSIDE-projected landcell (Position::get_outside_cell_id via
|
||||
// SmartBox::RenderNormalMode seen_outside arm) - derive it from
|
||||
// the camera origin (block-local, 24 m cells).
|
||||
landscape.ViewerCellX = Math.Clamp((int)MathF.Floor(cameraOrigin.X / 24f), 0, 7);
|
||||
landscape.ViewerCellY = Math.Clamp((int)MathF.Floor(cameraOrigin.Y / 24f), 0, 7);
|
||||
}
|
||||
|
||||
var cells = new Dictionary<uint, WalkCell>();
|
||||
var buildings = new Dictionary<WalkBuilding, WalkWorldDatAdapter.BuildingEntry>();
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public sealed class WalkLookInGateSweepTests
|
|||
try
|
||||
{
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId);
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public sealed class WalkPortalGateDumpTests
|
|||
Assert.NotNull(frame.Pose);
|
||||
using var dats = new DatCollection(datDir!, DatAccessType.Read);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId);
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
|
|
|
|||
|
|
@ -32,14 +32,7 @@ public sealed class WalkTraceConformanceTests
|
|||
return new DatCollection(datDir!, DatAccessType.Read);
|
||||
}
|
||||
|
||||
[Fact(Skip = "FW1 outdoor adjudication (2026-08-30 state): with the pinned "
|
||||
+ "camera basis the BLD roster+order match retail EXACTLY except one "
|
||||
+ "ring-1 boundary pair (aab50002 extra / a9b3003c missing). The "
|
||||
+ "look-in punches fire at the wrong buildings under BOTH GfxObj "
|
||||
+ "plane-winding conventions (retail: only 001a and 0022, cells "
|
||||
+ "a9b4016x) — adjudicate the PortalRef.PortalIndex join and the "
|
||||
+ "BuildingPortal side-flag decode with a per-building dump next. "
|
||||
+ "Re-enable as the outdoor gate when adjudicated.")]
|
||||
[Fact]
|
||||
public void Street_outdoor_first_frame_diff()
|
||||
{
|
||||
// The first landscape-involving conformance case: diff-first (the
|
||||
|
|
@ -51,7 +44,7 @@ public sealed class WalkTraceConformanceTests
|
|||
WalkOracleFrame frame = frames[1]; // frame 2: pose settled (marker timing)
|
||||
Assert.NotNull(frame.Pose);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId);
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
|
|
@ -68,6 +61,123 @@ public sealed class WalkTraceConformanceTests
|
|||
$"walk diverged from retail\nEXPECTED: {expected}\nACTUAL: {actual}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Doorway_still_first_frame_diff()
|
||||
{
|
||||
// Interior flood adjudication: DI + DC(ov=2, n=3) + the landscape
|
||||
// through two exit views — tests the flood depth in isolation from
|
||||
// the building look-in machinery.
|
||||
IReadOnlyList<WalkOracleFrame> frames =
|
||||
WalkOracleTrace.Load("posed/holtburg-doorway-still");
|
||||
Assert.NotEmpty(frames);
|
||||
using DatCollection dats = OpenDats();
|
||||
WalkOracleFrame frame = frames[1];
|
||||
Assert.NotNull(frame.Pose);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
};
|
||||
WalkCell camera = Assert.Contains(frame.Pose.CellId, world.Cells);
|
||||
var walk = new RetailFrameWalk();
|
||||
var recorder = new Recorder();
|
||||
|
||||
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
|
||||
|
||||
string expected = WalkTraceReplayContext.Signature(frame);
|
||||
string actual = WalkTraceReplayContext.Signature(recorder.Events);
|
||||
Assert.True(
|
||||
expected == actual,
|
||||
$"walk diverged from retail\nEXPECTED: {expected}\nACTUAL: {actual}");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("posed/terrace-center")]
|
||||
[InlineData("posed/terrace-edge")]
|
||||
[InlineData("posed/cathedral-arrival")]
|
||||
public void Still_fixture_first_frame_reproduces_exactly(string fixture)
|
||||
{
|
||||
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load(fixture);
|
||||
Assert.NotEmpty(frames);
|
||||
using DatCollection dats = OpenDats();
|
||||
WalkOracleFrame frame = frames[1];
|
||||
Assert.NotNull(frame.Pose);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
};
|
||||
WalkCell? camera = (frame.Pose.CellId & 0xFFFFu) >= 0x100
|
||||
? Assert.Contains(frame.Pose.CellId, world.Cells)
|
||||
: null;
|
||||
var walk = new RetailFrameWalk();
|
||||
var recorder = new Recorder();
|
||||
|
||||
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
|
||||
|
||||
string expected = WalkTraceReplayContext.Signature(frame);
|
||||
string actual = WalkTraceReplayContext.Signature(recorder.Events);
|
||||
Assert.True(
|
||||
expected == actual,
|
||||
$"walk diverged from retail ({fixture})\nEXPECTED: {expected}\nACTUAL: {actual}");
|
||||
}
|
||||
|
||||
[Theory(Skip = "FW1 moving-fixture tail (2026-08-30): the six STILL fixtures are "
|
||||
+ "fully conformant; the moving four diverge only at punch-edge frames "
|
||||
+ "(walkout/transitions F2: retail punches 001e where the replay does "
|
||||
+ "not; walkabout diverges at F9, foundry-entry at F67) - pose-timing/"
|
||||
+ "threshold sensitivity to adjudicate next (the exact pose-application "
|
||||
+ "point within the frame).")]
|
||||
[InlineData("posed/holtburg-walkout")]
|
||||
[InlineData("posed/holtburg-transitions")]
|
||||
[InlineData("posed/holtburg-walkabout")]
|
||||
[InlineData("posed/foundry-entry")]
|
||||
public void Moving_fixture_reproduces_every_pairable_frame(string fixture)
|
||||
{
|
||||
// Marker timing: the pose stamped at frame N+1 is the camera state
|
||||
// frame N drew with (fixture README) — pair events(N) with
|
||||
// pose(N+1). One walk + one landscape carry state across frames,
|
||||
// reproducing retail's cross-frame caches (the stale cell-order
|
||||
// quirk included).
|
||||
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load(fixture);
|
||||
Assert.True(frames.Count >= 3);
|
||||
using DatCollection dats = OpenDats();
|
||||
WalkOraclePose anchor = frames[1].Pose!;
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, anchor.CellId, anchor.Origin);
|
||||
var walk = new RetailFrameWalk();
|
||||
|
||||
for (int n = 1; n < frames.Count - 1; n++)
|
||||
{
|
||||
WalkOracleFrame frame = frames[n];
|
||||
WalkOraclePose pose = frames[n + 1].Pose!;
|
||||
Assert.NotNull(pose);
|
||||
WalkLandscapeDatBuilder.SetViewer(world.Landscape, pose.CellId, pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
};
|
||||
WalkCell? camera = null;
|
||||
if ((pose.CellId & 0xFFFFu) >= 0x100)
|
||||
{
|
||||
Assert.True(
|
||||
world.Cells.TryGetValue(pose.CellId, out camera),
|
||||
$"frame {frame.Number}: interior camera cell {pose.CellId:x8} not loaded");
|
||||
}
|
||||
var recorder = new Recorder();
|
||||
|
||||
walk.WalkFrame(pose.CellId, camera, world.Landscape, ctx, recorder);
|
||||
|
||||
string expected = WalkTraceReplayContext.Signature(frame);
|
||||
string actual = WalkTraceReplayContext.Signature(recorder.Events);
|
||||
Assert.True(
|
||||
expected == actual,
|
||||
$"frame {frame.Number} diverged ({fixture})\nEXPECTED: {expected}\nACTUAL: {actual}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Foundry_deep_reproduces_every_complete_frame_exactly()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -63,7 +63,10 @@ public static class WalkWorldDatAdapter
|
|||
? 0xFFFFFFFFu
|
||||
: lbMask | portal.OtherCellId,
|
||||
PolygonIndex = i,
|
||||
PortalSide = ((ushort)portal.Flags & 0x2) != 0 ? 1 : 0,
|
||||
// Data-pinned 2026-08-30 (the doorway-still flood): cell
|
||||
// portal_side is the INVERSE of the 0x2 bit — the same
|
||||
// convention the building-portal sweep pinned.
|
||||
PortalSide = ((ushort)portal.Flags & 0x2) != 0 ? 0 : 1,
|
||||
ExactMatch = ((ushort)portal.Flags & 0x1) != 0,
|
||||
OtherPortalId = unchecked((short)portal.OtherPortalId),
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue