From f64045ca6245ed853f4d8ac1bcce6f4fe13068ce Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 21 Sep 2026 08:16:10 +0800 Subject: feat(desktop): 固化样例世界与昼光呈现 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [变更性质] - this commit adds a deterministic desktop bootstrap for the playable sample world. [新增功能] - this commit places the avatar away from the home doorway and separates villagers from scene footprints. - this commit provides a warm high-contrast daylight tint and exits autoplay after SampleComplete. [实现方案] - this commit isolates desktop spawn policy in WorldBootstrap and routes game startup and sample tests through it. - this commit adds regression coverage for spawn positions, spacing, determinism, and daylight tint. [影响范围] - this commit affects the Desktop application and Desktop tests only. - this commit preserves Kernel behavior and does not include unrelated worktree changes. --- src/LivingVillage.Desktop/Game.fs | 11 ++++--- .../LivingVillage.Desktop.fsproj | 1 + src/LivingVillage.Desktop/M6Presentation.fs | 2 +- src/LivingVillage.Desktop/WorldBootstrap.fs | 36 ++++++++++++++++++++++ 4 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 src/LivingVillage.Desktop/WorldBootstrap.fs (limited to 'src/LivingVillage.Desktop') diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs index 1245e02..27e44a7 100644 --- a/src/LivingVillage.Desktop/Game.fs +++ b/src/LivingVillage.Desktop/Game.fs @@ -124,7 +124,7 @@ type LivingVillageGame() as this = let mutable cjkAtlas = Unchecked.defaultof let mutable pixel = Unchecked.defaultof let renderPlan = VillageArt.sampleRenderPlan () - let mutable world = Sim.initialWorldN 42UL Sim.npcCount + let mutable world = WorldBootstrap.initialWorld 42UL Sim.npcCount let mutable camera = Vector2.Zero let mutable avatarFacing = VillageArt.SouthFacing let mutable fpsFrames = 0 @@ -168,7 +168,7 @@ type LivingVillageGame() as this = this.CenterCamera() member private this.StartNewGame() = - world <- Sim.initialWorldN 42UL Sim.npcCount + world <- WorldBootstrap.initialWorld 42UL Sim.npcCount this.ResetWorldView() menu <- menu |> MenuState.setCurrentWorld true |> MenuState.enterGame printfn $"new-game=ok seed=42 npcs={Sim.npcCount}" @@ -258,6 +258,9 @@ type LivingVillageGame() as this = this.CenterCamera() if nextState.Stage <> stageBefore then printfn $"sample stage={nextState.Stage} tick={nextWorld.Tick} pos=({nextWorld.Avatar.Pos.X:F0},{nextWorld.Avatar.Pos.Y:F0})" + if nextState.Stage = SampleScript.SampleComplete then + printfn (if nextState.TimedOut then "sample result=timeout" else "sample result=ok") + this.Exit() member private this.UpdatePlaying (gameTime: GameTime) (kb: KeyboardState) (pressed: Keys -> bool) (pressedAny: Keys list -> bool) = if sampleMode then @@ -552,9 +555,9 @@ type LivingVillageGame() as this = spriteBatch.Begin() match m5View.HomeMode with | Inside _ -> - VillageArt.drawInterior spriteBatch pixel artTextures viewport.Width viewport.Height renderPlan.Interior Color.White + VillageArt.drawInterior spriteBatch pixel artTextures viewport.Width viewport.Height renderPlan.Interior profile.WorldTint let interiorAvatarSpec = VillageArt.avatarSpriteSpec avatarFacing world.Tick - VillageArt.drawInteriorCharacter spriteBatch artTextures viewport.Width viewport.Height world.Avatar.Pos interiorAvatarSpec Color.White + VillageArt.drawInteriorCharacter spriteBatch artTextures viewport.Width viewport.Height world.Avatar.Pos interiorAvatarSpec profile.WorldTint | Outside -> VillageArt.drawWorld spriteBatch diff --git a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj index 5bd431e..fcb1883 100644 --- a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj +++ b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj @@ -7,6 +7,7 @@ + diff --git a/src/LivingVillage.Desktop/M6Presentation.fs b/src/LivingVillage.Desktop/M6Presentation.fs index b6c24fa..8e13183 100644 --- a/src/LivingVillage.Desktop/M6Presentation.fs +++ b/src/LivingVillage.Desktop/M6Presentation.fs @@ -28,7 +28,7 @@ module M6Presentation = else { Mode = Day Background = Color(100, 160, 210) - WorldTint = Color.White } + WorldTint = Color(255, 248, 224) } let clockLabel (control: SimulationControl) : string = if control.Paused then diff --git a/src/LivingVillage.Desktop/WorldBootstrap.fs b/src/LivingVillage.Desktop/WorldBootstrap.fs new file mode 100644 index 0000000..612931b --- /dev/null +++ b/src/LivingVillage.Desktop/WorldBootstrap.fs @@ -0,0 +1,36 @@ +namespace LivingVillage.Desktop + +open LivingVillage.Kernel +open LivingVillage.Kernel.Sim + +module WorldBootstrap = + + let private tileCenter (x: int) (y: int) : Vec2 = + { X = float32 (x * Sim.tilePixels + Sim.tilePixels / 2) + Y = float32 (y * Sim.tilePixels + Sim.tilePixels / 2) } + + let private npcSpawnTiles : (int * int) array = + [| for y in [ 14; 18; 28; 32; 36; 40 ] do + for x in [ 24; 28; 32; 36; 40 ] do + yield x, y |] + + let private avatarSpawnPosition = tileCenter 31 25 + + let initialWorld (seed: uint64) (count: int) : World = + if count < 0 || count > npcSpawnTiles.Length then + invalidArg (nameof count) "Desktop bootstrap supports at most 30 NPCs." + + let baseWorld = Sim.initialWorldN seed count + let avatar = + { baseWorld.Avatar with + Pos = avatarSpawnPosition + Mind = { baseWorld.Avatar.Mind with Target = avatarSpawnPosition } } + let npcs = + baseWorld.Npcs + |> Array.mapi (fun index npc -> + let x, y = npcSpawnTiles.[index] + { npc with Pos = tileCenter x y }) + + { baseWorld with + Avatar = avatar + Npcs = npcs } -- cgit v1.2.3