秘宝批T7组E-2:内部标识符全族换词(inventory/unlocked/replace/overlays/slot/settlement use_aux等60+token)+存档键零触碰专项核通过(37文件六键普查零差异,点号访问零存在)+行为探针四件全绿+任务书§1.2基线回填(755/0实测)

This commit is contained in:
DKChan committed 2026-10-05 08:46:51 +08:00
1 parent f8c6db49c6
commit cd73e62346
21 files changed
+205 -203

No files matched your search

+4 -4
View File
@@ -94,8 +94,8 @@ static func bonus_pick_header(payload: Dictionary) -> String:
# 三选一选项卡文案(件名 + 档位 + 效果读数;值 = 冒险表行值,组合根喂入;TP0 令牌池——档位词
# 对齐 v2.2 追认 b 口径:小/大前缀(小GP/大GP 族)、恢复/AP 不带档位词;道具件掷定前置时
# aux_line = 具体道具「名 · 效果摘要」行——池种退役后恒空走通用分支,机械惰性保留)
static func bonus_card_text(id: String, tier: String, value: int, aux_line: String = "") -> String:
# treasure_line = 具体道具「名 · 效果摘要」行——池种退役后恒空走通用分支,机械惰性保留)
static func bonus_card_text(id: String, tier: String, value: int, treasure_line: String = "") -> String:
var ts := ""
if tier == "common":
ts = " · 小"
@@ -113,8 +113,8 @@ static func bonus_card_text(id: String, tier: String, value: int, aux_line: Stri
"heal":
return "恢复药剂\n生命 +%d(当即回复)" % value
"treasure_item":
if aux_line != "":
return "秘宝\n%s" % aux_line
if treasure_line != "":
return "秘宝\n%s" % treasure_line
return "秘宝\n随机获得一件"
"ap":
return "行动点提升\n每战首轮 AP +%d" % value
+10 -10
View File
@@ -29,7 +29,7 @@ var _gp_summary: Callable # () → 战斗总结 GP 行文案(组
var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用)
var _bonus_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路)
var _last_replace_items: Array = [] # 替换子态续玩重建缓存(aux_replace_presented 载荷)
var _last_replace_items: Array = [] # 替换子态续玩重建缓存(treasure_replace_presented 载荷)
var _last_replace_incoming := {} # 同上——incoming 记录
@@ -87,7 +87,7 @@ func on_presented(payload: Dictionary) -> void:
_refresh_presentations.call()
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 aux_replace_presented 续发直落子态)
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态)
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面,
# 道具件满位替换子交互同构)。原「秘宝投放渠道」三选一(迭代 7 过渡形态)随批退役。
func present_node_bonus() -> void:
@@ -101,7 +101,7 @@ func present_node_bonus() -> void:
bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 aux_replace_presented 续发直落子态)
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态)
func show_pick() -> void:
var payload: Dictionary = _pending_bonus
_open_modal.call(bonus_overlay)
@@ -111,11 +111,11 @@ func show_pick() -> void:
_refresh_presentations.call()
func _bonus_card_texts(candidates: Array, aux_detail: Array = []) -> Array:
func _bonus_card_texts(candidates: Array, treasure_detail: Array = []) -> Array:
# TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|relic_id" 复合键(同 kind
# 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(on_pick_confirmed)
var out: Array = []
var aux_i := 0
var treasure_i := 0
for tok in candidates:
var kind := String((tok as Dictionary).get("kind", ""))
var tier := String((tok as Dictionary).get("tier", ""))
@@ -125,9 +125,9 @@ func _bonus_card_texts(candidates: Array, aux_detail: Array = []) -> Array:
if kind == "relic":
text = Narrator.relic_card_text(db.relic_row(rid))
elif kind == "treasure_item":
var iid := String(aux_detail[aux_i]) if aux_i < aux_detail.size() else ""
aux_i += 1
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.feature_value(kind, tier)), _aux_card_line(iid))
var iid := String(treasure_detail[treasure_i]) if treasure_i < treasure_detail.size() else ""
treasure_i += 1
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.feature_value(kind, tier)), _treasure_card_line(iid))
else:
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.feature_value(kind, tier)))
out.append({"id": cid, "text": text})
@@ -135,7 +135,7 @@ func _bonus_card_texts(candidates: Array, aux_detail: Array = []) -> Array:
# 道具件卡片行(掷定前置——具体道具名 + 效果摘要;空 iid = 无预掷回退态显示通用文案)
func _aux_card_line(iid: String) -> String:
func _treasure_card_line(iid: String) -> String:
if iid == "":
return ""
var row: Dictionary = db.treasure_item_row(iid)
@@ -185,7 +185,7 @@ func on_pick_confirmed(item_id: String) -> void:
push_error("[bonus_interact] 三选一兑付被拒——见输出日志")
func on_aux_replace_presented(items: Array, incoming: Dictionary) -> void:
func on_treasure_replace_presented(items: Array, incoming: Dictionary) -> void:
_last_replace_items = items.duplicate(true)
_last_replace_incoming = incoming.duplicate(true)
if not bonus_overlay.visible:
+12 -12
View File
@@ -36,7 +36,7 @@ const ModalStack := preload("res://presentation/modal_stack.gd")
const TuneSimController := preload("res://presentation/tune_sim_controller.gd")
const DebugSessionScript := preload("res://presentation/debug_session.gd")
const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵(T3 拆件批)
const AuxInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互(T3 拆件批)
const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互(T3 拆件批)
const GpShadowLedgerScript := preload("res://presentation/gp_shadow_ledger.gd") # GP 影子账本(T3 拆件批)
const BonusInteractController := preload("res://presentation/bonus_interact_controller.gd") # 三选一与替换子交互(T4/D4 拆件批)
const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影(T4/D4 拆件批)
@@ -90,7 +90,7 @@ var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道
# T3 拆件批(2026-10-02)——三件迁出后的组合根持有面(职责见各类头注;装配见 _build_ui)
var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列)
var _aux = null # 秘宝两步交互(treasure_interact_controller.gd——本类只接线与转发意图)
var _treasure_ui = null # 秘宝两步交互(treasure_interact_controller.gd——本类只接线与转发意图)
var _bonus = null # 三选一与替换子交互(bonus_interact_controller.gd——本类只接线与回调注入)
var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发)
var _gp_ledger := GpShadowLedgerScript.new() # GP 冒险钱包影子账(gp_shadow_ledger.gd——wallet 事件投喂)
@@ -219,11 +219,11 @@ func _build_ui() -> void:
add_child(_row_pacer)
# 秘宝两步交互(T3 拆件批——迁 treasure_interact_controller.gd;settlement/runner 随关卡更替,
# 经现值回调注入——仿 identity_resolver 只读回调先例)
_aux = AuxInteractController.new(db, meta, info_panel, board_zone.toast, board_view,
_treasure_ui = TreasureInteractController.new(db, meta, info_panel, board_zone.toast, board_view,
func() -> Variant: return settlement, func() -> Variant: return runner)
board_zone.action_requested.connect(_on_board_action)
info_panel.aux_slot_clicked.connect(_aux.on_slot_clicked) # 迭代 7 T4——两步交互第一步
info_panel.treasure_slot_clicked.connect(_treasure_ui.on_slot_clicked) # 迭代 7 T4——两步交互第一步
board_zone.stop_pressed.connect(_on_stop_pressed)
board_zone.refresh_confirmed.connect(_on_refresh_confirmed)
board_zone.legend_toggled.connect(_on_legend_toggled)
@@ -434,7 +434,7 @@ func _resume_from_menu() -> void:
log_view.clear()
logger.start_session(int(bp.get("master_seed", 0)))
adventure.set_hero_override(debug_overlay.atk_pct(), debug_overlay.hp_pct())
if not adventure.resume_adventure(bp, {"hero_override": meta.hero_override() if meta != null else {}, "aux_unlocked": _aux.unlocked_set()}):
if not adventure.resume_adventure(bp, {"hero_override": meta.hero_override() if meta != null else {}, "treasure_unlocked": _treasure_ui.unlocked_set()}):
_fatal_screen("续玩恢复失败——见输出日志")
return
@@ -452,7 +452,7 @@ func _wire_adventure() -> void:
# 迭代 10 奖励重构批——big_reward_paid 退役,三选一信号族接线
adventure.bonus_choice_presented.connect(_bonus.on_presented)
adventure.bonus_resolved.connect(_bonus.on_resolved)
adventure.aux_replace_presented.connect(_bonus.on_aux_replace_presented)
adventure.treasure_replace_presented.connect(_bonus.on_treasure_replace_presented)
adventure.adventure_finished.connect(_on_adventure_finished)
adventure.level_started.connect(_on_adventure_level_started)
adventure.set_save_manager(save_manager) # 断点承载注入(迭代 8 卡①——停点快照写盘;调试/模拟不经过此路径)
@@ -472,7 +472,7 @@ func _start_adventure() -> void:
# 永久强化注入(元进度实现批):冒险开始参数面——meta 现算 override 显式传参(全零 = 白板)
# 首次冒险脚本(关卡节点图生成 v0.4):存档布尔位 false = 全关固定模板 0(教学重放);置位后逐关抽签
var first_script: bool = not save_manager.is_first_adventure_done()
if not adventure.start_adventure(seed_val, {"hero_override": meta.hero_override() if meta != null else {}, "first_script": first_script, "aux_unlocked": _aux.unlocked_set(), "shop_mode": "pool"}):
if not adventure.start_adventure(seed_val, {"hero_override": meta.hero_override() if meta != null else {}, "first_script": first_script, "treasure_unlocked": _treasure_ui.unlocked_set(), "shop_mode": "pool"}):
_fatal_screen("冒险启动失败——见输出日志")
return
@@ -956,15 +956,15 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
return
# 秘宝选中态劫持(迭代 7 T4——两步交互第二步):点格=使用目标拾取,不落挖格;
# 只劫持挖掘点击(move/flag 语义不冲突——选中期玩家仍可移动/插旗)
var aux_sel: int = _aux.active_selection()
if aux_sel != 0 and kind == "dig":
_aux.attempt_use(aux_sel, cell)
var treasure_sel: int = _treasure_ui.active_selection()
if treasure_sel != 0 and kind == "dig":
_treasure_ui.attempt_use(treasure_sel, cell)
return
var outcomes: Array = settlement.minesweeper.settle_batch([{"action": kind, "cell": cell}])
var accepted: bool = not outcomes.is_empty() and bool(outcomes[0].accepted)
logger.log_action(kind, cell, accepted)
if runner != null and runner.aux_inventory != null:
_aux.refresh_slots() # 迭代 7 T4——授予发生在 settle_batch 结算段内(同步走完,行尾重查)
if runner != null and runner.treasure_inventory != null:
_treasure_ui.refresh_slots() # 迭代 7 T4——授予发生在 settle_batch 结算段内(同步走完,行尾重查)
_handle_outcome_feedback(outcomes[0] if not outcomes.is_empty() else {})
# settle_batch 内部同步走完 结算→战斗结束→关卡推进(事件同步发射)——关卡终局时 settlement 已被
# runner 置空,清图演出判定须先判 settlement(基线既有重入隐患,2026-09-13 加固)
@@ -2,16 +2,16 @@
# 秘宝两步交互控制器(迭代 7 T4;T3 拆件批 2026-10-02 自 main.gd 迁出——规范 1.1 单一职责):
# C 区槽位选中/取消(toggle)→ 棋盘点格使用目标拾取的两步交互状态机 + 库存/叠加层只读投影装配。
# 数据源 = runner 级库存只读面(query_inventory/query_overlays)+ 表行投影(display_name/target/effect_kinds);
# 写调用唯一经 战斗结算.use_aux(core 侧四步编排——拒绝零消耗 toast 可辨认)。
# 写调用唯一经 战斗结算.use_treasure(core 侧四步编排——拒绝零消耗 toast 可辨认)。
# 装配纪律:settlement/runner 随关卡逐关更替——经组合根现值回调注入(仿 identity_resolver 只读回调
# 先例);呈现句柄(信息面板/toast/棋盘叠加层)由组合根注入;ADR-0001——信号连接不在此发生,
# info_panel.aux_slot_clicked 由组合根接至 on_slot_clicked。
# info_panel.treasure_slot_clicked 由组合根接至 on_slot_clicked。
# 无目标使用显式化(T3 收口):AP 类(target=none)本无坐标可给——原调用面伪造 Vector2i(-9,-9)
# 哨兵坐标;现调用面一律 attempt_use(handle)(cell 缺省=null=显式「无目标」),core 契约的
# Vector2i 形参收口于本类常量(aux_items.validate_use 对 ap 行忽略目标值,哨兵不出本类)。
# Vector2i 形参收口于本类常量(treasure_items.validate_use 对 ap 行忽略目标值,哨兵不出本类)。
extends RefCounted
const NO_TARGET := Vector2i(-9, -9) # core use_aux 形参占位(仅 target=none 行到达——validate_use 忽略该值)
const NO_TARGET := Vector2i(-9, -9) # core use_treasure 形参占位(仅 target=none 行到达——validate_use 忽略该值)
var db
var meta # 解锁门控数据源(工坊解锁×活行交集——冒险开始组装;组合根 _ready 建立后注入)
@@ -56,7 +56,7 @@ func active_selection() -> int:
func on_slot_clicked(slot_handle: int) -> void:
var settlement = _settlement_get.call()
var runner = _runner_get.call()
if settlement == null or runner == null or runner.aux_inventory == null:
if settlement == null or runner == null or runner.treasure_inventory == null:
return
if slot_handle == 0:
_use_selection = 0
@@ -82,13 +82,13 @@ func on_slot_clicked(slot_handle: int) -> void:
toast.show_toast("%s:点击目标格使用(9 宫窗口),再点道具取消" % String(rec.get("name", "")), "info")
# 使用受理(第二步;cell=空间类目标格,null=无目标;core 侧 use_aux 四步编排——拒绝零消耗 toast 可辨认)
# 使用受理(第二步;cell=空间类目标格,null=无目标;core 侧 use_treasure 四步编排——拒绝零消耗 toast 可辨认)
func attempt_use(handle: int, cell: Variant = null) -> void:
var settlement = _settlement_get.call()
if settlement == null:
return
var target: Vector2i = cell if cell != null else NO_TARGET
var v: Dictionary = settlement.use_aux(handle, target)
var v: Dictionary = settlement.use_treasure(handle, target)
if not bool(v.ok):
toast.show_toast(String(v.get("reason", "无法使用")), "warn")
return
@@ -103,7 +103,7 @@ func attempt_use(handle: int, cell: Variant = null) -> void:
# 库存投影(C 区四槽取数面——字形=名首字;usable=板级零信息预判回调接排雷玩法只读面)
func inventory_projection() -> Array:
var runner = _runner_get.call()
if runner == null or runner.aux_inventory == null:
if runner == null or runner.treasure_inventory == null:
return []
var settlement = _settlement_get.call()
var ms = settlement.minesweeper if settlement != null else null
@@ -112,7 +112,7 @@ func inventory_projection() -> Array:
cb = func(_c: Vector2i) -> bool:
return ms.has_unrevealed() # 只读查询面(排雷玩法 has_unrevealed——手搓全板双循环收口)
var out: Array = []
for rec in runner.aux_inventory.query_inventory(cb):
for rec in runner.treasure_inventory.query_inventory(cb):
var arow: Dictionary = db.treasure_item_row(String(rec.get("item_id", "")))
var dn := String(arow.get("display_name", ""))
out.append({
@@ -127,13 +127,13 @@ func inventory_projection() -> Array:
func refresh_slots() -> void:
info_panel.set_aux_inventory(inventory_projection(), _use_selection)
info_panel.set_treasure_inventory(inventory_projection(), _use_selection)
# 叠加层投影注入(use 成功/三清后——棋盘标记与预读徽章重算)
func refresh_overlays() -> void:
var runner = _runner_get.call()
if runner == null or runner.aux_inventory == null or board_view == null:
if runner == null or runner.treasure_inventory == null or board_view == null:
return
var ov: Dictionary = runner.aux_inventory.query_overlays()
board_view.set_aux_overlays(ov.get("mine_reveal", []), ov.get("reward_preview", []))
var ov: Dictionary = runner.treasure_inventory.query_overlays()
board_view.set_treasure_overlays(ov.get("mine_reveal", []), ov.get("reward_preview", []))
+5 -5
View File
@@ -82,7 +82,7 @@ var tex_base: Texture2D = null # ① 砖面底
var tex_mutation: Texture2D = null # ② 变异纹带
var tex_glyph: Texture2D = null # ③ 数字
var tex_badge: Texture2D = null # ③' 徽章
var tex_flag: Texture2D = null: # ④ 旗(T17 修复:槽位可运行时换装〔插旗〕——同族 facade/aux_mark
var tex_flag: Texture2D = null: # ④ 旗(T17 修复:槽位可运行时换装〔插旗〕——同族 facade/treasure_mark
set(v): # 均 setter 触发重绘;旧普通 var 赋值不重绘=旗插上不显示)
if tex_flag != v:
tex_flag = v
@@ -98,10 +98,10 @@ var facade_strip := false: # E 格开关(BoardView paint 按「已开
# 叠加层标记(迭代 7 T4——秘宝板级叠加层的格面呈现;派生信息层零回写):
# "mine_reveal" = 真视格(危险红内缩框——「这格是雷」的确定决策标记,插旗照常计费);
# "reward_preview" 格不复用本字段——预读徽章走 badge_glyph 注入(board_view refresh 注入 REWARD_BADGE 字形)
var aux_mark := "":
var treasure_mark := "":
set(v):
if aux_mark != v:
aux_mark = v
if treasure_mark != v:
treasure_mark = v
queue_redraw()
var mutation_mark := "":
set(v):
@@ -213,7 +213,7 @@ func _draw() -> void:
# ②' 变异角标(迭代 6 T6):左下角危险红三角(危险红=「开此格有代价」语义位,与雷族同源;
# 尺寸随格边比例——灰色可辨辅助=红对砖面明度差,禁喧宾)
# ②" 秘宝叠加层(迭代 7 T4):真视格=危险红内缩框(策略纹理——确定决策输入不喧宾)
if aux_mark == "mine_reveal":
if treasure_mark == "mine_reveal":
var inset := edge * 0.14
draw_rect(Rect2(inset, inset, edge - inset * 2.0, edge - inset * 2.0),
Color(UiKit.RED_MINE, 0.9), false, 2.0)
+9 -9
View File
@@ -98,8 +98,8 @@ var _minesweeper = null # 排雷玩法 只读查询引用(refresh
var _identity_cb := Callable() # 地图战斗资源 身份解析回调(组合根注入只读面)——Vector2i → Dictionary/null
var _mask_cb := Callable() # 地形变异 数字遮蔽回调(组合根注入只读面)——Vector2i → bool(true = 该格数字不显示)
var _vmark_cb := Callable() # 地形变异 角标回调(迭代 6 T6 只读面)——Vector2i → String("flat_damage"=伤害型变异未开格;""=无)
var _aux_reveal := {} # 秘宝 真视集(迭代 7 T4——Vector2i → true;组合根 set_aux_overlays 注入投影)
var _aux_preview := {} # 秘宝 预读集(同上——未开格徽章呈现挂点)
var _treasure_reveal := {} # 秘宝 真视集(迭代 7 T4——Vector2i → true;组合根 set_treasure_overlays 注入投影)
var _treasure_preview := {} # 秘宝 预读集(同上——未开格徽章呈现挂点)
var _diggable := {} # 可挖域(8 向相邻未开格)——呼吸微光作用集
var _breath_t := 0.0
var _contact_unit: Node2D = null # 接触带承载单元(=英雄格 CellUnit;rebuild 随清场复位)
@@ -214,13 +214,13 @@ func set_variant_mark_resolver(cb: Callable) -> void:
_vmark_cb = cb
# 秘宝叠加层投影注入(迭代 7 T4——组合根在 use 成功/三清后调;refresh 消费)
func set_aux_overlays(reveal_cells: Array, preview_cells: Array) -> void:
_aux_reveal.clear()
_aux_preview.clear()
func set_treasure_overlays(reveal_cells: Array, preview_cells: Array) -> void:
_treasure_reveal.clear()
_treasure_preview.clear()
for c in reveal_cells:
_aux_reveal[Vector2i(c)] = true
_treasure_reveal[Vector2i(c)] = true
for c in preview_cells:
_aux_preview[Vector2i(c)] = true
_treasure_preview[Vector2i(c)] = true
refresh()
@@ -791,8 +791,8 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
# ②( 秘宝叠加层(迭代 7 T4):真视格=红内缩框;预读格=奖励身份徽章(identity_resolver 既有
# 只读面取字形——零数据复制,badge 位复用);已开格标记自然退位(refresh 逐拍重算)
if not revealed:
u.aux_mark = "mine_reveal" if _aux_reveal.has(c) else ""
if _aux_preview.has(c) and glyph == "":
u.treasure_mark = "mine_reveal" if _treasure_reveal.has(c) else ""
if _treasure_preview.has(c) and glyph == "":
var pid = _identity_cb.call(c) if _identity_cb.is_valid() else null
if pid != null and REWARD_BADGE.has(String(pid.get("cell_type", ""))):
glyph = String(REWARD_BADGE[String(pid.get("cell_type", ""))])
@@ -172,7 +172,7 @@ func show_result(text: String) -> void:
visible = true
# 同调位替换子态(aux_replace_presented 到达):items = [{index, item_id, display_name}](组合根
# 同调位替换子态(treasure_replace_presented 到达):items = [{index, item_id, display_name}](组合根
# 喂显示名)。两路出口:丢旧收新(逐件钮)/ 放弃新件——终态恒 ≤ 同调位由核心受理面保证。
func open_replace(items: Array, incoming_name: String, holding: int, slots: int) -> void:
_label.text = "同调位已满(%d/%d)——收下「%s」需丢弃一件已有道具,或放弃新件" % [holding, slots, incoming_name]
@@ -14,11 +14,11 @@ const COL_INK := UiKit.INK
var _atk_label: Label
var _armor_label: Label
signal aux_slot_clicked(handle: int) # 迭代 7 T4——槽点击(组合根消费:AP 类即用/空间类进选中态)
signal treasure_slot_clicked(handle: int) # 迭代 7 T4——槽点击(组合根消费:AP 类即用/空间类进选中态)
var item_slots: HBoxContainer # 秘宝槽(迭代 7 T4 接线——库存条常读+两步交互第一步)
var _slot_panels: Array = [] # 槽 PanelContainer 引用(选中高亮/置灰重刷)
var _selected_handle := 0 # 选中态(0=无;组合根回写维持单源)
var _slot_handles: Array = [] # 槽位→handle 映射(set_aux_inventory 刷新)
var _slot_handles: Array = [] # 槽位→handle 映射(set_treasure_inventory 刷新)
var _bonus_section: VBoxContainer # 特性(预留——空态隐藏,接线归大奖励/锻造呈现批)
var _bonus_list: VBoxContainer
var _relic_section: VBoxContainer # 特性持有段(迭代 12 呈现补齐——空态隐藏)
@@ -66,7 +66,7 @@ func _init() -> void:
slot.gui_input.connect(func(ev: InputEvent):
if ev is InputEventMouseButton and ev.pressed and ev.button_index == MOUSE_BUTTON_LEFT:
# 空槽/越界槽点击=取消选中(handle 0);有效槽发其 handle(组合根判 AP 即用/进选中态)
aux_slot_clicked.emit(_slot_handles[idx] if idx < _slot_handles.size() else 0))
treasure_slot_clicked.emit(_slot_handles[idx] if idx < _slot_handles.size() else 0))
item_slots.add_child(slot)
_slot_panels.append(slot)
@@ -166,7 +166,7 @@ func _data_line() -> Label:
# 投影行 = {handle, glyph, name, used, usable}(组合根组装:字形=道具名首字;数据源 runner 级库存只读面)。
# 空槽=虚位;used=暗态「已用」保留占位(Used 不可逆——受理序展示语义);不可用=置灰(板级零信息);
# selected=金框高亮(两步交互第一步的可见态)。tooltip=全名+维度语义。
func set_aux_inventory(items: Array, selected_handle: int) -> void:
func set_treasure_inventory(items: Array, selected_handle: int) -> void:
_selected_handle = selected_handle
_slot_handles.clear()
for i in 4: