using System; using System.IO; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Options; using Xunit; using Xunit.Abstractions; using Env = System.Environment; namespace AcDream.Core.Tests.Rendering; /// /// #79/#93/#176/#177 A7.L1 (2026-07-09) — dat-truth check for the Town Network /// fountain room (0x00070144). Live probe evidence (launch-a7-lightscope.log) /// showed 0x00070144 NEVER appears in the [indoor-light] byCell histogram across /// every visit this session, regardless of the visible-cell scoping fix — every /// pooled light belongs to NEIGHBORING cells (0x132/0x133/0x143/0x145/0x155/0x157), /// never to 0x144 itself. Both registration sites (GameWindow.cs:3771 live weenie /// spawns, GameWindow.cs:7919 dat EnvCell statics) already tag CellId correctly, so /// this is either (a) the room genuinely has no dat-authored Setup.Lights of its /// own (a registration/spawn-side non-issue — the darkness is caused by something /// else entirely), or (b) it DOES have fixtures that never made it into /// EnvCell.StaticObjects / the live weenie spawn set (a real hydration gap). /// Dumps StaticObjects + each Setup-sourced static's Setup.Lights.Count for the /// fountain room and its immediate neighbors observed this session, so the answer /// is read from the dat directly instead of inferred from aggregate counts. /// public class Issue93TownNetworkFountainRoomLightInspectionTests { private readonly ITestOutputHelper _out; public Issue93TownNetworkFountainRoomLightInspectionTests(ITestOutputHelper output) => _out = output; private static string? ResolveDatDir() { var datDir = Env.GetEnvironmentVariable("ACDREAM_DAT_DIR") ?? Path.Combine(Env.GetFolderPath(Env.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); return Directory.Exists(datDir) ? datDir : null; } [Theory] [InlineData(0x00070144u)] // the fountain room — the user's original repro spot [InlineData(0x00070156u)] // player's teleport-in cell this session [InlineData(0x00070164u)] [InlineData(0x00070132u)] [InlineData(0x00070133u)] [InlineData(0x00070143u)] [InlineData(0x00070145u)] [InlineData(0x00070146u)] [InlineData(0x00070155u)] [InlineData(0x00070157u)] public void StaticObjects_SetupLightsCount_Dump(uint cellId) { var datDir = ResolveDatDir(); if (datDir is null) { _out.WriteLine("SKIP: no dat dir"); return; } using var dats = new DatCollection(datDir, DatAccessType.Read); var envCell = dats.Get(cellId); if (envCell is null) { _out.WriteLine($"=== 0x{cellId:X8} NOT FOUND in dat ==="); return; } _out.WriteLine($"=== 0x{cellId:X8} Env=0x{envCell.EnvironmentId:X4} struct={envCell.CellStructure} " + $"pos=({envCell.Position.Origin.X:F2},{envCell.Position.Origin.Y:F2},{envCell.Position.Origin.Z:F2}) " + $"StaticObjects={envCell.StaticObjects.Count} ==="); int totalLights = 0; foreach (var so in envCell.StaticObjects) { if ((so.Id & 0xFF000000u) == 0x02000000u) { var setup = dats.Get(so.Id); int lightCount = setup?.Lights.Count ?? -1; totalLights += Math.Max(lightCount, 0); _out.WriteLine($" SETUP id=0x{so.Id:X8} at ({so.Frame.Origin.X:F2},{so.Frame.Origin.Y:F2},{so.Frame.Origin.Z:F2}) Lights={lightCount}"); } else { _out.WriteLine($" id=0x{so.Id:X8} at ({so.Frame.Origin.X:F2},{so.Frame.Origin.Y:F2},{so.Frame.Origin.Z:F2}) (not a Setup — no Lights dict)"); } } _out.WriteLine($" TOTAL dat-authored Lights in cell 0x{cellId:X8} StaticObjects: {totalLights}"); } /// /// Setup 0x02000365 at (69.88,-69.92,5.01) — 5 m above the fountain, the room's /// ONLY dat-authored light (Lights=1) — never appeared in the live [indoor-light] /// byCell histogram. GameWindow.cs:7324 drops the ENTIRE hydrated entity (light /// included) when its flattened meshRefs.Count == 0 — mirror that exact /// gate here (IsRuntimeHiddenMarker skip, then GfxObj-resolves check) to see /// whether this specific fixture is a mesh-less/marker-only "light attach point" /// that the mesh-gate silently drops before the lights loop (GameWindow.cs:7892) /// ever sees it. /// [Fact] public void CeilingFixtureSetup_MeshFlattenSurvivorCount_Dump() { var datDir = ResolveDatDir(); if (datDir is null) { _out.WriteLine("SKIP: no dat dir"); return; } using var dats = new DatCollection(datDir, DatAccessType.Read); const uint setupId = 0x02000365u; var setup = dats.Get(setupId); Assert.NotNull(setup); _out.WriteLine($"=== Setup 0x{setupId:X8}: Parts={setup!.Parts.Count} PlacementFrames={setup.PlacementFrames.Count} Lights={setup.Lights.Count} ==="); foreach (var kvp in setup.Lights) _out.WriteLine($" light[{kvp.Key}] Color=({kvp.Value.Color?.Red},{kvp.Value.Color?.Green},{kvp.Value.Color?.Blue}) Intensity={kvp.Value.Intensity} Falloff={kvp.Value.Falloff} ConeAngle={kvp.Value.ConeAngle}"); var flat = AcDream.Core.Meshing.SetupMesh.Flatten(setup); _out.WriteLine($" SetupMesh.Flatten -> {flat.Count} MeshRefs"); int survivors = 0, markerSkipped = 0, gfxNull = 0; foreach (var mr in flat) { if (AcDream.Core.Meshing.GfxObjDegradeResolver.IsRuntimeHiddenMarker(dats, mr.GfxObjId)) { markerSkipped++; _out.WriteLine($" part gfx=0x{mr.GfxObjId:X8} -> MARKER (skipped)"); continue; } var gfx = dats.Get(mr.GfxObjId); if (gfx is null) { gfxNull++; _out.WriteLine($" part gfx=0x{mr.GfxObjId:X8} -> GFXOBJ-NULL (dropped)"); continue; } survivors++; _out.WriteLine($" part gfx=0x{mr.GfxObjId:X8} -> survives (Polygons={gfx.Polygons.Count})"); } _out.WriteLine($" meshRefs survivor count = {survivors} (markerSkipped={markerSkipped} gfxNull={gfxNull}) " + $"=> GameWindow.cs:7324 would {(survivors == 0 ? "DROP" : "KEEP")} this entity"); } }