diff options
Diffstat (limited to 'src/LivingVillage.Desktop/Game.fs')
| -rw-r--r-- | src/LivingVillage.Desktop/Game.fs | 145 |
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() = |
