From 16d21765768fcce64f52351961dfc74e07a7dc65 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 29 Oct 2026 08:53:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0(=E8=BF=AD=E4=BB=A343=20T5?= =?UTF-8?q?=E2=80=93T8+V5=E2=80=93V9):=20=E6=88=98=E6=96=97=E4=B8=80?= =?UTF-8?q?=E5=B1=8F=E4=BA=94=E9=A1=B9=E5=AE=9A=E5=80=BC=E8=90=BD=E7=A0=81?= =?UTF-8?q?=E2=80=94=E2=80=94=E8=A1=80=E6=9D=A1=E5=A1=AB=E5=85=85#A8321F?= =?UTF-8?q?=E7=BB=8FUiKit.HP=5FBAR=5FFILL=E6=96=B0=E5=B8=B8=E9=87=8F(?= =?UTF-8?q?=C2=A74.2=E8=AF=AD=E4=B9=89=E7=89=A9=E4=BB=B6=E6=98=BE=E5=BC=8F?= =?UTF-8?q?=E5=8F=A5,#689CE8=E9=80=80=E5=BD=B9=E5=80=BC=E5=85=A8=E6=BA=90?= =?UTF-8?q?=E9=9B=B6=E6=AE=8B=E7=95=99)/=E6=9D=A1=E5=BA=95=E6=94=B6HP=5FBA?= =?UTF-8?q?R=5FTRACK(=E8=A7=86=E5=9B=BE=E5=8E=9F=E8=A3=B8=E5=80=BC?= =?UTF-8?q?=E5=8D=95=E7=82=B9=E5=8C=96)/=E9=A1=BA=E4=BD=8D=E5=BE=BD?= =?UTF-8?q?=E6=A0=87=E6=8F=90=E6=A1=A3FS=5FHINT=2014(=C2=A77.4=E6=B8=85?= =?UTF-8?q?=E5=8D=95=E5=A4=96=E6=8F=90=E6=A1=A3)/=E6=8A=A4=E7=94=B2?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E4=B8=89=E9=94=AE=E6=8D=A224=C3=9724=20PNG(U?= =?UTF-8?q?iKit.TEX=5FARMOR=5F*=E5=8D=95=E7=82=B9,=E7=BC=BA=E4=BD=8D?= =?UTF-8?q?=E5=9B=9E=E9=80=80=E5=AD=97=E5=BD=A2=E4=B8=8E=E7=AB=8B=E7=BB=98?= =?UTF-8?q?=E7=BC=BA=E4=BD=8D=E9=93=BE=E5=90=8C=E6=9E=84,=E6=8C=82?= =?UTF-8?q?=E8=BD=BD=E5=BA=95=3D=E5=90=8D=E5=AD=97=E8=A1=8C=E7=B4=A0?= =?UTF-8?q?=E5=BA=95)/=E5=90=8D=E5=AD=97=E8=A1=8C=E6=95=B4=E8=A1=8C?= =?UTF-8?q?=E5=9E=ABHP=5FBAR=5FBACK=E7=B4=A0=E5=BA=95=E7=89=8C=E4=B8=94?= =?UTF-8?q?=E5=9F=BA=E7=BA=BF=E4=B8=8A=E7=A7=BB=E8=87=B3=E5=9C=B0=E5=8F=B0?= =?UTF-8?q?=E9=A1=B6=E4=B8=8A=E6=96=B9(=C2=A76.1/=C2=A77.2=E6=96=87?= =?UTF-8?q?=E5=AD=97=E7=A6=81=E5=8E=8B=E5=9C=B0=E5=8F=B0,=E5=87=A0?= =?UTF-8?q?=E4=BD=95=E5=8D=95=E7=82=B9=5Fname=5Fplate=5Frect+name=5Fline?= =?UTF-8?q?=5Fprobe=E6=89=A9=E5=87=A0=E4=BD=95=E4=B8=8E=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E9=9D=A2,ui=5Fprobe=E9=9D=9E=E7=A9=BA=E5=A5=91=E7=BA=A6?= =?UTF-8?q?=E4=BF=9D=E6=8C=81)/=E5=9C=B0=E5=8F=B0=E4=BB=B6TEX=5FSTAGE=5FPL?= =?UTF-8?q?AZA=E4=B9=9D=E5=AE=AB=E6=A0=BC=E6=A8=AA=E5=90=91=E6=8B=89?= =?UTF-8?q?=E4=BC=B8=E6=8E=A5=E8=A3=85(=E7=BC=BA=E4=BD=8D=E5=9B=9E?= =?UTF-8?q?=E9=80=80=E7=A8=8B=E5=BA=8F=E5=8C=96=E4=B8=89=E6=AE=B5);?= =?UTF-8?q?=E4=B8=89=E6=9E=9A=E6=8A=A4=E7=94=B2PNG=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E5=8C=96=E4=BA=A7=E5=87=BA=E5=85=A5=E5=8C=85;=E6=96=B0?= =?UTF-8?q?=E5=8D=95=E5=85=83=E5=A5=97=E4=BB=B6standee=5Fpresent=5Fvalues?= =?UTF-8?q?=5Ftest(V5=E2=80=93V9=E7=9C=9F=E5=AE=9E=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E6=96=AD=E8=A8=8018=E6=9D=A1:=E5=B8=B8=E9=87=8F=E5=80=BC+?= =?UTF-8?q?=E6=BA=90=E7=A0=81=E9=94=9A+=E6=8E=A2=E9=92=88=E5=87=A0?= =?UTF-8?q?=E4=BD=95+ResourceLoader=E8=A3=85=E8=BD=BD=E9=9D=A2)=E7=99=BB?= =?UTF-8?q?=E8=AE=B0run=5Fall=20manifest;STAND=5FSTONE=5FRIM=E4=BF=9D?= =?UTF-8?q?=E7=95=99(board=5Fcell=5Funit=E5=8F=A3=E6=B2=BF=E9=AB=98?= =?UTF-8?q?=E5=85=89=E5=9C=A8=E5=BD=B9)=E4=B8=8ESTAND=5FSTONE=5FEDGE?= =?UTF-8?q?=E4=BF=9D=E7=95=99(=E7=BC=BA=E4=BD=8D=E5=9B=9E=E9=80=80?= =?UTF-8?q?=E8=B7=AF=E5=BE=84)=E2=80=94=E2=80=94art-bible=E4=B8=89?= =?UTF-8?q?=E5=A4=84=E6=8D=A2=E8=89=B2=E5=90=88=E5=B9=B6=E4=B9=8B=E5=9C=B0?= =?UTF-8?q?=E5=8F=B0=E5=8D=8A=E8=BE=B9=E7=BB=8F=E7=BA=B9=E7=90=86=E4=BB=B6?= =?UTF-8?q?=E6=89=A7=E8=A1=8C,=E5=B8=B8=E9=87=8F=E5=BD=BB=E5=BA=95?= =?UTF-8?q?=E9=80=80=E5=BD=B9=E5=BD=92=E6=96=87=E6=A1=A3=E6=89=B9=E8=A3=81?= =?UTF-8?q?=E5=86=B3(=E5=8F=8D=E5=93=BA=E7=82=B9);=E9=9A=8F=E6=B5=8B?= =?UTF-8?q?=E5=8F=91=E7=8E=B0=E6=97=A2=E6=9C=89=E6=BD=9C=E5=9C=A8=E7=BC=BA?= =?UTF-8?q?=E9=99=B7:hp=5Fvalue=20tween=E7=82=B9=E5=8F=B7=E5=AD=90?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E8=B7=AF=E5=BE=84=E9=9D=9E=E6=B3=95=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E7=A9=BA=E8=BD=AC(=E6=9C=AC=E6=89=B9=E6=9C=AA?= =?UTF-8?q?=E8=A7=A6=E7=A2=B0=E8=AF=A5=E8=A1=8C,=E5=8F=8D=E5=93=BA?= =?UTF-8?q?=E7=82=B9=E7=99=BB=E8=AE=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/assets/ui/icons/ui_armor_heavy.png | Bin 0 -> 152 bytes game/assets/ui/icons/ui_armor_light.png | Bin 0 -> 193 bytes game/assets/ui/icons/ui_armor_ward.png | Bin 0 -> 148 bytes game/presentation/views/standee_body.gd | 126 +++++++++++++----- game/presentation/views/ui_kit.gd | 11 +- game/tests/run_all.gd | 1 + .../standee_present_values_test.gd | 84 ++++++++++++ .../standee_present_values_test.gd.uid | 1 + 8 files changed, 189 insertions(+), 34 deletions(-) create mode 100644 game/assets/ui/icons/ui_armor_heavy.png create mode 100644 game/assets/ui/icons/ui_armor_light.png create mode 100644 game/assets/ui/icons/ui_armor_ward.png create mode 100644 game/tests/unit/presentation/standee_present_values_test.gd create mode 100644 game/tests/unit/presentation/standee_present_values_test.gd.uid diff --git a/game/assets/ui/icons/ui_armor_heavy.png b/game/assets/ui/icons/ui_armor_heavy.png new file mode 100644 index 0000000000000000000000000000000000000000..abaca452c2c6237d6371157d23a2a2c2df3629b2 GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj@t!V@Ar*6y6C_w!7?h2UNBI7- zw>z4iZ=0&(D$B89fj8UotI1cll!YlrzM3JC!85~6SK>p6$rnatJ5MJ^N1q7^DJCTX zJ<84xPer^)Fp(EvS?p-?!SRU4>z-o=c!!>WXRstzz(W^>bP0l+XkKpwKTD literal 0 HcmV?d00001 diff --git a/game/assets/ui/icons/ui_armor_light.png b/game/assets/ui/icons/ui_armor_light.png new file mode 100644 index 0000000000000000000000000000000000000000..48b63d4f64689f9340b3f712670b97e103e67ef4 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj9iA?ZAr*6y6C_v{H%Jtexu36( zDe}2u^hbx!TVvA$ommTS=qTQ9+8n`S{7oa#Mf|Y90k)p2OEy;3Ut8j|r>&WR+fZzQ zG#gW9{POT( z=YNB34RcuIjAuw>@XW}_J*c6;VGz`?ah8cq>jdU=_o_~HTxWWs;=$+Ozs=cx=B5SQ w7Q0z4-#Yg&>_)A^!i2+YZFO2wtjzojtGxu?KYsky6le{Dr>mdKI;Vst05`oenE(I) literal 0 HcmV?d00001 diff --git a/game/presentation/views/standee_body.gd b/game/presentation/views/standee_body.gd index ed2b1f7e..f73876e0 100644 --- a/game/presentation/views/standee_body.gd +++ b/game/presentation/views/standee_body.gd @@ -8,12 +8,10 @@ extends Node2D const CHARGE_SKILL_IDS := ["heavy"] -# 护甲类型图标占位——形状+色相双通道(art-bible):轻甲=◇ 灰石中性 / 魔法护甲=◆ 小人蓝 / -# 重甲=▣ 深金(避开 Boss 专属紫赤与妨害紫)。 +# 护甲类型图标三键(light/ward/heavy)——终稿 = 三枚 24×24 PNG(UiKit.TEX_ARMOR_*,art-bible §5.3 +# 工单:形状承载身份、色相不承载身份;挂载底色 = 名字行素底 HP_BAR_BACK)。字形占位仅作素材缺位 +# 回退(与立绘缺位回退链同构——素材不入 git,缺位不炸)。 const ARMOR_ICONS := {"light": "◇", "ward": "◆", "heavy": "▣"} -# 名行/底座纵向预算余量(盒底−1px「声明值刚好=容器值」系高危写法; -# ≥8px 硬余量 + 字形下探预算 ⇒ 12;取值不属色板,不进 ui_kit) -const STANDEE_FOOT_MARGIN := 12.0 const DEATH_SHARD_N := 5 # 碎块数(§8.4 冻结值) const HIT_SPARK_N := 6 # 命中火花数(打击瞬间可读性一拍) @@ -97,9 +95,18 @@ func intent_probe() -> Dictionary: return {"visible": st._intent_visible, "text": st._intent_glyph} -# 名字行探针出口 +# 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、 +# 几何 = 素底牌矩形与文字基线、素底色——V6 判据 = baseline_y < plinth_top_y) func name_line_probe() -> Dictionary: - return {"name": st._display_name, "armor_icon": String(ARMOR_ICONS.get(st._armor, "◇"))} + var f := _efont() + var tex := UiKit.load_tex(_armor_tex_path(st._armor)) + var plate := _name_plate_rect(f, tex) + return {"name": st._display_name, + "armor_icon": _armor_tex_path(st._armor) if tex != null else String(ARMOR_ICONS.get(st._armor, "◇")), + "armor_tex_bound": tex != null, + "plate": plate, "baseline_y": _name_baseline(plate).y, + "plinth_top_y": _pedestal_rect().position.y, + "plate_color": UiKit.HP_BAR_BACK} # ---- setter 面(presenter/组合根零改排) ---- @@ -420,6 +427,36 @@ func _pedestal_rect() -> Rect2: return Rect2(18.0, box.y - 18.0, box.x - 36.0, 12.0) +# 护甲图标纹理路径(甲档键 → UiKit.TEX_ARMOR_*——显式 match 单点,禁动态拼键取常量; +# 未知键回退轻甲路径,由 load_tex exists 守卫接住) +func _armor_tex_path(armor: String) -> String: + match armor: + "ward": + return UiKit.TEX_ARMOR_WARD + "heavy": + return UiKit.TEX_ARMOR_HEAVY + _: + return UiKit.TEX_ARMOR_LIGHT + + +# 名字行素底牌矩形(_draw 与 name_line_probe 共用几何——单一来源防两处漂移): +# 牌高 26 = 护甲图标 24px(甲类 1× 禁缩放)+ 上下各 1px;牌底沿 = 地台顶(box.y-18)—— +# 文字禁直接压地台(§6.1/§7.2 素底落法);牌宽 = 行宽(图标+间距+名字)+ 8,水平居中 +func _name_plate_rect(f: Font, tex: Texture2D) -> Rect2: + var icon_w := 24.0 + if tex == null: + icon_w = f.get_string_size(String(ARMOR_ICONS.get(st._armor, "◇")), + HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION).x + var name_w := f.get_string_size(st._display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION).x + var total_w := icon_w + 3.0 + name_w + return Rect2((box.x - total_w - 8.0) * 0.5, box.y - 18.0 - 26.0, total_w + 8.0, 26.0) + + +# 名字行文字基线(牌内几何单点——图标 24px 垂直居中牌内〔中心=牌顶+13〕,12px 字形基线=中心+4) +func _name_baseline(plate: Rect2) -> Vector2: + return plate.position + Vector2(4.0, 17.0) + + # 绘制内容矩形(世界局部;ui_struct_probe ⑥standee「不出框+下余量≥8px」断言读数面—— # 最低绘制元素 = 底座下缘) func content_rect() -> Rect2: @@ -438,8 +475,9 @@ func _draw() -> void: ## 地面域:椭圆落影(圣经 §6.1 地台落影压脚:方向稿椭圆 (0,0,0,70),压在地台 ## 上缘周围;24 段多边形近似椭圆,中心=台面上方 6px、横半径=台宽一半) -## + 石板地台(v3.5——方向稿取色:板身/描边/顶缘受光线 2px——取色单点收 UiKit.STAND_STONE 族 -## ;几何沿用现行底座位〔margin 18/高 12——按方向稿对齐〕) +## + 石板地台(§6.1 近景地台独立件:TEX_STAGE_PLAZA 源 48×12 九宫格横向拉伸、顶缘受光线 2px +## 不随拉伸变形、跨三关恒定;素材缺位回退程序化三段绘制〔STAND_STONE 族方向稿取色,几何同源 +## _pedestal_rect——与 panel_skinned 缺位回退同构〕) func _draw_ground(f: Font) -> void: var sh_center := Vector2(box.x * 0.5, box.y - 24.0) var sh_rx := (box.x - 36.0) * 0.5 @@ -450,26 +488,41 @@ func _draw_ground(f: Font) -> void: pts.append(sh_center + Vector2(cos(ang) * sh_rx, sin(ang) * sh_ry)) draw_colored_polygon(pts, Color(0, 0, 0, 0.27)) var ped := _pedestal_rect() - draw_rect(ped, UiKit.STAND_STONE, true) - draw_rect(ped, UiKit.STAND_STONE_EDGE, false, 1.0) - draw_rect(Rect2(ped.position.x, ped.position.y, ped.size.x, 2.0), UiKit.STAND_STONE_RIM, true) + var plaza := UiKit.load_tex(UiKit.TEX_STAGE_PLAZA) + if plaza != null: + _draw_plinth_stretched(plaza, ped) + else: + draw_rect(ped, UiKit.STAND_STONE, true) + draw_rect(ped, UiKit.STAND_STONE_EDGE, false, 1.0) + draw_rect(Rect2(ped.position.x, ped.position.y, ped.size.x, 2.0), UiKit.STAND_STONE_RIM, true) -## 血条域(直角递减):敌附顺位徽标(GOLD_BRIGHT ①②③);血条几何左 8 / 右 -8; -## 血量数字「当前/最大」居中压条(白字+深描边双通道——11px 于 10px 条带垂直居中; -## 数字=目标值即时刷新,条 tween 只管填充宽度;死残位 0/max 照常可读) +## 地台九宫格横向拉伸域(循环体提函数——左右 4px 端头切片 + 中段拉伸;纵向恒 12px 源档, +## 顶缘受光线 2px 落纵向不缩放区,工单 §5.2 工艺要点) +func _draw_plinth_stretched(tex: Texture2D, ped: Rect2) -> void: + draw_texture_rect_region(tex, Rect2(ped.position, Vector2(4.0, 12.0)), Rect2(0, 0, 4, 12)) + draw_texture_rect_region(tex, Rect2(ped.position + Vector2(4.0, 0.0), + Vector2(ped.size.x - 8.0, 12.0)), Rect2(4, 0, 40, 12)) + draw_texture_rect_region(tex, Rect2(ped.position + Vector2(ped.size.x - 4.0, 0.0), + Vector2(4.0, 12.0)), Rect2(44, 0, 4, 12)) + + +## 血条域(直角递减):敌附顺位徽标(GOLD_BRIGHT ①②③,字号 FS_HINT——art-bible §7.4 清单外 +## 11–13px 提档归档);血条几何左 8 / 右 -8;血条填充 = 危险红 HP_BAR_FILL(§4.2 语义物件—— +## 不因敌我分色);条底 = HP_BAR_TRACK;血量数字「当前/最大」居中压条(白字+深描边双通道——11px 于 +## 10px 条带垂直居中;数字=目标值即时刷新,条 tween 只管填充宽度;死残位 0/max 照常可读) func _draw_hp_bar(f: Font) -> void: var bar_x := 8.0 if st._kind == "enemy": var order_txt := String("①②③"[clampi(st._order - 1, 0, 2)]) f.draw_string(get_canvas_item(), Vector2(bar_x, 12), order_txt, - HORIZONTAL_ALIGNMENT_LEFT, -1, 13, UiKit.GOLD_BRIGHT) + HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.GOLD_BRIGHT) bar_x += 20.0 var bar_w := box.x - 8.0 - bar_x - draw_rect(Rect2(bar_x, 2, bar_w, 10), Color(0.10, 0.10, 0.11)) # 条底(引擎默认主题深底近似平移) + draw_rect(Rect2(bar_x, 2, bar_w, 10), UiKit.HP_BAR_TRACK) # 条底(值单点收 ui_kit) var fill_w := bar_w * st.hp_value if fill_w > 0.0: - draw_rect(Rect2(bar_x, 2, fill_w, 10), Color(0.41, 0.61, 0.91)) # 直角填充(默认主题蓝近似——直角递减) + draw_rect(Rect2(bar_x, 2, fill_w, 10), UiKit.HP_BAR_FILL) # 直角填充(危险红——§4.2 血条显式句) var hp_txt := "%d/%d" % [st._hp_cur, int(st._hp_max)] var hts: Vector2 = f.get_string_size(hp_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 11) # 对比度垫牌(白 α.95 直压蓝条 2.67:1 违 §7.2; @@ -513,19 +566,26 @@ func _draw_intent_badge(f: Font) -> void: draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE) -## 底座下 护甲类型图标+名字 常驻行(敌 only——AC 数值不展示,类型图标化) +## 名字行域(敌 only——AC 数值不展示,类型图标化):整行垫 HP_BAR_BACK 素底牌(§6.1/§7.2 +## 「文字禁直接压地台」素底落法——牌底沿 = 地台顶,几何单点 _name_plate_rect);护甲图标 = +## 24×24 PNG(甲类 1×,缺位回退字形,色随甲档亮档族);名字 = 暖白(暗底双色墨制) func _draw_armor_name_line(f: Font) -> void: - var armor_txt := String(ARMOR_ICONS.get(st._armor, "◇")) - var col: Color = UiKit.WARM_FAINT # 轻甲 = 暖白弱化(v3.5 暗底双色墨制) - match st._armor: - "ward": - col = UiKit.HERO_BLUE_BRIGHT # 魔法护甲 = 小人蓝亮档 - "heavy": - col = UiKit.GOLD_BRIGHT # 重甲 = 金族亮档 - var name_w := f.get_string_size(st._display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, 12).x - var armor_w := f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 12).x - var total_w := armor_w + 3.0 + name_w - var baseline := Vector2((box.x - total_w) * 0.5, box.y - 1.0 - STANDEE_FOOT_MARGIN) # 纵向余量纪律 - f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 12, col) - f.draw_string(get_canvas_item(), baseline + Vector2(armor_w + 3.0, 0), st._display_name, - HORIZONTAL_ALIGNMENT_LEFT, -1, 12, UiKit.WARM) # v3.5 暗底双色墨制 + var tex := UiKit.load_tex(_armor_tex_path(st._armor)) + var plate := _name_plate_rect(f, tex) + draw_rect(plate, UiKit.HP_BAR_BACK, true) + var baseline := _name_baseline(plate) + var icon_w := 24.0 + if tex != null: + draw_texture_rect(tex, Rect2(baseline.x, plate.position.y + 1.0, 24.0, 24.0), false) + else: + var armor_txt := String(ARMOR_ICONS.get(st._armor, "◇")) + icon_w = f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION).x + var col: Color = UiKit.WARM_FAINT # 轻甲 = 暖白弱化(v3.5 暗底双色墨制) + match st._armor: + "ward": + col = UiKit.HERO_BLUE_BRIGHT # 魔法护甲 = 小人蓝亮档 + "heavy": + col = UiKit.GOLD_BRIGHT # 重甲 = 金族亮档 + f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION, col) + f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st._display_name, + HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION, UiKit.WARM) # v3.5 暗底双色墨制 diff --git a/game/presentation/views/ui_kit.gd b/game/presentation/views/ui_kit.gd index c7894476..c86883cc 100644 --- a/game/presentation/views/ui_kit.gd +++ b/game/presentation/views/ui_kit.gd @@ -190,7 +190,9 @@ const STAND_STONE_RIM := Color8(196, 172, 132) # 站桩石板地台·顶缘 const CAMP_HEADER_BAND := Color8(110, 69, 38) # 营地柜台头带深棕 #6E4526(4-camp.png 方向稿取色;对齐 demo --wood-deep) const CAMP_ROW_PLATE := Color8(240, 230, 206) # 营地行板暖米(同上) const CAMP_BADGE_PANEL := Color8(40, 32, 24) # 营地钥石徽暗底(同上) -const HP_BAR_BACK := Color8(40, 34, 32) # 迷你血条深底 #282220(兼血数垫牌) +const HP_BAR_BACK := Color8(40, 34, 32) # 迷你血条深底 #282220(兼血数垫牌;站桩名字行素底同源——§6.1/§7.2 文字禁压地台) +const HP_BAR_FILL := Color8(168, 50, 31) # 血条填充·危险红 #A8321F(§4.2 语义物件显式落法——不因敌我分色、不因关卡换族;蓝族 #689CE8 已登记违例退役) +const HP_BAR_TRACK := Color(0.10, 0.10, 0.11) # 血条条底(视图原裸值收口单点——非色板新增,现渲染值原样入册) # BBCode 十六进制(RichTextLabel 用;与上同值) const HEX_GOLD := "f0a828" @@ -244,6 +246,13 @@ const TEX_ENEMY_DRIDER := "res://assets/art/character/character_enemy_drider.png const TEX_ENEMY_DUERGAR := "res://assets/art/character/character_enemy_duergar.png" # 灰矮人 const TEX_ENEMY_HOOK_HORROR := "res://assets/art/character/character_enemy_hook_horror.png" # 钩爪恐魔 const TEX_ENEMY_DUERGAR_KING := "res://assets/art/character/character_enemy_duergar_king.png" # 灰矮人石君王 +# 近景地台独立件(§6.1——源 48×12 九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定不吃 wall_ambient tint) +const TEX_STAGE_PLAZA := "res://assets/art/scene/scene_stage_plaza.png" +# 护甲类型图标三键(art-bible §5.3 工单:对象键 = 甲档变体;源 24×24 显示 24px = 1× 甲类整数倍; +# 缺位 load_tex→null→字形回退,冒烟安全) +const TEX_ARMOR_LIGHT := "res://assets/ui/icons/ui_armor_light.png" +const TEX_ARMOR_WARD := "res://assets/ui/icons/ui_armor_ward.png" +const TEX_ARMOR_HEAVY := "res://assets/ui/icons/ui_armor_heavy.png" # 框材组纹理槽(♻ 复用件量化改色入包,产物 = tools/quantize_ui_skin.py, # 全 CC0;缺位 = load_tex null → 程序化回退原路径,冒烟安全) diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index c922897f..489f219b 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -134,6 +134,7 @@ func _suites() -> Array: {"path": "res://tests/unit/core/level_map_type_vis_test.gd", "expect": 0}, # 信息迷雾——类型可见性(层距 N+Boss/候选豁免+单圈+N 读表+续玩同值) {"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # AC⑤/T9——排空零分叉/零丢行/延迟双态 {"path": "res://tests/unit/presentation/standee_fx_sweep_test.gd", "expect": 0}, # 呈现域缺陷扫掠(d2 蓄力档/d4 庆祝句柄/d5+d8 互斥/d12 站桩按 id 解析) + {"path": "res://tests/unit/presentation/standee_present_values_test.gd", "expect": 0}, # 战斗一屏样张批 V5–V9(血条危险红/名字行素底不压地台/徽标 FS_HINT/护甲 PNG 绑定/素材入包) {"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 奖励节点 UI 直验(盲区补漏) {"path": "res://tests/unit/presentation/level_map_info_fog_test.gd", "expect": 0}, # 信息迷雾——呈现显隐/Boss 豁免/详情零显示/大图拟合 {"path": "res://tests/unit/presentation/forge_workbench_test.gd", "expect": 0}, # 工作间两区六路线 UI 直验(V13 自动化前置面) diff --git a/game/tests/unit/presentation/standee_present_values_test.gd b/game/tests/unit/presentation/standee_present_values_test.gd new file mode 100644 index 00000000..144d76e4 --- /dev/null +++ b/game/tests/unit/presentation/standee_present_values_test.gd @@ -0,0 +1,84 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# 战斗一屏样张批 V5–V9 断言面(2026-10-29 迭代 43)——五项实装定值的真实入口断言: +# V5 血条填充 = 危险红(UiKit.HP_BAR_FILL 值单点 + 绘制点源码锚:全仓零 #689CE8 与零裸 +# Color(0.41,0.61,0.91);条底收 HP_BAR_TRACK 常量); +# V6 站桩名字行垫 HP_BAR_BACK 素底且不压地台(名字行基线 y < 地台顶 y——name_line_probe 几何出口); +# V7 顺位徽标提档 UiKit.FS_HINT(值断言 + 绘制点源码锚,§7.4 清单外 11–13px 提档); +# V8 护甲图标三键(light/ward/heavy)绑对应 24×24 PNG(ResourceLoader 装载结果 + name_line_probe +# 绑定面); +# V9 地台件 scene_stage_plaza.png + 护甲三枚入包可加载且尺寸 48×12 / 24×24(§5.2/§5.3 工单)。 +# headless 无渲染面:像素采样以常量单点 + 源码锚 + 探针几何承载(观测面注记,验收对照表 V5 行)。 +# 运行:godot --headless --path game --script res://tests/unit/presentation/standee_present_values_test.gd(expect 0) +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") +const StandeeBodyScript := preload("res://presentation/views/standee_body.gd") + + +# 源码锚读面(静态判据——源文件即提交面) +func _read_source(rel_path: String) -> String: + var f := FileAccess.open(rel_path, FileAccess.READ) + if f == null: + return "" + return f.get_as_text() + + +func _init() -> void: + # ---- V5 血条填充 = 危险红 #A8321F(§4.2 语义物件显式落法) ---- + Kit.check("血条填充常量 = 危险红 #A8321F〔样张批 V5〕", + UiKit.HP_BAR_FILL.to_html(false) == "a8321f") + Kit.check("血条条底收常量 HP_BAR_TRACK(视图原裸值原样入册)〔样张批 V5〕", + UiKit.HP_BAR_TRACK.is_equal_approx(Color(0.10, 0.10, 0.11))) + var body_src := _read_source("res://presentation/views/standee_body.gd") + Kit.check("血条绘制点引用 UiKit.HP_BAR_FILL(源码锚)〔样张批 V5〕", + body_src.contains("UiKit.HP_BAR_FILL")) + Kit.check("全源零违例值 #689CE8 与零裸 Color(0.41,0.61,0.91)(源码锚)〔样张批 V5〕", + not body_src.contains("689CE8") and not body_src.contains("689ce8") + and not body_src.contains("0.41, 0.61, 0.91")) + + # ---- V6/V8 站桩实例面(名字行几何 + 护甲绑定) ---- + var standee: Node2D = StandeeBodyScript.new("enemy") + root.add_child(standee) + standee.set_data({"display_name": "地精", "enemy_id": "goblin", "hp": 3, "max_hp": 3, + "order": 1, "armor_type": "light"}) + var nl: Dictionary = standee.name_line_probe() + Kit.check("名字行基线 y < 地台顶 y(文字禁压地台——V6 判据坐标面)〔样张批 V6〕", + float(nl.baseline_y) < float(nl.plinth_top_y)) + Kit.check("素底牌底沿 ≤ 地台顶(整行不越地台)〔样张批 V6〕", + Rect2(nl.plate).end.y <= float(nl.plinth_top_y) + 0.01) + Kit.check("名字行素底 = HP_BAR_BACK 深档 #282220(挂载底色口径)〔样张批 V6〕", + Color(nl.plate_color).to_html(false) == "282220") + Kit.check("基线读数与实装几何同源(基线 = 地台顶上方 9px 档:149 < 158)〔样张批 V6〕", + absf(float(nl.baseline_y) - 149.0) < 0.51 and absf(float(nl.plinth_top_y) - 158.0) < 0.51) + + # ---- V7 顺位徽标提档 FS_HINT ---- + Kit.check("FS_HINT = 14(§7.4 六档)〔样张批 V7〕", UiKit.FS_HINT == 14) + Kit.check("顺位徽标绘制点取 UiKit.FS_HINT(源码锚)〔样张批 V7〕", + body_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT")) + + # ---- V8 护甲图标三键绑 24×24 PNG ---- + var armor_paths := {"light": UiKit.TEX_ARMOR_LIGHT, "ward": UiKit.TEX_ARMOR_WARD, + "heavy": UiKit.TEX_ARMOR_HEAVY} + for key: String in armor_paths: + var tex := UiKit.load_tex(armor_paths[key]) + Kit.check("护甲 %s 键绑对应 PNG 且 24×24(§5.3 工单)〔样张批 V8〕" % key, + tex != null and tex.get_width() == 24 and tex.get_height() == 24) + var ward_standee: Node2D = StandeeBodyScript.new("enemy") + root.add_child(ward_standee) + ward_standee.set_data({"display_name": "蛛化卓尔精灵", "enemy_id": "drider", "hp": 5, "max_hp": 5, + "order": 1, "armor_type": "ward"}) + var nl_ward: Dictionary = ward_standee.name_line_probe() + Kit.check("甲档键 → 纹理路径绑定(name_line_probe 出 armor_icon 路径)〔样张批 V8〕", + bool(nl_ward.armor_tex_bound) and String(nl_ward.armor_icon) == UiKit.TEX_ARMOR_WARD) + + # ---- V9 地台件与护甲三枚入包可载且尺寸对 ---- + var plaza := UiKit.load_tex(UiKit.TEX_STAGE_PLAZA) + Kit.check("地台件 scene_stage_plaza.png 可加载且 48×12(§5.2 工单)〔样张批 V9〕", + plaza != null and plaza.get_width() == 48 and plaza.get_height() == 12) + for key: String in armor_paths: + var tex9 := UiKit.load_tex(armor_paths[key]) + Kit.check("护甲三枚入包(24×24)且路径常量在 UiKit 单点〔样张批 V9〕" if key == "light" + else "护甲三枚入包(24×24)〔样张批 V9〕", + tex9 != null and tex9.get_width() == 24 and tex9.get_height() == 24) + + quit(Kit.finish("standee_present_values")) diff --git a/game/tests/unit/presentation/standee_present_values_test.gd.uid b/game/tests/unit/presentation/standee_present_values_test.gd.uid new file mode 100644 index 00000000..dba1f71f --- /dev/null +++ b/game/tests/unit/presentation/standee_present_values_test.gd.uid @@ -0,0 +1 @@ +uid://c7a6acc5aici0