fix(render) Campaign FW1: keep the conformance DAT adapter behind the seam guard
RuntimeDatAccessArchitectureTests correctly flagged WalkWorldDatAdapter taking raw DatCollection in production. The adapter is the conformance harness world builder, so it moves into the test assembly; the FW3 production adapter will consume the content-owner seam. Hermetic suite back to 6,687/0; adapter + conformance 10/10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0f01fb4430
commit
6caedf25f9
1 changed files with 10 additions and 4 deletions
|
|
@ -1,220 +0,0 @@
|
|||
using System.Numerics;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Types;
|
||||
using Environment = DatReaderWriter.DBObjs.Environment;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW1 — builds the walk's world model from real DAT data. The
|
||||
/// library parses everything the walk needs (survey 2026-08-30: PORT nodes
|
||||
/// with <c>PortalRef{PolyId, PortalIndex}</c> in retail field order, cell
|
||||
/// portal records with the PortalSide flag bit, building portals with stab
|
||||
/// lists); this adapter is the previously missing consumer.
|
||||
///
|
||||
/// Conventions (retail structs; the trace fixtures are the referee):
|
||||
/// <list type="bullet">
|
||||
/// <item>portal_side = <c>(Flags & 0x2) != 0</c> (PortalFlags.PortalSide),
|
||||
/// matching <c>EnvCellLandblockBuild</c>'s production decode.</item>
|
||||
/// <item>Portal-polygon planes use the first-three-vertices cross with
|
||||
/// <c>d = −dot(N, p0)</c> — the same formula the production visibility
|
||||
/// cell build has always used against retail's InitCell side test.</item>
|
||||
/// <item>A building's event id is its containing outdoor landcell:
|
||||
/// low word = cellX·8 + cellY + 1 at 24 m pitch.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static class WalkWorldDatAdapter
|
||||
{
|
||||
public static WalkCell? BuildCell(DatCollection dats, uint cellId)
|
||||
{
|
||||
if (dats.Get<EnvCell>(cellId) is not EnvCell envCell)
|
||||
return null;
|
||||
if (dats.Get<Environment>(0x0D000000u | envCell.EnvironmentId) is not Environment environment
|
||||
|| !environment.Cells.TryGetValue(envCell.CellStructure, out CellStruct? cellStruct)
|
||||
|| cellStruct is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
uint lbMask = cellId & 0xFFFF0000u;
|
||||
int portalCount = envCell.CellPortals.Count;
|
||||
var portals = new WalkCellPortal[portalCount];
|
||||
var polygons = new WalkPolygon[portalCount];
|
||||
for (int i = 0; i < portalCount; i++)
|
||||
{
|
||||
CellPortal portal = envCell.CellPortals[i];
|
||||
portals[i] = new WalkCellPortal
|
||||
{
|
||||
OtherCellId = portal.OtherCellId == 0xFFFF
|
||||
? 0xFFFFFFFFu
|
||||
: lbMask | portal.OtherCellId,
|
||||
PolygonIndex = i,
|
||||
PortalSide = ((ushort)portal.Flags & 0x2) != 0 ? 1 : 0,
|
||||
ExactMatch = ((ushort)portal.Flags & 0x1) != 0,
|
||||
OtherPortalId = unchecked((short)portal.OtherPortalId),
|
||||
};
|
||||
polygons[i] = BuildPolygon(cellStruct, portal.PolygonId)
|
||||
?? new WalkPolygon();
|
||||
}
|
||||
|
||||
Matrix4x4 worldTransform =
|
||||
Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation)
|
||||
* Matrix4x4.CreateTranslation(
|
||||
envCell.Position.Origin.X,
|
||||
envCell.Position.Origin.Y,
|
||||
envCell.Position.Origin.Z);
|
||||
Matrix4x4.Invert(worldTransform, out Matrix4x4 inverse);
|
||||
return new WalkCell
|
||||
{
|
||||
CellId = cellId,
|
||||
Portals = portals,
|
||||
PortalPolygons = polygons,
|
||||
StabList = envCell.VisibleCells.Select(v => lbMask | v).ToArray(),
|
||||
WorldTransform = worldTransform,
|
||||
InverseWorldTransform = inverse,
|
||||
};
|
||||
}
|
||||
|
||||
public static Dictionary<uint, WalkCell> BuildInteriorCells(
|
||||
DatCollection dats, uint landblockId)
|
||||
{
|
||||
uint lbMask = landblockId & 0xFFFF0000u;
|
||||
var cells = new Dictionary<uint, WalkCell>();
|
||||
for (uint low = 0x0100; low <= 0xFFFD; low++)
|
||||
{
|
||||
WalkCell? cell = BuildCell(dats, lbMask | low);
|
||||
if (cell is null) break; // interior cells are contiguous from 0x0100
|
||||
cells[cell.CellId] = cell;
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
/// <summary>Buildings of one landblock, with their walk event ids
|
||||
/// (containing landcell), portal records, and drawing-BSP portal trees.</summary>
|
||||
public static List<WalkBuilding> BuildBuildings(DatCollection dats, uint landblockId)
|
||||
{
|
||||
uint lbMask = landblockId & 0xFFFF0000u;
|
||||
var result = new List<WalkBuilding>();
|
||||
if (dats.Get<LandBlockInfo>(lbMask | 0xFFFEu) is not LandBlockInfo info
|
||||
|| info.Buildings is null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (BuildingInfo buildingInfo in info.Buildings)
|
||||
{
|
||||
Vector3 origin = new(
|
||||
buildingInfo.Frame.Origin.X,
|
||||
buildingInfo.Frame.Origin.Y,
|
||||
buildingInfo.Frame.Origin.Z);
|
||||
int cellX = (int)MathF.Floor(origin.X / 24f);
|
||||
int cellY = (int)MathF.Floor(origin.Y / 24f);
|
||||
uint positionCellId = lbMask | (uint)(cellX * 8 + cellY + 1);
|
||||
|
||||
var portals = new WalkBldPortal[buildingInfo.Portals.Count];
|
||||
for (int i = 0; i < portals.Length; i++)
|
||||
{
|
||||
BuildingPortal portal = buildingInfo.Portals[i];
|
||||
portals[i] = new WalkBldPortal
|
||||
{
|
||||
PortalSide = ((ushort)portal.Flags & 0x2) != 0 ? 1 : 0,
|
||||
ExactMatch = ((ushort)portal.Flags & 0x1) != 0,
|
||||
OtherCellId = portal.OtherCellId == 0xFFFF
|
||||
? 0xFFFFFFFFu
|
||||
: lbMask | portal.OtherCellId,
|
||||
OtherPortalId = unchecked((short)portal.OtherPortalId),
|
||||
StabList = portal.StabList.Select(s => lbMask | s).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
WalkBspNode? bsp = null;
|
||||
if (dats.Get<GfxObj>(buildingInfo.ModelId) is GfxObj gfxObj)
|
||||
bsp = ConvertDrawingBsp(gfxObj, gfxObj.DrawingBSP?.Root);
|
||||
|
||||
result.Add(new WalkBuilding
|
||||
{
|
||||
PositionCellId = positionCellId,
|
||||
Portals = portals,
|
||||
DrawingBsp = bsp,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static WalkBspNode? ConvertDrawingBsp(GfxObj gfxObj, DrawingBSPNode? node)
|
||||
{
|
||||
if (node is null) return null;
|
||||
var converted = new WalkBspNode
|
||||
{
|
||||
SplittingPlane = new WalkPlane(
|
||||
node.SplittingPlane.Normal, node.SplittingPlane.D),
|
||||
IsFail = node.Type == DatReaderWriter.Enums.BSPNodeType.Leaf,
|
||||
PosNode = ConvertDrawingBsp(gfxObj, node.PosNode),
|
||||
NegNode = ConvertDrawingBsp(gfxObj, node.NegNode),
|
||||
};
|
||||
if (node.Type == DatReaderWriter.Enums.BSPNodeType.Portal && node.Portals is not null)
|
||||
{
|
||||
var refs = new List<WalkPortalRef>(node.Portals.Count);
|
||||
foreach (PortalRef portalRef in node.Portals)
|
||||
{
|
||||
WalkPolygon? polygon = BuildGfxPolygon(gfxObj, portalRef.PolyId);
|
||||
if (polygon is not null)
|
||||
refs.Add(new WalkPortalRef
|
||||
{
|
||||
PortalIndex = portalRef.PortalIndex,
|
||||
Polygon = polygon,
|
||||
});
|
||||
}
|
||||
converted.InPortals = refs.ToArray();
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
private static WalkPolygon? BuildPolygon(CellStruct cellStruct, ushort polygonId)
|
||||
{
|
||||
if (!cellStruct.Polygons.TryGetValue(polygonId, out Polygon? poly)
|
||||
|| poly is null || poly.VertexIds.Count < 3)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return BuildPolygonFromVertices(
|
||||
poly.VertexIds,
|
||||
id => cellStruct.VertexArray.Vertices.TryGetValue((ushort)id, out SWVertex? v)
|
||||
? new Vector3(v.Origin.X, v.Origin.Y, v.Origin.Z)
|
||||
: null);
|
||||
}
|
||||
|
||||
private static WalkPolygon? BuildGfxPolygon(GfxObj gfxObj, ushort polygonId)
|
||||
{
|
||||
if (!gfxObj.Polygons.TryGetValue(polygonId, out Polygon? poly)
|
||||
|| poly is null || poly.VertexIds.Count < 3)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return BuildPolygonFromVertices(
|
||||
poly.VertexIds,
|
||||
id => gfxObj.VertexArray.Vertices.TryGetValue((ushort)id, out SWVertex? v)
|
||||
? new Vector3(v.Origin.X, v.Origin.Y, v.Origin.Z)
|
||||
: null);
|
||||
}
|
||||
|
||||
private static WalkPolygon? BuildPolygonFromVertices(
|
||||
IReadOnlyList<short> vertexIds, Func<short, Vector3?> resolve)
|
||||
{
|
||||
var vertices = new Vector3[vertexIds.Count];
|
||||
for (int i = 0; i < vertexIds.Count; i++)
|
||||
{
|
||||
Vector3? v = resolve(vertexIds[i]);
|
||||
if (v is null) return null;
|
||||
vertices[i] = v.Value;
|
||||
}
|
||||
Vector3 normal = Vector3.Normalize(
|
||||
Vector3.Cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
return new WalkPolygon
|
||||
{
|
||||
Vertices = vertices,
|
||||
Plane = new WalkPlane(normal, -Vector3.Dot(normal, vertices[0])),
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue