summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj1
-rw-r--r--src/LivingVillage.Desktop.Tests/P62EvidenceTests.fs52
-rw-r--r--src/LivingVillage.Desktop/Game.fs76
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