diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj | 1 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/P62EvidenceTests.fs | 52 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/Game.fs | 76 |
3 files changed, 129 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj index e87ca23..a6a05e6 100644 --- a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj +++ b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj @@ -35,6 +35,7 @@ <Compile Include="P55ProfessionRewardTests.fs" /> <Compile Include="P57MenuHighlightTests.fs" /> <Compile Include="P61GlyphCoverageTests.fs" /> + <Compile Include="P62EvidenceTests.fs" /> <Compile Include="SampleTests.fs" /> </ItemGroup> diff --git a/src/LivingVillage.Desktop.Tests/P62EvidenceTests.fs b/src/LivingVillage.Desktop.Tests/P62EvidenceTests.fs new file mode 100644 index 0000000..630b897 --- /dev/null +++ b/src/LivingVillage.Desktop.Tests/P62EvidenceTests.fs @@ -0,0 +1,52 @@ +namespace LivingVillage.Desktop.Tests + +open Microsoft.VisualStudio.TestTools.UnitTesting +open LivingVillage.Kernel +open LivingVillage.Kernel.Sim +open LivingVillage.Desktop +open LivingVillage.Desktop.VillagePresentation + +/// P62(G2 整改):样板村落「全貌俯瞰 / 民居内景」两帧取证的可复现约束。 +/// 1) 内景必须是真实 Interact 进门路径可达(HomeMode → Inside)。 +/// 2) 俯瞰必须让样板村落全部元素落在真实相机视口内(相机随角色居中,无缩尺)。 +[<TestClass>] +type P62EvidenceTests () = + + let tileCenter (t: TilePosition) : Vec2 = + { X = float32 (t.X * Sim.tilePixels + Sim.tilePixels / 2) + Y = float32 (t.Y * Sim.tilePixels + Sim.tilePixels / 2) } + + let sampleDoor () : TilePosition = + (VillagePresentation.scene.Homes |> List.head).Door + + /// 把角色放到样板民居门口,用真实 M5Command.Interact 进门,断言进入内景模式。 + [<TestMethod>] + member _.InteriorEntryViaRealInteractReachesInsideMode () = + let doorCenter = tileCenter (worldTileForSceneTile (sampleDoor ())) + let base_ = Sim.initialWorldN 42UL Sim.npcCount + let world = { base_ with Avatar = { base_.Avatar with Pos = doorCenter } } + let _, view = M5Interaction.apply M5Command.Interact world M5Interaction.initial + Assert.IsTrue(view.Status = "home entered", sprintf "真实 Interact 未从门口进入内景:%s" view.Status) + match view.HomeMode with + | Inside (HomeId 1) -> () + | other -> Assert.Fail(sprintf "内景模式应为 Inside(HomeId 1),实际 %A" other) + + /// 俯瞰点(scene (6,3))居中时,样板村落全部元素 + 民居门口都应在 1280x720 真实视口内。 + [<TestMethod>] + member _.OverviewViewportContainsWholeSampleVillage () = + let viewWidth = 1280.0f + let viewHeight = 720.0f + let worldWidth = float32 (Sim.mapWidthTiles * Sim.tilePixels) + let worldHeight = float32 (Sim.mapHeightTiles * Sim.tilePixels) + let vantage = tileCenter (worldTileForSceneTile { X = 6; Y = 3 }) + let camX = max 0.0f (min (vantage.X - viewWidth / 2.0f) (worldWidth - viewWidth)) + let camY = max 0.0f (min (vantage.Y - viewHeight / 2.0f) (worldHeight - viewHeight)) + let targets = + VillagePresentation.scene.Elements + |> List.map (fun e -> e.Prop, tileCenter (worldTileForSceneTile e.Position)) + |> fun xs -> (HomeDoor, tileCenter (worldTileForSceneTile (sampleDoor ()))) :: xs + for prop, center in targets do + let visible = + center.X >= camX && center.X <= camX + viewWidth + && center.Y >= camY && center.Y <= camY + viewHeight + Assert.IsTrue(visible, sprintf "俯瞰帧漏掉 %A @ tile(%.0f,%.0f)" prop center.X center.Y) diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs index cdfc06b..e5f2c11 100644 --- a/src/LivingVillage.Desktop/Game.fs +++ b/src/LivingVillage.Desktop/Game.fs @@ -324,6 +324,13 @@ type LivingVillageGame() as this = let mutable p61Step = 0 let mutable p61PendingName = "" let mutable p61Pending = false + // P62 证据钩子(G2):在可玩样板地图里拍「村庄全貌俯瞰」与「民居内景」两帧。 + // 内景走真实 MoveTo + PushM5 Interact(同 SampleScript),非合成相机。 + let p62ShotMode = Environment.GetEnvironmentVariable("LV_P62_SHOT") = "1" + let mutable p62Step = 0 + let mutable p62PendingName = "" + let mutable p62Pending = false + let mutable p62MoveFrames = 0 // P45 证据钩子:LV_P45_SHOT=1 时拍角色 idle / 行走朝下 / 行走朝右 三帧(真实游戏截图)。 let p45ShotMode = Environment.GetEnvironmentVariable("LV_P45_SHOT") = "1" let mutable p45Step = 0 @@ -1004,6 +1011,65 @@ type LivingVillageGame() as this = p61PendingName world.Tick (HudLayout.calendarLine1 reading) (M5Interaction.panelName m5View) p61Step <- p61Step + 1 + /// P62 证据(G2):可玩样板地图内的「村庄全貌俯瞰」+「真实进门的内景」。 + member private this.PrepareP62Shot () = + let tileCenter (t: TilePosition) : Vec2 = + { X = float32 (t.X * Sim.tilePixels + Sim.tilePixels / 2) + Y = float32 (t.Y * Sim.tilePixels + Sim.tilePixels / 2) } + let home = VillagePresentation.scene.Homes |> List.head + let doorCenter = tileCenter (worldTileForSceneTile home.Door) + let setHour (hour: float) = + let tick = M6Presentation.resolveStartTick (Some hour) false + world <- { world with Tick = tick; Time = float tick * Sim.dtSeconds } + if not p62Pending then + match p62Step with + | 0 -> + setHour 12.0 + // 俯瞰点:样板村落西侧上空,相机随角色居中,村舍/石桥/岸线/菜圃/竹林全部入画。 + let vantage = tileCenter (worldTileForSceneTile { X = 6; Y = 3 }) + world <- { world with Avatar = { world.Avatar with Pos = vantage } } + this.CenterCamera() + p62PendingName <- "p62-overview" + p62Pending <- true + | 1 -> + // 真实走入:沿真实 Sim.step 向门口移动,抵达后用真实 Interact 进门。 + let dx = doorCenter.X - world.Avatar.Pos.X + let dy = doorCenter.Y - world.Avatar.Pos.Y + if dx * dx + dy * dy <= 12.0f * 12.0f then + let nextWorld, nextView = M5Interaction.apply M5Command.Interact world m5View + world <- nextWorld + m5View <- nextView + match m5View.HomeMode with + | Inside _ -> + p62PendingName <- "p62-interior" + p62Pending <- true + | Outside -> + p62MoveFrames <- p62MoveFrames + 1 + else + let length = sqrt (dx * dx + dy * dy) + let input = + if length > 0.001f then { MoveX = dx / length; MoveY = dy / length } + else { MoveX = 0.0f; MoveY = 0.0f } + world <- Sim.step { Input = input } world + p62MoveFrames <- p62MoveFrames + 1 + if p62MoveFrames > 900 then + printfn "p62-shot=move-timeout frames=%d home=%A" p62MoveFrames m5View.HomeMode + this.Exit() + | _ -> + printfn "p62-shot=done" + this.Exit() + + /// P62 证据:落盘并打印相机/角色/内景模式,供 analyze 核验非空帧与真实进门。 + member private this.CaptureP62Shot () = + if p62Pending then + p62Pending <- false + System.IO.Directory.CreateDirectory recordDirectory |> ignore + this.SaveBackBuffer(sprintf "%s/%s.png" recordDirectory p62PendingName) + printfn + "p62-shot=%s tick=%d avatar=(%.0f,%.0f) camera=(%.0f,%.0f) home=%A" + p62PendingName world.Tick world.Avatar.Pos.X world.Avatar.Pos.Y camera.X camera.Y m5View.HomeMode + p62Step <- p62Step + 1 + member private this.PrepareP45Shot () = let baseTick = M6Presentation.resolveStartTick (Some 12.0) false let setTick (tick: int64) = @@ -1993,6 +2059,14 @@ type LivingVillageGame() as this = else this.DispatchMenuInput Confirm elif menu.Page = Playing then this.PrepareP61Shot() + elif p62ShotMode then + if menu.Page = MainMenu then + this.DispatchMenuInput Confirm + elif menu.Page = OccupationSelect then + if menu.Selected <> 0 then this.DispatchMenuInput Down + else this.DispatchMenuInput Confirm + elif menu.Page = Playing then + this.PrepareP62Shot() elif p45ShotMode then if menu.Page = MainMenu then this.DispatchMenuInput Confirm @@ -2465,6 +2539,8 @@ type LivingVillageGame() as this = this.CaptureP44Shot() if p61ShotMode && menu.Page = Playing then this.CaptureP61Shot() + if p62ShotMode && menu.Page = Playing then + this.CaptureP62Shot() if p45ShotMode && menu.Page = Playing then this.CaptureP45Shot() if p46ShotMode then |
