docs(#186): handoff — connector grey flap narrowed to the doorway-flap flood/pick family
Retail SEAMLESS at the same spot (user-confirmed) => real acdream bug: the eye seats in a sparse 5-poly connector cell 0xF6820118 looking back at the player's room 0116, and acdream drops 0116 (back-portal side-culled) so the doorway aperture shows the fog clear color = grey; retail keeps 0116 drawn. RULED OUT: null-root/AD-20/AD-21 (root valid, eyeInRoot=Y); the color-clear gating (retail's gated DrawCells Clear is depth/stencil, post-LScape::draw). Next step = retail cdb trace (viewer_cell + cell_draw_list at the grey pose) to pin viewer-cell PICK vs portal FLOOD, then a careful frozen-render fix. Full handoff + apparatus + DO-NOT-RETRY + code/decomp sites in the doc. Keeps the offline cell-geometry inspection test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cd42369581
commit
9d35a9786f
3 changed files with 266 additions and 11 deletions
|
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Options;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Env = System.Environment;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// #186 diagnostic (report-only): dump the render-shell + collision geometry of the
|
||||
/// connecting-room grey-flap cells (0xF6820116 player room, 0xF6820117 next room,
|
||||
/// 0xF6820118 the connector root where the frame greys). Answers: is 0118 a CLOSED
|
||||
/// shell (walls/floor/ceiling covering every direction bar the portal apertures) or a
|
||||
/// bare connector? And does it carry collision geometry (so the camera sweep would
|
||||
/// hard-stop) or none (so the boom coasts to a degenerate spot)? Skips without the dat.
|
||||
/// </summary>
|
||||
public class Issue186ConnectorCellGeometryInspectionTests
|
||||
{
|
||||
private readonly ITestOutputHelper _out;
|
||||
public Issue186ConnectorCellGeometryInspectionTests(ITestOutputHelper output) => _out = output;
|
||||
|
||||
private static string? DatDir()
|
||||
{
|
||||
var d = Env.GetEnvironmentVariable("ACDREAM_DAT_DIR")
|
||||
?? Path.Combine(Env.GetFolderPath(Env.SpecialFolder.UserProfile), "Documents", "Asheron's Call");
|
||||
return Directory.Exists(d) ? d : null;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dump_ConnectorCells_ShellAndCollision()
|
||||
{
|
||||
var datDir = DatDir();
|
||||
if (datDir is null) { _out.WriteLine("SKIP: no dat dir"); return; }
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
|
||||
foreach (uint cellId in new[] { 0xF6820116u, 0xF6820117u, 0xF6820118u })
|
||||
{
|
||||
var env = dats.Get<EnvCell>(cellId);
|
||||
if (env is null) { _out.WriteLine($"cell 0x{cellId:X8}: EnvCell NOT FOUND"); continue; }
|
||||
|
||||
var environment = dats.Get<DatReaderWriter.DBObjs.Environment>(0x0D000000u | env.EnvironmentId);
|
||||
environment!.Cells.TryGetValue(env.CellStructure, out var cs);
|
||||
|
||||
var portals = env.CellPortals?.Select(p => $"0x{(0xF6820000u | p.OtherCellId):X8}") ?? Enumerable.Empty<string>();
|
||||
_out.WriteLine($"=== cell 0x{cellId:X8} envId=0x{env.EnvironmentId:X4} struct={env.CellStructure} " +
|
||||
$"pos=({env.Position.Origin.X:F2},{env.Position.Origin.Y:F2},{env.Position.Origin.Z:F2}) ===");
|
||||
_out.WriteLine($" CellPortals -> [{string.Join(",", portals)}] VisibleCells={env.VisibleCells?.Count ?? 0}");
|
||||
|
||||
if (cs is null) { _out.WriteLine(" CellStruct: NULL"); continue; }
|
||||
|
||||
int renderPolys = cs.Polygons?.Count ?? 0;
|
||||
int physPolys = cs.PhysicsPolygons?.Count ?? 0;
|
||||
bool physBsp = cs.PhysicsBSP?.Root is not null;
|
||||
bool cellBsp = cs.CellBSP?.Root is not null;
|
||||
_out.WriteLine($" renderPolys={renderPolys} physicsPolys={physPolys} physicsBSP={(physBsp ? "YES" : "NO")} cellBSP={(cellBsp ? "YES" : "NO")}");
|
||||
|
||||
// Render-shell normal coverage: bucket each render poly's local normal by
|
||||
// dominant axis to see whether the shell encloses (has +/-X,+/-Y,+/-Z faces).
|
||||
if (cs.Polygons is not null && cs.VertexArray?.Vertices is not null)
|
||||
{
|
||||
var buckets = new Dictionary<string, int>();
|
||||
foreach (var poly in cs.Polygons.Values)
|
||||
{
|
||||
var n = PolyNormal(poly, cs.VertexArray);
|
||||
if (n is null) continue;
|
||||
buckets.TryGetValue(DomAxis(n.Value), out int c);
|
||||
buckets[DomAxis(n.Value)] = c + 1;
|
||||
}
|
||||
_out.WriteLine(" render-shell normal buckets: " +
|
||||
string.Join(" ", buckets.OrderBy(k => k.Key).Select(k => $"{k.Key}={k.Value}")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector3? PolyNormal(DatReaderWriter.Types.Polygon poly, DatReaderWriter.Types.VertexArray va)
|
||||
{
|
||||
if (poly.VertexIds is null || poly.VertexIds.Count < 3) return null;
|
||||
if (!va.Vertices.TryGetValue((ushort)poly.VertexIds[0], out var a)) return null;
|
||||
if (!va.Vertices.TryGetValue((ushort)poly.VertexIds[1], out var b)) return null;
|
||||
if (!va.Vertices.TryGetValue((ushort)poly.VertexIds[2], out var c)) return null;
|
||||
var n = Vector3.Cross(
|
||||
new Vector3(b.Origin.X, b.Origin.Y, b.Origin.Z) - new Vector3(a.Origin.X, a.Origin.Y, a.Origin.Z),
|
||||
new Vector3(c.Origin.X, c.Origin.Y, c.Origin.Z) - new Vector3(a.Origin.X, a.Origin.Y, a.Origin.Z));
|
||||
return n.LengthSquared() < 1e-9f ? (Vector3?)null : Vector3.Normalize(n);
|
||||
}
|
||||
|
||||
private static string DomAxis(Vector3 n)
|
||||
{
|
||||
float ax = MathF.Abs(n.X), ay = MathF.Abs(n.Y), az = MathF.Abs(n.Z);
|
||||
if (ax >= ay && ax >= az) return n.X >= 0 ? "+X" : "-X";
|
||||
if (ay >= ax && ay >= az) return n.Y >= 0 ? "+Y" : "-Y";
|
||||
return n.Z >= 0 ? "+Z" : "-Z";
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue