From 0ea26337830ada438df144b3a51f429f00dc1967 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Sun, 20 Sep 2026 21:58:31 +0800 Subject: feat(desktop): add deterministic Jiangnan art and Chinese text primitives [Change Nature] This commit adds the Task 3a desktop presentation data slice. [New Capability] - Defines immutable Jiangnan props, water and paving tiles, NPC variants, directions, and two-frame animation metadata. - Provides formal Chinese scene, menu, status, and missing-glyph text layout primitives. [Implementation] - Keeps the new modules pure and deterministic with no GUI, network, or global mutable state. - Adds focused prototype tests, F# compile ordering, and asset/license boundary documentation. [Impact] - Extends desktop presentation APIs without modifying Kernel or Game.fs. - Leaves GUI rendering integration for Task 3b. --- src/LivingVillage.Desktop.Tests/PrototypeTests.fs | 72 +++++++++++++++++++++++ 1 file changed, 72 insertions(+) (limited to 'src/LivingVillage.Desktop.Tests') diff --git a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs index 3abb064..a4bd1ea 100644 --- a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs +++ b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs @@ -5,6 +5,8 @@ open Microsoft.VisualStudio.TestTools.UnitTesting open LivingVillage.Kernel open LivingVillage.Kernel.Sim open LivingVillage.Desktop +open LivingVillage.Desktop.ChineseText +open LivingVillage.Desktop.VillageArt open LivingVillage.Desktop.VillagePresentation [] @@ -176,3 +178,73 @@ type PrototypeTests () = Assert.AreEqual("1倍速", M6Presentation.clockLabel { Paused = false; Speed = OneX }) Assert.AreEqual("2倍速", M6Presentation.clockLabel { Paused = false; Speed = TwoX }) Assert.AreEqual("5倍速", M6Presentation.clockLabel { Paused = false; Speed = FiveX }) + + [] + member _.JiangnanArtPlanIsDeterministicAndContainsRequiredElements () = + let first = samplePlan () + let second = samplePlan () + let requiredProps = + [ VillageArt.WhiteWallDarkTileHouse + VillageArt.StoneBridge + VillageArt.Bamboo + VillageArt.VegetableGarden ] + + Assert.AreEqual(first, second) + Assert.IsTrue( + requiredProps + |> List.forall (fun prop -> first.Props |> List.exists (fun sprite -> sprite.Kind = prop)), + "The Jiangnan plan is missing one or more required props.") + Assert.IsTrue(first.Tiles |> List.exists (fun tile -> tile.Kind = (VillageArt.RiverWater))) + Assert.IsTrue(first.Tiles |> List.exists (fun tile -> tile.Kind = (VillageArt.StonePaving))) + + [] + member _.NpcVariantsAreStableAndAnimationCoversFourDirectionsAndTwoFrames () = + let sameNpc = npcVisualVariant (NpcId 7) + let repeatedNpc = npcVisualVariant (NpcId 7) + let otherNpc = npcVisualVariant (NpcId 8) + let directions = + [ NorthFacing + SouthFacing + WestFacing + EastFacing ] + let sprites = directions |> List.map (fun direction -> npcSpriteSpec (NpcId 7) direction 4L) + + Assert.AreEqual(sameNpc, repeatedNpc) + Assert.AreNotEqual(sameNpc, otherNpc) + Assert.AreEqual(4, sprites |> List.map (fun sprite -> sprite.Direction) |> List.distinct |> List.length) + Assert.IsTrue(sprites |> List.forall (fun sprite -> sprite.Frame = FrameTwo)) + Assert.AreEqual(FrameOne, (VillageArt.animationFrame 0L)) + Assert.AreEqual(FrameTwo, (VillageArt.animationFrame 4L)) + Assert.AreEqual(FrameOne, (VillageArt.animationFrame 8L)) + + [] + member _.ChineseCopyProvidesFormalSceneTimeMenuAndStatusLabels () = + let labels = + [ sceneLabel VillageScene + sceneLabel Waterway + sceneLabel WhiteWallHome + sceneLabel StoneBridgeScene + sceneLabel BambooGrove + sceneLabel VegetableGardenScene + dayNightLabel DayTime + dayNightLabel NightTime + menuHeading MainMenuHeading + menuHeading SettingsHeading + menuHeading ControlsHeading + statusHeading StatusHeading + statusHeading ReadyStatus + statusHeading PausedStatus ] + + Assert.IsTrue(labels |> List.forall (fun label -> not (String.IsNullOrWhiteSpace label))) + Assert.IsTrue(labels |> List.forall (fun label -> not (label.Contains("?")))) + Assert.AreEqual("江南水乡", copy.SceneTitle) + Assert.AreEqual("准备就绪", copy.Ready) + + [] + member _.ChineseTextWrapsAndUsesAnExplicitMissingGlyphPolicy () = + let supported = Set.ofList [ '江'; '南'; '水'; '乡' ] + let layout = layoutText 4 supported "江南水乡村" + + Assert.AreEqual([ "江南水乡"; "□" ], layout.Lines) + Assert.AreEqual([ '村' ], layout.MissingGlyphs) + Assert.IsFalse(String.concat "" layout.Lines |> fun text -> text.Contains("?")) -- cgit v1.2.3