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:
1 parent
1e35ab947b
commit
4c3f0f6082
9 files changed
+255
-119
No files matched your search
@@ -173,7 +173,8 @@ func _build_status_bar(box: VBoxContainer) -> void:
|
||||
strip.add_child(abandon_btn)
|
||||
|
||||
|
||||
## 头像域:定高 48px=1×、宽随源图比自适应(源 46×48 → 46×48;纵横异倍消除)
|
||||
## 头像域:定高 48px 框、宽随源图比自适应(源 = 原稿分辨率;该面为独立消费面——
|
||||
## 显示档批不改框尺寸,画面随素材分辨率自动变化)
|
||||
func _build_face_rect() -> TextureRect:
|
||||
var face := TextureRect.new()
|
||||
var face_tex := UiKit.load_tex(UiKit.TEX_HERO_PORTRAIT)
|
||||
|
||||
@@ -281,7 +281,11 @@ func _portrait_glyph() -> String:
|
||||
|
||||
# 立绘落位探针出口(站桩纹理非空断言面;缺位回退链保持的对照面)
|
||||
func portrait_probe() -> Dictionary:
|
||||
return {"has_tex": mo._portrait_sprite != null and mo._portrait_sprite.texture != null}
|
||||
var sp: Sprite2D = mo._portrait_sprite
|
||||
if sp == null or sp.texture == null:
|
||||
return {"has_tex": false, "display_size": Vector2.ZERO, "display_scale": 0.0}
|
||||
var src := Vector2(sp.texture.get_width(), sp.texture.get_height())
|
||||
return {"has_tex": true, "display_size": src * sp.scale, "display_scale": sp.scale.x}
|
||||
|
||||
|
||||
# ---- 主角三态切换(攻击演出→attack/受击→hit/结算回 idle;§8.4 帧数档挂点) ----
|
||||
|
||||
@@ -12,6 +12,15 @@ const _HS_VALUE := {"idle": HeroState.IDLE, "reset": HeroState.RESET, "attack":
|
||||
var _owner: Node2D = null
|
||||
var _portrait_sprite: Sprite2D = null # 资产挂点(null = 未落位,字形在位)
|
||||
var _portrait_tex: Texture2D = null # 落位纹理引用(set_portrait_texture 装载;字形回退判据)
|
||||
# 站桩立绘显示档(三档绝对高——art-bible §8.2 显示档定值单点;九键逐键钉值,禁兜底字面量):
|
||||
# 小型 96 = 地精/巨蜘蛛/灰矮人;中型 144 = 主角/钩爪恐魔/熊地精;大型 188 = 酋长/卓尔/石君王。
|
||||
# 显示倍率 = 档高 / 源高(宽随源比例);源高 = 原稿表值(Python 侧 STANDEE_SRC_H——
|
||||
# 跨语言逐键对账由 standee_present_values_test V17 承担)。
|
||||
const PORTRAIT_DISPLAY_H := {
|
||||
"goblin": 96.0, "giant_spider": 96.0, "duergar": 96.0,
|
||||
"hook_horror": 144.0, "bugbear": 144.0, "hero": 144.0,
|
||||
"ogre_chief": 188.0, "drider": 188.0, "duergar_king": 188.0,
|
||||
}
|
||||
# 主角三态切换:state 名 → 纹理(battle_stage_view 组合点注册);缺态=保持现纹理
|
||||
# (缺位回退链延伸——三态素材缺任一不炸不空)。敌站桩不注册不消费。
|
||||
var _hero_tex := {}
|
||||
@@ -35,8 +44,9 @@ func _init(owner: Node2D) -> void:
|
||||
|
||||
|
||||
# 资产挂点:tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。
|
||||
# flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右)。盒高 96px = 48px 立绘源整数 2×
|
||||
# (圣经 §8.2 禁非整数缩放)——源换代时随源重算。(box = 宿主冻结 var,由宿主传入)
|
||||
# flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右)。显示高 = PORTRAIT_DISPLAY_H
|
||||
# 逐键绝对高(art-bible §8.2 三档),宽随源比例(高定宽随)。
|
||||
# (box = 宿主冻结 var,由宿主传入)
|
||||
func set_portrait_texture(tex: Texture2D, flip: bool, box: Vector2) -> void:
|
||||
if tex == null:
|
||||
if _portrait_sprite != null:
|
||||
@@ -56,14 +66,25 @@ func set_portrait_texture(tex: Texture2D, flip: bool, box: Vector2) -> void:
|
||||
|
||||
|
||||
func _place_portrait(box: Vector2) -> void:
|
||||
# 立绘盒 = (0,44)-(box.x, box.y-36);48 源 ×2 整数档居中
|
||||
# 立绘盒 = (0,44)-(box.x, box.y-36);显示档逐键取 PORTRAIT_DISPLAY_H 绝对高,
|
||||
# 宽随源比例(高定宽随);高于盒者沿既有居中溢出机制((box_h-h)*0.5 为负即上溢)
|
||||
if _portrait_sprite == null or _portrait_tex == null:
|
||||
return
|
||||
var eid: String = _owner.enemy_id()
|
||||
var key := "hero" if eid.is_empty() else eid
|
||||
var box_h := box.y - 44.0 - 36.0
|
||||
var w := _portrait_tex.get_width() * 2.0
|
||||
var h := _portrait_tex.get_height() * 2.0
|
||||
_portrait_sprite.scale = Vector2(2.0, 2.0) # 整数 2×(C5)
|
||||
_portrait_sprite.position = Vector2((box.x - w) * 0.5, 44.0 + (box_h - h) * 0.5)
|
||||
var disp_h: float = PORTRAIT_DISPLAY_H.get(key, -1.0)
|
||||
if disp_h < 0.0:
|
||||
# 缺键不崩、不静默兜档——push_error 报警后按 1× 落位(V18 键集自检在测试期报警)
|
||||
push_error("[站桩] 显示档表缺键: %s(逐键登记纪律)" % key)
|
||||
_portrait_sprite.scale = Vector2.ONE
|
||||
_portrait_sprite.position = Vector2((box.x - _portrait_tex.get_width()) * 0.5,
|
||||
44.0 + (box_h - _portrait_tex.get_height()) * 0.5)
|
||||
return
|
||||
var s := disp_h / float(_portrait_tex.get_height())
|
||||
_portrait_sprite.scale = Vector2(s, s)
|
||||
_portrait_sprite.position = Vector2((box.x - _portrait_tex.get_width() * s) * 0.5,
|
||||
44.0 + (box_h - disp_h) * 0.5)
|
||||
|
||||
|
||||
# 组合点注册(battle_stage_view _init——UiKit 常量 load_tex,缺位 null 不注册=该态保持现纹理)
|
||||
|
||||
@@ -147,11 +147,11 @@ func _ready() -> void:
|
||||
problems.append("普攻应零表示(意图指示应隐藏)")
|
||||
# 站桩纹理非空断言(八敌真图落位;缺位回退链保持的对照面)
|
||||
if not bool(standee.portrait_probe().get("has_tex", false)):
|
||||
problems.append("敌立绘纹理未落位(素材管线 48px 档——迭代 6 T2;缺位回退链不应在此态触发)")
|
||||
problems.append("敌立绘纹理未落位(原稿分辨率入包——显示档批;缺位回退链不应在此态触发)")
|
||||
if main.ui.battle.stage.hero_standee == null or not main.ui.battle.stage.hero_standee.has_world_hp_bar:
|
||||
problems.append("B 区主角站桩缺失(世界血条面缺失)")
|
||||
elif not bool(main.ui.battle.stage.hero_standee.portrait_probe().get("has_tex", false)):
|
||||
problems.append("主角立绘纹理未落位(48px 档——迭代 6 T2/T3)")
|
||||
problems.append("主角立绘纹理未落位(显示档 hero=144——显示档批)")
|
||||
# ---- BUG-0001 回归(tooltip 入树后 set_effects 槽位不错位——结算软锁修复沉淀)----
|
||||
var slots_row = main.ui.battle.stage.hero_standee.slot_row # BUG-0001 回归随组件迁入站桩(tooltip/槽位错位同源)
|
||||
var probe_eff: Array = [{"effect_id": "attack_up", "display_name": "增伤", "layers": 1,
|
||||
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user