refactor(ui): C 区精简批——信息归属拍板代码实装(2026-09-26):①C 区面板=攻击+护甲AC 数据框+魔法道具槽(预留 4 空槽)+本关强化列表(预留空态隐藏)——HP 条/立绘占位/状态槽/RAP/GP/钥石退役;②状态效果唯一展示面=B 区站桩(compact 档 StatusSlotRow 整体迁入——16px 图标+层数角标+悬停 tooltip 义务随迁,BUG-0001 回归组件同源;清空反馈逐槽化光+微文案随迁站桩);③RAP 读数迁 D 区 AP 条贴弃图刷新消费口(点阵+数字+清零脉冲微文案+拒绝脉冲载体随迁);④hud_presenter 单投喂改造(B/C 双投喂重复展示退役)+钱包投喂退役(GP/钥石迁出战斗屏,去向=关卡画面/结算/营地归对应 UX 批);冒烟 557/0+五探针全绿

This commit is contained in:
DKChan committed 2026-09-26 01:26:26 +08:00
1 parent d6eee49945
commit effb98e57d
9 files changed
+203 -224

No files matched your search

+16 -22
View File
@@ -15,30 +15,24 @@ extends RefCounted
# 每次结算行/轮次/节点变化/动作返回后调用(组合根全量重查装配) # 每次结算行/轮次/节点变化/动作返回后调用(组合根全量重查装配)
# 2026-09-26 C 区精简批:钱包 GP/钥石投喂退役(中/大循环资源迁出战斗屏——信息归属拍板);
# HP/状态效果单投喂 B 区站桩(原 B/C 双投喂为重复展示——信息归属拍板单一展示面原则)
static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApBarView, static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApBarView,
settlement, runner, level_active: bool, db, refresh_remaining: int, gp_label: Label = null, settlement, runner, level_active: bool, db, refresh_remaining: int) -> void:
keystone_label: Label = null, keystone_balance: int = 0) -> void:
# GP 余额常驻(GP+商店房实施批 2026-09-21——A 区顶栏邻接位;runner 缺位 = 非冒险态按 0 读数)
if gp_label != null:
gp_label.text = "金币 %d" % (int(runner.gp) if runner != null else 0)
# 钥石余额常驻(元进度实现批 2026-09-21——A 区顶栏邻接位;数据源 MetaProgression 账本,购买/收束后随全量重查刷新)
if keystone_label != null:
keystone_label.text = "钥石 %d" % keystone_balance
if settlement == null: if settlement == null:
_refresh_out_of_battle(stage, info, runner, level_active, db) _refresh_out_of_battle(stage, info, ap_bar, runner, level_active, db)
return return
var s: Dictionary = settlement.presentation_snapshot() var s: Dictionary = settlement.presentation_snapshot()
var hero: Dictionary = s.hero var hero: Dictionary = s.hero
if hero.is_empty() or not bool(hero.get("alive", false)): if hero.is_empty() or not bool(hero.get("alive", false)):
_refresh_out_of_battle(stage, info, runner, level_active, db) _refresh_out_of_battle(stage, info, ap_bar, runner, level_active, db)
return return
_refresh_hero(stage, info, s) _refresh_hero(stage, info, s)
_refresh_economy(info, ap_bar, s, db, refresh_remaining) _refresh_economy(ap_bar, s, db, refresh_remaining)
stage.set_enemies(s.enemies) stage.set_enemies(s.enemies)
info.set_status_effects(s.hero_effects)
# ---- B 区主角站桩 + C 区数据框 ---- # ---- B 区主角站桩(HP/状态唯一展示面)+ C 区数据框(攻击/AC) ----
static func _refresh_hero(stage: BattleStageView, info: HeroInfoPanelView, s: Dictionary) -> void: static func _refresh_hero(stage: BattleStageView, info: HeroInfoPanelView, s: Dictionary) -> void:
var hero: Dictionary = s.hero var hero: Dictionary = s.hero
@@ -55,19 +49,19 @@ static func _refresh_hero(stage: BattleStageView, info: HeroInfoPanelView, s: Di
var mod_text := "" var mod_text := ""
if mod_total != 0: if mod_total != 0:
mod_text = "(%+d%% %s)" % [mod_total, "·".join(sources)] mod_text = "(%+d%% %s)" % [mod_total, "·".join(sources)]
# B 区站桩只挂血条+状态小图标(2026-09-20 全站桩拍板)——攻击值等规划数据归 C 区数据框 # B 区站桩 = HP 与状态效果唯一展示面(2026-09-26 信息归属拍板)——C 区不再重复投喂
stage.hero_standee.set_hero(hp, max_hp) stage.hero_standee.set_hero(hp, max_hp)
stage.hero_standee.set_effects(s.hero_effects) stage.hero_standee.set_effects(s.hero_effects)
info.set_hero(hp, max_hp)
info.set_attack(atk, mod_text) info.set_attack(atk, mod_text)
# 我方 AC 唯一展示面 = C 区(2026-09-26 二轮拍板补入)——快照 armor_class 含战斗内 ac_up 折算
info.set_armor(int(hero.get("armor_class", 0)))
# ---- 行动经济(AP 点阵 / RAP / 弃图刷新按钮态) ---- # ---- 行动经济(AP 点阵 / RAP / 弃图刷新按钮态——RAP 读数 2026-09-26 自 C 区迁入本条) ----
static func _refresh_economy(info: HeroInfoPanelView, ap_bar: ApBarView, s: Dictionary, db, refresh_remaining: int) -> void: static func _refresh_economy(ap_bar: ApBarView, s: Dictionary, db, refresh_remaining: int) -> void:
ap_bar.set_ap(int(s.ap), int(db.action_economy.ap_per_round)) ap_bar.set_ap(int(s.ap), int(db.action_economy.ap_per_round))
# RAP 活值投影(C 区点阵)——清零反馈分支(行动经济 D1 义务)依赖本处持续投喂才会可达 ap_bar.set_rap(int(s.rap))
info.set_rap(int(s.rap), int(db.action_economy.rap_refresh_cost))
var digging := String(s.phase) == ActionEconomy.PHASE_ACTIVE var digging := String(s.phase) == ActionEconomy.PHASE_ACTIVE
ap_bar.set_refresh_state(refresh_remaining, ap_bar.set_refresh_state(refresh_remaining,
bool(s.can_refresh) and digging) # Waiting 代理已随 OQ12 退役——UI 仅挖掘期受理 bool(s.can_refresh) and digging) # Waiting 代理已随 OQ12 退役——UI 仅挖掘期受理
@@ -76,16 +70,16 @@ static func _refresh_economy(info: HeroInfoPanelView, ap_bar: ApBarView, s: Dict
# 战斗外(节点间隙/关卡结束)——HP 读 runner 携带值 # 战斗外(节点间隙/关卡结束)——HP 读 runner 携带值
static func _refresh_out_of_battle(stage: BattleStageView, info: HeroInfoPanelView, static func _refresh_out_of_battle(stage: BattleStageView, info: HeroInfoPanelView,
runner, level_active: bool, db) -> void: ap_bar: ApBarView, runner, level_active: bool, db) -> void:
if not level_active: if not level_active:
return return
if runner != null: if runner != null:
info.set_hero(runner.hero_hp, runner.effective_max_hp())
info.set_attack(runner.effective_atk(), "(关卡推进中)") info.set_attack(runner.effective_atk(), "(关卡推进中)")
info.set_armor(runner.effective_ac())
stage.hero_standee.set_hero(runner.hero_hp, runner.effective_max_hp()) stage.hero_standee.set_hero(runner.hero_hp, runner.effective_max_hp())
stage.hero_standee.set_effects([]) stage.hero_standee.set_effects([])
stage.clear_enemies() # 节点间隙清敌方站桩残位(死亡瞬间的灰化呈现已在结算行驱动完成) stage.clear_enemies() # 节点间隙清敌方站桩残位(死亡瞬间的灰化呈现已在结算行驱动完成)
info.set_rap(0, int(db.action_economy.rap_refresh_cost)) # battle_over RAP 清零反馈(行动经济 义务) ap_bar.set_rap(0) # battle_over RAP 清零反馈(行动经济 义务——载体 2026-09-26 迁 AP 条)
# 三源拒绝可感知(排雷玩法 reject_reason/sub_reason → 玩家文案;pattern 1 双源拒绝反馈) # 三源拒绝可感知(排雷玩法 reject_reason/sub_reason → 玩家文案;pattern 1 双源拒绝反馈)
+6 -11
View File
@@ -52,13 +52,11 @@ var _debug = null
# 由关卡画面本体选路覆盖层承载;E 区道具面板随可选类删除下线) # 由关卡画面本体选路覆盖层承载;E 区道具面板随可选类删除下线)
var stage: BattleStageView # B 区(含 log_view 明细带) var stage: BattleStageView # B 区(含 log_view 明细带)
var log_view: BattleLogView var log_view: BattleLogView
var info_panel: HeroInfoPanelView # C 区(含钱包行——原 A 区顶栏 GP/钥石余额常驻位) var info_panel: HeroInfoPanelView # C 区(攻击/AC 数据框+两预留区——2026-09-26 信息归属拍板精简)
var board_zone: BoardZoneView # D 区(含 ap_bar/board_view/toast/fx) var board_zone: BoardZoneView # D 区(含 ap_bar/board_view/toast/fx)
var board_view: BoardView # 探针兼容别名(= board_zone.board_view) var board_view: BoardView # 探针兼容别名(= board_zone.board_view)
var ap_bar: ApBarView # 探针兼容别名(= board_zone.ap_bar) var ap_bar: ApBarView # 探针兼容别名(= board_zone.ap_bar)
var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道/光带——A–D 之上、模态之下) var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道/光带——A–D 之上、模态之下)
var gp_label: Label # GP 余额常驻位(C 区钱包行——文案装配归 HudPresenter)
var keystone_label: Label # 钥石余额常驻位(C 区钱包行——文案装配归 HudPresenter)
# 模态 overlay(z 序 = 登记序;pattern 6 层级纪律) # 模态 overlay(z 序 = 登记序;pattern 6 层级纪律)
var legend_overlay: LegendOverlay var legend_overlay: LegendOverlay
var review_overlay: SettlementReviewOverlay var review_overlay: SettlementReviewOverlay
@@ -102,8 +100,6 @@ func _build_ui() -> void:
var root := VBoxContainer.new() var root := VBoxContainer.new()
add_child(root) add_child(root)
var battle_ui: Dictionary = UiAssembler.build_battle_ui(root, db) var battle_ui: Dictionary = UiAssembler.build_battle_ui(root, db)
gp_label = battle_ui.gp_label
keystone_label = battle_ui.keystone_label
stage = battle_ui.stage stage = battle_ui.stage
log_view = battle_ui.log_view log_view = battle_ui.log_view
info_panel = battle_ui.info_panel info_panel = battle_ui.info_panel
@@ -310,7 +306,7 @@ func _on_adventure_finished(summary: Dictionary) -> void:
settlement = null settlement = null
_close_modal(route_overlay) # 终局兜底(放弃入口新场景:选路层在开态——走模态栈恢复其遮挡面;未开过时 close 容错) _close_modal(route_overlay) # 终局兜底(放弃入口新场景:选路层在开态——走模态栈恢复其遮挡面;未开过时 close 容错)
_sync_settlement_visuals() # → 全关 _sync_settlement_visuals() # → 全关
info_panel.set_status_cleared_feedback() # 状态槽全清可见反馈(增益状态×4④) stage.hero_standee.set_effects_cleared_feedback() # 状态全清可见反馈(增益状态×4④——载体 2026-09-26 迁 B 区站桩)
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面) stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
# 存档入账(元进度实现批):settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变—— # 存档入账(元进度实现批):settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变——
# 调试两层不经过本处理器,天然不写档不污染生涯数据)。 # 调试两层不经过本处理器,天然不写档不污染生涯数据)。
@@ -441,8 +437,8 @@ func _on_battle_finished(payload: Dictionary) -> void:
var result: String = String(payload.get("result", "")) var result: String = String(payload.get("result", ""))
_sync_settlement_visuals() # → 全关(battle_over 反馈面) _sync_settlement_visuals() # → 全关(battle_over 反馈面)
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面) stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
info_panel.set_rap(0, int(db.action_economy.rap_refresh_cost)) # RAP 清零滚动 + 微文案 ap_bar.set_rap(0) # RAP 清零脉冲 + 微文案(载体 2026-09-26 迁 D 区 AP 条)
info_panel.set_status_cleared_feedback() # 状态槽全清可见反馈(增益状态×4④) stage.hero_standee.set_effects_cleared_feedback() # 状态全清可见反馈(增益状态×4④——载体迁 B 区站桩)
var decision: Dictionary = _debug.on_battle_finished(payload) var decision: Dictionary = _debug.on_battle_finished(payload)
match String(decision.outcome): match String(decision.outcome):
"next_layer": "next_layer":
@@ -734,7 +730,7 @@ func _on_refresh_confirmed() -> void:
logger.log_ux("ux_refresh_attempt", {"result": result, "round_refresh_index": _refresh_used_round + 1}) logger.log_ux("ux_refresh_attempt", {"result": result, "round_refresh_index": _refresh_used_round + 1})
if result == "rejected_rap": if result == "rejected_rap":
board_zone.toast.show_toast("RAP 不足 · 还差 %d 点(弃图刷新需 8)" % (cost - rap), "warn") board_zone.toast.show_toast("RAP 不足 · 还差 %d 点(弃图刷新需 8)" % (cost - rap), "warn")
info_panel.pulse_rap() ap_bar.pulse_rap()
else: else:
board_zone.toast.show_toast("本轮刷新次数已用完(每轮 2 次)", "warn") board_zone.toast.show_toast("本轮刷新次数已用完(每轮 2 次)", "warn")
board_view.refresh() board_view.refresh()
@@ -757,8 +753,7 @@ func _handle_outcome_feedback(outcome: Dictionary) -> void:
func _refresh_presentations() -> void: func _refresh_presentations() -> void:
# 每个核心信号/写调用返回后的全量重查装配(更新触发注记——无推送信号,命令式重查) # 每个核心信号/写调用返回后的全量重查装配(更新触发注记——无推送信号,命令式重查)
var refresh_remaining: int = maxi(0, int(db.action_economy.refresh_cap_per_round) - _refresh_used_round) var refresh_remaining: int = maxi(0, int(db.action_economy.refresh_cap_per_round) - _refresh_used_round)
HudPresenter.refresh(stage, info_panel, ap_bar, settlement, runner, _level_active, db, refresh_remaining, HudPresenter.refresh(stage, info_panel, ap_bar, settlement, runner, _level_active, db, refresh_remaining)
gp_label, keystone_label, meta.keystone_balance() if meta != null else 0)
_sync_settlement_visuals() _sync_settlement_visuals()
+4 -5
View File
@@ -11,7 +11,8 @@ extends RefCounted
# 进度由关卡画面本体(选路覆盖层整图)承载,28px 纵向空间拨给 C/D 底行 520→548;E 区道具面板 # 进度由关卡画面本体(选路覆盖层整图)承载,28px 纵向空间拨给 C/D 底行 520→548;E 区道具面板
# 已随可选类删除下线,2026-09-15 格面资源批)。 # 已随可选类删除下线,2026-09-15 格面资源批)。
# + 结算演出覆盖层(A–D 之上、模态之下——跨区弹道/光带载体,结算演出链批 2026-09-20)。 # + 结算演出覆盖层(A–D 之上、模态之下——跨区弹道/光带载体,结算演出链批 2026-09-20)。
# 返回句柄字典:{gp_label, keystone_label, stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx} # 返回句柄字典:{stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx}
# (gp_label/keystone_label 随 2026-09-26 信息归属拍板退役——GP/钥石迁出战斗屏,句柄不再透出)
static func build_battle_ui(root: Control, db) -> Dictionary: static func build_battle_ui(root: Control, db) -> Dictionary:
# set_anchors_and_offsets_preset(非 set_anchors_preset):后者会把控件重置回最小内容尺寸 # set_anchors_and_offsets_preset(非 set_anchors_preset):后者会把控件重置回最小内容尺寸
# (实测 927×613——缩顶、棋盘 45px 格 ×10 行超出 D 区 333px 高被裁,2026-09-13 用户报告回归) # (实测 927×613——缩顶、棋盘 45px 格 ×10 行超出 D 区 333px 高被裁,2026-09-13 用户报告回归)
@@ -39,11 +40,9 @@ static func build_battle_ui(root: Control, db) -> Dictionary:
var settlement_fx := SettlementFxView.new() var settlement_fx := SettlementFxView.new()
root.add_child(settlement_fx) root.add_child(settlement_fx)
# GP/钥石余额常驻位(GP+商店房实施批 2026-09-21 原居 A 区顶栏——顶栏退役后迁 C 区底行, # GP/钥石余额位已随 2026-09-26 信息归属拍板退役(中/大循环资源迁出战斗屏——
# 句柄经 info_panel 视图成员透出;文案装配归 HudPresenter,此处只透传引用) # GP 去向 = 关卡画面+战斗结算画面、钥石去向 = 结算画面+营地,呈现归对应 UX spec 批次)
return { return {
"gp_label": info_panel.gp_label,
"keystone_label": info_panel.keystone_label,
"stage": stage, "stage": stage,
"log_view": stage.log_view, "log_view": stage.log_view,
"info_panel": info_panel, "info_panel": info_panel,
+56
View File
@@ -2,6 +2,8 @@
# 实现 GDD:design/ux/battle.md(D 区 AP 条 44px——点阵+数字〔pattern 5 点阵资源显示〕、 # 实现 GDD:design/ux/battle.md(D 区 AP 条 44px——点阵+数字〔pattern 5 点阵资源显示〕、
# 停手按钮居左、图例+弃图刷新按钮居右〔雷图右上角〕;弃图刷新确认微交互与常规确认不同位 # 停手按钮居左、图例+弃图刷新按钮居右〔雷图右上角〕;弃图刷新确认微交互与常规确认不同位
# 〔pattern 7 破坏性确认异位:RAP 扣费 · 旧板旗作废显式代价〕)。 # 〔pattern 7 破坏性确认异位:RAP 扣费 · 旧板旗作废显式代价〕)。
# 2026-09-26 信息归属批:RAP 读数(点阵+数字)自 C 区迁入本条、置弃图刷新按钮左邻
# (RAP 唯一消费口 = 弃图刷新——就近原则;RAP 清零反馈/资源拒绝脉冲载体随迁)。
# Date: 2026-09-12 # Date: 2026-09-12
# 高内聚边界:纯视图——意图经信号外发(stop_pressed/refresh_confirmed/legend_toggled), # 高内聚边界:纯视图——意图经信号外发(stop_pressed/refresh_confirmed/legend_toggled),
# 状态由组合根经 setter 注入;刷新可用性预判 = RAP≥8 读数(行动经济 无推送信号——每次调用返回后重查)。 # 状态由组合根经 setter 注入;刷新可用性预判 = RAP≥8 读数(行动经济 无推送信号——每次调用返回后重查)。
@@ -24,6 +26,10 @@ var _btn_refresh: Button
var _refresh_sub: Label var _refresh_sub: Label
var _confirm_row: HBoxContainer var _confirm_row: HBoxContainer
var _confirm_lab: Label var _confirm_lab: Label
var _rap_dots: HBoxContainer
var _rap_label: Label
var _rap_feedback: Label
var _last_rap := 0 # 上次投影值(清零反馈判据——禁解析自身 label 文本当状态)
var refresh_cost: int = 0 # 表值驱动(装配经 set_refresh_cost 注入 ActionEconomyTable.rap_refresh_cost) var refresh_cost: int = 0 # 表值驱动(装配经 set_refresh_cost 注入 ActionEconomyTable.rap_refresh_cost)
var _confirm_armed := false var _confirm_armed := false
var _arm_tween: Tween var _arm_tween: Tween
@@ -56,6 +62,20 @@ func _init() -> void:
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
row.add_child(spacer) row.add_child(spacer)
# RAP 读数(2026-09-26 信息归属拍板自 C 区迁入):RAP 唯一消费口 = 弃图刷新——资源数字贴消费按钮
# (就近原则);扣费文案「RAP−%d」已在刷新按钮副行,此处不重复代价提示(单一展示面原则)
_rap_dots = HBoxContainer.new()
_rap_dots.add_theme_constant_override("separation", 2)
_rap_dots.size_flags_vertical = Control.SIZE_SHRINK_CENTER
row.add_child(_rap_dots)
_rap_label = UiKit.label("RAP 0", 14, true)
_rap_label.size_flags_vertical = Control.SIZE_SHRINK_CENTER
row.add_child(_rap_label)
_rap_feedback = UiKit.label("", 11, false)
_rap_feedback.add_theme_color_override("font_color", UiKit.STONE_FAINT)
_rap_feedback.size_flags_vertical = Control.SIZE_SHRINK_CENTER
row.add_child(_rap_feedback)
# 破坏性确认异位(pattern 7):确认 chips 出现在刷新按钮左方——与常规"按钮自身确认"不同位 # 破坏性确认异位(pattern 7):确认 chips 出现在刷新按钮左方——与常规"按钮自身确认"不同位
_confirm_row = HBoxContainer.new() _confirm_row = HBoxContainer.new()
_confirm_row.add_theme_constant_override("separation", 4) _confirm_row.add_theme_constant_override("separation", 4)
@@ -117,6 +137,42 @@ func set_stop_enabled(on: bool) -> void:
_btn_stop.disabled = not on _btn_stop.disabled = not on
# RAP 活值投影(点阵+数字——pattern 5 双承载);清零反馈分支(行动经济 D1 义务)依赖持续投喂可达
func set_rap(rap: int) -> void:
var prev := _last_rap
_last_rap = rap
_rap_label.text = "RAP %d" % rap
_render_rap_dots(rap)
if prev > 0 and rap == 0:
# battle_over RAP 清零可见反馈(行动经济 D1 义务):数字脉冲 + 微文案(载体自 C 区迁入)
_rap_feedback.text = "随战斗结束消散"
var tw := create_tween()
tw.tween_property(_rap_label, "modulate", Color(1.4, 1.2, 0.7), UiKit.motion(0.12))
tw.tween_property(_rap_label, "modulate", Color(1, 1, 1), UiKit.motion(0.3))
elif rap > 0:
_rap_feedback.text = ""
# 资源面拒绝反馈(pattern 1 resource_face 腿):RAP 数字脉冲(RAP<8 弃图刷新被拒)
func pulse_rap() -> void:
var tw := create_tween()
tw.tween_property(_rap_label, "modulate", Color(1.0, 0.4, 0.3), UiKit.motion(0.1))
tw.tween_property(_rap_label, "modulate", Color(1, 1, 1), UiKit.motion(0.25))
func _render_rap_dots(rap: int) -> void:
var shown := mini(rap, 16)
while _rap_dots.get_child_count() < shown:
var d := ColorRect.new()
d.custom_minimum_size = Vector2(8, 8)
d.size_flags_vertical = Control.SIZE_SHRINK_CENTER
d.mouse_filter = Control.MOUSE_FILTER_IGNORE
_rap_dots.add_child(d)
for i in _rap_dots.get_child_count():
var d: ColorRect = _rap_dots.get_child(i)
d.color = COL_GOLD if i < shown else COL_DOT_OFF
func set_legend_enabled(on: bool) -> void: func set_legend_enabled(on: bool) -> void:
_btn_legend.disabled = not on _btn_legend.disabled = not on
+55 -128
View File
@@ -1,29 +1,22 @@
# VERTICAL SLICE - NOT FOR PRODUCTION # VERTICAL SLICE - NOT FOR PRODUCTION
# 实现 GDD:design/ux/battle.md(C 区角色信息 240×548〔底行 520→548——停点前移批 2026-09-23, # 实现 GDD:design/ux/battle.md(C 区角色信息 240×548——2026-09-26 信息归属拍板精简:
# 原 A 区顶栏空间并入〕——HP/攻击(+修正)/RAP 直角细线数据框 # 攻击(+修正来源角标)/护甲 AC 数据框 + 魔法道具槽(预留)+ 本关强化列表(预留)。
# 〔修正来源角标〕、状态效果槽 ×4、小人立绘占位;RAP 清零可见反馈〔行动经济 义务〕; # HP 条/立绘占位/状态效果槽/RAP 池/GP/钥石退役——HP 与状态唯一展示面 = B 区站桩(同一数据
# 状态槽空态 = 槽隐藏上移补位〔EC12〕)。 # hud_presenter 双投喂 B/C 两区的重复展示);RAP 迁 D 区 AP 条(贴弃图刷新消费口);
# Date: 2026-09-12 # GP(中循环)/钥石(大循环)迁出战斗屏)。
# 高内聚边界:纯视图 setter 面——装配(含修正文案)归 hud_presenter.gd;定长槽 + clip_contents # Date: 2026-09-12(2026-09-26 C 区精简批重写)
# 高内聚边界:纯视图 setter 面——装配(含修正文案/AC 投喂)归 hud_presenter.gd;定长槽 + clip_contents
# (缺陷①回归约束)。 # (缺陷①回归约束)。
class_name HeroInfoPanelView class_name HeroInfoPanelView
extends PanelContainer extends PanelContainer
const COL_INK := UiKit.INK const COL_INK := UiKit.INK
const COL_GOLD := UiKit.GOLD
const COL_DOT_OFF := UiKit.DOT_OFF
var _hp_bar: ProgressBar
var _hp_label: Label
var _atk_label: Label var _atk_label: Label
var _rap_dots: HBoxContainer var _armor_label: Label
var _rap_label: Label var item_slots: HBoxContainer # 魔法道具槽(预留——4 空槽占位;数量/交互形态待魔法道具批定稿)
var _rap_feedback: Label var _bonus_section: VBoxContainer # 本关强化(预留——空态隐藏,接线归大奖励/锻造呈现批)
var _status_feedback: Label var _bonus_list: VBoxContainer
var slot_row: StatusSlotRow
var gp_label: Label # 冒险钱包余额常驻位(停点前移批 2026-09-23——原 A 区顶栏迁入 C 区底行)
var keystone_label: Label # 钥石余额常驻位(同上——文案装配归 HudPresenter)
var _last_rap := 0 # 上次投影值(清零反馈判据——禁解析自身 label 文本当状态)
func _init() -> void: func _init() -> void:
@@ -36,68 +29,35 @@ func _init() -> void:
add_child(col) add_child(col)
col.add_child(UiKit.label("探险者", 16, true)) col.add_child(UiKit.label("探险者", 16, true))
_hp_bar = ProgressBar.new()
_hp_bar.custom_minimum_size = Vector2(0, 12)
_hp_bar.show_percentage = false
col.add_child(_hp_bar)
_hp_label = _data_line()
col.add_child(_hp_label)
_atk_label = _data_line() _atk_label = _data_line()
col.add_child(_atk_label) col.add_child(_atk_label)
_armor_label = _data_line()
col.add_child(_armor_label)
# RAP 框(pattern 5 点阵资源显示——点阵图形 + 数字精确值双承载) # 魔法道具槽(预留):随身战斗用道具的唯一展示面——空槽占位承诺格子在此,
var rap_head := HBoxContainer.new() # 不预设交互(挖掘期指向交互归 E 区既有裁决);终稿样式/槽位数归魔法道具批
rap_head.add_theme_constant_override("separation", 6) col.add_child(UiKit.label("魔法道具", 14, true))
col.add_child(rap_head) item_slots = HBoxContainer.new()
_rap_dots = HBoxContainer.new() item_slots.add_theme_constant_override("separation", 6)
_rap_dots.add_theme_constant_override("separation", 2) col.add_child(item_slots)
rap_head.add_child(_rap_dots) for i in 4:
_rap_label = UiKit.label("RAP 0", 13, true) var slot := PanelContainer.new()
rap_head.add_child(_rap_label) slot.custom_minimum_size = Vector2(40, 40)
_rap_feedback = UiKit.label("", 12, false) slot.add_theme_stylebox_override("panel",
_rap_feedback.add_theme_color_override("font_color", UiKit.STONE_FAINT) UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(4, 4, 4, 4)))
col.add_child(_rap_feedback) slot.mouse_filter = Control.MOUSE_FILTER_IGNORE
item_slots.add_child(slot)
col.add_child(UiKit.label("状态效果", 14, true)) # 本关强化(预留):锻造/大奖励/关卡奖励节点的属性提升——中循环数据因改变战斗数学准入;
slot_row = StatusSlotRow.new() # 与战斗内状态效果(B 区站桩,随战斗消散)分区,防「战斗结束被清」误读;空态整节隐藏
col.add_child(slot_row) _bonus_section = VBoxContainer.new()
_status_feedback = UiKit.label("", 12, false) _bonus_section.add_theme_constant_override("separation", 4)
_status_feedback.add_theme_color_override("font_color", UiKit.STONE_FAINT) _bonus_section.visible = false
col.add_child(_status_feedback) col.add_child(_bonus_section)
_bonus_section.add_child(UiKit.label("本关强化", 14, true))
# 小人立绘占位(程序化色块——真实立绘归 战斗表现) _bonus_list = VBoxContainer.new()
var portrait := ColorRect.new() _bonus_list.add_theme_constant_override("separation", 2)
portrait.color = Color(UiKit.HERO_BLUE, 56.0 / 255.0) # 原 Color8(46, 110, 142, 56) _bonus_section.add_child(_bonus_list)
portrait.custom_minimum_size = Vector2(0, 120)
portrait.size_flags_vertical = Control.SIZE_EXPAND_FILL
col.add_child(portrait)
var portrait_glyph := UiKit.label("小人立绘占位", 13, false)
portrait_glyph.add_theme_color_override("font_color", COL_INK)
portrait_glyph.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
portrait_glyph.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
portrait_glyph.mouse_filter = Control.MOUSE_FILTER_IGNORE
portrait_glyph.set_anchors_preset(Control.PRESET_FULL_RECT)
portrait.add_child(portrait_glyph)
# 钱包行(停点前移批 2026-09-23:A 区 28px 顶栏退役——GP/钥石余额常驻位迁本面板底行,
# 建节点归视图、文案装配归 HudPresenter,句柄经 ui_assembler 透传组合根)
var wallet_row := HBoxContainer.new()
wallet_row.add_theme_constant_override("separation", 10)
col.add_child(wallet_row)
gp_label = UiKit.label("金币 0", 15, true)
gp_label.add_theme_color_override("font_color", UiKit.GOLD_DEEP)
wallet_row.add_child(gp_label)
keystone_label = UiKit.label("钥石 0", 15, true)
keystone_label.add_theme_color_override("font_color", UiKit.GOLD)
keystone_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
keystone_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
wallet_row.add_child(keystone_label)
func set_hero(hp: int, max_hp: int) -> void:
_hp_bar.max_value = maxi(1, max_hp)
_hp_bar.value = hp
_hp_label.text = "HP %d / %d" % [hp, max_hp]
func set_attack(atk: int, mod_text: String) -> void: func set_attack(atk: int, mod_text: String) -> void:
@@ -105,68 +65,35 @@ func set_attack(atk: int, mod_text: String) -> void:
_atk_label.text = "攻击 %d %s" % [atk, mod_text] _atk_label.text = "攻击 %d %s" % [atk, mod_text]
func set_rap(rap: int, cap_hint: int) -> void: func set_armor(ac: int) -> void:
var prev := _last_rap # 护甲 AC(2026-09-26 二轮拍板补入)——我方 AC 唯一展示面;掷点判定轴受击侧可读性
_last_rap = rap _armor_label.text = "护甲 AC %d" % ac
_rap_label.text = "RAP %d(攒 %d 刷图)" % [rap, cap_hint]
_render_rap_dots(rap)
if prev > 0 and rap == 0:
# battle_over RAP 清零可见反馈(行动经济 D1 义务):数字滚动归零 + 微文案占位
_rap_feedback.text = "随战斗结束消散"
var tw := create_tween()
tw.tween_property(_rap_label, "modulate", Color(1.4, 1.2, 0.7), UiKit.motion(0.12))
tw.tween_property(_rap_label, "modulate", Color(1, 1, 1), UiKit.motion(0.3))
elif rap > 0:
_rap_feedback.text = ""
func set_status_effects(effects: Array) -> void: # 本关强化列表(预留接线面):lines = 文案行数组(如「锻造 +2 攻击」)——空数组 = 整节隐藏
slot_row.set_effects(effects) func set_level_bonuses(lines: Array) -> void:
_status_feedback.text = "" for c in _bonus_list.get_children():
_bonus_list.remove_child(c)
c.queue_free()
# battle_over:状态槽全清可见反馈(逐槽化光 + 微文案——增益状态×4④) for line in lines:
func set_status_cleared_feedback() -> void: var l := UiKit.label(String(line), 13, false)
slot_row.set_cleared_feedback() l.add_theme_color_override("font_color", COL_INK)
_status_feedback.text = "状态随战斗结束消散" _bonus_list.add_child(l)
_bonus_section.visible = not lines.is_empty()
func set_idle() -> void: func set_idle() -> void:
_hp_bar.value = 0
_hp_label.text = "HP —"
_atk_label.text = "攻击 —" _atk_label.text = "攻击 —"
_last_rap = 0 _armor_label.text = "护甲 AC —"
_render_rap_dots(0) set_level_bonuses([])
_rap_label.text = "RAP —"
slot_row.set_effects([])
func hp_text() -> String: func attack_text() -> String:
return _hp_label.text return _atk_label.text
func rap_text() -> String: func armor_text() -> String:
return _rap_label.text return _armor_label.text
# 资源面拒绝反馈(pattern 1 resource_face 腿):RAP 数字脉冲
func pulse_rap() -> void:
var tw := create_tween()
tw.tween_property(_rap_label, "modulate", Color(1.0, 0.4, 0.3), UiKit.motion(0.1))
tw.tween_property(_rap_label, "modulate", Color(1, 1, 1), UiKit.motion(0.25))
func _render_rap_dots(rap: int) -> void:
var shown := mini(rap, 16)
while _rap_dots.get_child_count() < shown:
var d := ColorRect.new()
d.custom_minimum_size = Vector2(8, 8)
d.size_flags_vertical = Control.SIZE_SHRINK_CENTER
d.mouse_filter = Control.MOUSE_FILTER_IGNORE
_rap_dots.add_child(d)
for i in _rap_dots.get_child_count():
var d: ColorRect = _rap_dots.get_child(i)
d.color = COL_GOLD if i < shown else COL_DOT_OFF
func _data_line() -> Label: func _data_line() -> Label:
+34 -43
View File
@@ -2,9 +2,9 @@
# 实现 GDD:design/ux/battle.md(B 区全站桩形态——2026-09-20 用户看对照样张拍板,台账 99: # 实现 GDD:design/ux/battle.md(B 区全站桩形态——2026-09-20 用户看对照样张拍板,台账 99:
# 主角与敌人均为无框立绘+底座,英雄卡/敌人卡卡片式整体退役禁再提)。 # 主角与敌人均为无框立绘+底座,英雄卡/敌人卡卡片式整体退役禁再提)。
# 形态契约:无框立绘(程序占位字形;set_portrait_texture = 资产挂点,战斗表现样张批落位零代码换装) # 形态契约:无框立绘(程序占位字形;set_portrait_texture = 资产挂点,战斗表现样张批落位零代码换装)
# + 底座;血条悬浮头顶;状态小图标一排挂血条正下方(2026-09-24 状态图标接入批:定稿 PNG 纹理 # + 底座;血条悬浮头顶;状态效果唯一展示面挂血条正下方(2026-09-26 C 区精简批:compact 档
# 取代程序化占位字形,极性染色退役——绿↑红↓极性语言内化于图标本体;层数 ×N 角标保留; # StatusSlotRow 组件整体迁入——定稿 PNG 图标+层数 ×N 角标+悬停 tooltip〔来源+语义+剩余〕,
# 词汇单点复用 StatusSlotRow.EFFECT_ICON,未知键同款 fail-fast + ? 占位)。 # tooltip 义务自 C 区状态槽并入;空态 = 图标行隐藏)。
# 敌方专属:顺位徽标悬浮头顶;意图三档动作动画占位(表现层升级批拍板:不显示技能名——普攻零表示 / # 敌方专属:顺位徽标悬浮头顶;意图三档动作动画占位(表现层升级批拍板:不显示技能名——普攻零表示 /
# 攻击系 ⚔ / 增益系 ✦ / 妨害系 ☠ / 蓄力 = ⚔ 金档;开轮摇定播一次性短动画后静帧保持, # 攻击系 ⚔ / 增益系 ✦ / 妨害系 ☠ / 蓄力 = ⚔ 金档;开轮摇定播一次性短动画后静帧保持,
# 挖掘期不循环——art-bible §2.1 能量纪律,终稿动画密度归战斗表现批);底座下 护甲类型图标+名字 # 挖掘期不循环——art-bible §2.1 能量纪律,终稿动画密度归战斗表现批);底座下 护甲类型图标+名字
@@ -28,7 +28,9 @@ var _platform: Panel
var _hp_bar: ProgressBar var _hp_bar: ProgressBar
var _order_badge: Label = null var _order_badge: Label = null
var _intent_badge: Label = null var _intent_badge: Label = null
var _status_row: HBoxContainer var _top_row: VBoxContainer
var slot_row: StatusSlotRow # 状态效果唯一展示面(2026-09-26 C 区精简批——compact 档组件整体迁入;
# 悬停 tooltip 来源+语义+剩余 义务随之从 C 区状态槽并入,主角/敌方共用)
var _name_line: HBoxContainer = null var _name_line: HBoxContainer = null
var _armor_icon: Label = null var _armor_icon: Label = null
var _name_label: Label = null var _name_label: Label = null
@@ -46,6 +48,7 @@ func _init(kind: String = "enemy") -> void:
# 头顶悬浮层:血条(敌附顺位徽标)+ 状态小图标行 # 头顶悬浮层:血条(敌附顺位徽标)+ 状态小图标行
var top := VBoxContainer.new() var top := VBoxContainer.new()
_top_row = top
top.set_anchors_preset(Control.PRESET_TOP_WIDE) top.set_anchors_preset(Control.PRESET_TOP_WIDE)
top.offset_left = 8.0 top.offset_left = 8.0
top.offset_right = -8.0 top.offset_right = -8.0
@@ -64,10 +67,12 @@ func _init(kind: String = "enemy") -> void:
_hp_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL _hp_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_hp_bar.size_flags_vertical = Control.SIZE_SHRINK_CENTER _hp_bar.size_flags_vertical = Control.SIZE_SHRINK_CENTER
hp_row.add_child(_hp_bar) hp_row.add_child(_hp_bar)
_status_row = HBoxContainer.new() slot_row = StatusSlotRow.new()
_status_row.alignment = BoxContainer.ALIGNMENT_CENTER slot_row.compact = true # 站桩迷你档——图标 16px+层数角标,tooltip 全明细
_status_row.add_theme_constant_override("separation", 4) slot_row.alignment = BoxContainer.ALIGNMENT_CENTER
top.add_child(_status_row) slot_row.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
slot_row.mouse_filter = Control.MOUSE_FILTER_IGNORE # 行容器放行鼠标事件给槽位(悬停 tooltip)
top.add_child(slot_row)
# 立绘(程序占位字形) # 立绘(程序占位字形)
_portrait = UiKit.label("人" if kind == "hero" else "敌", 42, true) _portrait = UiKit.label("人" if kind == "hero" else "敌", 42, true)
@@ -164,32 +169,28 @@ func set_idle() -> void:
set_effects([]) set_effects([])
# 主角/敌方共用——状态小图标行重建(B 区迷你版:定稿纹理 16px + 层数 ×N 角标; # 主角/敌方共用——状态效果唯一展示面(2026-09-26 C 区精简批:compact 档 StatusSlotRow 整体迁入,
# 完整 tooltip 义务归 C 区状态槽。2026-09-24 状态图标接入批:占位字形+极性染色退役) # 原 B 区迷你字形行退役;悬停 tooltip(来源+语义+剩余结算次数)义务自 C 区状态槽并入本组件;
# 空态 = 图标行隐藏不占位(增益状态 EC12))
func set_effects(effects: Array) -> void: func set_effects(effects: Array) -> void:
for c in _status_row.get_children(): slot_row.set_effects(effects)
_status_row.remove_child(c) slot_row.visible = not effects.is_empty()
c.queue_free()
for eff in effects:
var tex := _icon_tex(String(eff.get("effect_id", ""))) # battle_over:状态图标行全清可见反馈(增益状态×4④ 载体迁移——逐槽化光 + 微文案,
var layers := maxi(1, int(eff.get("layers", 1))) # 自 C 区状态槽迁入;主角侧调用,敌方不适用)
if tex != null: func set_effects_cleared_feedback() -> void:
var icon := TextureRect.new() slot_row.visible = true
icon.texture = tex slot_row.set_cleared_feedback()
icon.custom_minimum_size = Vector2(16, 16) # 32px 源 ÷2 整数档缩放 var note := UiKit.label("状态随战斗结束消散", 11, false)
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE note.add_theme_color_override("font_color", UiKit.STONE_FAINT)
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR note.mouse_filter = Control.MOUSE_FILTER_IGNORE
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE _top_row.add_child(note)
_status_row.add_child(icon) var tw := create_tween()
else: tw.tween_interval(1.2)
var ph := UiKit.label(StatusSlotRow.ICON_PLACEHOLDER, 12, true) # release 回退占位(debug 已断言) tw.tween_property(note, "modulate:a", 0.0, UiKit.motion(0.8))
ph.mouse_filter = Control.MOUSE_FILTER_IGNORE tw.tween_callback(note.queue_free)
_status_row.add_child(ph)
if layers > 1:
var lab := UiKit.label("×%d" % layers, 12, true)
lab.mouse_filter = Control.MOUSE_FILTER_IGNORE
_status_row.add_child(lab)
func set_order(i: int) -> void: func set_order(i: int) -> void:
@@ -321,13 +322,3 @@ func _refresh_portrait_glyph() -> void:
_portrait.text = "█" # 最大体量 _portrait.text = "█" # 最大体量
_: _:
_portrait.text = "敌" _portrait.text = "敌"
# 状态图标键解析(词汇单点复用 C 区 StatusSlotRow——HUD框架:查不到的键按错误处理,
# 立即断言 + ? 占位 + 诊断,禁止静默显示空图标)
func _icon_tex(effect_id: String) -> Texture2D:
if StatusSlotRow.EFFECT_ICON.has(effect_id):
return StatusSlotRow.EFFECT_ICON[effect_id]
push_error("[站桩] 未知状态图标键(映射表外键——立即断言): %s" % effect_id)
assert(false, "未知状态图标键: " + effect_id)
return null
+23 -8
View File
@@ -53,6 +53,7 @@ var _tip: PanelContainer
var _tip_label: Label var _tip_label: Label
var _slots: Array[PanelContainer] = [] # 槽引用数组持有——tooltip _tip 悬停后入子节点表,按位序 get_child 会误取(BUG-0001) var _slots: Array[PanelContainer] = [] # 槽引用数组持有——tooltip _tip 悬停后入子节点表,按位序 get_child 会误取(BUG-0001)
var _cleared_feedback := false var _cleared_feedback := false
var compact := false # B 区站桩迷你档(2026-09-26 C 区精简批):16px 图标+层数角标、无 meta 行——tooltip 义务随组件迁入站桩
func _init() -> void: func _init() -> void:
@@ -103,17 +104,22 @@ func _sync_slots(n: int) -> void:
func _make_slot() -> PanelContainer: func _make_slot() -> PanelContainer:
var slot := PanelContainer.new() var slot := PanelContainer.new()
slot.custom_minimum_size = Vector2(50, 46) if compact:
var sb := UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER) slot.add_theme_stylebox_override("panel",
slot.add_theme_stylebox_override("panel", sb) UiKit.panel_flat(Color(0, 0, 0, 0.18), UiKit.PANEL_BORDER, 2, 1, Vector4(2, 1, 2, 1)))
else:
slot.custom_minimum_size = Vector2(50, 46)
slot.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER))
slot.mouse_entered.connect(_on_slot_hover.bind(slot)) slot.mouse_entered.connect(_on_slot_hover.bind(slot))
slot.mouse_exited.connect(func(): _tip.visible = false) slot.mouse_exited.connect(func(): _tip.visible = false)
var col := VBoxContainer.new() var col := VBoxContainer.new()
col.alignment = BoxContainer.ALIGNMENT_CENTER col.alignment = BoxContainer.ALIGNMENT_CENTER
if compact:
col.add_theme_constant_override("separation", 0)
slot.add_child(col) slot.add_child(col)
# 定稿纹理图标(32px 源 → 24px 槽内呈现;线性过滤匹配抗锯齿矢量风源图) # 定稿纹理图标(32px 源 → 24px 槽内呈现;线性过滤匹配抗锯齿矢量风源图)
var icon := TextureRect.new() var icon := TextureRect.new()
icon.custom_minimum_size = Vector2(24, 24) icon.custom_minimum_size = Vector2(24, 24) if not compact else Vector2(16, 16)
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
@@ -130,16 +136,25 @@ func _fill_slot(slot: PanelContainer, eff: Dictionary) -> void:
slot.set_meta("eff", eff) slot.set_meta("eff", eff)
var col := slot.get_child(0) as VBoxContainer var col := slot.get_child(0) as VBoxContainer
var icon := col.get_child(0) as TextureRect var icon := col.get_child(0) as TextureRect
var tex := _icon_tex(String(eff.effect_id))
icon.visible = tex != null
icon.texture = tex
var layers := int(eff.get("layers", 1))
if compact:
# 迷你档:层数角标挂图标下方(×N)——槽随内容自适应,无 meta 行/剩余点(悬停 tooltip 承载全部明细)
var tag := col.get_child(1) as Label
tag.text = "×%d" % layers if layers > 1 else ""
tag.add_theme_font_size_override("font_size", 10)
if tex == null:
tag.text = (ICON_PLACEHOLDER + tag.text).strip_edges()
return
var meta := col.get_child(1) as Label var meta := col.get_child(1) as Label
# 层数角标 + 剩余结算次数点(● 未耗余量)——叠层数字呈现逻辑保持不动 # 层数角标 + 剩余结算次数点(● 未耗余量)——叠层数字呈现逻辑保持不动
var dots := "" var dots := ""
var left: int = int(eff.get("remaining_settlements", 0)) var left: int = int(eff.get("remaining_settlements", 0))
for i in maxi(left, 1): for i in maxi(left, 1):
dots += "●" if i < left else "○" dots += "●" if i < left else "○"
meta.text = "×%d %s" % [int(eff.get("layers", 1)), dots] meta.text = "×%d %s" % [layers, dots]
var tex := _icon_tex(String(eff.effect_id))
icon.visible = tex != null
icon.texture = tex
if tex == null: if tex == null:
meta.text = ICON_PLACEHOLDER + " " + meta.text # release 回退占位(debug 已断言) meta.text = ICON_PLACEHOLDER + " " + meta.text # release 回退占位(debug 已断言)
+1 -1
View File
@@ -220,7 +220,7 @@ func _run_modal_probes_once() -> void:
_problems.append("明细回看关闭后未恢复") _problems.append("明细回看关闭后未恢复")
# ③ tooltip(状态效果槽悬停——来源+语义+剩余):有槽可见时触发 # ③ tooltip(状态效果槽悬停——来源+语义+剩余):有槽可见时触发
if not _tested_tooltip: if not _tested_tooltip:
var row = main.info_panel.slot_row var row = main.stage.hero_standee.slot_row # 状态 tooltip 载体 2026-09-26 迁 B 区站桩
for child in row.get_children(): for child in row.get_children():
if child is PanelContainer and child.visible and child.has_meta("eff"): if child is PanelContainer and child.visible and child.has_meta("eff"):
_tested_tooltip = true _tested_tooltip = true
+8 -6
View File
@@ -85,8 +85,8 @@ func _ready() -> void:
problems.append("棋盘格未生成(cells=0)") problems.append("棋盘格未生成(cells=0)")
if main.board_view.board() == null: if main.board_view.board() == null:
problems.append("board 未装载") problems.append("board 未装载")
if main.info_panel.hp_text().begins_with("HP —") or main.info_panel.hp_text().contains("—"): if main.info_panel.attack_text().contains("—") or main.info_panel.armor_text().contains("—"):
problems.append("C 区血量未显示(%s)" % main.info_panel.hp_text()) problems.append("C 区攻击/护甲未显示(%s | %s)" % [main.info_panel.attack_text(), main.info_panel.armor_text()])
if main.settlement == null or main.settlement.battle_result != "": if main.settlement == null or main.settlement.battle_result != "":
problems.append("首战未处于进行中") problems.append("首战未处于进行中")
if not main.log_view.parsed_text().contains("节点 1/"): if not main.log_view.parsed_text().contains("节点 1/"):
@@ -102,8 +102,10 @@ func _ready() -> void:
problems.append("D 区弃图刷新按钮文案缺失") problems.append("D 区弃图刷新按钮文案缺失")
if main.board_zone.toast == null: if main.board_zone.toast == null:
problems.append("D 区 toast 槽缺失") problems.append("D 区 toast 槽缺失")
if main.info_panel.slot_row == null: if main.info_panel.item_slots == null or main.info_panel.item_slots.get_child_count() != 4:
problems.append("C 区状态效果槽缺失") problems.append("C 区魔法道具槽(预留)缺失")
if main.stage.hero_standee.slot_row == null:
problems.append("B 区站桩状态行缺失(状态效果唯一展示面——2026-09-26 C 区精简批)")
if main.log_view.mode() != "dig" or main.log_view.custom_minimum_size.y != 24.0: if main.log_view.mode() != "dig" or main.log_view.custom_minimum_size.y != 24.0:
problems.append("B 区明细带挖掘期应收起 24px(mode=%s h=%s)" % [main.log_view.mode(), str(main.log_view.custom_minimum_size.y)]) problems.append("B 区明细带挖掘期应收起 24px(mode=%s h=%s)" % [main.log_view.mode(), str(main.log_view.custom_minimum_size.y)])
# E 区道具面板已随可选类删除下线(格面资源批 2026-09-15)——四区构建由其余断言覆盖 # E 区道具面板已随可选类删除下线(格面资源批 2026-09-15)——四区构建由其余断言覆盖
@@ -124,7 +126,7 @@ func _ready() -> void:
if main.stage.hero_standee == null or main.stage.hero_standee._hp_bar == null: if main.stage.hero_standee == null or main.stage.hero_standee._hp_bar == null:
problems.append("B 区主角站桩缺失") problems.append("B 区主角站桩缺失")
# ---- BUG-0001 回归(tooltip 入树后 set_effects 槽位不错位——结算软锁修复沉淀)---- # ---- BUG-0001 回归(tooltip 入树后 set_effects 槽位不错位——结算软锁修复沉淀)----
var slots_row = main.info_panel.slot_row var slots_row = main.stage.hero_standee.slot_row # BUG-0001 回归随组件迁入站桩(tooltip/槽位错位同源)
var probe_eff: Array = [{"effect_id": "attack_up", "display_name": "增伤", "layers": 1, var probe_eff: Array = [{"effect_id": "attack_up", "display_name": "增伤", "layers": 1,
"remaining_settlements": 1, "source_class": "safe_cell", "round_index": 1, "effective_value": 50}] "remaining_settlements": 1, "source_class": "safe_cell", "round_index": 1, "effective_value": 50}]
slots_row.set_effects(probe_eff) slots_row.set_effects(probe_eff)
@@ -195,7 +197,7 @@ func _ready() -> void:
if problems.is_empty(): if problems.is_empty():
print("[ui_probe] PASS — cells=%d board=%dx%d hp='%s' enemies=%d ap_dots=%d" % [ print("[ui_probe] PASS — cells=%d board=%dx%d hp='%s' enemies=%d ap_dots=%d" % [
main.board_view._cells.size(), main.board_view.board().width, main.board_view.board().height, main.board_view._cells.size(), main.board_view.board().width, main.board_view.board().height,
main.info_panel.hp_text(), main.info_panel.attack_text(),
main.stage.enemy_box.get_child_count(), main.ap_bar._dots.get_child_count()]) main.stage.enemy_box.get_child_count(), main.ap_bar._dots.get_child_count()])
get_tree().quit(0) get_tree().quit(0)
else: else: