diff --git a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs index 27d6d0fc..748d6f35 100644 --- a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs +++ b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs @@ -19,11 +19,6 @@ using AcDream.Content; using AcDream.Core.Rendering.Wb; using PixelFormat = Silk.NET.OpenGL.PixelFormat; using BoundingBox = Chorizite.Core.Lib.BoundingBox; -using BCnEncoder.Decoder; -using BCnEncoder.Shared; -using BCnEncoder.ImageSharp; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; namespace AcDream.App.Rendering.Wb { /// diff --git a/src/AcDream.Content/AcDream.Content.csproj b/src/AcDream.Content/AcDream.Content.csproj index e54e275e..93c7dbd2 100644 --- a/src/AcDream.Content/AcDream.Content.csproj +++ b/src/AcDream.Content/AcDream.Content.csproj @@ -4,6 +4,8 @@ net10.0 enable enable + latest + true true diff --git a/src/AcDream.Content/IDatReaderWriter.cs b/src/AcDream.Content/IDatReaderWriter.cs index fc63b11f..164bb42f 100644 --- a/src/AcDream.Content/IDatReaderWriter.cs +++ b/src/AcDream.Content/IDatReaderWriter.cs @@ -5,15 +5,14 @@ using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; // Phase O-T7: verbatim copy of WorldBuilder.Shared.Services.IDatReaderWriter + -// IDatDatabase into the AcDream.App.Rendering.Wb namespace so the -// WorldBuilder.Shared project reference can be dropped. -// The only consumer of IDatReaderWriter in acdream is DatCollectionAdapter + -// ObjectMeshManager, both already in this namespace. +// IDatDatabase, taken into our tree so the WorldBuilder.Shared project +// reference could be dropped. // // MP1a (2026-07-05, Task 4): moved to AcDream.Content, namespace only — // MeshExtractor's constructor takes IDatReaderWriter and must be GL-free. +// Consumers: MeshExtractor (this assembly) reads through it; // DatCollectionAdapter (the concrete DatCollection-backed implementation) -// stays in AcDream.App; it now implements this Content-namespaced interface. +// and ObjectMeshManager live in AcDream.App and implement/hold it from there. namespace AcDream.Content; diff --git a/src/AcDream.Content/MeshExtractor.cs b/src/AcDream.Content/MeshExtractor.cs index 5117cb85..bc728132 100644 --- a/src/AcDream.Content/MeshExtractor.cs +++ b/src/AcDream.Content/MeshExtractor.cs @@ -49,10 +49,14 @@ public sealed class MeshExtractor { /// The MP1b bake tool passes its own collector. The sink must be /// thread-safe: one MeshExtractor is shared by up to MaxParallelLoads (4) /// decode workers. + /// The constructor argument is REQUIRED (no default): a bake tool that + /// forgot the sink would silently lose particle-preload meshes; requiring + /// the argument forces the decision. The type stays nullable so a caller + /// can consciously pass null when preloads are irrelevant. /// private readonly Action? _sideStagedSink; - public MeshExtractor(IDatReaderWriter dats, ILogger logger, Action? sideStagedSink = null) { + public MeshExtractor(IDatReaderWriter dats, ILogger logger, Action? sideStagedSink) { _dats = dats; _logger = logger; _sideStagedSink = sideStagedSink; @@ -120,7 +124,7 @@ public sealed class MeshExtractor { } } - public ObjectMeshData? PrepareSetupMeshData(ulong id, Setup setup, CancellationToken ct) { + private ObjectMeshData? PrepareSetupMeshData(ulong id, Setup setup, CancellationToken ct) { var parts = new List<(ulong GfxObjId, Matrix4x4 Transform)>(); var min = new Vector3(float.MaxValue); var max = new Vector3(float.MinValue); @@ -146,7 +150,7 @@ public sealed class MeshExtractor { }; } - public void CollectEmittersFromScript(uint scriptId, List emitters, CancellationToken ct) { + private void CollectEmittersFromScript(uint scriptId, List emitters, CancellationToken ct) { if (_dats.Portal.TryGet(scriptId, out var script)) { foreach (var hook in script.ScriptData) { if (hook.Hook.HookType == AnimationHookType.CreateParticle && hook.Hook is CreateParticleHook particleHook) { @@ -295,7 +299,7 @@ public sealed class MeshExtractor { } } - public ObjectMeshData? PrepareGfxObjMeshData(ulong id, GfxObj gfxObj, Vector3 scale, CancellationToken ct) { + private ObjectMeshData? PrepareGfxObjMeshData(ulong id, GfxObj gfxObj, Vector3 scale, CancellationToken ct) { var vertices = new List(); var UVLookup = new Dictionary<(ushort vertId, ushort uvIdx, bool isNeg), ushort>(); var batchesByFormat = new Dictionary<(int Width, int Height, TextureFormat Format), List>(); @@ -562,7 +566,7 @@ public sealed class MeshExtractor { }; } - public ObjectMeshData? PrepareEnvCellMeshData(ulong id, EnvCell envCell, CancellationToken ct) { + private ObjectMeshData? PrepareEnvCellMeshData(ulong id, EnvCell envCell, CancellationToken ct) { var parts = new List<(ulong GfxObjId, Matrix4x4 Transform)>(); var min = new Vector3(float.MaxValue); var max = new Vector3(float.MinValue); @@ -1069,7 +1073,7 @@ public sealed class MeshExtractor { return (min, max); } - public ObjectMeshData? PrepareCellStructEdgeLineData(ulong id, Dictionary cellStructs, Matrix4x4 transform, CancellationToken ct) { + private ObjectMeshData? PrepareCellStructEdgeLineData(ulong id, Dictionary cellStructs, Matrix4x4 transform, CancellationToken ct) { var cellStructList = cellStructs.ToList(); if (cellStructList.Count == 0) { return null; diff --git a/src/AcDream.Content/ObjectMeshData.cs b/src/AcDream.Content/ObjectMeshData.cs index 49089475..b5f7d5b0 100644 --- a/src/AcDream.Content/ObjectMeshData.cs +++ b/src/AcDream.Content/ObjectMeshData.cs @@ -48,15 +48,16 @@ public class ObjectMeshData { public List Batches { get; set; } = new(); /// - /// #125 (2026-06-12): GL upload-retry counter. A failed - /// (returns null from its + /// #125 (2026-06-12): GL upload-retry counter. A failed upload through + /// the App-side upload path (it returns null from its /// catch) used to be dropped permanently — the staged item was consumed, /// no render data was produced, and the prepared data lingered in the CPU /// cache where PrepareMeshDataAsync's cache-hit short-circuit /// returned it without ever re-staging it for upload (session-sticky /// invisible mesh, one [wb-error] line). The drain loop now re-stages a - /// failed upload for the NEXT frame up to times. The counter lives on the mesh-data object so + /// failed upload for the NEXT frame up to the App-side upload retry + /// limit (MaxUploadRetries). The counter lives on the mesh-data + /// object so /// it resets to 0 naturally whenever the id is re-prepared (fresh object), /// and bounds a deterministic GL failure to a few loud lines instead of a /// silent permanent drop OR an unbounded per-frame retry storm. Retail diff --git a/tests/AcDream.Core.Tests/Conformance/Issue119UpNullGfxObjDumpTests.cs b/tests/AcDream.Core.Tests/Conformance/Issue119UpNullGfxObjDumpTests.cs index be5e4413..0b123199 100644 --- a/tests/AcDream.Core.Tests/Conformance/Issue119UpNullGfxObjDumpTests.cs +++ b/tests/AcDream.Core.Tests/Conformance/Issue119UpNullGfxObjDumpTests.cs @@ -16,9 +16,11 @@ namespace AcDream.Core.Tests.Conformance; /// invisible)` at startup (t5-gate-launch.log:33-34); the old tower shows /// missing stair parts (visible in retail — user axiom). UploadGfxObjMeshData /// returns null only when the PREPARE phase produced ZERO vertices -/// (ObjectMeshManager.cs:1780), so the upload is innocent — some extraction +/// (ObjectMeshManager.UploadGfxObjMeshData's empty-vertices guard), so the +/// upload is innocent — some extraction /// gate dropped every polygon. This dump prints the raw dat facts per polygon -/// and replicates PrepareGfxObjMeshData's gates (ObjectMeshManager.cs:1040-1058) +/// and replicates MeshExtractor.PrepareGfxObjMeshData's gates (moved from +/// ObjectMeshManager in MP1a) /// so the zeroing gate reads directly off the output. /// public sealed class Issue119UpNullGfxObjDumpTests diff --git a/tests/AcDream.Core.Tests/Conformance/StipplingSurfaceEquivalenceTests.cs b/tests/AcDream.Core.Tests/Conformance/StipplingSurfaceEquivalenceTests.cs index 721a0ca1..db553c80 100644 --- a/tests/AcDream.Core.Tests/Conformance/StipplingSurfaceEquivalenceTests.cs +++ b/tests/AcDream.Core.Tests/Conformance/StipplingSurfaceEquivalenceTests.cs @@ -16,8 +16,8 @@ namespace AcDream.Core.Tests.Conformance; /// (0x2) and BASE1_CLIPMAP (0x4) are skipped (D3DPolyRender inner draw, /// Ghidra 0x0059d4a0; default on @0x00820e30). acdream suppresses them at /// BUILD time via Stippling.NoPos in all four extraction paths -/// (ObjectMeshManager.PrepareGfxObjMeshData:1046 + PrepareCellStructMeshData -/// :1394, CellMesh.Build:44, GfxObjMesh.Build:71). +/// (MeshExtractor.PrepareGfxObjMeshData + PrepareCellStructMeshData +/// [moved from ObjectMeshManager in MP1a], CellMesh.Build:44, GfxObjMesh.Build:71). /// /// These criteria are equivalent ONLY if NoPos ⇔ untextured-surface holds on /// the content. This sweep pins both directions across the populated