From 0ae2666ef0824a7d2771111d66e695920822d54e Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 5 Sep 2026 15:15:59 +0200 Subject: [PATCH] Finish bounded lighting-observer cleanup and record verification --- ...-09-01-campaign-overhaul-world-solidity.md | 7 + .../478-observer-removal-contract.md | 4 +- .../478-observer-removal-verification.md | 51 ++++ .../s5-consumers-material-closeout-packet.md | 8 + .../Composition/FrameRootComposition.cs | 2 - .../WorldLifecycleAutomationController.cs | 4 +- .../WorldLifecycleResourceSnapshotSource.cs | 23 -- .../Wb/EnvCellRenderer.LightingObservation.cs | 178 -------------- .../Composition/FrameRootCompositionTests.cs | 66 ----- ...WorldLifecycleAutomationControllerTests.cs | 79 ------ .../Rendering/Wb/EnvCellRendererTests.cs | 230 ------------------ 11 files changed, 70 insertions(+), 582 deletions(-) create mode 100644 docs/research/2026-09-01-overhaul/478-observer-removal-verification.md delete mode 100644 src/AcDream.App/Rendering/Wb/EnvCellRenderer.LightingObservation.cs diff --git a/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md b/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md index e41c4f722..27d6ec800 100644 --- a/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md +++ b/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md @@ -814,6 +814,13 @@ room fixtures now reach every drawn cell (7+40product, unchanged ambient.2). of the temporary observer is now active, followed by final observer-free re-gate. FPS deferred; owner G4 still FAIL/unpassed. +**#478 observer cleanup:** reviewed scratch `ed2c8b12c` integrates seven +paths with unchanged retained blobs; temporary observer and only its tests +removed. Full scratch Release0W0E, App180/Core69/native2 PASS, lead independent +106/106. Permanent RHI47/ordinary8/native witnesses remain. Evidence: +`478-observer-removal-verification.md`. Fresh integrated Release and unchanged +Town/control re-gate next; FPS deferred, no owner acceptance or main merge. + ## 14. Risk register (condensed) - **R1 second scene graph.** Graph records own only topology/membership facts; diff --git a/docs/research/2026-09-01-overhaul/478-observer-removal-contract.md b/docs/research/2026-09-01-overhaul/478-observer-removal-contract.md index f0d308cb8..e8c5f24a2 100644 --- a/docs/research/2026-09-01-overhaul/478-observer-removal-contract.md +++ b/docs/research/2026-09-01-overhaul/478-observer-removal-contract.md @@ -1,6 +1,8 @@ # #478 temporary lighting observer removal -ACTIVE after candidatea5debaca2: unchanged Town High/off/High and0125/8C04 +IMPLEMENTED and lead-reviewed at scratch ed2c8b12c; integration's fresh Release +and final graphical repeat are pending. See `478-observer-removal-verification.md`. +Activated after candidatea5debaca2: unchanged Town High/off/High and0125/8C04 control PNGs inspected, eight provisional lighting/reveal PASS frames, `478-repaired-lighting-graphical.md`. Both own clients exited0/gracefully. This is not owner acceptance. A failed final visual check returns to diff --git a/docs/research/2026-09-01-overhaul/478-observer-removal-verification.md b/docs/research/2026-09-01-overhaul/478-observer-removal-verification.md new file mode 100644 index 000000000..44e844b3f --- /dev/null +++ b/docs/research/2026-09-01-overhaul/478-observer-removal-verification.md @@ -0,0 +1,51 @@ +# #478 observer removal — lead verification + +The eight candidate PNGs at `a5debaca2` passed the narrow lighting/reveal +smoke provisionally (`478-repaired-lighting-graphical.md`). Under +`478-observer-removal-contract.md`, OpenAI James removed only the temporary +observer and its corresponding tests. Lead committed scratch +`ed2c8b12c1ecd31dbc0069419db8104ed0e2bd49`, base `1772c1a5a`, and verified +the six retained integration blobs against that commit; the seventh path is +the deleted `EnvCellRenderer.LightingObservation.cs`. + +## Scope review + +Exactly seven paths, 1 insertion / 581 deletions. No lighting selection, +ambient, shader, generated module, package, geometry or FPS changes. Four +composition/DTO/test files restore the exact pre-observer (`59b0b6e72^`) Git +blobs; snapshot-source restoration differs only by one blank line. Remaining +EnvCell test changes remove only observer tests and unused helpers/imports. +The permanent two-cell 47-index RHI upload and next-generation overwrite test, +its required helpers, ordinary 8-index upload and both compiled shader pixel +witnesses remain. Observer symbol search over `src` and `tests` returns none. +Unrelated lifecycle and current-render-scene checkpoint evidence remains. + +Lead checked the complete diff and all six raw source hashes against the +frozen implementer return. No new retail algorithm or deviation is introduced; +the preceding independent retail and production/ABI reviews still cover the +unchanged repair. No additional retail re-port or review round was needed. +The removed temporary source remains recoverable from Git history. + +## Verification before integration + +Artifacts under scratch `s5-478-observer-cleanup/artifacts/478-observer-cleanup/`: + +- `release-build-r2/console.log`: full solution Release, 0 warnings/errors. + First build caught a removed Wb import also needed by `GpuMemoryTracker`; + that import was restored before the final build and freeze. +- `app-focused/app-focused.trx`: 180/180 affected/permanent tests. +- `core-lighting/core-lighting.trx`: 69/69, including installed Town DAT. +- `shader-pixels/shader-pixels.trx`: both committed-module witnesses pass. +- Lead independent `root-removal/root-removal.trx`: 106/106 affected + lifecycle/composition/EnvCell/ordinary-upload tests plus both native pixels. + +Lead inspected actual build output and all TRX counters. Both client names +were absent before the independent execution. These are focused correctness +tests, not the full solution test lane or an FPS benchmark. + +## Final graphical gate + +Pending fresh Release on the integrated observer-free commit and the unchanged +Town High/off/High plus 0125/8C04/Holtburg control routes. Candidate consumed- +light measurements remain evidence only for `a5debaca2`, not for this binary. +Owner acceptance remains owed; G4 FAIL/unpassed, no main merge. FPS deferred. diff --git a/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md b/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md index fa15c056e..ff2012d6f 100644 --- a/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md +++ b/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md @@ -4982,3 +4982,11 @@ requested but its live module check missed the already-exited process; no stronger claim. `478-repaired-lighting-graphical.md` seals paths/hashes/limits. Observer removal contract activated; final observer-free re-gate still owed. No FPS work, no owner acceptance, G4FAIL/no main merge. + +Observer cleanup reviewed and integrated from scratch `ed2c8b12c`: exactly +seven paths, temporary capture/DTO/wiring and observer-only tests removed. +Permanent RHI47/ordinary8/native-pixel coverage retained; six retained blobs +match. Full scratch Release0W0E, App180/Core69/native2 PASS; lead independent +affected/native106/106. `478-observer-removal-verification.md` records scope +and actual reports. No new deviation, retail algorithm or FPS edit. Fresh +integrated Release and unchanged final graphical repeats remain next. diff --git a/src/AcDream.App/Composition/FrameRootComposition.cs b/src/AcDream.App/Composition/FrameRootComposition.cs index dfdd4ff3f..1bb087282 100644 --- a/src/AcDream.App/Composition/FrameRootComposition.cs +++ b/src/AcDream.App/Composition/FrameRootComposition.cs @@ -596,8 +596,6 @@ internal sealed class FrameRootCompositionPhase foundation.MeshAdapter, foundation.TextureCache, live.DrawDispatcher, - live.EnvCellRenderer, - d.Lighting, d.FrameProfiler, content.Dats, foundation.Residency, diff --git a/src/AcDream.App/Diagnostics/WorldLifecycleAutomationController.cs b/src/AcDream.App/Diagnostics/WorldLifecycleAutomationController.cs index f9aba34ef..2034fbb4b 100644 --- a/src/AcDream.App/Diagnostics/WorldLifecycleAutomationController.cs +++ b/src/AcDream.App/Diagnostics/WorldLifecycleAutomationController.cs @@ -2,7 +2,6 @@ using System.Text.Json; using AcDream.App.Rendering; using AcDream.App.Rendering.Residency; using AcDream.App.Rendering.Scene; -using AcDream.App.Rendering.Wb; using AcDream.App.Streaming; using AcDream.App.UI.Testing; using AcDream.Core.Physics; @@ -97,8 +96,7 @@ internal sealed record WorldLifecycleResourceSnapshot( ResidencySnapshot Residency, double Fps, double FrameMilliseconds, - string? LastFrameProfile, - EnvCellLightingConsumptionSnapshot? EnvCellLighting = null); + string? LastFrameProfile); internal sealed record WorldLifecycleCheckpoint( int Sequence, diff --git a/src/AcDream.App/Diagnostics/WorldLifecycleResourceSnapshotSource.cs b/src/AcDream.App/Diagnostics/WorldLifecycleResourceSnapshotSource.cs index ec0bf46fa..e0640c1a2 100644 --- a/src/AcDream.App/Diagnostics/WorldLifecycleResourceSnapshotSource.cs +++ b/src/AcDream.App/Diagnostics/WorldLifecycleResourceSnapshotSource.cs @@ -7,7 +7,6 @@ using AcDream.App.Streaming; using AcDream.App.World; using AcDream.Content; using AcDream.Core.Physics; -using AcDream.Core.Lighting; using AcDream.Core.Vfx; using DatReaderWriter.Lib.IO; @@ -40,8 +39,6 @@ internal sealed class WorldLifecycleResourceSnapshotSource private readonly WbMeshAdapter? _meshes; private readonly TextureCache _textures; private readonly WbDrawDispatcher? _dispatcher; - private readonly EnvCellRenderer? _envCellRenderer; - private readonly LightManager _lighting; private readonly FrameProfiler _frameProfiler; private readonly IDatReaderWriter _dats; private readonly ResidencyManager _residency; @@ -65,8 +62,6 @@ internal sealed class WorldLifecycleResourceSnapshotSource WbMeshAdapter? meshes, TextureCache textures, WbDrawDispatcher? dispatcher, - EnvCellRenderer? envCellRenderer, - LightManager lighting, FrameProfiler frameProfiler, IDatReaderWriter dats, ResidencyManager residency, @@ -93,8 +88,6 @@ internal sealed class WorldLifecycleResourceSnapshotSource _meshes = meshes; _textures = textures ?? throw new ArgumentNullException(nameof(textures)); _dispatcher = dispatcher; - _envCellRenderer = envCellRenderer; - _lighting = lighting ?? throw new ArgumentNullException(nameof(lighting)); _frameProfiler = frameProfiler ?? throw new ArgumentNullException(nameof(frameProfiler)); _dats = dats ?? throw new ArgumentNullException(nameof(dats)); @@ -163,10 +156,6 @@ internal sealed class WorldLifecycleResourceSnapshotSource ParticleOwners: _particleSink.TrackedOwnerCount, EffectOwners: _effects.ReadyOwnerCount, LightOwners: _lights.TrackedOwnerCount, - EnvCellLighting: CaptureEnvCellLighting( - outcome, - _envCellRenderer, - _lighting), ScriptOwners: _scripts.ActiveOwnerCount, ActiveScripts: _scripts.ActiveScriptCount, MeshRenderData: mesh.RenderData, @@ -215,16 +204,4 @@ internal sealed class WorldLifecycleResourceSnapshotSource LastFrameProfile: _frameProfiler.LastReport); } - internal static EnvCellLightingConsumptionSnapshot? CaptureEnvCellLighting( - RenderFrameOutcome outcome, - EnvCellRenderer? renderer, - LightManager lighting) - { - ArgumentNullException.ThrowIfNull(lighting); - return renderer?.CaptureLightingConsumption( - lighting, - currentWorldFrame: - !outcome.SkippedZeroArea - && outcome.World.NormalWorldDrawn); - } } diff --git a/src/AcDream.App/Rendering/Wb/EnvCellRenderer.LightingObservation.cs b/src/AcDream.App/Rendering/Wb/EnvCellRenderer.LightingObservation.cs deleted file mode 100644 index 00d1bc90b..000000000 --- a/src/AcDream.App/Rendering/Wb/EnvCellRenderer.LightingObservation.cs +++ /dev/null @@ -1,178 +0,0 @@ -using AcDream.Core.Lighting; - -namespace AcDream.App.Rendering.Wb; - -internal sealed record EnvCellLightingVectorBits( - uint X, - uint Y, - uint Z); - -internal sealed record EnvCellLightingAmbientSnapshot( - EnvCellLightingVectorBits AmbientColor, - EnvCellLightingVectorBits SunColor, - EnvCellLightingVectorBits SunDirection); - -internal sealed record EnvCellLightingSourceSnapshot( - int Index, - uint OwnerId, - uint CellId, - int Kind, - bool IsDynamic, - bool IsLit, - bool TracksOwnerPose, - EnvCellLightingVectorBits RankingOrigin, - EnvCellLightingVectorBits Position, - EnvCellLightingVectorBits Forward, - EnvCellLightingVectorBits Color, - uint Intensity, - uint Range, - uint ConeAngle, - uint DistSq, - uint[] LocalPose); - -internal sealed record EnvCellLightingCellSetSnapshot( - uint CellId, - int[] Indices); - -/// -/// Checkpoint-only copy of the point-light inputs the EnvCell RHI submission -/// already consumed. PackedGlobalLightBits is the authoritative binding-4 -/// payload; Sources records its identity/provenance and CellSets records the -/// exact cached 47-index values copied to binding 5. -/// -internal sealed record EnvCellLightingConsumptionSnapshot( - bool HasCurrentEnvCellInput, - int FrameGeneration, - int FrameSlot, - int RegisteredLightCount, - int LogicalGlobalLightCount, - int UploadedGlobalLightCount, - uint[] PackedGlobalLightBits, - EnvCellLightingSourceSnapshot[] Sources, - EnvCellLightingCellSetSnapshot[] CellSets, - EnvCellLightingAmbientSnapshot Ambient); - -public sealed partial class EnvCellRenderer -{ - /// - /// Copies the retained inputs from this frame's completed EnvCell submission - /// for an explicitly requested lifecycle checkpoint. This method never - /// rebuilds a light snapshot or invokes either cell selector. - /// - internal EnvCellLightingConsumptionSnapshot CaptureLightingConsumption( - LightManager lighting, - bool currentWorldFrame) - { - ArgumentNullException.ThrowIfNull(lighting); - - EnvCellLightingAmbientSnapshot ambient = CaptureAmbient( - lighting.CurrentAmbient); - if (!currentWorldFrame) - return EmptyLightingConsumption(lighting.RegisteredCount, ambient); - - List> currentCells = []; - foreach (KeyValuePair entry in _cellLightSetCache) - { - if (entry.Value.FrameGeneration == _lightFrameGeneration) - currentCells.Add(entry); - } - - currentCells.Sort(static (left, right) => left.Key.CompareTo(right.Key)); - if (currentCells.Count == 0) - return EmptyLightingConsumption(lighting.RegisteredCount, ambient); - - IReadOnlyList? pointSnapshot = _pointSnapshot; - int logicalCount = pointSnapshot?.Count ?? 0; - int uploadedCount = Math.Max(logicalCount, 1); - int packedFloatCount = checked( - uploadedCount * GlobalLightPacker.FloatsPerLight); - var packedBits = new uint[packedFloatCount]; - for (int index = 0; index < packedFloatCount; index++) - packedBits[index] = Bits(_globalLightData[index]); - - var sources = new EnvCellLightingSourceSnapshot[logicalCount]; - for (int index = 0; index < logicalCount; index++) - sources[index] = CaptureSource(index, pointSnapshot![index]); - - var cellSets = new EnvCellLightingCellSetSnapshot[currentCells.Count]; - for (int index = 0; index < currentCells.Count; index++) - { - KeyValuePair entry = currentCells[index]; - cellSets[index] = new EnvCellLightingCellSetSnapshot( - entry.Key, - (int[])entry.Value.Indices.Clone()); - } - - return new EnvCellLightingConsumptionSnapshot( - HasCurrentEnvCellInput: true, - FrameGeneration: _lightFrameGeneration, - FrameSlot: _dynamicFrameSlot, - RegisteredLightCount: lighting.RegisteredCount, - LogicalGlobalLightCount: logicalCount, - UploadedGlobalLightCount: uploadedCount, - PackedGlobalLightBits: packedBits, - Sources: sources, - CellSets: cellSets, - Ambient: ambient); - } - - private EnvCellLightingConsumptionSnapshot EmptyLightingConsumption( - int registeredLightCount, - EnvCellLightingAmbientSnapshot ambient) => - new( - HasCurrentEnvCellInput: false, - FrameGeneration: _lightFrameGeneration, - FrameSlot: _dynamicFrameSlot, - RegisteredLightCount: registeredLightCount, - LogicalGlobalLightCount: 0, - UploadedGlobalLightCount: 0, - PackedGlobalLightBits: [], - Sources: [], - CellSets: [], - Ambient: ambient); - - private static EnvCellLightingSourceSnapshot CaptureSource( - int index, - LightSource source) => - new( - Index: index, - OwnerId: source.OwnerId, - CellId: source.CellId, - Kind: (int)source.Kind, - IsDynamic: source.IsDynamic, - IsLit: source.IsLit, - TracksOwnerPose: source.TracksOwnerPose, - RankingOrigin: CaptureVector(source.RankingOrigin), - Position: CaptureVector(source.WorldPosition), - Forward: CaptureVector(source.WorldForward), - Color: CaptureVector(source.ColorLinear), - Intensity: Bits(source.Intensity), - Range: Bits(source.Range), - ConeAngle: Bits(source.ConeAngle), - DistSq: Bits(source.DistSq), - LocalPose: - [ - Bits(source.LocalPose.M11), Bits(source.LocalPose.M12), - Bits(source.LocalPose.M13), Bits(source.LocalPose.M14), - Bits(source.LocalPose.M21), Bits(source.LocalPose.M22), - Bits(source.LocalPose.M23), Bits(source.LocalPose.M24), - Bits(source.LocalPose.M31), Bits(source.LocalPose.M32), - Bits(source.LocalPose.M33), Bits(source.LocalPose.M34), - Bits(source.LocalPose.M41), Bits(source.LocalPose.M42), - Bits(source.LocalPose.M43), Bits(source.LocalPose.M44), - ]); - - private static EnvCellLightingAmbientSnapshot CaptureAmbient( - CellAmbientState ambient) => - new( - CaptureVector(ambient.AmbientColor), - CaptureVector(ambient.SunColor), - CaptureVector(ambient.SunDirection)); - - private static EnvCellLightingVectorBits CaptureVector( - System.Numerics.Vector3 value) => - new(Bits(value.X), Bits(value.Y), Bits(value.Z)); - - private static uint Bits(float value) => - BitConverter.SingleToUInt32Bits(value); -} diff --git a/tests/AcDream.App.Tests/Composition/FrameRootCompositionTests.cs b/tests/AcDream.App.Tests/Composition/FrameRootCompositionTests.cs index 0ebe0959b..d70ff2347 100644 --- a/tests/AcDream.App.Tests/Composition/FrameRootCompositionTests.cs +++ b/tests/AcDream.App.Tests/Composition/FrameRootCompositionTests.cs @@ -7,7 +7,6 @@ using AcDream.App.Rendering.Wb; using AcDream.App.Tests.Architecture; using AcDream.App.Update; using AcDream.App.World; -using AcDream.Core.Lighting; namespace AcDream.App.Tests.Composition; @@ -78,52 +77,6 @@ public sealed class FrameRootCompositionTests Assert.True(secondTransfer > firstTransfer); } - [Fact] - public void ProductionLightingCheckpointReceivesCanonicalRendererAndManager() - { - MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod( - "ComposeCore", - BindingFlags.Instance | BindingFlags.NonPublic) - ?? throw new MissingMethodException( - typeof(FrameRootCompositionPhase).FullName, - "ComposeCore"); - IReadOnlyList calls = CompiledCallGraph.Read(compose); - - int constructor = CallIndex( - calls, - typeof(WorldLifecycleResourceSnapshotSource), - ".ctor"); - int renderer = calls - .Select((call, index) => (call, index)) - .Last(pair => pair.index < constructor - && pair.call.Target.DeclaringType == typeof(LivePresentationResult) - && pair.call.Target.Name == "get_EnvCellRenderer") - .index; - int lighting = calls - .Select((call, index) => (call, index)) - .Last(pair => pair.index < constructor - && pair.call.Target.DeclaringType == typeof(FrameRootDependencies) - && pair.call.Target.Name == "get_Lighting") - .index; - int dispatcher = calls - .Select((call, index) => (call, index)) - .Last(pair => pair.index < constructor - && pair.call.Target.DeclaringType == typeof(LivePresentationResult) - && pair.call.Target.Name == "get_DrawDispatcher") - .index; - int profiler = calls - .Select((call, index) => (call, index)) - .Last(pair => pair.index < constructor - && pair.call.Target.DeclaringType == typeof(FrameRootDependencies) - && pair.call.Target.Name == "get_FrameProfiler") - .index; - - Assert.Equal(dispatcher + 1, renderer); - Assert.Equal(renderer + 1, lighting); - Assert.Equal(lighting + 1, profiler); - Assert.True(profiler < constructor); - } - [Fact] public void ProductionWiresTheOptionalScreenshotAdapterIntoTheOuterRenderOwner() { @@ -196,19 +149,6 @@ public sealed class FrameRootCompositionTests BindingFlags.Instance | BindingFlags.NonPublic), field => field.FieldType == typeof(GameWindow)); - FieldInfo[] snapshotFields = typeof(WorldLifecycleResourceSnapshotSource) - .GetFields(BindingFlags.Instance | BindingFlags.NonPublic); - Assert.Contains(snapshotFields, field => field.FieldType == typeof(EnvCellRenderer)); - Assert.Contains(snapshotFields, field => field.FieldType == typeof(LightManager)); - ParameterInfo[] snapshotParameters = typeof(WorldLifecycleResourceSnapshotSource) - .GetConstructors().Single().GetParameters(); - Assert.Contains(snapshotParameters, - parameter => parameter.ParameterType == typeof(EnvCellRenderer) - && !parameter.HasDefaultValue); - Assert.Contains(snapshotParameters, - parameter => parameter.ParameterType == typeof(LightManager) - && !parameter.HasDefaultValue); - MethodInfo capture = typeof(WorldLifecycleResourceSnapshotSource) .GetMethod(nameof(WorldLifecycleResourceSnapshotSource.Capture))!; Assert.Equal( @@ -227,12 +167,6 @@ public sealed class FrameRootCompositionTests calls, call => call.Target.DeclaringType == typeof(FrameProfiler) && call.Target.Name == "get_LastReport"); - Assert.Contains( - calls, - call => call.Target.DeclaringType - == typeof(WorldLifecycleResourceSnapshotSource) - && call.Target.Name == nameof( - WorldLifecycleResourceSnapshotSource.CaptureEnvCellLighting)); Assert.Contains( calls, call => call.Target.DeclaringType == typeof(WorldRenderFrameOutcome) diff --git a/tests/AcDream.App.Tests/Diagnostics/WorldLifecycleAutomationControllerTests.cs b/tests/AcDream.App.Tests/Diagnostics/WorldLifecycleAutomationControllerTests.cs index 7e41a9d19..5c6fbbec2 100644 --- a/tests/AcDream.App.Tests/Diagnostics/WorldLifecycleAutomationControllerTests.cs +++ b/tests/AcDream.App.Tests/Diagnostics/WorldLifecycleAutomationControllerTests.cs @@ -4,7 +4,6 @@ using AcDream.App.Rendering; using AcDream.App.Rendering.Packs; using AcDream.App.Rendering.Residency; using AcDream.App.Rendering.Scene; -using AcDream.App.Rendering.Wb; using AcDream.App.Streaming; using AcDream.App.UI.Testing; using AcDream.Runtime; @@ -413,62 +412,6 @@ public sealed class WorldLifecycleAutomationControllerTests { LoadedLandblocks = 1, WorldEntities = 42, - EnvCellLighting = new EnvCellLightingConsumptionSnapshot( - HasCurrentEnvCellInput: true, - FrameGeneration: 31, - FrameSlot: 2, - RegisteredLightCount: 214, - LogicalGlobalLightCount: 1, - UploadedGlobalLightCount: 1, - PackedGlobalLightBits: - [ - 0x3F800000u, 0x40000000u, 0x40400000u, 0x3F800000u, - 0u, 0u, 0x3F800000u, 0x41200000u, - 0x3DCCCCCDu, 0x3E4CCCCDu, 0x3E99999Au, 0x40000000u, - 0u, 0x3F800000u, 0u, 0u, - ], - Sources: - [ - new EnvCellLightingSourceSnapshot( - Index: 0, - OwnerId: 0x5000000Au, - CellId: 0x00070143u, - Kind: 1, - IsDynamic: true, - IsLit: true, - TracksOwnerPose: true, - RankingOrigin: new EnvCellLightingVectorBits( - 0x3F000000u, 0x3F800000u, 0x40000000u), - Position: new EnvCellLightingVectorBits( - 0x3F800000u, 0x40000000u, 0x40400000u), - Forward: new EnvCellLightingVectorBits( - 0u, 0u, 0x3F800000u), - Color: new EnvCellLightingVectorBits( - 0x3DCCCCCDu, 0x3E4CCCCDu, 0x3E99999Au), - Intensity: 0x40000000u, - Range: 0x41200000u, - ConeAngle: 0u, - DistSq: 0x42F60000u, - LocalPose: - [ - 0x3F800000u, 0u, 0u, 0u, - 0u, 0x3F800000u, 0u, 0u, - 0u, 0u, 0x3F800000u, 0u, - 0u, 0u, 0u, 0x3F800000u, - ]), - ], - CellSets: - [ - new EnvCellLightingCellSetSnapshot( - 0x00070143u, - [0, .. Enumerable.Repeat(-1, 46)]), - ], - Ambient: new EnvCellLightingAmbientSnapshot( - new EnvCellLightingVectorBits( - 0x3E4CCCCDu, 0x3E4CCCCDu, 0x3E4CCCCDu), - new EnvCellLightingVectorBits(0u, 0u, 0u), - new EnvCellLightingVectorBits( - 0xBF800000u, 0x3F000000u, 0x3E800000u))), RenderSceneOracle = new CurrentRenderSceneOracleSnapshot( Enabled: true, CompletedFrameSequence: 19, @@ -587,28 +530,6 @@ public sealed class WorldLifecycleAutomationControllerTests Assert.True(json.RootElement.GetProperty("render").GetProperty("presentation") .GetProperty("screenshotCaptured").GetBoolean()); Assert.Equal(42, json.RootElement.GetProperty("resources").GetProperty("worldEntities").GetInt32()); - JsonElement envCellLighting = json.RootElement - .GetProperty("resources") - .GetProperty("envCellLighting"); - Assert.True(envCellLighting - .GetProperty("hasCurrentEnvCellInput").GetBoolean()); - Assert.Equal(31, envCellLighting.GetProperty("frameGeneration").GetInt32()); - Assert.Equal(214, envCellLighting - .GetProperty("registeredLightCount").GetInt32()); - Assert.Equal(0x3F800000u, envCellLighting - .GetProperty("sources")[0] - .GetProperty("position") - .GetProperty("x").GetUInt32()); - Assert.Equal(0x00070143u, envCellLighting - .GetProperty("cellSets")[0] - .GetProperty("cellId").GetUInt32()); - Assert.Equal(-1, envCellLighting - .GetProperty("cellSets")[0] - .GetProperty("indices")[7].GetInt32()); - Assert.Equal(0xBF800000u, envCellLighting - .GetProperty("ambient") - .GetProperty("sunDirection") - .GetProperty("x").GetUInt32()); JsonElement renderSceneOracle = json.RootElement .GetProperty("resources") .GetProperty("renderSceneOracle"); diff --git a/tests/AcDream.App.Tests/Rendering/Wb/EnvCellRendererTests.cs b/tests/AcDream.App.Tests/Rendering/Wb/EnvCellRendererTests.cs index 50905a7e8..aedcddcb9 100644 --- a/tests/AcDream.App.Tests/Rendering/Wb/EnvCellRendererTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Wb/EnvCellRendererTests.cs @@ -18,13 +18,11 @@ // over the same RecordingGpuDevice, plus a no-op IPreparedAssetSource. using System.Collections.Generic; -using System.Collections.Immutable; using System.Numerics; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using AcDream.App.Rendering; -using AcDream.App.Diagnostics; using AcDream.App.Rendering.Gpu; using AcDream.App.Rendering.Gpu.Vk; using AcDream.App.Rendering.Wb; @@ -40,206 +38,6 @@ namespace AcDream.App.Tests.Rendering.Wb; public class EnvCellRendererTests { - [Fact] - public void LightingCheckpoint_ReportsActualBoundInputsAndRejectsStaleFrames() - { - const uint landblockId = 0x8C04FFFFu; - const uint cellA = 0x8C040101u; - const uint cellB = 0x8C040102u; - - using var device = new RecordingGpuDevice(); - using var meshManager = CreateMeshManager(device); - var frameLifetime = new GpuDeviceFrameLifetime(device); - var scope = new VulkanWorldPassScope(sampleCount: 1); - using var renderer = new EnvCellRenderer( - device, - frameLifetime, - scope, - meshManager, - new WbFrustum()); - - renderer.CommitLandblock(new EnvCellLandblockBuild( - landblockId, - [], - [ - LightingShell(cellA, 1, new Vector3(-1f), new Vector3(1f)), - LightingShell(cellB, 2, new Vector3(99f, -1f, -1f), - new Vector3(101f, 1f, 1f)), - ])); - - var lighting = new LightManager - { - CurrentAmbient = new CellAmbientState( - new Vector3(0.2f, 0.3f, 0.4f), - new Vector3(0.5f, 0.6f, 0.7f), - new Vector3(-0.1f, 0.8f, -0.9f)), - }; - var dynamicLight = new LightSource - { - Kind = LightKind.Spot, - WorldPosition = new Vector3(50f, 60f, 70f), - RankingOrigin = new Vector3(5f, 6f, 7f), - WorldForward = new Vector3(0.25f, -0.5f, 0.75f), - ColorLinear = new Vector3(0.11f, 0.22f, 0.33f), - Intensity = 4.5f, - Range = 1.25f, - ConeAngle = 0.625f, - OwnerId = 0x50000001u, - CellId = cellA, - IsDynamic = true, - IsLit = true, - TracksOwnerPose = true, - DistSq = 123.5f, - LocalPose = new Matrix4x4( - 1f, 2f, 3f, 4f, - 5f, 6f, 7f, 8f, - 9f, 10f, 11f, 12f, - 13f, 14f, 15f, 16f), - }; - var staticA = new LightSource - { - Kind = LightKind.Point, - WorldPosition = Vector3.Zero, - RankingOrigin = Vector3.Zero, - WorldForward = Vector3.UnitX, - ColorLinear = new Vector3(0.9f, 0.1f, 0.2f), - Intensity = 2f, - Range = 4f, - OwnerId = 0x50000002u, - CellId = cellA, - IsLit = true, - }; - var staticB = new LightSource - { - Kind = LightKind.Point, - WorldPosition = new Vector3(100f, 0f, 0f), - RankingOrigin = new Vector3(100f, 0f, 0f), - WorldForward = Vector3.UnitY, - ColorLinear = new Vector3(0.2f, 0.8f, 0.4f), - Intensity = 3f, - Range = 4f, - OwnerId = 0x50000003u, - CellId = cellB, - IsLit = true, - }; - lighting.Register(dynamicLight); - lighting.Register(staticA); - lighting.Register(staticB); - lighting.BuildPointLightSnapshot(Vector3.Zero); - renderer.SetPointSnapshot(lighting.PointSnapshot); - - frameLifetime.BeginFrame(); - IGpuFrame frame = frameLifetime.CurrentFrame!; - using IGpuPassEncoder pass = frame.BeginPass( - GpuPassDescription.BackbufferClear( - "envcell-lighting-observation", - Vector4.Zero, - sampleCount: 1)); - using IDisposable publication = scope.Publish(pass); - renderer.BeginFrame(frame.SlotIndex); - SeedLightingSubmission(renderer, instanceCount: 2); - - // Deliberately submit in the opposite order from deterministic JSON's - // outer cell-id ordering. Each exported set must still match the exact - // binding-5 block associated with its submitted cell. - var instances = new List - { - new() { Transform = Matrix4x4.Identity, CellId = cellB }, - new() { Transform = Matrix4x4.Identity, CellId = cellA }, - }; - device.Clear(); - InvokeSubmitRhi(renderer, instances, instanceCount: 2); - - EnvCellLightingConsumptionSnapshot snapshot = - renderer.CaptureLightingConsumption(lighting, currentWorldFrame: true); - Assert.True(snapshot.HasCurrentEnvCellInput); - Assert.Equal(frame.SlotIndex, snapshot.FrameSlot); - Assert.Equal(3, snapshot.RegisteredLightCount); - Assert.Equal(3, snapshot.LogicalGlobalLightCount); - Assert.Equal(3, snapshot.UploadedGlobalLightCount); - Assert.Equal([cellA, cellB], snapshot.CellSets.Select(set => set.CellId)); - - GpuRecordedStorageBind globalBind = Assert.Single( - device.Calls.OfType(), - bind => bind.Binding == GpuBindingModel.StorageGlobalLights); - uint[] actualGlobalBits = MemoryMarshal.Cast( - device.RingBytes.Slice( - (int)globalBind.OffsetBytes, - (int)globalBind.SizeBytes)).ToArray(); - Assert.Equal(actualGlobalBits, snapshot.PackedGlobalLightBits); - - GpuRecordedStorageBind setBind = Assert.Single( - device.Calls.OfType(), - bind => bind.Binding == GpuBindingModel.StorageInstanceLightSets); - int[] actualSets = MemoryMarshal.Cast( - device.RingBytes.Slice( - (int)setBind.OffsetBytes, - (int)setBind.SizeBytes)).ToArray(); - Assert.Equal( - actualSets.AsSpan(0, LightManager.MaxLightsPerEnvCell).ToArray(), - Assert.Single(snapshot.CellSets, set => set.CellId == cellB).Indices); - Assert.Equal( - actualSets.AsSpan( - LightManager.MaxLightsPerEnvCell, - LightManager.MaxLightsPerEnvCell).ToArray(), - Assert.Single(snapshot.CellSets, set => set.CellId == cellA).Indices); - int[] expectedSet = [0, 1, 2, .. Enumerable.Repeat(-1, 44)]; - Assert.Equal(expectedSet, - Assert.Single(snapshot.CellSets, set => set.CellId == cellA).Indices); - Assert.Equal(expectedSet, - Assert.Single(snapshot.CellSets, set => set.CellId == cellB).Indices); - - EnvCellLightingSourceSnapshot source = snapshot.Sources[0]; - Assert.Equal(0, source.Index); - Assert.Equal(dynamicLight.OwnerId, source.OwnerId); - Assert.Equal(dynamicLight.CellId, source.CellId); - Assert.Equal((int)LightKind.Spot, source.Kind); - Assert.True(source.IsDynamic); - Assert.True(source.IsLit); - Assert.True(source.TracksOwnerPose); - Assert.Equal(BitConverter.SingleToUInt32Bits(5f), source.RankingOrigin.X); - Assert.Equal(BitConverter.SingleToUInt32Bits(50f), source.Position.X); - Assert.Equal(BitConverter.SingleToUInt32Bits(-0.5f), source.Forward.Y); - Assert.Equal(BitConverter.SingleToUInt32Bits(0.33f), source.Color.Z); - Assert.Equal(BitConverter.SingleToUInt32Bits(16f), source.LocalPose[15]); - - RenderFrameOutcome nonWorld = new( - new WorldRenderFrameOutcome(0, 0, NormalWorldDrawn: false), - default); - AssertNoCurrentLighting(WorldLifecycleResourceSnapshotSource - .CaptureEnvCellLighting(nonWorld, renderer, lighting)!); - AssertNoCurrentLighting(WorldLifecycleResourceSnapshotSource - .CaptureEnvCellLighting(RenderFrameOutcome.ZeroArea, renderer, lighting)!); - - // A real zero-light submission still binds one all-zero dummy light. - var emptyLighting = new LightManager(); - renderer.BeginFrame(frame.SlotIndex); - renderer.SetPointSnapshot(emptyLighting.PointSnapshot); - device.Clear(); - InvokeSubmitRhi(renderer, [instances[0]], instanceCount: 1); - EnvCellLightingConsumptionSnapshot empty = - renderer.CaptureLightingConsumption(emptyLighting, currentWorldFrame: true); - Assert.True(empty.HasCurrentEnvCellInput); - Assert.Equal(0, empty.LogicalGlobalLightCount); - Assert.Equal(1, empty.UploadedGlobalLightCount); - Assert.Equal(GlobalLightPacker.FloatsPerLight, empty.PackedGlobalLightBits.Length); - Assert.All(empty.PackedGlobalLightBits, bits => Assert.Equal(0u, bits)); - GpuRecordedStorageBind emptyGlobalBind = Assert.Single( - device.Calls.OfType(), - bind => bind.Binding == GpuBindingModel.StorageGlobalLights); - Assert.Equal( - MemoryMarshal.Cast(device.RingBytes.Slice( - (int)emptyGlobalBind.OffsetBytes, - (int)emptyGlobalBind.SizeBytes)).ToArray(), - empty.PackedGlobalLightBits); - - // BeginFrame advances the existing cache generation. Without a draw, - // the following checkpoint cannot reuse the preceding frame's cell set. - renderer.BeginFrame(frame.SlotIndex); - AssertNoCurrentLighting( - renderer.CaptureLightingConsumption(emptyLighting, currentWorldFrame: true)); - } - [Fact] public void SubmitRhi_BindsTwoCompleteFortySevenIndexCellSetsInCurrentGeneration() { @@ -850,23 +648,6 @@ public class EnvCellRendererTests Assert.Empty(b); } - private static EnvCellShellPlacement LightingShell( - uint cellId, - ulong geometryId, - Vector3 minimum, - Vector3 maximum) => - new( - CellId: cellId, - GeometryId: geometryId, - EnvironmentId: 0x0D000001u, - CellStructure: 1, - Surfaces: ImmutableArray.Empty, - WorldPosition: Vector3.Zero, - Rotation: Quaternion.Identity, - Transform: Matrix4x4.Identity, - LocalBounds: new WbBoundingBox(minimum, maximum), - WorldBounds: new WbBoundingBox(minimum, maximum)); - private static void SeedLightingSubmission( EnvCellRenderer renderer, int instanceCount) @@ -918,17 +699,6 @@ public class EnvCellRendererTests }); } - private static void AssertNoCurrentLighting( - EnvCellLightingConsumptionSnapshot snapshot) - { - Assert.False(snapshot.HasCurrentEnvCellInput); - Assert.Equal(0, snapshot.LogicalGlobalLightCount); - Assert.Equal(0, snapshot.UploadedGlobalLightCount); - Assert.Empty(snapshot.PackedGlobalLightBits); - Assert.Empty(snapshot.Sources); - Assert.Empty(snapshot.CellSets); - } - // ----------------------------------------------------------------------- // Prepare gate (2026-07-24) — pure camera-tolerance half. // The tolerance must swallow the ~36 µm eye rest jitter (RetailPViewRenderer