merge: M-1 迭代 2「样张冻结与表现定稿」loop——样张 v2 机检 16/16 四冻结+框材组 8 件接线+演出密度终稿+棋盘数字终稿+M-1 出口闸检查单

五批次 a08d89c/f9b0765→2d1388b:①T1 样张 v2(机检闸形 A 全过即冻结——新增 A3 Δ≥55/F6 双明度带/F7 DEPTH_PX 同源/A6 观感密度四断言,art-bible v2.13 落四冻结+UiKit 小人蓝同步 #2E86DE+图例旗色封印黑漏网修)②T2 框材组 8 件(♻ Kenney/OGA 全 CC0 量化改色入包,UiKit 单点工厂+五视图接线,✦ 背景横幅列缺料)③T3 演出四项(死亡碎裂/胜利纹章/失败掸灰/意图三档+结算光带缘线)+fx_probe 新探针④T4 数字形制冻结值落码(三档墨色温/字号 ≥18 公式/色牌 0.60·0.62)⑤T5 M-1 出口闸检查单(design/qa/m1-exit-gate-checklist-2026-09-28.md——真人目检八项+✦ 缺料清单)+06 两已解决条目移除+roadmap 挂起清单对齐。
验证:样张机检 16/16+冒烟 557/0(基线持平)+五探针(ui_input/ui_probe/ui_click/ui_tune/fx 新增)全 PASS+doc 三闸对基线零新增(lint 8=8/term 215=215/死链 0)+熵增闸 PASS。
This commit is contained in:
DKChan committed 2026-09-28 04:58:58 +08:00
commit 9abf5ff448
20 files changed
+748 -76

No files matched your search

+10
View File
@@ -103,6 +103,16 @@ static func build_overlays(host: Control, db, base_atk: int, base_hp: int) -> Di
# 单场遭遇用户入口已删(2026-09-13 用户裁决——单场原语仅探针经 debug_session.start_single 使用))
static func build_start_overlay(host: Control, db = null) -> Dictionary:
var overlay := overlay_bg(host, UiKit.BG_OVERLAY)
# 石纹底噪底层(样张 v2 冻结批 T2——z 序=dim 之上/内容之下;缺位不挂即回退平色)
var bd_tex := UiKit.load_tex(UiKit.TEX_BACKDROP_STONE)
if bd_tex != null:
var bd := TextureRect.new()
bd.texture = bd_tex
bd.stretch_mode = TextureRect.STRETCH_TILE
bd.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
bd.set_anchors_preset(Control.PRESET_FULL_RECT)
bd.mouse_filter = Control.MOUSE_FILTER_IGNORE
overlay.add_child(bd)
var box := overlay_box(overlay)
var title := UiKit.label("Dungeon Sweeper", 34, true)
title.add_theme_color_override("font_color", UiKit.WARM)
@@ -73,6 +73,7 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect
_board_rect = board_rect
visible = true
_play_seal_fall()
_play_crest_assemble() # 碎砖拼纹章(§2.4——样张 v2 冻结批 T3 终稿)
_play_flag_recall(0) # 胜利不放旗收回
@@ -85,12 +86,14 @@ func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2
_board_rect = board_rect
visible = true
_seal.visible = false
_play_dust_sadle() # 坐砖掸灰(§2.5——终稿)
_play_flag_recall(8)
func close() -> void:
visible = false
_seal.visible = false
_clear_crest_bricks()
func is_open() -> bool:
@@ -115,6 +118,7 @@ func _play_seal_fall() -> void:
# 旗化光点收回(失败——插过的旗逐面化光点收回占位,§2.5)
func _play_flag_recall(count: int) -> void:
_fx_flag_dots = count
for i in count:
var dot := ColorRect.new()
dot.color = COL_GOLD
@@ -129,3 +133,62 @@ func _play_flag_recall(count: int) -> void:
tw.tween_property(dot, "position:y", dot.position.y - 120.0, UiKit.motion(0.8))
tw.parallel().tween_property(dot, "modulate:a", 0.0, UiKit.motion(0.8))
tw.tween_callback(dot.queue_free)
# ---- 演出密度终稿(样张 v2 冻结批 T3——程序化 tween,确定性散布,零新帧资产) ----
var _crest_bricks: Array = [] # 纹章碎砖驻留面(close 时回收)
var _fx_crest_shards := 0
var _fx_dust_motes := 0
var _fx_flag_dots := 0
# 碎砖拼纹章(胜利 §2.4——蜡封落定后十枚碎砖自四周向蜡封外环收拢拼成纹章环,驻留至 close)
func _play_crest_assemble() -> void:
_clear_crest_bricks()
_fx_crest_shards = 10
var c := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
for i in 10:
var brick := ColorRect.new()
brick.color = UiKit.BRICK if i % 2 == 0 else UiKit.FLOOR # 碎砖=砖面族色(掩尘/裸土——图底同源)
brick.size = Vector2(12, 7)
brick.mouse_filter = Control.MOUSE_FILTER_IGNORE
var ang := TAU * float(i) / 10.0
var ring := c + Vector2(cos(ang), sin(ang)) * 64.0
brick.position = c + Vector2(cos(ang), sin(ang)) * 200.0 # 自四周场外入场
add_child(brick)
_crest_bricks.append(brick)
var btw := brick.create_tween()
btw.tween_interval(UiKit.motion(0.52)) # 待蜡封落定(seal 0.42+0.22 弹跳中段)
btw.tween_property(brick, "position", ring - brick.size * 0.5, UiKit.motion(0.4)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
btw.parallel().tween_property(brick, "rotation", sin(ang) * 0.35, UiKit.motion(0.4))
# 坐砖掸灰(失败 §2.5——暖灰尘絮自棋盘中带缓缓飘落,最静一拍的运动承载)
func _play_dust_sadle() -> void:
_fx_dust_motes = 8
var base := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
for i in 8:
var mote := ColorRect.new()
mote.color = Color(UiKit.WARM_FAINT, 0.85)
mote.size = Vector2(4, 4)
mote.mouse_filter = Control.MOUSE_FILTER_IGNORE
mote.position = base + Vector2((float(i % 4) - 1.5) * 30.0, -24.0 - 8.0 * float(i / 4))
add_child(mote)
var mtw := mote.create_tween()
mtw.tween_interval(UiKit.motion(0.08 * i))
mtw.tween_property(mote, "position:y", mote.position.y + 64.0, UiKit.motion(0.9))
mtw.parallel().tween_property(mote, "modulate:a", 0.0, UiKit.motion(0.9))
mtw.tween_callback(mote.queue_free)
func _clear_crest_bricks() -> void:
for b in _crest_bricks:
if is_instance_valid(b):
b.queue_free()
_crest_bricks.clear()
# 演出探针出口(fx_probe——胜利纹章/失败掸灰/旗光收回三断言面)
func fx_probe() -> Dictionary:
return {"crest_shards": _fx_crest_shards, "dust_motes": _fx_dust_motes, "flag_dots": _fx_flag_dots}
+3 -2
View File
@@ -41,9 +41,10 @@ var world: Node2D # StageWorld(preload 脚本实例——Node2D 世
func _init(db_ref) -> void:
custom_minimum_size = Vector2(0, 252)
# 面板底(PanelContainer 面板样式等价平移——Control 壳不自绘 stylebox,Panel 子面承载)
# 面板底(PanelContainer 面板样式等价平移——Control 壳不自绘 stylebox,Panel 子面承载;
# 石板皮 = 样张 v2 冻结批 T2——缺位回退 PANEL_DATA flat,渲染观感不变)
var bg := Panel.new()
var sb := UiKit.panel_flat(UiKit.PANEL_STAGE, UiKit.PANEL_BORDER, 0, 1, Vector4(6, 4, 6, 4))
var sb := UiKit.panel_skinned(Vector4(6, 4, 6, 4), Color(0.956, 0.952, 0.945)) # tint=PANEL_STAGE 对 PANEL_DATA 比值
bg.add_theme_stylebox_override("panel", sb)
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
+6 -6
View File
@@ -167,19 +167,19 @@ func _draw() -> void:
draw_texture_rect(tex_mutation, rect, false)
# ③ 数字/徽章(数字 ≥ 格高 50% 墨族对格底 ≥4.5:1;徽章右下角金,遮蔽态 = "?")
if glyph != "":
# 数字色牌(原则二:数字一律垫素色直角牌——对比由牌墨承载,可读不依赖格底;暗地面瓦片批的
# 可读性修复 2026-09-26。牌尺寸 0.66/0.68 格边居中——字形 ≥0.5 格边在牌内留白 ≥1px;
# 旗=未开格不垫牌(旗朱在墙瓦片上已可读))
# 数字色牌(原则二:数字一律垫素色直角牌——对比由牌墨承载,可读不依赖格底。
# 牌尺寸 0.60/0.62 格边居中 = 样张 v2 冻结值(2026-09-28 四冻结·数字形制;字形 0.5 格边
# 在牌内留白 ≥2px);旗=未开格不垫牌(封印黑在墙瓦片上已可读))
if plate:
var pw := edge * 0.66
var ph := edge * 0.68
var pw := edge * 0.60
var ph := edge * 0.62
var pr := Rect2((edge - pw) * 0.5, (edge - ph) * 0.5, pw, ph)
draw_rect(pr, UiKit.DIGIT_PLATE, true)
draw_rect(pr, UiKit.DIGIT_PLATE_EDGE, false, 1.0)
if tex_glyph != null:
draw_texture_rect(tex_glyph, rect, false)
else:
_draw_glyph(glyph, glyph_color, maxi(14, int(edge * 0.5)))
_draw_glyph(glyph, glyph_color, maxi(18, int(ceil(edge * 0.5)))) # 字号下限 18(§7.4 冻结公式)
if badge_glyph != "":
if tex_badge != null:
draw_texture_rect(tex_badge, rect, false)
+11
View File
@@ -626,6 +626,16 @@ func _badge_glyph(category: String) -> String:
# ---- 格态渲染(灰度截图可辨:完整覆尘 / 碎裂裸土 / 已开数字) ----
# 数字墨三档色温(样张 v2 冻结批 T4——art-bible §4.1:1-3 青墨/4-5 赭墨/6-8 锈墨;
# 冷=稀疏、暖=密集的氛围档,字形为唯一数值载体——禁八色分色)
func _digit_ink(d: int) -> Color:
if d <= 3:
return UiKit.INK_COLD
if d <= 5:
return UiKit.INK_OCHRE
return UiKit.INK_RUST
func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
var u: Node2D = _cells.get(c)
if u == null:
@@ -650,6 +660,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
glyph = "?" # 数字遮蔽(地形变异 hide_number——信息型:该格真实数字不显示)
else:
glyph = str(d)
digit_col = _digit_ink(d) # 数字墨三档色温(样张 v2 冻结批 T4——§4.1)
else:
if flag:
bg = COL_BRICK
+24 -8
View File
@@ -34,30 +34,39 @@ func _init() -> void:
var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.add_child(center)
var frame := PanelContainer.new()
frame.add_theme_stylebox_override("panel", UiKit.frame_skinned(Vector4(26, 22, 26, 22))) # 仪式石框(T2;缺位回退暗面板)
center.add_child(frame)
var box := VBoxContainer.new()
box.add_theme_constant_override("separation", 6)
center.add_child(box)
frame.add_child(box)
var title := UiKit.label("营地 · 永久强化", 24, true)
title.add_theme_color_override("font_color", UiKit.WARM)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(title)
var bal_row := HBoxContainer.new()
bal_row.add_theme_constant_override("separation", 6)
bal_row.alignment = BoxContainer.ALIGNMENT_CENTER
var keystone_icon := UiKit.icon_rect(UiKit.TEX_ICON_KEYSTONE, 18) # 缺位 null → 纯文本回退
if keystone_icon != null:
bal_row.add_child(keystone_icon)
_balance_label = UiKit.label("钥石 0", 18, true)
_balance_label.add_theme_color_override("font_color", UiKit.GOLD)
_balance_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(_balance_label)
bal_row.add_child(_balance_label)
box.add_child(bal_row)
box.add_child(HSeparator.new())
box.add_child(_counter_header("属性锻造(钥石直购 · 每级 flat 整数 · 价格几何递增)"))
box.add_child(_counter_header("属性锻造(钥石直购 · 每级 flat 整数 · 价格几何递增)", UiKit.TEX_ICON_FORGE))
_forge_holder = VBoxContainer.new()
_forge_holder.add_theme_constant_override("separation", 4)
box.add_child(_forge_holder)
box.add_child(_counter_header("道具工坊(累计通关次数解锁 · 局内效果排 Alpha 魔法道具重设计批)"))
box.add_child(_counter_header("道具工坊(累计通关次数解锁 · 局内效果排 Alpha 魔法道具重设计批)", UiKit.TEX_ICON_WORKSHOP))
_workshop_holder = VBoxContainer.new()
_workshop_holder.add_theme_constant_override("separation", 4)
box.add_child(_workshop_holder)
box.add_child(_counter_header("奖励格图鉴"))
box.add_child(_counter_header("奖励格图鉴", UiKit.TEX_ICON_ATLAS))
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)", 13, false)
lock_l.add_theme_color_override("font_color", UiKit.WARM_FAINT)
box.add_child(lock_l)
@@ -80,10 +89,17 @@ func _init() -> void:
visible = false
func _counter_header(text: String) -> Label:
func _counter_header(text: String, icon_path := "") -> Control:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 6)
if icon_path != "":
var ic := UiKit.icon_rect(icon_path, 16) # 缺位 null → 纯文本回退
if ic != null:
row.add_child(ic)
var l := UiKit.label(text, 15, true)
l.add_theme_color_override("font_color", UiKit.WARM_SOFT)
return l
row.add_child(l)
return row
# ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡)----
@@ -22,7 +22,8 @@ var _bonus_list: VBoxContainer
func _init() -> void:
custom_minimum_size = Vector2(240, 0)
clip_contents = true
var sb := UiKit.panel_flat(UiKit.PANEL_HERO, UiKit.PANEL_BORDER, 0, 1, Vector4(8, 6, 8, 6))
# 主面板穿石板戏服(样张 v2 冻结批 T2——纹理本色≈PANEL_HERO;数据框仍素面 §7.1;缺位回退 flat)
var sb := UiKit.panel_skinned(Vector4(8, 6, 8, 6))
add_theme_stylebox_override("panel", sb)
var col := VBoxContainer.new()
col.add_theme_constant_override("separation", 14) # 布局充实(2026-09-26 反馈③:区段间距放大)
+7 -3
View File
@@ -12,8 +12,8 @@ signal closed
const ENTRIES := [
["#C9AF83", "完整覆尘地砖", "未开格——左键挖开(1AP,须相邻)· 右键插旗(1AP)"],
["#B39A78", "碎裂裸土", "已开 0 格——可通行,左键移动(免费)"],
["#C63D22", "旗", "一次性承诺(不能拔)——轮末自动判定标中;插错可左键直接挖开(安全格零奖励、雷格吃挖雷惩罚)"],
["#2E6E8E", "小人", "探险者——8 向相邻未开格常驻微光 = 可挖域"],
["#000000", "旗", "封印印记(呈现名·不能拔)——轮末自动判定标中;插错可左键直接挖开(安全格零奖励、雷格吃挖雷惩罚)"],
["#2e86de", "小人", "探险者——8 向相邻未开格常驻微光 = 可挖域"],
["#F0A828", "金徽章", "已开格奖励身份 icon(旗之下、数值之上,不进数字区)"],
["#33261B", "状态槽空态", "无状态效果时槽位隐藏,数据框上移补位——非异常"],
]
@@ -33,12 +33,15 @@ func _init() -> void:
center.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.add_child(center)
_panel = PanelContainer.new()
_panel.add_theme_stylebox_override("panel", UiKit.panel_skinned(Vector4(12, 10, 12, 10))) # 石板皮(T2;缺位回退 flat)
center.add_child(_panel)
var box := VBoxContainer.new()
box.custom_minimum_size = Vector2(520, 0)
box.add_theme_constant_override("separation", 6)
_panel.add_child(box)
box.add_child(UiKit.label("图例 · 格态", 20, true))
var title := UiKit.label("图例 · 格态", 20, true)
title.add_theme_color_override("font_color", UiKit.INK_TITLE) # 亮底墨字(§7.2 数据素面)
box.add_child(title)
box.add_child(HSeparator.new())
for e in ENTRIES:
var row := HBoxContainer.new()
@@ -48,6 +51,7 @@ func _init() -> void:
chip.color = Color(e[0])
row.add_child(chip)
var lab := UiKit.label("%s —— %s" % [e[1], e[2]], 13, false)
lab.add_theme_color_override("font_color", UiKit.INK_LABEL) # 亮底墨字(§7.2 数据素面)
lab.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
lab.custom_minimum_size = Vector2(440, 0)
row.add_child(lab)
+12 -1
View File
@@ -17,7 +17,8 @@ func _init() -> void:
# 结算轮光带:金橙半透明横带自棋盘上缘扫至下缘(一次 0.5s,自动回收)——
# "棋盘能量向战斗画面汇聚"的开轮语义(§2.2 色彩转金橙主导;区分于 board_fx 的完美排雷金光带)
# "棋盘能量向战斗画面汇聚"的开轮语义(§2.2 色彩转金橙主导;区分于 board_fx 的完美排雷金光带)。
# 终稿(样张 v2 冻结批 T3):加 2px 亮金前导缘线——扫带运动方向可读(兑现光带逐行横扫 §2.2)
func play_round_sweep(board_rect: Rect2) -> void:
if board_rect.size == Vector2.ZERO:
return
@@ -27,10 +28,20 @@ func play_round_sweep(board_rect: Rect2) -> void:
band.position = Vector2(board_rect.position.x, board_rect.position.y)
band.size = Vector2(board_rect.size.x, 34.0)
add_child(band)
var edge := ColorRect.new()
edge.color = Color(UiKit.GOLD_LIT, 0.7)
edge.mouse_filter = Control.MOUSE_FILTER_IGNORE
edge.size = Vector2(board_rect.size.x, 2.0)
edge.position = band.position
add_child(edge)
var tw := band.create_tween()
tw.tween_property(band, "position:y", board_rect.end.y - 34.0, UiKit.motion(0.5)).set_trans(Tween.TRANS_SINE)
tw.parallel().tween_property(band, "modulate:a", 0.0, UiKit.motion(0.5)).set_delay(UiKit.motion(0.15))
tw.tween_callback(band.queue_free)
var etw := edge.create_tween()
etw.tween_property(edge, "position:y", board_rect.end.y - 2.0, UiKit.motion(0.5)).set_trans(Tween.TRANS_SINE)
etw.parallel().tween_property(edge, "modulate:a", 0.0, UiKit.motion(0.2)).set_delay(UiKit.motion(0.3))
etw.tween_callback(edge.queue_free)
# 跨区弹道:glyph 字形自 from 飞向 to(0.35s + 轻微上抛弧线 + 终点淡出,自动回收)——
+43 -1
View File
@@ -183,13 +183,22 @@ func set_pending(ptype: String, pid: String) -> void:
_play_intent_motion()
# 三档动作动画占位:摇定时一次性短动画(意图图标弹入 + 立绘前倾回正),随后静帧保持
# 意图三档动作动画终稿(样张 v2 冻结批 T3——§4.6.4 三档:①普通攻击=零表示〔set_pending 早退〕
# ②特殊准备=徽标弹入+二次弹跳(高举语义,运动手段 §2)③重击/硬化=缓推蓄力+定格微顿+回落;
# 三档运动签名互异,灰度/剪影层可辨)。摇定时一次性短动画,随后静帧保持。
func _play_intent_motion() -> void:
var tw := create_tween()
tw.tween_property(self, "_intent_scale", 1.6, UiKit.motion(0.10))
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.16))
tw.parallel().tween_property(self, "_glyph_rot", 4.0, UiKit.motion(0.10))
tw.tween_property(self, "_glyph_rot", 0.0, UiKit.motion(0.14))
if _intent_col == UiKit.GOLD_DEEP:
tw.tween_property(self, "_intent_scale", 1.22, UiKit.motion(0.28)).set_trans(Tween.TRANS_SINE) # ③蓄力缓推
tw.tween_interval(UiKit.motion(0.40)) # 定格微顿
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.18))
else:
tw.tween_property(self, "_intent_scale", 1.26, UiKit.motion(0.11)) # ②二次弹跳(高举)
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.13))
# 资产挂点(战斗表现样张批):tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。
@@ -234,6 +243,7 @@ func mark_dead() -> void:
tw.tween_property(self, "rotation_degrees", 2.5, UiKit.motion(0.12))
tw.tween_property(self, "rotation_degrees", -1.5, UiKit.motion(0.1))
tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08))
_spawn_death_shards() # 碎裂退场(§4.6.4 语义硬规则:灰化+碎裂禁瞬间消失;§8.4 v2 冻结 5 块 0.45s)
_refresh_portrait_glyph()
_intent_visible = false # 死亡残位意图退位(旧同款)
_set_hp(0)
@@ -241,6 +251,38 @@ func mark_dead() -> void:
slot_row.visible = false # 状态行随 mark_dead 同拍隐藏(批内代拍板②待追认)
# ---- 演出密度终稿(样张 v2 冻结批 T3——程序化 tween,零新帧资产) ----
const DEATH_SHARD_N := 5 # 碎块数(§8.4 冻结值)
var _death_shards := 0 # 探针出口计数(碎块节点回收后仍保留读数)
# 死亡碎块弹飞:立绘盒中心五块碎石抛物线散落(程序化色块——确定性散布,禁全局随机)
func _spawn_death_shards() -> void:
_death_shards = DEATH_SHARD_N
var center := _portrait_rect().get_center()
for i in DEATH_SHARD_N:
var shard := ColorRect.new()
shard.color = UiKit.STONE if i % 2 == 0 else UiKit.STONE.darkened(0.2)
shard.size = Vector2(7, 5) + Vector2(2, 0) * float(i % 2)
shard.mouse_filter = Control.MOUSE_FILTER_IGNORE
shard.position = center + Vector2(float(i % 3) - 1.0, float(i / 3) - 0.5) * 6.0
shard.rotation = float(i) * 0.7
add_child(shard)
var dir := Vector2((float(i % 3) - 1.0) * 30.0, -36.0 - 6.0 * float(i / 3))
var peak := shard.position + dir
var stw := shard.create_tween()
stw.tween_property(shard, "position", peak, UiKit.motion(0.18)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
stw.tween_property(shard, "position", peak + Vector2(dir.x * 0.4, 52.0), UiKit.motion(0.27)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
stw.parallel().tween_property(shard, "modulate:a", 0.0, UiKit.motion(0.18)).set_delay(UiKit.motion(0.27))
stw.tween_callback(shard.queue_free)
# 演出探针出口(fx_probe——死亡碎裂断言面)
func fx_probe() -> Dictionary:
return {"death_shards": _death_shards}
func is_dead() -> bool:
return _dead
+88 -1
View File
@@ -7,6 +7,9 @@ extends RefCounted
# ── 色板(语义族;值 = 2026-09-20 收敛时现值) ──
const INK := Color8(51, 38, 27) # 墨族——正文/数字(对格底 ≥4.5:1)
const INK_COLD := Color8(19, 46, 54) # 青墨 1-3 #132E36(数字墨三档·冷=稀疏——样张 v2 定稿 2026-09-28)
const INK_OCHRE := Color8(60, 37, 12) # 赭墨 4-5 #3C250C(数字墨三档·中)
const INK_RUST := Color8(74, 30, 20) # 锈墨 6-8 #4A1E14(数字墨三档·暖=密集)
const INK_LABEL := Color8(46, 34, 24) # 标签深墨
const INK_TITLE := Color8(30, 26, 22) # 亮底标题墨
const INK_ROUTE := Color8(24, 20, 16) # 节点图最深墨
@@ -31,7 +34,7 @@ const RED_MINE := Color8(150, 44, 36) # 引爆雷字朱(已开雷格"
const RED_FORBID := Color8(120, 60, 50) # 禁止框朱
const RED_ALERT := Color8(200, 60, 48) # 报错朱
const HERO_BLUE := Color8(46, 110, 142) # 小人蓝
const HERO_BLUE := Color8(46, 134, 222) # 小人蓝 #2E86DE(样张 v2 定稿 2026-09-28——圣经 §4.1 候选值冻结;旧 #2E6E8E 收敛期快照退役)
const BOSS_PURPLE := Color8(143, 47, 106) # 紫赤(Boss 专属)
const DEBUFF_PURPLE := Color8(122, 66, 148) # 妨害紫(意图 ☠)
@@ -98,6 +101,18 @@ const TEX_ENEMY_DUERGAR := "res://assets/art/character/character_enemy_duergar.p
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" # 灰矮人石君王
# 框材组纹理槽(样张 v2 冻结批 T2 2026-09-28——♻ 复用件量化改色入包,产物 = tools/quantize_ui_skin.py,
# 全 CC0;缺位 = load_tex null → 程序化回退原路径,冒烟安全)
const TEX_PANEL_STONE := "res://assets/art/ui/panel_stone.png" # 石板亮面板九宫格 48×48
const TEX_BUTTON_STONE := "res://assets/art/ui/button_stone.png" # 按钮九宫格·常态(可按的砖)
const TEX_BUTTON_STONE_PRESSED := "res://assets/art/ui/button_stone_pressed.png" # 按钮九宫格·按压(下沉一档)
const TEX_BACKDROP_STONE := "res://assets/art/ui/backdrop_stone.png" # 石纹底噪 256×256 无缝
const TEX_BORDER_STONE := "res://assets/art/ui/border_stone.png" # 仪式石框(空心角括框)
const TEX_ICON_KEYSTONE := "res://assets/art/ui/icon_keystone.png" # 钥石图标(奖励金)
const TEX_ICON_FORGE := "res://assets/art/ui/icon_forge.png" # 柜台一·属性锻造
const TEX_ICON_WORKSHOP := "res://assets/art/ui/icon_workshop.png" # 柜台二·道具工坊
const TEX_ICON_ATLAS := "res://assets/art/ui/icon_atlas.png" # 柜台三·奖励格图鉴
# 素材纹理守卫装载(exists 检查避开缺位树上 ResourceLoader 报错刷屏——冒烟/无素材检出静默回退)
static func load_tex(path: String) -> Texture2D:
@@ -144,4 +159,76 @@ static func label(text: String, size: int, bold: bool) -> Label:
static func button(text: String) -> Button:
var b := Button.new()
b.text = text
_apply_button_skin(b)
return b
# ── 框材纹理工厂(样张 v2 冻结批 T2——§7.1 容器穿戏服、数据素面;缺位回退 flat/主题默认) ──
# 九宫格 StyleBoxTexture(texture_margin 8px——quantize 脚本同值);tint 供消费点微调色调
static func _tex_sb(tex: Texture2D, margin: float, tint := Color.WHITE) -> StyleBoxTexture:
var sb := StyleBoxTexture.new()
sb.texture = tex
sb.texture_margin_left = margin
sb.texture_margin_top = margin
sb.texture_margin_right = margin
sb.texture_margin_bottom = margin
sb.modulate_color = tint
return sb
# 石板亮面板(B 区舞台/C 区主面板/图例——容器穿戏服;数据框仍 panel_flat 素面 §7.1)
static func panel_skinned(margin: Vector4 = Vector4.ZERO, tint := Color.WHITE) -> StyleBox:
var tex := load_tex(TEX_PANEL_STONE)
if tex == null:
return panel_flat(PANEL_DATA, PANEL_BORDER, 4, 1, margin)
var sb := _tex_sb(tex, 8.0, tint)
sb.content_margin_left = margin.x
sb.content_margin_top = margin.y
sb.content_margin_right = margin.z
sb.content_margin_bottom = margin.w
return sb
# 仪式石框(空心角括框——营地容器框;缺位回退暗面板)
static func frame_skinned(margin: Vector4 = Vector4.ZERO) -> StyleBox:
var tex := load_tex(TEX_BORDER_STONE)
if tex == null:
return panel_flat(BG_OVERLAY, PANEL_BORDER, 6, 1, margin)
var sb := _tex_sb(tex, 12.0)
sb.content_margin_left = margin.x
sb.content_margin_top = margin.y
sb.content_margin_right = margin.z
sb.content_margin_bottom = margin.w
return sb
# 图标槽(营地钥石/柜台件——缺位返回 null,消费点不挂 TextureRect 即回退纯文本)
static func icon_rect(tex_path: String, px: float) -> TextureRect:
var t := load_tex(tex_path)
if t == null:
return null
var tr := TextureRect.new()
tr.texture = t
tr.custom_minimum_size = Vector2(px, px)
tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
tr.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
return tr
# 按钮皮(常态/按压两态+墨字——缺位 = 引擎默认主题,素材批前逐位一致)
static func _apply_button_skin(b: Button) -> void:
var tex_n := load_tex(TEX_BUTTON_STONE)
if tex_n == null:
return
var tex_p := load_tex(TEX_BUTTON_STONE_PRESSED)
b.add_theme_stylebox_override("normal", _tex_sb(tex_n, 8.0))
b.add_theme_stylebox_override("hover", _tex_sb(tex_n, 8.0))
if tex_p != null:
b.add_theme_stylebox_override("pressed", _tex_sb(tex_p, 8.0))
b.add_theme_stylebox_override("disabled", _tex_sb(tex_p, 8.0))
b.add_theme_color_override("font_color", INK)
b.add_theme_color_override("font_hover_color", INK)
b.add_theme_color_override("font_pressed_color", INK)
b.add_theme_color_override("font_focus_color", INK)
b.add_theme_color_override("font_disabled_color", Color(INK, 0.45))