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.Tests/DesktopTests.fs | 53 +++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'src/LivingVillage.Desktop.Tests/DesktopTests.fs') diff --git a/src/LivingVillage.Desktop.Tests/DesktopTests.fs b/src/LivingVillage.Desktop.Tests/DesktopTests.fs index 1c175ce..20cbbe9 100644 --- a/src/LivingVillage.Desktop.Tests/DesktopTests.fs +++ b/src/LivingVillage.Desktop.Tests/DesktopTests.fs @@ -4,6 +4,7 @@ open Microsoft.VisualStudio.TestTools.UnitTesting open LivingVillage.Kernel open LivingVillage.Kernel.Sim open LivingVillage.Desktop +open LivingVillage.Desktop.VillagePresentation module private Harness = @@ -71,3 +72,55 @@ type DesktopTests () = Assert.AreEqual(Night, night.Mode) Assert.AreNotEqual(day.Background, night.Background) Assert.AreNotEqual(day.WorldTint, night.WorldTint) + + [] + member _.DesktopBootstrapMovesAvatarOffTheHomeDoorAndSeparatesVillagers () = + let world = WorldBootstrap.initialWorld 42UL Sim.npcCount + let avatarPixel : PixelPosition = + { X = world.Avatar.Pos.X + Y = world.Avatar.Pos.Y } + let avatarTile = + sceneTileFromPixel avatarPixel + let home = scene.Homes |> List.head + let renderPlan = VillageArt.sampleRenderPlan () + let blockedTiles = + (renderPlan.Grounds |> List.map (fun (tile, _) -> VillageArt.worldTile renderPlan tile)) + @ (renderPlan.Props |> List.map (fun prop -> VillageArt.worldTile renderPlan prop.Position)) + |> List.map (fun tile -> tile.X, tile.Y) + |> Set.ofList + let sceneTileOf (position: Vec2) : TilePosition = + sceneTileFromPixel + { X = position.X + Y = position.Y } + let distanceSquared (first: Vec2) (second: Vec2) : float32 = + let dx = second.X - first.X + let dy = second.Y - first.Y + dx * dx + dy * dy + let npcTiles = world.Npcs |> Array.map (fun npc -> sceneTileOf npc.Pos) + + Assert.AreEqual(6, avatarTile.X) + Assert.AreEqual(7, avatarTile.Y) + Assert.IsFalse(isNearHomeDoor avatarTile home, "The avatar must start beyond the doorway interaction radius.") + Assert.IsTrue( + npcTiles |> Array.forall (fun tile -> not (Set.contains (tile.X, tile.Y) blockedTiles)), + "A villager must not spawn on the house, water, bridge, or garden footprint.") + Assert.IsTrue( + world.Npcs + |> Array.forall (fun npc -> distanceSquared world.Avatar.Pos npc.Pos >= 96.0f * 96.0f), + "Villagers must not overlap the avatar spawn.") + + for first in 0 .. world.Npcs.Length - 2 do + for second in first + 1 .. world.Npcs.Length - 1 do + Assert.IsTrue( + distanceSquared world.Npcs.[first].Pos world.Npcs.[second].Pos >= 96.0f * 96.0f, + sprintf "villagers %d and %d are too close" first second) + + let repeated = WorldBootstrap.initialWorld 42UL Sim.npcCount + Assert.AreEqual(world, repeated) + + [] + member _.DaylightUsesAWarmHighContrastWorldTint () = + let day = M6Presentation.profileAtTick (6L * Sim.ticksPerDay / 24L) + + Assert.IsTrue(day.WorldTint.R > day.WorldTint.G) + Assert.IsTrue(day.WorldTint.G > day.WorldTint.B) -- cgit v1.2.3