diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-29 06:12:57 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-29 06:12:57 +0800 |
| commit | ec49a835361fd0f6d92a41204dcc9547073b46ee (patch) | |
| tree | e487c692e327484bfc813db2f65cb6a43a13b4cc | |
| parent | 166fbeb4efd1c36c5c8289defdf480c89835d540 (diff) | |
| download | living-village-ec49a835361fd0f6d92a41204dcc9547073b46ee.tar.gz | |
p64: 整改桥梁栏杆可辨(深墨栏体/亮扶手/望柱)与内景四角布局(Hermes vision 复核)
| -rw-r--r-- | docs/evidence/p64-analysis.txt | 14 | ||||
| -rw-r--r-- | docs/evidence/p64-bridge.png | bin | 111350 -> 111188 bytes | |||
| -rw-r--r-- | docs/evidence/p64-interior.png | bin | 73380 -> 73415 bytes | |||
| -rw-r--r-- | docs/evidence/p64-overview.png | bin | 110787 -> 110637 bytes | |||
| -rw-r--r-- | docs/evidence/p64-run-log.txt | 2 | ||||
| -rw-r--r-- | docs/evidence/p64-verification.txt | 83 | ||||
| -rw-r--r-- | scripts/analyze-p64.py | 40 | ||||
| -rw-r--r-- | scripts/make-jiangnan-art.py | 31 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/P64EvidenceTests.fs | 27 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/Assets/jiangnan-world.png | bin | 4512 -> 4559 bytes | |||
| -rw-r--r-- | src/LivingVillage.Desktop/VillageArt.fs | 65 |
11 files changed, 164 insertions, 98 deletions
diff --git a/docs/evidence/p64-analysis.txt b/docs/evidence/p64-analysis.txt index e23e475..671f54e 100644 --- a/docs/evidence/p64-analysis.txt +++ b/docs/evidence/p64-analysis.txt @@ -4,20 +4,22 @@ P64(A3 成熟度)证据分析 — 真实 Desktop 实跑 xvfb 1280x720 plan: upper_band_props=11 upper_band_kinds=4 bridge_props=1 bridge_foot_tiles=3 bridge_abutments=4 interior_elements=8 -p64-overview.png 1280x720 mean=86.3 std=33.2 max_dark_block=0 max_white_block=15 -p64-bridge.png 1280x720 mean=86.0 std=33.0 max_dark_block=0 max_white_block=15 +p64-overview.png 1280x720 mean=86.3 std=33.3 max_dark_block=0 max_white_block=15 +p64-bridge.png 1280x720 mean=86.0 std=33.1 max_dark_block=0 max_white_block=15 p64-interior.png 1280x720 mean=118.7 std=54.1 max_dark_block=0 max_white_block=52 overview upper-band non-background ratio (y 120..360) = 0.1812 (P62 基线 0.1030) -bridge region wood-pixel ratio (x 560..720, y 360..500) = 9604/22400 = 0.4288 +bridge region wood-pixel ratio (x 560..720, y 360..500) = 9065/22400 = 0.4047 +rail strips [p64-overview] cam=(384,344) left=0.942 right=0.933 (threshold 0.7) +rail strips [p64-bridge] cam=(384,568) left=1.000 right=0.996 (threshold 0.7) log: p64-shot=p64-bridge tick=2592000 avatar=(1008,912) camera=(384,568) home=Outside log: p64-shot=p64-interior tick=2592056 avatar=(1008,763) camera=(384,568) home=Inside (HomeId 1) log: p64-shot=p64-overview tick=2592000 avatar=(1008,688) camera=(384,344) home=Outside -p64-overview vs p64-bridge mean_abs_diff=12.5 -p64-overview vs p64-interior mean_abs_diff=51.0 -p64-bridge vs p64-interior mean_abs_diff=51.0 +p64-overview vs p64-bridge mean_abs_diff=12.6 +p64-overview vs p64-interior mean_abs_diff=51.1 +p64-bridge vs p64-interior mean_abs_diff=51.1 缺字空白位 total=0(三帧仅中文经 CjkGlyphAtlas 绘制;P61 覆盖测试绿) diff --git a/docs/evidence/p64-bridge.png b/docs/evidence/p64-bridge.png Binary files differindex 5ce6060..5149ff6 100644 --- a/docs/evidence/p64-bridge.png +++ b/docs/evidence/p64-bridge.png diff --git a/docs/evidence/p64-interior.png b/docs/evidence/p64-interior.png Binary files differindex 8806462..fcc8741 100644 --- a/docs/evidence/p64-interior.png +++ b/docs/evidence/p64-interior.png diff --git a/docs/evidence/p64-overview.png b/docs/evidence/p64-overview.png Binary files differindex 775bcb6..414483b 100644 --- a/docs/evidence/p64-overview.png +++ b/docs/evidence/p64-overview.png diff --git a/docs/evidence/p64-run-log.txt b/docs/evidence/p64-run-log.txt index 0d9529a..826da18 100644 --- a/docs/evidence/p64-run-log.txt +++ b/docs/evidence/p64-run-log.txt @@ -9,6 +9,6 @@ new-game=ok seed=42 npcs=30 occupation=农夫 tick=0 p64-plan upper_band_props=11 upper_band_kinds=4 bridge_props=1 bridge_foot_tiles=3 bridge_abutments=4 interior_elements=8 p64-shot=p64-overview tick=2592000 avatar=(1008,688) camera=(384,344) home=Outside p64-shot=p64-bridge tick=2592000 avatar=(1008,912) camera=(384,568) home=Outside -fps=60.0 tick=2592015 pos=(1008,872) +fps=60.0 tick=2592008 pos=(1008,891) p64-shot=p64-interior tick=2592056 avatar=(1008,763) camera=(384,568) home=Inside (HomeId 1) p64-shot=done diff --git a/docs/evidence/p64-verification.txt b/docs/evidence/p64-verification.txt index 690ea31..4942afd 100644 --- a/docs/evidence/p64-verification.txt +++ b/docs/evidence/p64-verification.txt @@ -1,36 +1,31 @@ -P64(A3 成熟度:样板三处视觉缺口)验证记录 -commit 前置:HEAD=128164d(docs tick) - -变更(仅限现有江南样板,不扩展全村) -1. 好处:村庄俯瞰上半部空旷 - - `VillageArt.sampleRenderPlan` 在 local y0..6 / x0..19 补种(复用既有 RenderPropKind 与低饱和配色): - 4 菜畦(2格) + 2 竹丛(3格) + 2 灌木 + 2 花丛,并铺北侧田埂石径 x0..19 y0。 - - 无新增几何占位:全部走既有 GardenSprite/BambooSprite/ShrubSprite/FlowerSprite 与 StonePath。 -2. 桥梁可读性 - - `scripts/make-jiangnan-art.py` 重绘 slot5:栈板桥面 + 两侧通长木栏杆 + 望柱 + 石缘(单格 32px,尺寸不变)。 - - `VillageArt.drawBridge` 在桥两侧水线补 4 块护岸石(`bridgeAbutmentOffsets`,仅江南图集;cc0 不变); - 桥位置与通行逻辑零改动。 -3. 民居内景下半家具 - - `InteriorElementKind` 增 Chair/Stool/BedFoot/Teapot;图集在 392x96 自由区新增 4 个绘制单元, - `interiorSourceRectangle`/`elementDestination` 同步;`sampleRenderPlan.Interior` 4 → 8 件。 - - 进门流程与 HomeMode 语义未改(内部元素纯绘制)。 +P64 整改(Hermes vision 复核不通过)验证记录 +基线:commit 4971a2a(保留,不重写历史);本单在其上整改。 + +整改三点 +1) 桥梁栏杆可读性(原未达目标) + - `scripts/make-jiangnan-art.py` slot5 重绘为「深墨栏杆体 (34,28,30) + 暖亮扶手线 (214,196,158) + + 亮色望柱」三段对比;桥面木色 (119,76,47) 为中间调,栏杆与桥面差 >= 2 档。单格 32px、尺寸不变。 + - analyze-p64.py 新增判据 4「栏杆可辨」:按日志相机定位桥格,取两侧栏杆条带(相对桥格 + x 偏移 1..6 与 26..31,纵向 3 格);栏杆像素口径 = 亮色 r>150&g>130&b>100 或深色 r/g/b<80 + (纯桥面木色 ≈(119,77,47) 两类都不满足),要求左右两条带各自 rail_ratio >= 0.70。 +2) 内景布局 + - `sampleRenderPlan.Interior` 改排到全屋四角/沿墙:窗(18,1) 上墙中、床(33,2)+床尾凳(33,5) 右上、 + 屏风(34,11) 右下、桌(5,8)+椅(8,8)+壶(9,8) 左中、凳(5,11) 左下。 + - 新增纯函数 `interiorElementScreenRect`(drawInterior 改用它),供「不重叠/不压道」回归复用。 + - P64EvidenceTests 新增 `InteriorFurnitureIsSpreadAcrossRoomAndNonOverlapping`:8 件两两不重叠、 + 覆盖 4 个象限、不覆盖房间中心通道。 +3) 回归与收口:见下。 改动文件 -- scripts/make-jiangnan-art.py(桥 slot5 + 内景 4 家具单元) -- src/LivingVillage.Desktop/Assets/jiangnan-world.png, jiangnan-interior.png(脚本重生成,尺寸不变) -- src/LivingVillage.Desktop/VillageArt.fs(类型/源矩形/落位/补种/护岸石/采样) -- src/LivingVillage.Desktop/Game.fs(新增 LV_P64_SHOT 钩子,+~100 行,无删除) -- src/LivingVillage.Desktop.Tests/P64EvidenceTests.fs(新,+3 测试)+ fsproj -- src/LivingVillage.Desktop.Tests/PrototypeTests.fs(内容计数更新:内景元素 4→8;footprint 37→55) -- scripts/analyze-p64.py(新) - -构建 -dotnet build LivingVillage.sln -c Release → 已成功生成,0 错误,1 警告(P40ArtTests.fs(38,28) FS0760,既有) +- scripts/make-jiangnan-art.py、src/LivingVillage.Desktop/Assets/jiangnan-world.png +- scripts/analyze-p64.py(新增判据 4) +- src/LivingVillage.Desktop/VillageArt.fs(slot5 语义注释、interiorElementScreenRect、内景改排) +- src/LivingVillage.Desktop.Tests/P64EvidenceTests.fs(新增布局回归) +- docs/evidence/p64-{overview,bridge,interior}.png、p64-analysis.txt、p64-run-log.txt(重出) -测试 -dotnet test LivingVillage.sln -c Release --no-build -- Desktop 313/313(310 → +3:P64EvidenceTests) -- Kernel 119/119 +构建/测试 +- dotnet build LivingVillage.sln -c Release → 0 错误,1 警告(P40ArtTests.fs FS0760,既有) +- dotnet test LivingVillage.sln -c Release --no-build → Desktop 314/314(313→+1),Kernel 119/119 证据实跑(真实 Desktop + xvfb 1280x720) LV_P64_SHOT=1 LV_LEGACY_MAP=1 xvfb-run -a -s "-screen 0 1280x720x24"(exit=0) @@ -39,23 +34,21 @@ log: p64-shot=p64-overview tick=2592000 avatar=(1008,688) camera=(384,344) home=Outside p64-shot=p64-bridge tick=2592000 avatar=(1008,912) camera=(384,568) home=Outside p64-shot=p64-interior tick=2592056 avatar=(1008,763) camera=(384,568) home=Inside (HomeId 1) -(内景为真实 Sim.step 向门口移动后真实 Interact 进门,tick 前进 56) -scripts/analyze-p64.py /tmp/opencode/lv-p64 docs/evidence/p64-analysis.txt → verdict: PASS - p64-overview.png 1280x720 mean=86.3 std=33.2 黑块=0 白块=0 - p64-bridge.png 1280x720 mean=86.0 std=33.0 黑块=0 白块=0 - p64-interior.png 1280x720 mean=118.7 std=54.1 黑块=0 白块=0 - 俯瞰上半部内容密度(y120..360 非背景占比)= 0.1812,P62 同带基线 0.1030(+76%) - 桥区域木色像素占比(x560..720,y360..500)= 9604/22400 = 0.4288 - 三帧两两不同(min mean_abs_diff=12.5);缺字空白位=0 -scripts/check-no-white-blocks.py 三帧 → checked=3 failures=0 OK +scripts/analyze-p64.py → verdict: PASS + p64-overview 1280x720 mean=86.3 std=33.3 黑块=0 白块=0 + p64-bridge 1280x720 mean=86.0 std=33.1 黑块=0 白块=0 + p64-interior 1280x720 mean=118.7 std=54.1 黑块=0 白块=0 + 俯瞰上半部内容密度 (y120..360) = 0.1812(P62 基线 0.1030) + 桥区域木色占比 = 0.4047 + 栏杆条带占比:overview left=0.942 right=0.933;bridge left=1.000 right=0.996(阈值 0.70) + 三帧两两不同;缺字空白位=0 +check-no-white-blocks.py 三帧 → failures=0 确定性 -LivingVillage.Headless.dll --performance-baseline ×3 → final_digest= -953775FAEB2FDDE97289491AA260BD8D390C571E48A7A13AD2CB6FB7124F7F6C ×3,performance_determinism=PASS +--performance-baseline ×3 → 953775FAEB2FDDE97289491AA260BD8D390C571E48A7A13AD2CB6FB7124F7F6C ×3,PASS 边界 -- 未改 Kernel/Sim.fs、Sim.step、地图/seed 口径、HUD/对话框结构、字形表、进门流程/HomeMode 语义 -- 未引外部素材;未生成 512×384;未 push -- 桥位置与通行逻辑未动(只加护岸石/栏杆贴图) -- 逐帧视觉复核仍受本写入模型不支持图像输入限制(P60 G3),需 vision 能力方复核 +- 未改 Kernel/Sim、HUD 结构、字形表、外部素材、进门流程/HomeMode、512x384;未 push +- 逐帧视觉复核仍受本写入模型不支持图像输入限制(P60 G3)——本单按 Hermes vision 结论整改并量化; + 最终可否通过仍需 vision 能力方复核(范围外,不做) diff --git a/scripts/analyze-p64.py b/scripts/analyze-p64.py index f1b0e0c..243d7dd 100644 --- a/scripts/analyze-p64.py +++ b/scripts/analyze-p64.py @@ -10,8 +10,13 @@ (P62 同带基线 0.103;HUD 顶部条带 y<120 不计,避免把 UI 当内容)。 3. 桥近景:桥所在区域 (x 560..720, y 360..500) 的「木色像素」占比 >= 0.20, 证明桥面/栏杆木色进入实拍帧。 - 4. 三帧均 1280x720、非空、无 64x64 纯白块、无 24x24 纯黑块,且两两不同。 - 5. 缺字空白位:中文经 CjkGlyphAtlas 绘制,P61 已证覆盖(P61GlyphCoverageTests 绿)。 + 4. 栏杆可辨(整改新增):以日志相机定位桥格(世界 tile x31,y29..31),取两侧栏杆条带 + (相对桥格 x 偏移 1..6 与 26..31,纵向 3 格)。栏杆像素口径 = 亮色 (r>150,g>130,b>100) + 或深色 (r<80,g<80,b<80)——纯桥面木色 (≈119,77,47) 两类都不满足,故该指标直接区分 + 「有栏杆」与「只有桥面木板」。要求左右两条带各自 rail_ratio >= 0.70。 + 在俯瞰帧(1x 全帧)与桥近景帧上都成立。 + 5. 三帧均 1280x720、非空、无 64x64 纯白块、无 24x24 纯黑块,且两两不同。 + 6. 缺字空白位:中文经 CjkGlyphAtlas 绘制,P61 已证覆盖(P61GlyphCoverageTests 绿)。 Usage: python3 scripts/analyze-p64.py <record-dir> <out-txt> Exit 0 pass / 1 fail / 2 usage. @@ -73,6 +78,26 @@ def non_background_ratio(img, y0, y1): return 1.0 - float(counts.max()) / float(counts.sum()) +BRIDGE_TILE_X = 31 +BRIDGE_TILE_Y0 = 29 +BRIDGE_TILE_H = 3 +RAIL_THRESHOLD = 0.70 + + +def rail_strip_ratios(img, camx, camy): + """Return (left_ratio, right_ratio) of rail-colored pixels in the two rail strips.""" + bx = BRIDGE_TILE_X * 32 - camx + y0 = BRIDGE_TILE_Y0 * 32 - camy + y1 = y0 + BRIDGE_TILE_H * 32 + results = [] + for sx0 in (bx + 1, bx + 26): + reg = img[y0:y1, sx0 : sx0 + 5].astype(np.int16) + r, g, b = reg[:, :, 0], reg[:, :, 1], reg[:, :, 2] + rail = ((r > 150) & (g > 130) & (b > 100)) | ((r < 80) & (g < 80) & (b < 80)) + results.append(float(rail.mean()) if rail.size else 0.0) + return results[0], results[1] + + def main(): if len(sys.argv) != 3: print(__doc__) @@ -151,6 +176,17 @@ def main(): lines.append(f"bridge region wood-pixel ratio (x {x0}..{x1}, y {y0}..{y1}) = {wood}/{total} = {wood/total:.4f}") if wood / total < 0.20: failures.append(f"bridge wood ratio {wood/total:.4f} < 0.20") + for name in ("p64-overview", "p64-bridge"): + if name not in stats or name not in shots: + continue + camx = int(float(shots[name].group(5))) + camy = int(float(shots[name].group(6))) + left, right = rail_strip_ratios(stats[name], camx, camy) + lines.append(f"rail strips [{name}] cam=({camx},{camy}) left={left:.3f} right={right:.3f} (threshold {RAIL_THRESHOLD})") + if left < RAIL_THRESHOLD: + failures.append(f"{name} left rail ratio {left:.3f} < {RAIL_THRESHOLD}") + if right < RAIL_THRESHOLD: + failures.append(f"{name} right rail ratio {right:.3f} < {RAIL_THRESHOLD}") lines.append("") for name, shot in sorted(shots.items()): diff --git a/scripts/make-jiangnan-art.py b/scripts/make-jiangnan-art.py index c28244f..caf0c01 100644 --- a/scripts/make-jiangnan-art.py +++ b/scripts/make-jiangnan-art.py @@ -192,24 +192,29 @@ def build_world_atlas(): # P64 桥:在既有栈板桥面上补可读的两侧栏杆与望柱(桥沿纵向铺 3 格,栏杆随格连续), # 并保留桥面横铺板纹。低饱和木色,仍为 slot 5 单格、尺寸不变,不改位置/通行。 + # P64 桥(整改):栏杆须在 1x 俯瞰下与桥面木色差 >= 2 档,故改用「深墨栏杆体 + 暖亮扶手线 + + # 亮色望柱」三段对比:桥面 (119,76,47) 为中间调,栏杆体远暗、扶手/望柱远亮。仍为 slot5 单格。 x0 = slot(5) c.rect(x0, 0, 32, 32, wood) + # 桥面栈板(保留中间调木色) for y in (0, 8, 16, 24): - c.rect(x0 + 5, y, 22, 1, wood_light) + c.rect(x0 + 6, y, 20, 1, wood_light) for y in (7, 15, 23, 31): - c.rect(x0 + 5, y, 22, 1, wood_dark) - bridge_rail = (132, 84, 52, 255) - bridge_rail_light = (176, 122, 71, 255) - bridge_rail_dark = (83, 52, 39, 255) + c.rect(x0 + 6, y, 20, 1, wood_dark) + bridge_rail_ink = (34, 28, 30, 255) + bridge_rail_light = (214, 196, 158, 255) bridge_kerb = (150, 146, 138, 255) - for bx in (1, 27): - c.rect(x0 + bx, 0, 4, 32, bridge_rail) - c.rect(x0 + bx, 0, 1, 32, bridge_rail_light) - c.rect(x0 + bx + 3, 0, 1, 32, bridge_rail_dark) - for by in (2, 10, 18, 26): - c.rect(x0 + 1, by, 5, 3, bridge_rail_dark) - c.rect(x0 + 27, by, 5, 3, bridge_rail_dark) - for bx in (5, 26): + for bx in (1, 26): + # 通长栏杆体(深墨,宽 5px) + c.rect(x0 + bx, 0, 5, 32, bridge_rail_ink) + # 扶手亮线(沿栏杆全长,与桥面亮 2 档以上) + c.rect(x0 + bx + 1, 0, 1, 32, bridge_rail_light) + # 望柱:每格两根,暖亮柱头压住深墨栏体 + for by in (1, 17): + c.rect(x0 + bx, by, 5, 5, bridge_rail_light) + c.rect(x0 + bx, by + 5, 5, 1, bridge_rail_ink) + # 桥面与栏杆交界的石缘 + for bx in (6, 25): c.rect(x0 + bx, 0, 1, 32, bridge_kerb) x0 = slot(6) diff --git a/src/LivingVillage.Desktop.Tests/P64EvidenceTests.fs b/src/LivingVillage.Desktop.Tests/P64EvidenceTests.fs index 9000a04..714168a 100644 --- a/src/LivingVillage.Desktop.Tests/P64EvidenceTests.fs +++ b/src/LivingVillage.Desktop.Tests/P64EvidenceTests.fs @@ -15,6 +15,9 @@ type P64EvidenceTests () = let samplePlan () = VillageArt.sampleRenderPlan () + let rectsOverlap (a: Microsoft.Xna.Framework.Rectangle) (b: Microsoft.Xna.Framework.Rectangle) = + a.X < b.X + b.Width && b.X < a.X + a.Width && a.Y < b.Y + b.Height && b.Y < a.Y + a.Height + [<TestMethod>] member _.OverviewUpperBandIsDenselyPopulated () = let plan = samplePlan () @@ -54,3 +57,27 @@ type P64EvidenceTests () = Assert.AreEqual<int>(8, plan.Interior.Elements.Length) for kind in [ VillageArt.Chair; VillageArt.Stool; VillageArt.BedFoot; VillageArt.Teapot ] do Assert.IsTrue(kinds |> List.contains kind, sprintf "内景缺少 %A" kind) + + /// P64 整改:家具分布全屋四角、两两不重叠、不压中央通道。red→green 门。 + [<TestMethod>] + member _.InteriorFurnitureIsSpreadAcrossRoomAndNonOverlapping () = + let plan = samplePlan () + let layout = VillageArt.interiorLayout 1280 720 + let placed = + plan.Interior.Elements + |> List.map (fun element -> element.Kind, VillageArt.interiorElementScreenRect layout element) + for i in 0 .. placed.Length - 2 do + for j in i + 1 .. placed.Length - 1 do + let (kindA, a) = placed.[i] + let (kindB, b) = placed.[j] + Assert.IsFalse(rectsOverlap a b, sprintf "内景家具 %A 与 %A 重叠" kindA kindB) + let midX = layout.RoomX + layout.RoomWidth / 2 + let midY = layout.RoomY + layout.RoomHeight / 2 + let quadrant (r: Microsoft.Xna.Framework.Rectangle) = + (r.X + r.Width / 2 < midX, r.Y + r.Height / 2 < midY) + let quads = placed |> List.map (fun (_, r) -> quadrant r) |> List.distinct + Assert.IsTrue(quads.Length >= 4, "家具须分布在四个象限(四角/沿墙)") + // 中央通道(房间中心点)不得被家具矩形覆盖。 + for kind, r in placed do + let coversCenter = r.X <= midX && midX < r.X + r.Width && r.Y <= midY && midY < r.Y + r.Height + Assert.IsFalse(coversCenter, sprintf "%A 压住中央通道" kind) diff --git a/src/LivingVillage.Desktop/Assets/jiangnan-world.png b/src/LivingVillage.Desktop/Assets/jiangnan-world.png Binary files differindex 90d21e3..220ae9f 100644 --- a/src/LivingVillage.Desktop/Assets/jiangnan-world.png +++ b/src/LivingVillage.Desktop/Assets/jiangnan-world.png diff --git a/src/LivingVillage.Desktop/VillageArt.fs b/src/LivingVillage.Desktop/VillageArt.fs index 031b577..76abc4a 100644 --- a/src/LivingVillage.Desktop/VillageArt.fs +++ b/src/LivingVillage.Desktop/VillageArt.fs @@ -278,16 +278,17 @@ module VillageArt = renderProp FlowerBush 2 3 renderProp FlowerBush 6 1 ] Interior = + // P64 整改:家具改排至全屋四角/沿墙(左上窗、右上床、右墙屏风、左中桌+椅、 + // 桌旁壶、左下凳),两两不重叠且不压角色进场中央通道。 { Elements = - [ interiorElement Window 2 2 - interiorElement Table 4 4 - interiorElement Bed 7 3 - interiorElement Screen 7 6 - // P64 内景下半部家具补件:椅/凳/床尾凳/柜旁壶。 - interiorElement BedFoot 5 4 - interiorElement Chair 2 5 - interiorElement Stool 6 5 - interiorElement Teapot 4 5 ] } } + [ interiorElement Window 18 1 + interiorElement Bed 33 2 + interiorElement BedFoot 33 5 + interiorElement Table 5 8 + interiorElement Chair 8 8 + interiorElement Teapot 9 8 + interiorElement Stool 5 11 + interiorElement Screen 34 11 ] } } let worldTile (plan: JiangnanRenderPlan) (localPosition: TilePosition) : TilePosition = { X = plan.Origin.X + localPosition.X @@ -1447,6 +1448,29 @@ module VillageArt = { PixelPosition.X = float32 (layout.RoomX + tileX * Sim.tilePixels + Sim.tilePixels / 2) Y = float32 (floorY + (tileY + 1) * Sim.tilePixels) } + /// P64 整改:内景单个元素在屏幕上的矩形(纯函数,供绘制与「不重叠/不挡道」回归共用)。 + let interiorElementScreenRect (layout: InteriorLayout) (element: InteriorElement) : Microsoft.Xna.Framework.Rectangle = + let floorY = interiorFloorY layout + let baseY = floorY + (element.Position.Y + 1) * Sim.tilePixels + match element.Kind with + | Window -> + XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 8, layout.RoomY + 24, 48, 40) + | Table -> + XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 16, baseY - 48, 64, 48) + | Bed -> + XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 8, baseY - 96, 48, 96) + | Screen -> + XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels, baseY - 96, 32, 96) + // P64 补件按其像素体量落位(低矮家具贴近地面线)。 + | Chair -> + XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 8, baseY - 44, 28, 44) + | Stool -> + XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 8, baseY - 22, 24, 22) + | BedFoot -> + XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 16, baseY - 14, 56, 18) + | Teapot -> + XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 8, baseY - 16, 20, 16) + let drawInterior (spriteBatch: SpriteBatch) (pixel: Texture2D) @@ -1481,29 +1505,8 @@ module VillageArt = let rugY = floorY + 96 spriteBatch.Draw(textures.InteriorAtlas, XnaRectangle(rugX, rugY, 96, 64), rugSource, tint) - let elementDestination (element: InteriorElement) : XnaRectangle = - let baseY = floorY + (element.Position.Y + 1) * Sim.tilePixels - match element.Kind with - | Window -> - XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 8, layout.RoomY + 24, 48, 40) - | Table -> - XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 16, baseY - 48, 64, 48) - | Bed -> - XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 8, baseY - 96, 48, 96) - | Screen -> - XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels, baseY - 96, 32, 96) - // P64 补件按其像素体量落位(低矮家具贴近地面线)。 - | Chair -> - XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 8, baseY - 44, 28, 44) - | Stool -> - XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 8, baseY - 22, 24, 22) - | BedFoot -> - XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 16, baseY - 14, 56, 18) - | Teapot -> - XnaRectangle(layout.RoomX + element.Position.X * Sim.tilePixels - 8, baseY - 16, 20, 16) - for element in plan.Elements |> List.sortBy (fun element -> element.Position.Y, element.Position.X) do - spriteBatch.Draw(textures.InteriorAtlas, elementDestination element, interiorSourceRectangle element.Kind, tint) + spriteBatch.Draw(textures.InteriorAtlas, interiorElementScreenRect layout element, interiorSourceRectangle element.Kind, tint) let chestX = layout.RoomX + layout.RoomWidth - 44 let chestY = layout.RoomY + layout.RoomHeight - 36 |
