diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-29 04:58:29 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-29 04:58:29 +0800 |
| commit | fd40d78fd7506a95f0c5ad3295adfeb0a636e815 (patch) | |
| tree | a948f264a7924e52d804d12735c66ed2cb529b6d /src/LivingVillage.Desktop.Tests | |
| parent | f274ed8c920bbea383a4bf0e2ffe1219f7fe30b5 (diff) | |
| download | living-village-fd40d78fd7506a95f0c5ad3295adfeb0a636e815.tar.gz | |
p61: G1 字形图集补齐 16 个 UI 用字(红→绿),中文界面完整
红:新增 src/LivingVillage.Desktop.Tests/P61GlyphCoverageTests.fs——扫描 Desktop 源码
字符串字面量内的全部 CJK 字,断言 100% 在 CjkGlyphAtlas 内;并逐字钉 G1 缺的
16 字(冬包卷品夏料春玩秋第背车轴页香鱼)、requiredUiLabels、以及运行期文案
(日历「春·第1天」/页脚「第 1/3 页…翻页」/「V 卖出 背包物品×1」/鱼卷轴香料玩车回)。
实现前实测 3 失败:缺字形 16。
绿:向 CjkGlyphAtlas.characterTable 补入 16 字(保持有序、去重),同步
Assets/cjk-glyphs.txt,并跑 scripts/generate-cjk-glyph-atlas.sh 用既有 Noto Sans CJK
重生成预生成 PNG:384x504(16x21 格,+16 字),16 个新字格均有真实字体墨迹
(ink_px 148-233,非程序几何伪字形)。更新 PrototypeTests 的图集几何常量
(307→323、rowCount 20→21、pixelHeight 480→504)。
证据:LV_P61_SHOT=1 拍 2 帧(p61-calendar 右上日历「春 · 第1天」;p61-dialog-footer
真实 Interact 开对话,页脚「第 1/3 页…翻页」+ 选项「V 卖出 背包物品×1」)。
analyze(p61-analysis.txt):缺字空白位=0、黑块=0、白块=0/2;全部抽检文案
missing_glyph_slots=0。钩子为 Game.fs 纯追加(53 行,0 删除),未动 HUD 结构。
回归(p61-verification.txt):Desktop 308/308(+4 P61)+ Kernel 119/119;
--performance-baseline ×3 digest 953775FAEB2F… 不变,determinism PASS。
未改 Kernel/Sim,未引外部素材(沿用既有 Noto 字体来源),未 push。
Diffstat (limited to 'src/LivingVillage.Desktop.Tests')
3 files changed, 101 insertions, 3 deletions
diff --git a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj index 951e59b..e87ca23 100644 --- a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj +++ b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj @@ -34,6 +34,7 @@ <Compile Include="P54ProfessionDailyTests.fs" /> <Compile Include="P55ProfessionRewardTests.fs" /> <Compile Include="P57MenuHighlightTests.fs" /> + <Compile Include="P61GlyphCoverageTests.fs" /> <Compile Include="SampleTests.fs" /> </ItemGroup> diff --git a/src/LivingVillage.Desktop.Tests/P61GlyphCoverageTests.fs b/src/LivingVillage.Desktop.Tests/P61GlyphCoverageTests.fs new file mode 100644 index 0000000..f5f1a10 --- /dev/null +++ b/src/LivingVillage.Desktop.Tests/P61GlyphCoverageTests.fs @@ -0,0 +1,97 @@ +namespace LivingVillage.Desktop.Tests + +open System.IO +open System.Text.RegularExpressions +open Microsoft.VisualStudio.TestTools.UnitTesting +open LivingVillage.Desktop + +/// P61(G1 整改):中文界面字形覆盖。 +/// 断言 Desktop 全部用户可见中文文案所需字形 100% 在 CjkGlyphAtlas 内, +/// 并对 G1 盘点缺的 16 字逐字断言。缺字会被 PixelText 置空(Game.fs:73-92), +/// 因此该测试是「中文界面完整」的红→绿门。 +[<TestClass>] +type P61GlyphCoverageTests () = + + let isCjk (c: char) = c >= '\u4e00' && c <= '\u9fff' + let isInAtlas (c: char) = CjkGlyphAtlas.contains c + + /// G1 盘点确认缺失、P61 修复的 16 个字。 + let g1MissingChars = "冬包卷品夏料春玩秋第背车轴页香鱼".ToCharArray() + + let repoRoot () : string = + let mutable dir = System.AppContext.BaseDirectory + let mutable found = None + while found.IsNone && not (System.String.IsNullOrEmpty dir) do + if File.Exists(Path.Combine(dir, "LivingVillage.sln")) then found <- Some dir + else + let parent = Path.GetDirectoryName dir + if parent = dir then dir <- "" else dir <- parent + match found with + | Some d -> d + | None -> failwith "找不到仓库根(LivingVillage.sln)" + + /// 扫描 Desktop 源内字符串字面量里的 CJK 字符(跳过注释行)。 + let sourceCjkChars () : Set<char> = + let root = repoRoot () + let dir = Path.Combine(root, "src", "LivingVillage.Desktop") + let literal = Regex("\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"") + let mutable chars = Set.empty + for file in Directory.GetFiles(dir, "*.fs") do + for line in File.ReadAllLines file do + let trimmed = line.TrimStart() + if not (trimmed.StartsWith "//") then + for m in literal.Matches line do + for c in m.Groups.[1].Value do + if isCjk c then chars <- Set.add c chars + chars + + [<TestMethod>] + member _.AllSourceChineseLiteralsHaveAtlasGlyphs () = + let chars = sourceCjkChars () + let missing = chars |> Set.filter (fun c -> not (isInAtlas c)) |> Set.toList + CollectionAssert.AreEqual( + [||], + missing |> List.map string |> List.toArray, + sprintf "Desktop 用户可见中文文案缺字形 %d 个: %s(需补入 CjkGlyphAtlas.characterTable 并重生成图集)" + missing.Length (missing |> List.map string |> String.concat "")) + + [<TestMethod>] + member _.G1MissingCharsAreNowInAtlas () = + let missing = g1MissingChars |> Array.filter (fun c -> not (isInAtlas c)) |> Array.toList + CollectionAssert.AreEqual( + [||], + missing |> List.map string |> List.toArray, + sprintf "G1 缺字仍未入图集: %s" (missing |> List.map string |> String.concat "")) + + [<TestMethod>] + member _.RequiredUiLabelsAreAtlasBacked () = + let missing = + ChineseText.requiredUiLabels + |> List.collect (fun s -> s.ToCharArray() |> Array.toList) + |> List.filter isCjk + |> List.filter (fun c -> not (isInAtlas c)) + |> List.distinct + CollectionAssert.AreEqual( + [||], + missing |> List.map string |> List.toArray, + sprintf "requiredUiLabels 缺字形: %s" (missing |> List.map string |> String.concat "")) + + [<TestMethod>] + member _.CalendarAndFooterAndItemCopyAreAtlasBacked () = + // 派发词点名要可见完整的三类运行期文案,逐字钉住,防止再次缺字。 + let pinned = + [ HudLayout.seasonNames |> String.concat "" + HudLayout.calendarLine1 { Season = "春"; DayNumber = 1L; Period = "清晨"; Clock = "06:00" } + "第 1/3 页 空格 翻页 Esc 关闭" + "背包物品×1" + "鱼"; "卷轴"; "香料"; "玩家"; "回车:接受任务" ] + let missing = + pinned + |> List.collect (fun s -> s.ToCharArray() |> Array.toList) + |> List.filter isCjk + |> List.distinct + |> List.filter (fun c -> not (isInAtlas c)) + CollectionAssert.AreEqual( + [||], + missing |> List.map string |> List.toArray, + sprintf "运行期文案缺字形: %s" (missing |> List.map string |> String.concat "")) diff --git a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs index 2de0996..55de3c8 100644 --- a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs +++ b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs @@ -1094,14 +1094,14 @@ type PrototypeTests () = [<TestMethod>] member _.CjkGlyphAtlasHasAStableCharacterTableAndGeometry () = - Assert.AreEqual<int>(307, characters.Length) + Assert.AreEqual<int>(323, characters.Length) CollectionAssert.AreEqual(characters, characters |> Array.sort) Assert.AreEqual<int>(characters.Length, characters |> Array.distinct |> Array.length) Assert.AreEqual<int>(16, columns) Assert.AreEqual<int>(24, cellPixels) - Assert.AreEqual<int>(20, rowCount) + Assert.AreEqual<int>(21, rowCount) Assert.AreEqual<int>(384, pixelWidth) - Assert.AreEqual<int>(480, pixelHeight) + Assert.AreEqual<int>(504, pixelHeight) Assert.AreEqual<string>("Assets/cjk-glyph-atlas.png", assetRelativePath) Assert.AreEqual<string>("Assets/cjk-glyphs.txt", manifestRelativePath) |
