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.fs145
1 files changed, 145 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index 0cf996c..284c246 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -252,6 +252,8 @@ type LivingVillageGame() as this =
let mutable relationView = false
let mutable relationSnapshot: float32[,] option = None
let mutable m5View = M5Interaction.initial
+ // P54:记录上一帧玩家所在瓦片,用于折叠「到达瓦片」环境信号(仅在跨越瓦片时触发)。
+ let mutable lastAvatarTile : (int * int) option = None
let mutable simulationControl = startSimulationControl
let mutable legacyStepsPerFrame = (if sampleMode then None else initialLegacyStepsPerFrame)
let mutable menu = MenuState.create startSimulationControl false
@@ -357,6 +359,20 @@ type LivingVillageGame() as this =
let mutable p53Step = 0
let mutable p53PendingName = ""
let mutable p53Pending = false
+ // P54 证据钩子:LV_P54_SHOT=1 时以农夫真实开局,走「任务面板(待接受) → 接受 →
+ // 环境到达 + 对话 + 卖出三条真实信号」全流程,拍「待接受 / 进行中 / 完成」帧;
+ // 每步打印任务模板与状态,供 analyze 脚本核验 Offered→Active→Done 只由真实信号驱动。
+ // LV_P54_OCCUPATION=farmer|fisher|peddler|scholar(默认 farmer)。
+ let p54ShotMode = Environment.GetEnvironmentVariable("LV_P54_SHOT") = "1"
+ let p54Occupation =
+ match Environment.GetEnvironmentVariable("LV_P54_OCCUPATION") with
+ | "fisher" -> Occupation.Fisher
+ | "peddler" -> Occupation.Peddler
+ | "scholar" -> Occupation.Scholar
+ | _ -> Occupation.Farmer
+ let mutable p54Step = 0
+ let mutable p54PendingName = ""
+ let mutable p54Pending = false
let mutable autoplayFrames = 0
let mutable flowStep = 0
let mutable flowHold = 0
@@ -1198,6 +1214,99 @@ type LivingVillageGame() as this =
bag
p52Step <- p52Step + 1
+ /// P54 证据:农夫真实开局 → 任务面板(待接受) → 接受 → 环境到达 + 对话 + 买/卖
+ /// 四条真实信号,验证 Offered→Active→Done 只由真实事件驱动。
+ member private this.PrepareP54Shot () =
+ let setHour (hour: float) =
+ let tick = M6Presentation.resolveStartTick (Some hour) false
+ world <- { world with Tick = tick; Time = float tick * Sim.dtSeconds }
+ let ask (command: M5Command) =
+ let nextWorld, nextView = M5Interaction.apply command world m5View
+ world <- nextWorld
+ m5View <- nextView
+ if not p54Pending then
+ match p54Step with
+ | 0 ->
+ setHour 12.0
+ let npc = world.Npcs.[0]
+ world <- { world with Avatar = { world.Avatar with Pos = npc.Pos } }
+ ask M5Command.ShowTaskPanel
+ this.CenterCamera()
+ p54PendingName <- "p54-task-offered"
+ p54Pending <- true
+ | 1 ->
+ ask M5Command.AcceptTask
+ p54PendingName <- "p54-task-active"
+ p54Pending <- true
+ | 2 ->
+ ask M5Command.ClosePanel
+ // 模拟跨入当前瓦片:以相邻瓦片为基线喂「到达瓦片」环境信号(真实折叠函数)。
+ let tile = (int (world.Avatar.Pos.X / float32 Sim.tilePixels), int (world.Avatar.Pos.Y / float32 Sim.tilePixels))
+ match m5View.Task with
+ | Some state ->
+ let previousTile = (fst tile + 1, snd tile)
+ let signals = TaskRuntime.environmentSignals false false (Some previousTile) tile
+ let advanced, message = TaskRuntime.foldSignals signals state
+ m5View <- { m5View with Task = Some advanced; Status = (match message with Some m -> "task|" + m | None -> m5View.Status) }
+ | None -> ()
+ this.CenterCamera()
+ p54PendingName <- "p54-arrive"
+ p54Pending <- true
+ | 3 ->
+ ask M5Command.Interact
+ ask M5Command.Intent1
+ p54PendingName <- "p54-dialogue"
+ p54Pending <- true
+ | 4 ->
+ ask M5Command.Interact
+ ask M5Command.BuyFromTarget
+ p54PendingName <- "p54-buy"
+ p54Pending <- true
+ | 5 ->
+ ask M5Command.Interact
+ ask M5Command.SellToTarget
+ p54PendingName <- "p54-sell"
+ p54Pending <- true
+ | 6 ->
+ ask M5Command.ShowTaskPanel
+ p54PendingName <- "p54-task-done"
+ p54Pending <- true
+ | _ ->
+ printfn "p54-shot=done"
+ this.Exit()
+
+ /// P54 证据:在 `Draw` 末尾落盘并打印任务模板/状态/反馈/背包。
+ member private this.CaptureP54Shot () =
+ if p54Pending then
+ p54Pending <- false
+ System.IO.Directory.CreateDirectory recordDirectory |> ignore
+ this.SaveBackBuffer(sprintf "%s/%s.png" recordDirectory p54PendingName)
+ let stateName (state: Occupation.TaskState) =
+ match state with
+ | Occupation.Offered -> "offered"
+ | Occupation.Active -> "active"
+ | Occupation.Done -> "done"
+ | Occupation.Failed -> "failed"
+ let taskText =
+ match m5View.Task with
+ | None -> "occupation=none template=none state=none"
+ | Some state ->
+ match state.Today with
+ | None -> "occupation=yes template=none state=none"
+ | Some task ->
+ sprintf "occupation=yes template=%s state=%s"
+ (Occupation.taskNameOf task.TemplateId) (stateName task.State)
+ let bag =
+ match m5View.Task with
+ | None -> "-"
+ | Some state ->
+ state.Backpack
+ |> List.map (fun (item, quantity) -> sprintf "%A=%d" item quantity)
+ |> String.concat ";"
+ printfn "p54-shot=%s %s panel=%s status=%s bag=[%s]"
+ p54PendingName taskText (M5Interaction.panelName m5View) m5View.Status bag
+ p54Step <- p54Step + 1
+
/// P53 证据:真实录制提示出现/消失序列(远处 → 靠近 → 互动 → 关闭+离开)。
member private this.PrepareP53Recording () =
let setHour (hour: float) =
@@ -1439,6 +1548,7 @@ type LivingVillageGame() as this =
elif pressed Keys.Tab then Some ToggleNeeds
elif pressed Keys.C then Some ShowChronicle
elif pressed Keys.T then Some ShowTaskPanel
+ elif m5View.Panel = TaskPanel && pressedAny [ Keys.Enter; Keys.Space ] then Some AcceptTask
else None
match m5Command with
| Some command ->
@@ -1493,6 +1603,29 @@ type LivingVillageGame() as this =
if refreshed <> state then
m5View <- { m5View with Task = Some refreshed }
| None -> ()
+ // P54:把真实世界环境折叠成任务信号 —— 跨瓦片触发「到达瓦片」;
+ // 夜里站在水边触发「夜在水边」。全部走 Kernel 纯函数,不按日历计数。
+ match m5View.Task with
+ | Some state ->
+ let tileX = int (world.Avatar.Pos.X / float32 Sim.tilePixels)
+ let tileY = int (world.Avatar.Pos.Y / float32 Sim.tilePixels)
+ let isNight = (M6Presentation.profileAtTick world.Tick).Mode = Night
+ let nearWater =
+ match riverscapeMap with
+ | Some map ->
+ [ 1, 0; -1, 0; 0, 1; 0, -1 ]
+ |> List.exists (fun (dx, dy) -> SceneDetail.isWater map (tileX + dx) (tileY + dy))
+ | None -> false
+ let signals = TaskRuntime.environmentSignals isNight nearWater lastAvatarTile (tileX, tileY)
+ lastAvatarTile <- Some(tileX, tileY)
+ let advanced, message = TaskRuntime.foldSignals signals state
+ if advanced <> state then
+ m5View <-
+ { m5View with
+ Task = Some advanced
+ Status = (match message with Some text -> "task|" + text | None -> m5View.Status)
+ StatusTick = (match message with Some _ -> Some world.Tick | None -> m5View.StatusTick) }
+ | None -> ()
m5View <- M5Interaction.refreshPrompt world m5View
member private this.RunAutoplayFlow() =
@@ -1701,6 +1834,16 @@ type LivingVillageGame() as this =
else this.DispatchMenuInput Confirm
elif menu.Page = Playing then
this.PrepareP53Recording()
+ elif p54ShotMode then
+ if menu.Page = MainMenu then
+ this.DispatchMenuInput Confirm
+ elif menu.Page = OccupationSelect then
+ let targetIndex =
+ MenuState.occupationOptions |> List.findIndex (fun option -> option = Some p54Occupation)
+ if menu.Selected <> targetIndex then this.DispatchMenuInput Down
+ else this.DispatchMenuInput Confirm
+ elif menu.Page = Playing then
+ this.PrepareP54Shot()
elif menu.Page = Playing then
this.UpdatePlaying gameTime kb pressed pressedAny
elif menuShotMode then
@@ -2118,6 +2261,8 @@ type LivingVillageGame() as this =
this.CaptureP52Shot()
if p53RecordMode && menu.Page = Playing then
this.CaptureP53Recording()
+ if p54ShotMode && menu.Page = Playing then
+ this.CaptureP54Shot()
/// 启动画面:分层天色 + 屋脊/水面剪影 + 标题淡入上浮 + 跳过提示,全部为帧计数纯函数。
member private this.DrawSplash() =