summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/Game.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop/Game.fs')
-rw-r--r--src/LivingVillage.Desktop/Game.fs96
1 files changed, 95 insertions, 1 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index 8e58727..7b1dfb1 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -288,6 +288,13 @@ type LivingVillageGame() as this =
let mutable p37Step = 0
let mutable p37Hold = 0
let mutable p37Captured = false
+ // P39 证据钩子:LV_P39_SHOT=1 时在真实 256x192 生成器世界里,把相机分别对准
+ // 「桥+乌篷船+芦苇」与「主路石灯笼+菜摊」,拍昼/夜整帧后退出。位置来自
+ // VillageArt 的确定性摆放纯函数,含真实 tint/光晕,非 mockup。
+ let p39ShotMode = Environment.GetEnvironmentVariable("LV_P39_SHOT") = "1"
+ let mutable p39Step = 0
+ let mutable p39PendingName = ""
+ let mutable p39Pending = false
let mutable autoplayFrames = 0
let mutable flowStep = 0
let mutable flowHold = 0
@@ -618,6 +625,72 @@ type LivingVillageGame() as this =
this.Exit()
| _ -> ()
+ /// P39 证据:真实生成器世界里对准 P39 中式元素拍昼/夜整帧。
+ /// 相机对准「桥+船+苇」与「主路石灯笼+菜摊」两处,输出 1280x720 真实帧
+ /// (含 M6Presentation 的昼/夜 tint、golden/moonlight veil 与 lantern glow)。
+ /// 由于软件渲染下 Update 可能一帧内跑多次,截图统一在 `Draw` 末尾落盘(见
+ /// `CaptureP39Shot`),确保落盘的是「新状态已经画进后台缓冲」的那一帧。
+ member private this.PrepareP39Shot () =
+ let placeCameraOn (tileX: int) (tileY: int) =
+ world <- { world with Avatar = { world.Avatar with Pos = { X = float32 (tileX * Sim.tilePixels); Y = float32 (tileY * Sim.tilePixels) } } }
+ this.CenterCamera()
+ let setHour (hour: float) =
+ let tick = M6Presentation.resolveStartTick (Some hour) false
+ world <- { world with Tick = tick; Time = float tick * Sim.dtSeconds }
+ let dayHour = 12.0
+ let nightHour = 23.5
+ if not p39Pending then
+ match riverscapeMap with
+ | None -> this.Exit()
+ | Some map ->
+ let boat = VillageArt.cc0BoatTile map
+ // 取「离船最近的桥」的中点取景,保证桥/船/苇落在同一帧同一河段。
+ let bridgeTarget =
+ match boat with
+ | Some (boatX, boatY) ->
+ let nearest =
+ map.Bridges
+ |> List.minBy (fun (bx, by) -> abs (bx - boatX) + abs (by - boatY))
+ ((fst nearest + boatX) / 2, (snd nearest + boatY) / 2)
+ | None ->
+ match map.Bridges |> List.sortBy (fun (x, y) -> (x, y)) |> List.tryHead with
+ | Some (bx, by) -> (bx, by)
+ | None -> (map.Core.MinX, map.Core.MinY)
+ let lanternTarget =
+ match VillageArt.cc0LanternTiles map 12 with
+ | (lx, ly) :: _ -> (lx, ly)
+ | [] -> bridgeTarget
+ match p39Step with
+ | 0 ->
+ setHour dayHour
+ placeCameraOn (fst bridgeTarget) (snd bridgeTarget)
+ p39PendingName <- "p39-day"
+ p39Pending <- true
+ | 1 ->
+ setHour nightHour
+ placeCameraOn (fst bridgeTarget) (snd bridgeTarget)
+ p39PendingName <- "p39-night"
+ p39Pending <- true
+ | 2 ->
+ setHour nightHour
+ placeCameraOn (fst lanternTarget) (snd lanternTarget)
+ p39PendingName <- "p39-lantern-night"
+ p39Pending <- true
+ | _ ->
+ printfn "p39-shot=done"
+ this.Exit()
+
+ /// P39 证据:在 `Draw` 末尾把刚画好的后台缓冲落盘(软件渲染下 Update 可能一帧多跑,
+ /// 只有 Draw 之后的缓冲区才是当前状态的真实帧)。
+ member private this.CaptureP39Shot () =
+ if p39Pending then
+ p39Pending <- false
+ System.IO.Directory.CreateDirectory recordDirectory |> ignore
+ this.SaveBackBuffer(sprintf "%s/%s.png" recordDirectory p39PendingName)
+ printfn "p39-shot=%s tick=%d pos=(%.0f,%.0f) camera=(%.0f,%.0f)"
+ p39PendingName world.Tick world.Avatar.Pos.X world.Avatar.Pos.Y camera.X camera.Y
+ p39Step <- p39Step + 1
+
/// P20 第三步取证:真走位巡游(Sim.step 驱动)沿桥/路/民居门移动,用于桥面行走帧与昼夜录制。
member private this.RunRiverscapeTour () =
if not riverscapeTourWatchStarted then
@@ -916,6 +989,15 @@ type LivingVillageGame() as this =
this.RunStoryShotSetup()
elif autoplayFlow then
this.RunAutoplayFlow()
+ elif p39ShotMode 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.PrepareP39Shot()
elif p37ShotMode then
this.RunP37Shot()
elif menu.Page = Playing then
@@ -1295,6 +1377,8 @@ type LivingVillageGame() as this =
this.Exit()
if p36ShotMode && menu.Page = Playing && not relationView then
this.CaptureP36PromptShot()
+ if p39ShotMode && menu.Page = Playing then
+ this.CaptureP39Shot()
/// 启动画面:分层天色 + 屋脊/水面剪影 + 标题淡入上浮 + 跳过提示,全部为帧计数纯函数。
member private this.DrawSplash() =
@@ -1509,7 +1593,17 @@ type LivingVillageGame() as this =
let tileY y = y * Sim.tilePixels - int camera.Y
match riverscapeMap with
| Some map ->
- VillageArt.drawNightGlows spriteBatch pixel map (4 * Sim.tilePixels) (int (150.0f * glow)) tileX tileY Sim.tilePixels
+ // P39:cc0 模式下把主路石灯笼并入夜光;fallback 保持原灯光源。
+ VillageArt.drawNightGlows
+ spriteBatch
+ pixel
+ map
+ (4 * Sim.tilePixels)
+ (int (150.0f * glow))
+ tileX
+ tileY
+ Sim.tilePixels
+ artTextures.Cc0TileAtlas.IsSome
| None ->
for prop in renderPlan.Props do
match prop.Kind with