summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-29 06:12:57 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-29 06:12:57 +0800
commitec49a835361fd0f6d92a41204dcc9547073b46ee (patch)
treee487c692e327484bfc813db2f65cb6a43a13b4cc
parent166fbeb4efd1c36c5c8289defdf480c89835d540 (diff)
downloadliving-village-ec49a835361fd0f6d92a41204dcc9547073b46ee.tar.gz
p64: 整改桥梁栏杆可辨(深墨栏体/亮扶手/望柱)与内景四角布局(Hermes vision 复核)
-rw-r--r--docs/evidence/p64-analysis.txt14
-rw-r--r--docs/evidence/p64-bridge.pngbin111350 -> 111188 bytes
-rw-r--r--docs/evidence/p64-interior.pngbin73380 -> 73415 bytes
-rw-r--r--docs/evidence/p64-overview.pngbin110787 -> 110637 bytes
-rw-r--r--docs/evidence/p64-run-log.txt2
-rw-r--r--docs/evidence/p64-verification.txt83
-rw-r--r--scripts/analyze-p64.py40
-rw-r--r--scripts/make-jiangnan-art.py31
-rw-r--r--src/LivingVillage.Desktop.Tests/P64EvidenceTests.fs27
-rw-r--r--src/LivingVillage.Desktop/Assets/jiangnan-world.pngbin4512 -> 4559 bytes
-rw-r--r--src/LivingVillage.Desktop/VillageArt.fs65
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
index 5ce6060..5149ff6 100644
--- a/docs/evidence/p64-bridge.png
+++ b/docs/evidence/p64-bridge.png
Binary files differ
diff --git a/docs/evidence/p64-interior.png b/docs/evidence/p64-interior.png
index 8806462..fcc8741 100644
--- a/docs/evidence/p64-interior.png
+++ b/docs/evidence/p64-interior.png
Binary files differ
diff --git a/docs/evidence/p64-overview.png b/docs/evidence/p64-overview.png
index 775bcb6..414483b 100644
--- a/docs/evidence/p64-overview.png
+++ b/docs/evidence/p64-overview.png
Binary files differ
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
index 90d21e3..220ae9f 100644
--- a/src/LivingVillage.Desktop/Assets/jiangnan-world.png
+++ b/src/LivingVillage.Desktop/Assets/jiangnan-world.png
Binary files differ
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