From cf214889490a62ba4fe84fb0055bc2133776e8a2 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 21 Sep 2026 17:18:11 +0800 Subject: feat(occupation): 职业系统第一片 — Kernel 结构 / bootstrap 注入 / 存档 v3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Occupation.fs(Kind/Profile/State + 农夫/渔夫/货郎/书生 中文名常量,按设计 §1 表给初始 Money/Energy/Inventory);ItemKind 扩出 Fish/Spice/Scroll(basePrice 分支补齐,Food 10.0f 数值路径不变 → 无职业 digest 不动);WorldBootstrap.initialWorldWithOccupation 默认 None = 旧行为(Money/Energy 覆盖,库存字段先留数据侧、物理挂接留交易单——取舍见 design §2-C 反对加字段);WorldSave: v3 头仅在有职业时写入(保存 saveToFileWith / loadFromFileWith,v1/v2/v3 无职业类型默认,token 顺序 version|w|h|kind|task|story|…),默认路径 save 输出逐字节等同现行 v2;P10 测试 5 例(§1 表值、v3 round-trip、v1/v2 不吞档、双 seed 逐字节、中文名);Kernel.Tests 引用 Desktop 以驱动 WorldBootstrap(取舍:不做测试专用 shim)。TaskToken/StoryStage 为 v3 占位(任务系统后续单实现)。 --- .../LivingVillage.Kernel.Tests.fsproj | 2 +- src/LivingVillage.Kernel.Tests/M6aTests.fs | 66 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) (limited to 'src/LivingVillage.Kernel.Tests') diff --git a/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj b/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj index f618dc9..f7a50d5 100644 --- a/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj +++ b/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj @@ -22,7 +22,7 @@ - + diff --git a/src/LivingVillage.Kernel.Tests/M6aTests.fs b/src/LivingVillage.Kernel.Tests/M6aTests.fs index 7f8d5ea..7184a75 100644 --- a/src/LivingVillage.Kernel.Tests/M6aTests.fs +++ b/src/LivingVillage.Kernel.Tests/M6aTests.fs @@ -5,6 +5,7 @@ open System.IO open Microsoft.VisualStudio.TestTools.UnitTesting open LivingVillage.Kernel open LivingVillage.Kernel.Sim +open LivingVillage.Desktop module private M6aHarness = @@ -226,3 +227,68 @@ type M6aTests () = Assert.AreEqual(0, SimulationControl.stepsPerFrameWithLegacy legacySteps (SimulationControl.togglePause control)) let manuallySelected = SimulationControl.setSpeed TwoX control Assert.AreEqual(2, SimulationControl.stepsPerFrameWithLegacy None manuallySelected) + + [] + member _.OccupationProfilesMatchTheDesignTable () = + let farmer = LivingVillage.Kernel.Occupation.profileOf Occupation.Farmer + Assert.AreEqual(60, farmer.InitialMoney) + Assert.AreEqual(100.0f, farmer.InitialEnergy) + Assert.AreEqual(12, snd (List.head farmer.InitialInventory)) + let fisher = Occupation.profileOf Occupation.Fisher + Assert.AreEqual(45, fisher.InitialMoney) + Assert.AreEqual(95.0f, fisher.InitialEnergy) + Assert.AreEqual(8, snd (List.head fisher.InitialInventory)) + Assert.AreEqual(3, snd (List.nth fisher.InitialInventory 1)) + let peddler = Occupation.profileOf Occupation.Peddler + Assert.AreEqual(90, peddler.InitialMoney) + Assert.AreEqual(6, snd (List.head peddler.InitialInventory)) + Assert.AreEqual(4, snd (List.nth peddler.InitialInventory 1)) + let scholar = Occupation.profileOf Occupation.Scholar + Assert.AreEqual(70, scholar.InitialMoney) + Assert.AreEqual(10, snd (List.head scholar.InitialInventory)) + Assert.AreEqual(2, snd (List.nth scholar.InitialInventory 1)) + + [] + member _.WorldSaveV3RoundTripsOccupation () = + let world = Sim.initialWorld (uint64 11) + let state = Occupation.stateOf Occupation.Fisher + WorldSave.saveToFileWith (Some state) "p10-v3.save" world + let text = System.IO.File.ReadAllText "p10-v3.save" + Assert.IsTrue(text.StartsWith("LV_WORLD_SAVE_V3"), "v3 header expected") + match WorldSave.loadFromFileWith "p10-v3.save" with + | Ok (_loaded, restored) -> + match restored with + | Some st -> + Assert.AreEqual(45, st.Profile.InitialMoney) + Assert.AreEqual(0, st.TaskToken) + Assert.AreEqual(0, st.StoryStage) + | None -> Assert.Fail("v3 round trip lost the occupation") + | Error message -> Assert.Fail(sprintf "v3 round trip failed: %s" message) + + [] + member _.WorldSaveLegacySavesStillReadWithoutOccupation () = + let world = Sim.initialWorld (uint64 42) + WorldSave.saveToFile "p10-v2.save" world + Assert.IsTrue((System.IO.File.ReadAllText "p10-v2.save").StartsWith("LV_WORLD_SAVE_V2")) + match WorldSave.loadFromFileWith "p10-v2.save" with + | Ok (_w, occ) -> Assert.AreEqual(null, occ |> Option.map (fun _ -> box 1) |> Option.defaultValue null) + | Error message -> Assert.Fail(sprintf "v2 read failed: %s" message) + Sim.configureBounds 64 48 + + [] + member _.OccupationBootstrapSavesAreByteIdenticalForSameSeed () = + let seed = uint64 4242 + let state1 = Some (Occupation.stateOf Occupation.Peddler) + let state2 = Some (Occupation.stateOf Occupation.Peddler) + let world1 = WorldBootstrap.initialWorldWithOccupation false seed 4 state1 + let world2 = WorldBootstrap.initialWorldWithOccupation false seed 4 state2 + let text1 = WorldSave.saveWith state1 world1 + let text2 = WorldSave.saveWith state2 world2 + Assert.AreEqual(text1, text2) + + [] + member _.OccupationChineseNamesAreKernelSideConstants () = + Assert.AreEqual("农夫", Occupation.nameOf Occupation.Farmer) + Assert.AreEqual("渔夫", Occupation.nameOf Occupation.Fisher) + Assert.AreEqual("货郎", Occupation.nameOf Occupation.Peddler) + Assert.AreEqual("书生", Occupation.nameOf Occupation.Scholar) -- cgit v1.2.3