summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-28 09:01:48 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-28 09:01:48 +0800
commit9e0d3986482474828be6c9aebaec7d7e55fd4634 (patch)
treeecf4b9201e4a8d524526ec9594e482cae2243099
parent5cb4edfa29bdfcdcd6c799d549c41415a2033e54 (diff)
downloadliving-village-9e0d3986482474828be6c9aebaec7d7e55fd4634.tar.gz
feat(desktop): P36 情境提示取证钩子 LV_P36_SHOT
复用 sample 脚本走位/互动序列,在自然状态下抓 4 帧关键帧 (靠近→提示出现→对话层打开且世界交互被隔离→关闭走远提示消失) 并逐帧打印 panel/prompt/home 对照点,第 4 帧后退出。仅证据用,不影响默认玩法。
-rw-r--r--src/LivingVillage.Desktop/Game.fs50
1 files changed, 49 insertions, 1 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index c68cf6a..c796953 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -96,7 +96,10 @@ type LivingVillageGame() as this =
let autoplay = (Environment.GetEnvironmentVariable("LV_AUTOPLAY") = "1") || (Environment.GetEnvironmentVariable("LV_MAP_TOUR") = "1")
let autoplayFlow = Environment.GetEnvironmentVariable("LV_AUTOPLAY_FLOW") = "1"
- let sampleMode = Environment.GetEnvironmentVariable("LV_AUTOPLAY_SAMPLE") = "1"
+ // P36 证据钩子:复用 sample 脚本的走位/互动序列,但按状态抓 4 帧关键帧
+ // (靠近→提示出现→对话层打开且世界交互被隔离→关闭离开提示消失)后退出。
+ let p36ShotMode = Environment.GetEnvironmentVariable("LV_P36_SHOT") = "1"
+ let sampleMode = Environment.GetEnvironmentVariable("LV_AUTOPLAY_SAMPLE") = "1" || p36ShotMode
let menuShotMode = Environment.GetEnvironmentVariable("LV_AUTOPLAY_MENU_SHOT") = "1"
let daylightAutoplay = Environment.GetEnvironmentVariable("LV_AUTOPLAY_DAYLIGHT") = "1"
let automated = autoplay || autoplayFlow
@@ -277,6 +280,7 @@ type LivingVillageGame() as this =
let mutable flowStep = 0
let mutable flowHold = 0
let mutable sampleState = SampleScript.initial
+ let mutable p36Captured = Array.zeroCreate<bool> 4
let mutable hudClock = 0L
let mutable hudStatus = ""
let mutable hudStatusAt = 0L
@@ -518,6 +522,48 @@ type LivingVillageGame() as this =
printfn (if nextState.TimedOut then "sample result=timeout" else "sample result=ok")
this.Exit()
+ /// P36 证据:在 sample 序列的自然状态下抓 4 帧,证明「情境提示」契约:
+ /// 1 靠近目标(进入 ApproachNpc)2 提示出现 3 对话层打开(浮动提示消失=世界交互被隔离)
+ /// 4 关闭并走远后提示消失。每帧打印 panel/prompt/home 对照点,第 4 帧后退出。
+ member private this.CaptureP36PromptShot () =
+ if p36ShotMode then
+ let save (index: int) (label: string) =
+ System.IO.Directory.CreateDirectory recordDirectory |> ignore
+ this.SaveBackBuffer(sprintf "%s/p36-prompt-approach-%d.png" recordDirectory index)
+ printfn
+ "p36-shot=%s index=%d tick=%d pos=(%.0f,%.0f) panel=%s prompt=%s home=%A"
+ label
+ index
+ world.Tick
+ world.Avatar.Pos.X
+ world.Avatar.Pos.Y
+ (M5Interaction.panelName m5View)
+ (m5View.Prompt |> Option.defaultValue "<none>")
+ m5View.HomeMode
+ let stage = sampleState.Stage
+ // 第 4 帧必须已真正走远(超出所有村民的交谈范围)且提示消失,才能证明「离开即消失」。
+ let leaveMargin = Sim.chatRangePx + 24.0f
+ let outsideEveryRange =
+ world.Npcs
+ |> Array.forall (fun npc ->
+ let dx = npc.Pos.X - world.Avatar.Pos.X
+ let dy = npc.Pos.Y - world.Avatar.Pos.Y
+ dx * dx + dy * dy > leaveMargin * leaveMargin)
+ if not p36Captured.[0] && stage = SampleScript.ApproachNpc then
+ p36Captured.[0] <- true
+ save 1 "approach"
+ elif not p36Captured.[1] && stage = SampleScript.ApproachNpc && m5View.Prompt.IsSome then
+ p36Captured.[1] <- true
+ save 2 "prompt"
+ elif not p36Captured.[2] && m5View.Panel = DialoguePanel && m5View.Menu.IsSome then
+ p36Captured.[2] <- true
+ save 3 "dialogue-isolated"
+ elif not p36Captured.[3] && stage = SampleScript.WalkAway && m5View.Prompt.IsNone && outsideEveryRange then
+ p36Captured.[3] <- true
+ save 4 "left"
+ printfn "p36-shot=done tick=%d" world.Tick
+ this.Exit()
+
/// P16-FIX:菜单截图模式。停在开始界面,按确定性帧表向下切到「读档」,帧表见 MenuShotScript。
member private this.RunMenuShot () =
menuShotFrame <- menuShotFrame + 1L
@@ -1197,6 +1243,8 @@ type LivingVillageGame() as this =
storyShotStage)
printfn "story-shot=saved occupation=%s stage=%d" (Occupation.saveToken storyShotKind) storyShotStage
this.Exit()
+ if p36ShotMode && menu.Page = Playing && not relationView then
+ this.CaptureP36PromptShot()
/// 启动画面:分层天色 + 屋脊/水面剪影 + 标题淡入上浮 + 跳过提示,全部为帧计数纯函数。
member private this.DrawSplash() =