iter10 追加批: 奖励节点三选一共享池(用户拍板)——AdventureRunner 节点API+AuxItems 句柄面+main 共用面分派+node_overlay 道具面退役+autoplay 节点驱动+reward_overlay_test 随面迁移+a裁备选(L3复杀3)+g裁semver(v2兼容断言+存档序列化v1.3)+f裁调研待办(06新条)+文档五件+任务书(冒烟628/0+run_all 15套+五探针+三闸零新增+熵增PASS)

This commit is contained in:
DKChan committed 2026-10-01 06:12:25 +08:00
1 parent 93e4764ca0
commit 4f62a489b6
19 files changed
+377 -154

No files matched your search

+5
View File
@@ -121,6 +121,11 @@ static func bonus_keystone_line(payload: Dictionary) -> String:
return "钥石 +%d · 累计 %d" % [int(payload.get("keystone_gained", 0)), int(payload.get("keystone_total", 0))]
# 奖励节点三选一头部行(迭代 10 追加批——与通关三选一共享同池;node-reward.md v2)
static func node_bonus_header() -> String:
return "关卡奖励点\n从提升池抽三选一(与通关三选一同池同权重同上限):\n选择一件带走:"
# 冒险结算屏文案(摘要五件事:打穿没有 / 通关几关 / 止步第几关 / 钥石合计 / 主种子)
static func adventure_summary_text(summary: Dictionary) -> String:
var verdict := "冒险失败 · 未打穿"
+21 -47
View File
@@ -45,7 +45,8 @@ var _settlement_stop_pending := false # 卡⑥ 战斗胜利总结结算框挂起
var _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷)
var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷)
var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用)
var bonus_overlay: BonusPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载)
var bonus_overlay: BonusPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载;追加批起奖励节点共用)
var _bonus_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路)
var _start_overlay: Control
var _end_overlay: BattleEndOverlay
var _inter_overlay: Control
@@ -174,7 +175,6 @@ func _build_ui() -> void:
legend_overlay.closed.connect(_on_modal_closed.bind(legend_overlay))
review_overlay.closed.connect(_on_modal_closed.bind(review_overlay))
node_overlay.continue_pressed.connect(_on_node_continue)
# 迭代 7 T5——item_chosen 连接在奖励分支首唤时防重接线(上方)
level_screen.route_chosen.connect(_on_route_chosen)
level_screen.abandon_requested.connect(_on_abandon_requested) # 放弃入口(2026-09-21 用户拍板——台账 119)
shop_overlay.buy_requested.connect(_on_shop_buy)
@@ -394,10 +394,13 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
_show_board_fx_for_node(t)
log_view.append_bb(Narrator.node_banner(runner, node, t))
elif t == "reward":
_open_modal(node_overlay)
node_overlay.item_chosen.connect(_on_reward_item_chosen) if not node_overlay.item_chosen.is_connected(_on_reward_item_chosen) else null
node_overlay.show_reward_items(_reward_node_choices())
log_view.append_bb(Narrator.reward_line())
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面,
# 道具件满位替换子交互同构)。原「魔法道具投放渠道」三选一(迭代 7 过渡形态)随批退役。
_bonus_context = "reward_node"
var node_cands: Array = adventure.node_bonus_candidates()
_open_modal(bonus_overlay)
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands))
adventure.present_node_bonus(node_cands)
log_view.append_bb(Narrator.reward_line())
elif t == "rest":
var healed: int = int(info.get("healed", 0))
@@ -422,6 +425,7 @@ func _on_bonus_presented(payload: Dictionary) -> void:
if int(payload.get("aux_cleared", 0)) > 0:
board_zone.toast.show_toast("上关剩余 %d 件魔法道具已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈
_pending_bonus = payload.duplicate(true)
_bonus_context = "level_clear"
if bool(payload.get("replace_pending", false)):
# 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽)
_show_bonus_pick()
@@ -452,7 +456,10 @@ func _bonus_card_texts(candidates: Array) -> Array:
func _on_bonus_pick_confirmed(item_id: String) -> void:
if adventure == null or not adventure.pick_bonus(item_id):
if adventure == null:
return
var ok: bool = adventure.apply_node_bonus(item_id) if _bonus_context == "reward_node" else adventure.pick_bonus(item_id)
if not ok:
push_error("[组合根] 三选一兑付被拒——见输出日志")
@@ -474,8 +481,8 @@ func _open_bonus_replace(items: Array, incoming: Dictionary) -> void:
named.size(), int(db.adventure.transitional_sync_slots))
func _on_bonus_replace_chosen(discard_index: int) -> void:
if adventure == null or not adventure.resolve_aux_replace(discard_index):
func _on_bonus_replace_chosen(discard: int) -> void:
if adventure == null or not adventure.resolve_bonus_replace(discard):
push_error("[组合根] 同调位替换受理被拒——见输出日志")
@@ -486,10 +493,14 @@ func _on_bonus_resolved(result: Dictionary) -> void:
_refresh_presentations()
# 继续钮:收三选一面 → 推进下一关(零自动推进——兑付后受理)
# 继续钮:收三选一面 → 上下文分派(通关停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进)
func _on_bonus_continue() -> void:
_close_modal(bonus_overlay)
bonus_overlay.close_pick()
if _bonus_context == "reward_node":
if runner != null:
runner.continue_after_node()
return
if _adventure_mode and adventure != null:
if not adventure.continue_to_next_level():
_fatal_screen("关卡推进失败——见输出日志")
@@ -1039,43 +1050,6 @@ func _aux_unlocked_set() -> Dictionary:
return out
# 摇取面:活行 ∩ 工坊解锁集(拍板③甲——解锁进投放池同源口径);池 ≤3 全列(当前三件=选一即全列),
# 池扩充后加权摇三(确定性摇取:读 seed 流外——呈现侧零 RNG 消费纪律 → 全列前 3 按注册序)。
func _reward_node_choices() -> Array:
if runner == null or runner.aux_inventory == null:
return []
var out: Array = []
for row in db.aux_active_rows():
var iid := String(row.get("item_id", ""))
if not _aux_unlocked.has(iid):
continue # 未解锁不入奖励池(与掉落投放同门控)
out.append({
"item_id": iid,
"display_name": String(row.get("display_name", "")),
"glyph": String(row.get("display_name", "?")).left(1),
})
if out.size() >= 3:
break
return out
# 选择兑付:入关卡域库存(来源归因 reward_node)→ 收屏 → 兑付确认回选路(零自动推进同款)
func _on_reward_item_chosen(item_id: String) -> void:
if runner == null or runner.aux_inventory == null:
return
var rec: Dictionary = runner.grant_aux_item(item_id, "reward_node")
if rec.is_empty():
# 同调位满位拒绝(迭代 10 过渡承载——奖励节点渠道满位不授予;替换交互归三选一渠道,
# 全渠道统一收口随同调实施批)。覆盖层保持开、零入账零关闭(玩家可改选继续/离开)
board_zone.toast.show_toast("同调位已满——「%s」未获取" % String(db.aux_item_row(item_id).get("display_name", item_id)), "warn")
return
node_overlay.close_reward()
_refresh_aux_slots()
board_zone.toast.show_toast("获得 %s(入关卡背包)" % String(db.aux_item_row(item_id).get("display_name", item_id)), "info")
_on_node_continue()
# 槽点击(第一步):AP 类(target=none)点击即用(无目标);空间类进/出选中态(toggle 取消)。
func _on_aux_slot_clicked(slot_handle: int) -> void:
if settlement == null or runner == null or runner.aux_inventory == null:
@@ -127,9 +127,9 @@ func open_replace(items: Array, incoming_name: String, holding: int, slots: int)
for c in _replace_box.get_children():
c.queue_free()
for it in items:
var idx := int(it.get("index", -1))
var tag: int = int(it.get("index", it.get("handle", -1))) # 通关渠道=携带序号 / 节点渠道=库存句柄(核心分发面裁决)
var btn := UiKit.button("丢弃「%s」换新" % String(it.get("display_name", it.get("item_id", ""))))
btn.pressed.connect(func(): replace_chosen.emit(idx))
btn.pressed.connect(func(): replace_chosen.emit(tag))
_replace_box.add_child(btn)
var decline := UiKit.button("放弃新件(不获取)")
decline.pressed.connect(func(): replace_chosen.emit(-1))
-42
View File
@@ -6,11 +6,9 @@ class_name NodeOverlay
extends Control
signal continue_pressed
signal item_chosen(item_id: String) # 迭代 7 T5——奖励节点三选一兑付(拍板②丙:奖励屏=小构筑点)
var _label: Label
var _btn: Button
var _choice_box: HBoxContainer # 道具三选一按钮区(show_reward_items 时呈现)
func _init() -> void:
@@ -30,56 +28,16 @@ func _init() -> void:
box.add_child(_label)
_btn = UiKit.button("继续")
_btn.pressed.connect(_emit_continue_pressed)
_choice_box = HBoxContainer.new()
_choice_box.add_theme_constant_override("separation", 12)
_choice_box.alignment = BoxContainer.ALIGNMENT_CENTER
box.add_child(_choice_box)
box.add_child(_btn)
visible = false
# 奖励节点兑付(迭代 7 T5——「节点奖励=功能道具正式渠道」关卡节点地图.md:110 定稿兑现;
# 拍板②丙=三选一呈现:池 ≤3 全列(当前有效道具三件——选一即全列),池扩充后加权摇三)。
# choices = [{item_id, display_name, glyph}](组合根组装——视图纯呈现,选择经信号外发)。
# 选择前「继续」隐藏(强制选择);选择即入库存+屏收。
func show_reward_items(choices: Array) -> void:
for c in _choice_box.get_children():
c.queue_free()
if choices.is_empty():
# 池空(全未解锁/表空合法态)——直通节点确认(切片期合法空态语义保留)
_label.text = "关卡奖励点
(此行无道具可取——继续前行)"
_btn.text = "继续"
_btn.visible = true
_choice_box.visible = false
visible = true
return
_label.text = "关卡奖励点
取一件魔法道具(加入关卡背包——关卡结束清空)"
for ch in choices:
var cid := String(ch.get("item_id", ""))
var btn := UiKit.button("%s %s" % [String(ch.get("glyph", "?")), String(ch.get("display_name", ""))])
btn.custom_minimum_size = Vector2(150, 72)
btn.pressed.connect(func(): item_chosen.emit(cid))
_choice_box.add_child(btn)
_btn.visible = false # 选择前不给出口(强制三选一——构筑点语义)
_choice_box.visible = true
visible = true
func show_rest(healed: int, hp_after: int, max_hp: int) -> void:
_label.text = "血量恢复点\n休息回复 %d HP(%d → %d / %d)" % [
healed, hp_after - healed, hp_after, max_hp]
_btn.text = "休息完毕,继续"
visible = true
# 选择完成收屏(组合根在 item_chosen 处理毕调——入库存+确认后回选路由同拍)
func close_reward() -> void:
_choice_box.visible = false
_btn.visible = true
visible = false
# 探针复用:以真实按钮路径触发继续
func press_continue() -> void:
_btn.pressed.emit()