using System.Reflection; using System.Reflection.Emit; using AcDream.App.Composition; using AcDream.App.Rendering; using AcDream.App.Rendering.Sky; using AcDream.App.Rendering.Walk; using AcDream.App.Tests.Architecture; namespace AcDream.App.Tests.Rendering; public sealed class RetailPViewPassExecutorTests { [Fact] public void Extracted_contracts_retain_no_window_callbacks_or_visibility_owner() { Assert.DoesNotContain( typeof(RetailPViewFrameInput).GetProperties(), property => typeof(Delegate).IsAssignableFrom(property.PropertyType)); FieldInfo[] fields = typeof(RetailPViewPassExecutor).GetFields( BindingFlags.Instance | BindingFlags.NonPublic); Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow)); Assert.DoesNotContain(fields, field => field.FieldType == typeof(CellVisibility)); Assert.DoesNotContain(fields, field => field.FieldType == typeof(RetailPViewFrameInput)); Assert.DoesNotContain(fields, field => field.FieldType == typeof(RetailPViewFrameResult)); Assert.DoesNotContain(fields, field => field.FieldType == typeof(ClipFrameAssembly)); Assert.DoesNotContain( fields, field => typeof(Delegate).IsAssignableFrom(field.FieldType)); } [Fact] public void Concrete_executor_accumulates_walk_terrain_batch_timing() { // S3 chunk 3 fix round 1 (F3): the walk leaf no longer brackets // itself with Begin()/Complete() (that stopwatch-restart pair would // push one timing SAMPLE per batch, not one per frame) — it times // itself with a raw Stopwatch.GetTimestamp() delta and hands the // elapsed ticks to AccumulateWalkBatch, which only accumulates. MethodInfo landscape = typeof(RetailPViewPassExecutor).GetMethod( "DrawWalkLandCellBatch", BindingFlags.Instance | BindingFlags.NonPublic)!; IReadOnlyList landscapeCalls = CompiledCallGraph.Read(landscape); int terrainDraw = RequiredCallIndex( landscapeCalls, typeof(TerrainModernRenderer), nameof(TerrainModernRenderer.DrawLandCells)); int accumulate = RequiredCallIndex( landscapeCalls, typeof(TerrainDrawDiagnosticsController), nameof(TerrainDrawDiagnosticsController.AccumulateWalkBatch)); Assert.True(terrainDraw < accumulate); Assert.DoesNotContain( landscapeCalls, call => call.Target.DeclaringType == typeof(TerrainDrawDiagnosticsController) && call.Target.Name == nameof(TerrainDrawDiagnosticsController.Begin)); Assert.DoesNotContain( landscapeCalls, call => call.Target.DeclaringType == typeof(TerrainDrawDiagnosticsController) && call.Target.Name == nameof(TerrainDrawDiagnosticsController.Complete)); } [Fact] public void Concrete_executor_pushes_the_walk_terrain_frame_sample_at_replay_end() { // S3 chunk 3 fix round 1 (F3): DrawWalkDrivenStatics is the ONE call // site of driver.Replay in production — CompleteWalkTerrainFrame // must run immediately after it, so the frame's sample is pushed // exactly once, at "the end of the walk replay". MethodInfo drawWalkDrivenStatics = typeof(RetailPViewRenderer).GetMethod( "DrawWalkDrivenStatics", BindingFlags.Instance | BindingFlags.NonPublic)!; IReadOnlyList calls = CompiledCallGraph.Read(drawWalkDrivenStatics); int replay = RequiredCallIndex( calls, typeof(AcDream.App.Rendering.Walk.WalkFrameDriver), nameof(AcDream.App.Rendering.Walk.WalkFrameDriver.Replay)); int completeWalkFrame = RequiredCallIndex( calls, typeof(RetailPViewPassExecutor), nameof(RetailPViewPassExecutor.CompleteWalkTerrainFrame)); Assert.True(replay < completeWalkFrame); } [Fact] public void Frame_composition_constructs_one_walk_executor() { MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod( "ComposeCore", BindingFlags.Instance | BindingFlags.NonPublic)!; IReadOnlyList calls = CompiledCallGraph.Read(compose); int executor = RequiredCallIndex( calls, typeof(RetailPViewPassExecutor), ".ctor"); int renderer = RequiredCallIndex( calls, typeof(WorldScenePViewRenderer), ".ctor"); Assert.True(executor < renderer); Assert.Single( calls, call => call.Target.DeclaringType == typeof(RetailPViewPassExecutor) && call.Target.Name == ".ctor"); Assert.Single( calls, call => call.Target.DeclaringType == typeof(WorldScenePViewRenderer) && call.Target.Name == ".ctor"); } /// /// S3 chunk 1 fix round 2 (§11.6 H1): the weather MESH draw + its OC /// print moved OUT of /// entirely — S3 chunk 4 (O3) relocated the print to /// WalkFrameDriver.OnWeatherTurn, fired at Collect time by /// RetailFrameWalk.DrawLandscape (see the real transcript pins in /// WalkFrameDriverTranscriptTests: Collect_OutdoorRoot_... /// and Collect_InteriorRoot_...). This method now draws the /// weather MESH and the rain PARTICLES only — the former per-outside- /// view-slice loop that used to run before this call (the walk's own /// screen-space terrain-clip writer + ClearClipRouting + the old /// DrawLandscapeSliceLate leaf) is deleted outright (§10.2): retail /// draws the weather mesh and its /// rain particles ONCE, unclipped, never once per doorway aperture. /// MUTATION: re-inlining a /// WalkTranscriptDump.PrintObjectCellTurn call back into this /// method makes the Assert.DoesNotContain below fail; deleting /// either the mesh or the particle call makes the matching /// Assert.Single fail (zero matches instead of one). /// [Fact] public void DrawWeatherOnce_DrawsTheWeatherMeshAndParticlesButNeverPrints() { MethodInfo method = typeof(RetailPViewPassExecutor).GetMethod( nameof(RetailPViewPassExecutor.DrawWeatherOnce), BindingFlags.Instance | BindingFlags.Public)!; IReadOnlyList calls = CompiledCallGraph.Read(method); Assert.Single( calls, call => call.Target.DeclaringType == typeof(SkyRenderer) && call.Target.Name == nameof(SkyRenderer.RenderWeather)); Assert.Single( calls, call => call.Target.DeclaringType == typeof(ParticleRenderer) && call.Target.Name == nameof(ParticleRenderer.Draw)); Assert.DoesNotContain( calls, call => call.Target.DeclaringType == typeof(WalkTranscriptDump)); } /// /// S3 chunk 4 (§10.2): the former per-outside-view-slice loop /// (the walk's own screen-space terrain-clip writer + /// ClearClipRouting + the old DrawLandscapeSliceLate leaf, /// one call per active landscape view) is deleted — DrawLandscapeDynamicsPhase now calls /// exactly once, /// unconditionally, with no loop of any kind around it. This /// Assert.Single alone proved insufficient at fix round 1 (K1): /// it counts DISTINCT call-site offsets, so it stays green even with a /// foreach wrapped around the one call site (the exact round-1 /// regression this file's review caught) — see /// /// for the pin that actually rules that out. Kept as a cheap first-line /// check: MUTATION: adding a second, textually distinct call site (e.g. /// a duplicated call, not a loop) makes Assert.Single fail. /// [Fact] public void DrawLandscapeDynamicsPhase_CallsDrawWeatherOnceExactlyOnce() { MethodInfo method = typeof(RetailPViewRenderer).GetMethod( "DrawLandscapeDynamicsPhase", BindingFlags.Instance | BindingFlags.NonPublic)!; IReadOnlyList calls = CompiledCallGraph.Read(method); Assert.Single( calls, call => call.Target.DeclaringType == typeof(RetailPViewPassExecutor) && call.Target.Name == nameof(RetailPViewPassExecutor.DrawWeatherOnce)); } /// /// S3 chunk 4 fix round 1 (K1, blocking): the real loop-shape pin. /// 's /// Assert.Single over call-site offsets still passes when the ONE /// call site sits inside a foreach — an IL-offset ORDER pin has /// now failed three times to be discriminating for this exact class of /// regression, so this asks the LOOP-SHAPE question directly: does any /// BACKWARD branch (a branch whose target offset is lower than its own /// offset — the shape every C# loop compiles to, whether /// for/foreach/while) enclose the /// DrawWeatherOnce call's own IL offset? A call sitting strictly /// between a backward branch's target and its own offset is inside that /// loop's body and can run more than once per method invocation; a call /// outside every backward branch's span cannot. MUTATION: wrap the call /// in foreach (var slice in clipAssembly.OutsideViewSlices) — /// the compiled foreach emits a backward branch (the /// condition-check jump back to the loop body) whose span now contains /// the call's offset, so this test fails; restore the single /// unconditional call to make it pass again. /// [Fact] public void DrawLandscapeDynamicsPhase_DrawWeatherOnceCallSiteHasNoEnclosingBackwardBranch() { MethodInfo method = typeof(RetailPViewRenderer).GetMethod( "DrawLandscapeDynamicsPhase", BindingFlags.Instance | BindingFlags.NonPublic)!; IReadOnlyList calls = CompiledCallGraph.Read(method); int callIndex = RequiredCallIndex( calls, typeof(RetailPViewPassExecutor), nameof(RetailPViewPassExecutor.DrawWeatherOnce)); int callOffset = calls[callIndex].Offset; IReadOnlyList branches = CompiledCallGraph.ReadBranches(method); Assert.DoesNotContain( branches, branch => branch.TargetOffset < branch.Offset && branch.TargetOffset <= callOffset && callOffset < branch.Offset); } /// /// S3 chunk 4 fix round 2 (L1, BLOCKING). Round 1's three-lens review /// found that neither existing pin above actually looks at the /// production CONDITION gating DrawWeatherOnce: restoring the /// pre-fix gate if (clipAssembly.OutsideViewSlices.Length != 0) — /// the exact regression K2 was supposed to close — leaves both green, /// because both only ask "is the call site shaped correctly", never /// "does the call site read WalkFrameDriver.WeatherTurnFired". /// This pin reads the compiled condition directly: with c the /// index of the DrawWeatherOnce call, (a) calls[c-1] must /// be the WeatherTurnFired getter — the LAST call before the draw /// — and (b) exactly one branch must sit strictly between that getter /// call and the draw call, be a brfalse/brfalse.s, and /// jump FORWARD past the draw call — the compiled shape of /// if (walkDriver.WeatherTurnFired) passes.DrawWeatherOnce(ctx); /// and nothing else (an inverted test, an unconditional call, or a /// different condition entirely all fail one of the two checks). /// MUTATION M1 (restores the pre-fix regression): change the gate back /// to if (clipAssembly.OutsideViewSlices.Length != 0) — check (a) /// fails because calls[c-1] is no longer the /// WeatherTurnFired getter. MUTATION M2 (drops the gate /// entirely): make the call unconditional — check (b) fails because no /// branch sits between the getter call and the draw call (in fact the /// getter call itself disappears with the gate, so check (a) fails /// first). MUTATION M3 (inverts the condition): change the gate to /// if (!walkDriver.WeatherTurnFired)calls[c-1] is still /// the getter (check (a) passes), but the compiler emits a /// brtrue/brtrue.s to skip the draw instead of a /// brfalse/brfalse.s, so check (b)'s opcode filter finds /// nothing and Assert.Single fails on zero matches. /// [Fact] public void DrawLandscapeDynamicsPhase_GatesDrawWeatherOnceOnWalkDriverWeatherTurnFired() { MethodInfo method = typeof(RetailPViewRenderer).GetMethod( "DrawLandscapeDynamicsPhase", BindingFlags.Instance | BindingFlags.NonPublic)!; IReadOnlyList calls = CompiledCallGraph.Read(method); int callIndex = RequiredCallIndex( calls, typeof(RetailPViewPassExecutor), nameof(RetailPViewPassExecutor.DrawWeatherOnce)); Assert.True(callIndex > 0, "Expected a call before DrawWeatherOnce — the gate condition."); CompiledCall condition = calls[callIndex - 1]; Assert.Equal(typeof(AcDream.App.Rendering.Walk.WalkFrameDriver), condition.Target.DeclaringType); Assert.Equal("get_WeatherTurnFired", condition.Target.Name); int conditionOffset = condition.Offset; int drawOffset = calls[callIndex].Offset; IReadOnlyList branches = CompiledCallGraph.ReadBranches(method); Assert.Single( branches, branch => branch.Offset > conditionOffset && branch.Offset < drawOffset && (branch.OpCode == OpCodes.Brfalse || branch.OpCode == OpCodes.Brfalse_S) && branch.TargetOffset > drawOffset); } /// /// S3 chunk 4 fix round 2 (L8): the identical loop-shape question K1 /// asked of 's call /// site, applied to 's /// own call — retail draws the sky /// dome exactly once per frame too (K4's own doc comment on /// DrawWalkSky), so nothing may wrap this call in a loop either. /// Note for reviewers: reads /// only br/brtrue/brfalse-family single-target /// branches — it does not decode a compiled switch jump table, /// but no C# loop construct (for/foreach/while/ /// do) ever compiles to one, so this pin's blind spot is not a /// loop shape it could miss. MUTATION: wrapping the call in /// for (int i = 0; i < 2; i++) { _sky?.RenderSky(...); } makes /// this fail; restoring the single unconditional call makes it pass /// again. /// [Fact] public void DrawWalkSky_RenderSkyCallSiteHasNoEnclosingBackwardBranch() { MethodInfo method = typeof(RetailPViewPassExecutor).GetMethod( "DrawWalkSky", BindingFlags.Instance | BindingFlags.NonPublic)!; IReadOnlyList calls = CompiledCallGraph.Read(method); int callIndex = RequiredCallIndex( calls, typeof(SkyRenderer), nameof(SkyRenderer.RenderSky)); int callOffset = calls[callIndex].Offset; IReadOnlyList branches = CompiledCallGraph.ReadBranches(method); Assert.DoesNotContain( branches, branch => branch.TargetOffset < branch.Offset && branch.TargetOffset <= callOffset && callOffset < branch.Offset); } /// /// S3 chunk 1 fix round 2 (§11.6 H1): is /// 's /// gate, extracted as a pure predicate so this suite can pin "no OC line /// while the player stands indoors" without a live GL/DAT /// — combined with the two structural tests /// above (moved out of the loop; drawn/printed exactly once per call), /// this proves both halves of the spec's pin: an outdoor root (or an /// interior root with several exit-view slices) prints exactly one OC /// line, and an indoor player prints none. Retail's own check: /// SmartBox::is_player_outside @0x00451e80, /// (player objcell_id & 0xFFFF) < 0x100. MUTATION: negating /// the < 0x100 comparison (or dropping either bool AND) makes /// one of the four rows below fail. /// [Theory] [InlineData(true, true, 0xF4180003u, true)] // outdoor root, player outside a land cell -> draws [InlineData(true, true, 0xA9B40100u, false)] // player indoors (local id >= 0x100) -> no draw [InlineData(false, true, 0xF4180003u, false)] // RenderSky off -> no draw [InlineData(true, false, 0xF4180003u, false)] // RenderWeather off -> no draw public void ShouldDrawWeatherOnce_MatchesRetailIsPlayerOutsideGate( bool renderSky, bool renderWeather, uint playerCellId, bool expected) { Assert.Equal( expected, RetailPViewPassExecutor.ShouldDrawWeatherOnce(renderSky, renderWeather, playerCellId)); } private static int RequiredCallIndex( IReadOnlyList calls, Type declaringType, string methodName) { int index = CompiledCallGraph.IndexOf(calls, declaringType, methodName); Assert.True( index >= 0, $"Expected call to {declaringType.Name}.{methodName}."); return index; } }