Files
gplan-2/game/tests/unit/presentation/standee_present_values_test.gd
T

209 lines
13 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VERTICAL SLICE - NOT FOR PRODUCTION
# 战斗一屏样张批 V5–V12 断言面(2026-10-29 迭代 43 立五项;2026-10-31 迭代 46 卡B 增 V10–V12)——
# 实装定值的真实入口断言:
# 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 工单);
# V10 名字行提档 FS_HINT 14(探针 name_font_size 实读 + 两绘制点源码锚 + FS_CAPTION 计数锚==1);
# V11 退役常量不在册 + 掩尘 BRICK 逐位 + 顶缘线/口沿高光两绘制点改指源码锚;
# V12 PLAZA_EDGE 逐位 #4C4038 + 回退描边改指源码锚。
# V13–V18 立绘显示档(2026-11-01 迭代 48 卡②)——显示档 = 三档绝对高逐键钉值(96/144/188)+
# 入包件 = 原稿紧裁零降采样(源高 == 原稿表值)+纵横倍率恒等+主角三态显示 144+
# 跨语言显示档表逐键对账+键集恰九键(§8.2 显示档定值单点)。
# 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:
# oracle: design/gdd/ux/battle.md:154 (站桩呈现定值与素材绑定组)
# ---- 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("基线读数与实装几何同源(基线 = 地台顶上方 8px 档:150 < 158——14px 档牌顶+18)〔样张批 V6〕",
absf(float(nl.baseline_y) - 150.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"))
# ---- V10 名字行提档 FS_HINT 14(迭代 46 卡B——五取值点+基线随档联动) ----
Kit.check("名字行探针字号实读 = FS_HINT(几何单点出口 name_font_size)〔名字行 V10〕",
int(nl.name_font_size) == UiKit.FS_HINT)
Kit.check("名字与护甲两绘制点取 FS_HINT(源码锚——沿 V7 先例)〔名字行 V10〕",
body_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.WARM")
and body_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, col"))
Kit.check("全文件 FS_CAPTION 仅剩消散提示一处(计数锚==1——三处宽度测量点强制换代)〔名字行 V10〕",
body_src.count("UiKit.FS_CAPTION") == 1)
# ---- V11/V12 地台色合并(迭代 46 卡B——常量面载体=Script 常量表,源码锚正证改指目标色) ----
var uikit_script: Script = load("res://presentation/views/ui_kit.gd")
var cmap: Dictionary = uikit_script.get_script_constant_map()
var cell_src := _read_source("res://presentation/views/cell_base_layer.gd")
Kit.check("退役常量不在册(STAND_STONE_RIM/STAND_STONE_EDGE 零键)〔名字行 V11〕",
not cmap.has("STAND_STONE_RIM") and not cmap.has("STAND_STONE_EDGE"))
Kit.check("掩尘 BRICK 值逐位 #C9AF83(合并目标①)〔名字行 V11〕",
UiKit.BRICK.to_html(false) == "c9af83")
Kit.check("顶缘受光线与口沿高光两绘制点改指 BRICK(源码锚)〔名字行 V11〕",
body_src.contains("ped.size.x, 2.0), UiKit.BRICK, true")
and cell_src.contains("FACADE_HIGHLIGHT_PX), UiKit.BRICK, true"))
Kit.check("PLAZA_EDGE 值逐位 #4C4038(合并目标②)〔名字行 V12〕",
UiKit.PLAZA_EDGE.to_html(false) == "4c4038")
Kit.check("回退描边绘制点改指 PLAZA_EDGE(源码锚)〔名字行 V12〕",
body_src.contains("UiKit.PLAZA_EDGE"))
# ---- 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)
# ---- V13–V18 立绘显示档(迭代 48 卡②) ----
_check_display_tier()
quit(Kit.finish("standee_present_values"))
# ---- V13–V18 立绘显示档(迭代 48 卡②——显示档 = 三档绝对高,入包件 = 原稿紧裁零降采样) ----
# 规格钉值(显示档定值单点 = art-bible §8.2;源高 = 原稿表值——旧降采样稿必红;
# 显示宽 = round(源宽×档高/源高),食人魔酋长 157 为已接受超宽项):
# 键 → [源高, 显示档高, 显示宽]
const _DISPLAY_SPEC := {
"goblin": [90, 96, 95], "giant_spider": [81, 96, 121], "duergar": [126, 96, 71],
"hook_horror": [94, 144, 113], "bugbear": [123, 144, 119], "hero": [92, 144, 138],
"ogre_chief": [189, 188, 157], "drider": [189, 188, 139], "duergar_king": [189, 188, 132],
}
func _enemy_tex_path(key: String) -> String:
return "res://assets/art/character/character_enemy_%s.png" % key
func _check_display_tier() -> void:
# oracle: design/gdd/ux/battle.md:154 (显示档与分辨率绑定组)
# V13/V15 九键逐一:真实入口 StandeeBody → set_data(enemy_id) → set_portrait_texture(真纹理) → portrait_probe
for key: String in _DISPLAY_SPEC:
var spec: Array = _DISPLAY_SPEC[key]
var body: Node2D = StandeeBodyScript.new("hero" if key == "hero" else "enemy")
root.add_child(body)
var tex: Texture2D = UiKit.load_tex(UiKit.TEX_HERO_PORTRAIT if key == "hero" else _enemy_tex_path(key))
if key == "hero":
body.set_portrait_texture(tex, true)
else:
body.set_data({"display_name": key, "enemy_id": key, "hp": 3, "max_hp": 3,
"order": 1, "armor_type": "light"})
body.set_portrait_texture(tex, false)
var pp: Dictionary = body.portrait_probe()
Kit.check("显示档 %s:源高 %d(原稿表值——旧降采样稿必红)+显示高 %d 逐键钉值〔立绘显示档 V13〕"
% [key, int(spec[0]), int(spec[1])],
tex != null and int(tex.get_height()) == int(spec[0])
and is_equal_approx(float(pp.display_size.y), float(spec[1]))
and is_equal_approx(float(pp.display_scale) * float(tex.get_height()), float(spec[1])))
Kit.check("显示档 %s:纵横倍率恒等(双轴同倍率)+显示宽随源比例(round 档 %d)〔立绘显示档 V15〕"
% [key, int(spec[2])],
is_equal_approx(float(pp.display_size.x), float(tex.get_width()) * float(pp.display_scale))
and is_equal_approx(float(pp.display_size.y), float(tex.get_height()) * float(pp.display_scale))
and absf(float(pp.display_size.x) - float(spec[2])) < 0.51)
body.queue_free()
# V14 静态源锚:显示档表读表单点 + 零硬编码 2× 倍率
var motion_src := _read_source("res://presentation/views/standee_motion.gd")
Kit.check("显示档表单点在位(_place_portrait 读 PORTRAIT_DISPLAY_H)且零 Vector2(2.0, 2.0) 字面倍率〔立绘显示档 V14〕",
motion_src.contains("PORTRAIT_DISPLAY_H") and not motion_src.contains("Vector2(2.0, 2.0)"))
# V16 主角三态:组合根装载(hero_tex 注册链)→ 逐态 apply_hero_state 复读探针
var stage_view: Node = load("res://presentation/views/battle_stage_view.gd").new(null, load("res://data/tables/battle_layout.tres"))
root.add_child(stage_view)
var hs: Node2D = stage_view.hero_standee
var pp0: Dictionary = hs.portrait_probe()
Kit.check("主角 normal 态:源高 ≥92 且显示高 144(组合根真实装载链)〔立绘显示档 V16〕",
bool(pp0.has_tex) and is_equal_approx(float(pp0.display_size.y), 144.0))
for hstate: String in ["attack", "hit"]:
hs.set_hero_state(hstate)
var pph: Dictionary = hs.portrait_probe()
Kit.check("主角 %s 态:源高 ≥92 且显示高 144(三态同档)〔立绘显示档 V16〕" % hstate,
bool(pph.has_tex) and is_equal_approx(float(pph.display_size.y), 144.0))
stage_view.queue_free()
# V17 跨语言对账:Python 显示档表(STANDEE_DISPLAY_H 八敌 + STANDEE_SLOT_H 主角)逐键 == GDScript 表
# (读不到工具源 = L2 记录退回双端字面钉值+独立审计对账——禁当红亦禁当绿)
var py_src := ""
for py_path: String in ["../tools/pixelize_candidates.py", "tools/pixelize_candidates.py"]:
var pf := FileAccess.open(py_path, FileAccess.READ)
if pf != null:
py_src = pf.get_as_text()
break
if py_src.is_empty():
print("[L2] V17 工具源不可读(../tools/ 路径不通)——退回双端字面钉值+独立审计对账(记录不判红)")
else:
var py_ok := true
for key: String in _DISPLAY_SPEC:
if key == "hero":
py_ok = py_ok and py_src.contains("STANDEE_SLOT_H = %d" % int(_DISPLAY_SPEC[key][1]))
else:
py_ok = py_ok and py_src.contains("\"%s\": %d" % [key, int(_DISPLAY_SPEC[key][1])])
Kit.check("Python 显示档表与 GDScript PORTRAIT_DISPLAY_H 九键逐键相等(源文本对账)〔立绘显示档 V17〕", py_ok)
# V18 键集恰九键(八敌 ∪ hero,无多余键——防新敌种漏登记被 push_error 静默)
var motion_script: Script = load("res://presentation/views/standee_motion.gd")
var mcmap: Dictionary = motion_script.get_script_constant_map()
var disp_tbl: Dictionary = mcmap.get("PORTRAIT_DISPLAY_H", {})
var keys_ok: bool = disp_tbl.size() == 9
for k: String in ["bugbear", "drider", "duergar", "duergar_king", "giant_spider",
"goblin", "hook_horror", "ogre_chief", "hero"]:
keys_ok = keys_ok and disp_tbl.has(k)
Kit.check("显示档表键集 = 八敌 ∪ hero 恰九键(无多余键)〔立绘显示档 V18〕", keys_ok)