iter48-卡2:卡②(立绘显示档批): 九件素材原稿分辨率入包(紧裁零降采样,STANDEE_SRC_H九键+产物计数自证)+standee_motion显示档PORTRAIT_DISPLAY_H九键钉值(_place_portrait按档算倍率,缺键push_error按1×)+portrait_probe扩display_size/display_scale+闸件standee_asset_check换口径(源高==原稿表值/显示宽≤150酋长157例外逐字)+样张站桩段按显示档取数(A4解冻重校88×92,V10并排30/30)+V13–V18全绿(50checks)+三面对照图

This commit is contained in:
DKChan committed 2026-11-02 01:58:23 +08:00
1 parent 1e35ab947b
commit 4c3f0f6082
9 files changed
+255 -119

No files matched your search

@@ -11,6 +11,9 @@
# 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
@@ -110,4 +113,94 @@ func _init() -> void:
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:
# 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)