From 73db171cb4b453f0645f3a19df683c792c0fa17b Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 12 Oct 2026 19:31:04 +0800 Subject: [PATCH] =?UTF-8?q?T5(=E8=BF=AD=E4=BB=A322=E7=A7=98=E5=AE=9D?= =?UTF-8?q?=E6=89=B9):=20=E5=AD=98=E6=A1=A3=E9=9D=A2+=E6=AD=BB=E9=80=9A?= =?UTF-8?q?=E9=81=93=E6=8B=86=E9=99=A4=E2=80=94=E2=80=94treasure=5Finvento?= =?UTF-8?q?ry=E4=BF=9D=E7=95=99=E6=B3=A8=E8=AE=B0(=E4=B8=BB=E5=8A=A8?= =?UTF-8?q?=E4=BB=B6=E9=AA=91=E5=BA=93=E5=AD=98=E6=89=BF=E8=BD=BD=E5=9C=A8?= =?UTF-8?q?=E5=BD=B9:=E7=BA=BF=E9=A6=99/=E5=A4=A9=E7=9C=BChandle+=E6=96=AD?= =?UTF-8?q?=E7=82=B9aux=5Fitems=E5=BF=AB=E7=85=A7+set=5Fattune=5Fcharges?= =?UTF-8?q?=E5=B7=AE=E9=A2=9D=E6=B3=A8=E5=85=A5=E5=85=A8=E9=93=BE=E4=BF=9D?= =?UTF-8?q?=E7=95=99);=20=E6=9B=BF=E6=8D=A2=E5=AD=90=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E6=95=B4=E4=BD=93=E6=8B=86=E9=99=A4(CURIO=5FKINDS=E9=99=A4?= =?UTF-8?q?=E5=90=8D=E5=90=8E=E4=B8=8D=E5=8F=AF=E8=BE=BE=E6=AD=BB=E9=80=9A?= =?UTF-8?q?=E9=81=93=3D=E8=A3=85=E9=85=8D=E4=B8=8D=E5=8F=AF=E5=8F=98?= =?UTF-8?q?=E5=A5=91=E7=BA=A61=E5=AE=9E=E7=8E=B0=E9=9D=A2=E4=BF=9D?= =?UTF-8?q?=E8=AF=81):=20adventure=5Frunner=E5=85=AD=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E5=88=A0(=5Fpick=5Ftreasure=5Fitem/resolve=5Ftreasure=5Freplac?= =?UTF-8?q?e/=5Fresolve=5Fnode=5Freplace/=5Freplace=5Fin=5Ftransit/=5Fdraw?= =?UTF-8?q?=5Fpending=5Ftreasure/resolve=5Fcurio=5Freplace)+=5Fbonus=5Ftre?= =?UTF-8?q?asure=5Fcarry=E8=B7=A8=E5=85=B3=E6=90=BA=E5=B8=A6/=5Flast=5Fdra?= =?UTF-8?q?w=5Ftreasure=E6=8E=B7=E5=AE=9A=E9=93=BE/=5Froll=5Ffrom/=5Fbonus?= =?UTF-8?q?=5Ftreasure=5Fpool/bonus=5Faux=5Fcarry=E6=96=AD=E7=82=B9?= =?UTF-8?q?=E9=94=AE;=20level=5Frunner.grant=5Ftreasure=5Fitem/treasure=5F?= =?UTF-8?q?items.holding=5Frecords=E5=88=A0;=20=E5=91=88=E7=8E=B0=E9=9D=A2?= =?UTF-8?q?=3Dcontroller=E6=9B=BF=E6=8D=A2=E5=AD=90=E6=80=81=E4=B8=89?= =?UTF-8?q?=E5=87=BD=E6=95=B0+aux=5Fdetail=E5=8D=A1=E7=89=87=E7=9B=B4?= =?UTF-8?q?=E6=98=BE=E9=93=BE/curio=5Fpick=5Foverlay=20replace=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=9C=BA(=E4=BF=A1=E5=8F=B7+=E9=9D=A2=E6=9D=BF+open?= =?UTF-8?q?=5Freplace)/main=E4=B8=A4=E6=8E=A5=E7=BA=BF/level=5Fautoplay?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E7=AD=96=E7=95=A5;=20smoke=E4=B8=8Eui=5Fclic?= =?UTF-8?q?k=5Fprobe=E6=9B=BF=E6=8D=A2=E5=88=86=E6=94=AF=E6=91=98=E9=99=A4?= =?UTF-8?q?+reward=5Foverlay=5Ftest=E6=9B=BF=E6=8D=A2=E6=AE=B5=E6=91=98?= =?UTF-8?q?=E9=99=A4;=20=E4=BF=AE=E6=AD=A3=E5=88=99=E8=B4=AA=E5=90=83?= =?UTF-8?q?=E8=AF=AF=E5=88=A0=3Dapply=5Fnode=5Fcurio=E6=99=AE=E9=80=9A?= =?UTF-8?q?=E5=85=91=E4=BB=98=E4=B8=BB=E4=BD=93=E8=A1=A5=E5=9B=9E=E3=80=82?= =?UTF-8?q?=E5=86=92=E7=83=9F895/0+run=5Fall=2022=E5=A5=97+=E6=8E=A2?= =?UTF-8?q?=E9=92=88=E5=85=A8=E7=BB=BF=E3=80=94=E7=A7=98=E5=AE=9D=E6=89=B9?= =?UTF-8?q?=20V15/V16=E6=8B=86=E9=99=A4=E8=B7=AF=E7=BA=BF/V13=E3=80=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/adventure_runner.gd | 180 +----------------- game/core/level_runner.gd | 9 - game/core/save_manager.gd | 2 +- game/core/treasure_items.gd | 8 - .../presentation/curio_interact_controller.gd | 64 +------ game/presentation/main.gd | 2 - game/presentation/views/curio_pick_overlay.gd | 50 +---- game/smoke/level_autoplay.gd | 14 +- game/smoke/smoke_all.gd | 10 +- game/smoke/ui_audit_probe.gd | 2 +- game/smoke/ui_click_probe.gd | 14 +- game/smoke/ui_struct_probe.gd | 2 +- .../unit/presentation/reward_overlay_test.gd | 31 +-- 13 files changed, 42 insertions(+), 346 deletions(-) diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index 1e42ae8a..a84134eb 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -43,7 +43,6 @@ extends RefCounted signal level_started(level_index: int, display_name: String) # 每关开始(含冒险首关)——呈现层重取 runner()、刷新图条与层名行 signal curio_choice_presented(payload: Dictionary) # 专长三选一停点(非末关通关恰一次;迭代 20 T5 换代正名——旧「提升三选一」退役,通道白名单后候选恒专长+回血在载荷)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared} signal curio_resolved(result: Dictionary) # 三选一兑付完成(pick_curio/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index} -signal treasure_replace_presented(items: Array, incoming: Dictionary) # 同调位满位替换子交互(pick_curio 道具件满位时)——items=现有持有件记录、incoming=新件记录 signal adventure_finished(summary: Dictionary) # 冒险结算(completed/failed/abandoned)——summary 五件事:打穿没有/通关几关/止步第几关/钥石合计/主种子(+additive keystone_drops 迭代 10) # ---- 转发族(签名与 LevelRunner 同款——呈现层对接透明;ADR-0001 S6 经 _forward_* 方法)---- signal node_entered(node: Dictionary, info: Dictionary) @@ -94,9 +93,7 @@ var _feats: Array[String] = [] # 特性持有集(迭代 12 T1—— var _tree_rules: Array = [] # 树 rule 键集(迭代 12 T10——Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面) var _curio_pending := {} # 停点态:{"candidates": Array, "payload": Dictionary, "chosen"/"incoming": 替换在途}——空 = 无停点 var _curio_result := {} # 已兑付待推进态(curio_resolved 载荷副本——中断续玩不重兑付) -var _bonus_treasure_carry: Array[String] = [] # 道具件跨关携带(兑付后注入下一关库存——清库后授予的体验窗) var _keystone_drops: Array = [] # Boss 逐杀掉落明细(additive 摘要键 keystone_drops——结算明细/复盘归因数据源) -var _last_draw_treasure: Array[String] = [] # 最近一次抽签掷定的道具件(掷定前置——卡片直显具体道具;追加批二) var _node_curio_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽) var _feat_ticket_pity := 0 # 门票保底计数(T5——展示次数:节点开面/货架抽签/刷新重展各计一次;触发清零) var _feat_ticket_sold := 0 # 门票限购计数(T5——选中/买下即 +1;拿到即开不进持有集) @@ -212,8 +209,6 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: _feats = [] for r in (bp.get("relics", []) as Array): _feats.append(String(r)) - for v in (bp.get("bonus_aux_carry", []) as Array): - _bonus_treasure_carry.append(String(v)) _keystone_drops = (bp.get("keystone_drops", []) as Array).duplicate(true) _base_override = _normalize_override(params.get("hero_override", {})) # 生命账本越界钳制(T17——spawn 守卫 EC16 免疫):断点可能由旧口径版本写出(如调试上限 @@ -256,19 +251,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: else: migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON}) _curio_pending = {"candidates": migrated} - if bp_pending.has("aux_detail"): - _curio_pending["aux_detail"] = (bp_pending.get("aux_detail", []) as Array).duplicate(true) # 掷定前置随停点快照续接 var payload: Dictionary = (bp_pending.get("payload", {}) as Dictionary).duplicate(true) - if bp_pending.has("incoming"): - _curio_pending["chosen"] = "treasure_item" - var incoming: Array = (bp_pending.get("incoming", []) as Array).duplicate(true) - _curio_pending["incoming"] = incoming - state = "running" - curio_choice_presented.emit(payload) - # 替换在途续发(呈现层直落替换子态——持有件记录按恢复后 carry 重建) - treasure_replace_presented.emit(_carry_records(), - {"index": -1, "item_id": String(incoming[0]) if not incoming.is_empty() else "", "count": incoming.size()}) - return true state = "running" curio_choice_presented.emit(payload) return true @@ -295,7 +278,6 @@ func _reset_curio_state() -> void: _feats = [] _curio_pending = {} _curio_result = {} - _bonus_treasure_carry = [] _keystone_drops = [] _node_curio_pending = {} adventure_counters = {} @@ -420,8 +402,6 @@ func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool: return _redeem_feat_ticket() # T5 拿到即开——停点候选替换为专长三选一(零摘牌/不进持有集) var value: int = int(db.adventure.curio_value(kind, tier)) var hp_before := int(ledger.get("hero_hp", 0)) - if kind == "treasure_item": - return _pick_treasure_item(value) # 道具件分派通关渠道(持有容器 = 跨关携带——与节点渠道分轨;池种退役后不可达) if not _apply_curio_pick(kind, tier, value, feat_id): push_error("[冒险结构] pick_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, feat_id]) return false @@ -549,44 +529,6 @@ func _pool_stock_snapshot() -> Dictionary: return out -# 道具件兑付(同调位约束):有位直接入跨关携带;满位转替换子交互(incoming 暂存、等 resolve_treasure_replace)。 -# 掷定前置(追加批二):具体道具随候选抽定(_last_draw_treasure / _curio_pending.aux_detail)——本函数消费预掷值, -# 断点续玩/无预掷值时回退当场掷定(流序不同但消费数不变,确定性由调用序保证)。 -# 持有容器 = 跨关携带 _bonus_treasure_carry(与节点渠道的关卡域库存分轨——容器差异保守不合,注记在案)。 -func _pick_treasure_item(count: int) -> bool: - var picked: Array = _draw_pending_treasure(_curio_pending, count, true) - if picked.is_empty(): - push_error("[冒险结构] pick_curio(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)") - return false - var hp_before := int(ledger.get("hero_hp", 0)) - # 迭代 11 第①批 T7——同调位守卫随过渡键退役拆除(aux 卡出池后本路径不可达;能力件容量闸 - # 正主 = 营地装配面 Attunement.slots 满位禁选) - for iid in picked: - _bonus_treasure_carry.append(String(iid)) - _finish_curio_pick("treasure_item", picked.size(), hp_before) - return true - - -# 替换子交互受理(discard_index = -1 放弃新件不获取;否则丢该件腾位收新)——终态恒 ≤ 同调位 -# 持有容器 = 跨关携带(序号寻址);与节点渠道(库存句柄寻址)的丢弃/授予/收尾三步保守不合。 -func resolve_treasure_replace(discard_index: int) -> bool: - if not _replace_in_transit(_curio_pending): - push_error("[冒险结构] resolve_treasure_replace 拒绝——无替换在途") - return false - var incoming: Array = _curio_pending.get("incoming", []) as Array - var hp_before := int(ledger.get("hero_hp", 0)) - if discard_index >= 0: - if discard_index >= _bonus_treasure_carry.size(): - push_error("[冒险结构] resolve_treasure_replace 拒绝——序号不在持有集: %d" % discard_index) - return false - _bonus_treasure_carry.remove_at(discard_index) - for iid in incoming: - _bonus_treasure_carry.append(String(iid)) - # 放弃新件(discard_index < 0)= 原持有集不动、新件不获取 - _finish_curio_pick("treasure_item", incoming.size(), hp_before) - return true - - # 兑付收口(恰一次):清停点态 → 记已兑付态 → emit → 停点快照(等继续确认——中断续玩不重兑付)。 # picked = kind;tier 随载荷(TP0——大小档位读数面);relic 伴随键随载荷(迭代 12 T1——反馈行取数) func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String = "", feat_id: String = "") -> void: @@ -608,16 +550,15 @@ func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String # 候选呈现(节点到达时调用——恰一 reward_pick scope 洗牌 + 道具件掷定 scope;本停点断点不载, # 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。 -# 掷定前置(追加批二):返回 dict 带 aux_detail(道具件具体道具)供卡片直显。 func node_curio_candidates() -> Dictionary: var cands: Array = _draw_curio_candidates("reward_node") - return {"candidates": cands, "aux_detail": _last_draw_treasure.duplicate()} + return {"candidates": cands} -func present_node_curio(candidates: Array, treasure_detail: Array = []) -> void: +func present_node_curio(candidates: Array) -> void: _reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转) _curio_paid_refreshes = 0 # 付费刷计数随停点清零(对齐商店进店清零——2026-10-06) - _node_curio_pending = {"candidates": candidates.duplicate(true), "aux_detail": treasure_detail.duplicate(true)} + _node_curio_pending = {"candidates": candidates.duplicate(true)} # 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与通关三选一同库;relic 同权入持有集) @@ -632,8 +573,6 @@ func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") -> push_error("[冒险结构] apply_node_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id]) return false var value: int = int(db.adventure.curio_value(kind, tier)) - if kind == "treasure_item": - return _apply_node_treasure(value) # 道具件分派节点渠道(持有容器 = 关卡域库存——与通关渠道分轨;池种退役后不可达) if not _apply_curio_pick(kind, tier, value, feat_id): push_error("[冒险结构] apply_node_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, feat_id]) return false @@ -641,46 +580,6 @@ func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") -> return true -# 节点渠道道具件(同调位约束):持有=关卡域库存+跨关携带,有位直接入本关库存(当关可用); -# 满位转替换子交互(丢弃目标=库存件 handle——与通关渠道的 carry 序号分轨,容器语义保守不合)。 -# 掷定前置:消费 _node_curio_pending.aux_detail 预掷值(缺省回退当场掷定)。 -func _apply_node_treasure(count: int) -> bool: - var picked: Array = _draw_pending_treasure(_node_curio_pending, count, false) - if picked.is_empty(): - push_error("[冒险结构] apply_node_curio(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)") - return false - var records: Array = [] - if _runner != null and _runner.treasure_inventory != null: - records = _runner.treasure_inventory.holding_records() - # 迭代 11 第①批 T7——守卫随过渡键退役拆除(CURIO_KINDS 已除名 treasure_item,本分支不可达; - # 替换子交互路由随容量闸一并退役——能力件容量闸正主 = 营地装配面满位禁选) - if _runner != null: - for iid in picked: - var rec: Dictionary = _runner.grant_treasure_item(String(iid), "reward_node") - if rec.is_empty(): - push_warning("[冒险结构] 奖励节点道具授予被拒(防御兜底留痕): %s" % String(iid)) - _finish_node_bonus("treasure_item", picked.size()) - return true - - -func _resolve_node_replace(discard_handle: int) -> bool: - if not _replace_in_transit(_node_curio_pending): - push_error("[冒险结构] resolve_node_replace 拒绝——无替换在途") - return false - var incoming: Array = _node_curio_pending.get("incoming", []) as Array - if discard_handle >= 0: - if _runner == null or _runner.treasure_inventory == null or not _runner.treasure_inventory.discard_item(discard_handle): - push_error("[冒险结构] resolve_node_replace 拒绝——句柄不在持有集: %d" % discard_handle) - return false - for iid in incoming: - var rec: Dictionary = _runner.grant_treasure_item(String(iid), "reward_node") - if rec.is_empty(): - push_warning("[冒险结构] 替换子道具授予被拒(防御兜底留痕): %s" % String(iid)) - # 放弃新件(discard_handle < 0)= 原持有集不动、新件不获取 - _finish_node_bonus("treasure_item", incoming.size()) - return true - - # 冒险开始充能账本重置(T6——装备 active_charged 件各回 charges_per_adventure;被动件不入账) func _seed_attune_charges() -> void: attune_charges = {} @@ -742,11 +641,10 @@ func battery_pending() -> int: return _battery_pending -# 节点停点在途态读回(组合根重 present 用——candidates/aux_detail 值拷贝) +# 节点停点在途态读回(组合根重 present 用——candidates 值拷贝) func node_curio_pending_state() -> Dictionary: return { "candidates": (_node_curio_pending.get("candidates", []) as Array).duplicate(true), - "aux_detail": (_node_curio_pending.get("aux_detail", []) as Array).duplicate(true), } @@ -792,10 +690,8 @@ func _refresh_curio_common(paid: bool) -> bool: if not _curio_pending.is_empty(): var cands: Array = _draw_curio_candidates("level_clear") _curio_pending["candidates"] = cands.duplicate(true) - _curio_pending["aux_detail"] = _last_draw_treasure.duplicate() var payload: Dictionary = (_curio_pending.get("payload", {}) as Dictionary).duplicate(true) payload["candidates"] = cands - payload["aux_detail"] = _last_draw_treasure.duplicate() payload["refreshed"] = true # 重发载荷带标——呈现层直落选择面,不再重开胜利总结面(2026-10-06) curio_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途) _snapshot_breakpoint() @@ -803,44 +699,10 @@ func _refresh_curio_common(paid: bool) -> bool: if not _node_curio_pending.is_empty(): var cands2: Array = _draw_curio_candidates("reward_node") _node_curio_pending["candidates"] = cands2.duplicate(true) - _node_curio_pending["aux_detail"] = _last_draw_treasure.duplicate() return true return false -# 替换在途守卫(2026-10-02 修复批——双渠道替换受理的共用前置谓词:停点态非空且 chosen=treasure_item) -func _replace_in_transit(pending: Dictionary) -> bool: - return not pending.is_empty() and String(pending.get("chosen", "")) == "treasure_item" - - -# 双渠道道具件掷定共享体(2026-10-02 修复批——_pick_treasure_item/_apply_node_treasure 复制债收口一步): -# 解锁道具池空集守卫 + N 件掷定(先消费停点预掷 aux_detail,不足部分当场经 reward_pick 封闭流补掷—— -# 取数序与拆分前逐位一致)。返回空数组 = 池空集(调用方按渠道名报错)。 -# 保守不合处:通关渠道允许回退 _last_draw_treasure(allow_detail_fallback)、节点渠道不回退——掷定回退 -# 语义渠道各异,参数化保留差异;持有容器与兑付收尾仍归调用方渠道。 -func _draw_pending_treasure(pending: Dictionary, count: int, allow_detail_fallback: bool) -> Array: - var pool: Array = _bonus_treasure_pool() - if pool.is_empty(): - return [] - var picked: Array = [] - var detail: Array = (pending.get("aux_detail", []) as Array).duplicate() - if detail.is_empty() and allow_detail_fallback: - detail = _last_draw_treasure.duplicate() - for i in maxi(1, count): - if i < detail.size(): - picked.append(String(detail[i])) - else: - picked.append(_roll_bonus_treasure_item(pool)) - return picked - - -# 替换受理分发(单入口——节点停点在途优先按库存句柄,否则按通关停点的携带序号) -func resolve_curio_replace(discard: int) -> bool: - if not _node_curio_pending.is_empty() and _node_curio_pending.has("incoming"): - return _resolve_node_replace(discard) - return resolve_treasure_replace(discard) - - func _finish_node_bonus(picked: String, value: int, tier: String = "", feat_id: String = "") -> void: _node_curio_pending = {} var payload := { @@ -858,8 +720,7 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", feat_id: # 一枚**〔先到先得,档位取该 kind 首枚——「三选一=三种不同选项」体验延续,与货架抽签同规则〕; # kind 出现率∝剩余令牌数〔档位配额即分布——无独立权重 knob〕;抽签不摘牌、兑付才摘牌=未选 # 自动回池;池耗即枯竭——余库不足 N kind 时出齐剩余,空库 = 零候选合法态)。候选 = 令牌字典 -# {"kind","tier"}。消费 reward_pick 封闭流——一次停点恰一 scope,同种子同候选;道具件掷定前置 -# 随池种退役拆除(_last_draw_treasure 恒空集,机械惰性承载)。 +# {"kind","tier"}。消费 reward_pick 封闭流——一次停点恰一 scope,同种子同候选。 # 渠道白名单过滤(迭代 15 卡② T4——渠道轴定位:关底 Boss 击败=通关渠道只出专长(feat 令牌); # 奖励节点/商店只出属性值提升+资源(非 relic 令牌),专长在非 Boss 渠道只以门票形态出现(T5)。 # 过滤式而非物理拆池:deck 物理结构/存档键 bonus_deck 混编余库/消耗语义(选中踢池未选回池)原样, @@ -869,7 +730,6 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", feat_id: # 〔独立计数体系〕;已拿上限 cap(案②提前退役):taken≥cap 的身份在候选视图剔除〔牌库本体不动, # 拿了才计、刷出不计——_curio_cap_ok〕。 func _draw_curio_candidates(channel: String = "reward_node") -> Array: - _last_draw_treasure = [] var pool := _curio_deck.duplicate() if channel == "level_clear": pool = pool.filter(func(tok): return String((tok as Dictionary).get("kind", "")) == "relic") @@ -1007,33 +867,6 @@ func _redeem_feat_ticket() -> bool: return true -func _roll_from(rg: RandomNumberGenerator) -> String: - var pool: Array = _bonus_treasure_pool() - return String(pool[rg.randi_range(0, pool.size() - 1)]) - - -# 三选一道具件随机池(与投放门控同源:aux 活行 ∩ 解锁门控集——空集 = 道具件不入牌库) -func _bonus_treasure_pool() -> Array: - var out: Array = [] - var live: Dictionary = db.treasure_live_ids() - for iid in live.keys(): - if _treasure_unlocked.has(String(iid)): - out.append(String(iid)) - return out - - -func _roll_bonus_treasure_item(pool: Array) -> String: - var rg: RandomNumberGenerator = rng.open_stream("reward_pick") - return String(pool[rg.randi_range(0, pool.size() - 1)]) - - -func _carry_records() -> Array: - var out: Array = [] - for i in _bonus_treasure_carry.size(): - out.append({"index": i, "item_id": _bonus_treasure_carry[i]}) - return out - - # Boss 钥石逐杀掉落(迭代 10——渠道改 Boss/精英击杀掉落;首杀/复杀两档读 meta.tres boss_keystone; # 首杀判据 = 存档 boss_first_kill 标记位〔断点未注入 = 恒首杀——模拟/冒烟零扰动;标记仍在收束 # apply_adventure_summary 逐通关关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因) @@ -1158,7 +991,6 @@ func _on_level_finished(payload: Dictionary) -> void: var pick_payload: Dictionary = { "level_index": level_index, "candidates": _draw_curio_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=通关渠道只出专长 - "aux_detail": _last_draw_treasure.duplicate(), # 掷定前置(追加批二——道具件具体道具随候选抽定,卡片直显) "heal_gained": heal, "hp_before": hp_before, "hp_after": int(ledger.get("hero_hp", 0)), @@ -1168,7 +1000,6 @@ func _on_level_finished(payload: Dictionary) -> void: "aux_cleared": int(payload.get("aux_cleared", 0)), # additive 扩签(迭代 7 T4——关卡末清空反馈取数) } _curio_pending = {"candidates": (pick_payload["candidates"] as Array).duplicate(true), - "aux_detail": _last_draw_treasure.duplicate(), "payload": pick_payload.duplicate(true)} curio_choice_presented.emit(pick_payload) _snapshot_breakpoint() # 选择停点可中断(契约 5——续玩不重抽不静默丢失) @@ -1267,7 +1098,6 @@ func _snapshot_breakpoint() -> void: "refresh_stock": _refresh_stock, # 迭代 20 T4/T7——刷新存量账随断点(F-1 新账) "battery_pending": _battery_pending, # 迭代 20 T4/T7——电池待分配充能随断点 "bonus_result": _curio_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付) - "bonus_aux_carry": _bonus_treasure_carry.duplicate(true), # 道具件跨关携带(注入前中断防静默丢失) "keystone_drops": _keystone_drops.duplicate(true), # 逐杀掉落明细(摘要归因续接——迭代 10) } _save_manager.set_breakpoint(bp) diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index c06b14e8..aa524ccf 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -679,15 +679,6 @@ func set_attune_charges(charges: Dictionary) -> void: func set_vision_counter(counter: Dictionary) -> void: _vision_counter = counter -# 奖励节点兑付透传(迭代 7 T5——呈现层三选一 → 关卡域库存;来源归因 reward_node)。 -# 迭代 11 第①批 T7——同调位持有闸随过渡键退役拆除(aux 卡出池后仅剩防御面;能力件容量闸 -# 正主 = 营地装配面 Attunement.slots 满位禁选)。 -func grant_treasure_item(item_id: String, source: String) -> Dictionary: - if treasure_inventory == null: - treasure_inventory = TreasureItems.new(db) - return treasure_inventory.add_item(item_id, source, Vector2i(-1, -1), 0) - - # 三选一 GP 袋件入账(迭代 10——钱包收口扩「奖励入账」入口;复用战斗完成同一入账原语, # wallet_changed 载荷带 source 供呈现归因) func grant_curio_gp(amount: int) -> void: diff --git a/game/core/save_manager.gd b/game/core/save_manager.gd index d613aa3e..8b054c6a 100644 --- a/game/core/save_manager.gd +++ b/game/core/save_manager.gd @@ -78,7 +78,7 @@ func _migrate_v1_v2(data: Dictionary) -> Dictionary: # v2→v3:断点附随件扩列(迭代 10 奖励重构批——bonus_taken/bonus_pending/bonus_result/ -# bonus_aux_carry/keystone_drops + bonus.ap 轴)与摘要 additive 键 keystone_drops——旧档缺键 +# keystone_drops + bonus.ap 轴)与摘要 additive 键 keystone_drops——旧档缺键〔bonus_aux_carry 键随迭代 22 T5 替换子交互拆除退役〕 # = 合法空态(读取方 .get 兜底),无结构迁移体;版本号推进仅为 schema_version 对齐 func _migrate_v2_v3(data: Dictionary) -> Dictionary: data["version"] = SAVE_FORMAT_VERSION diff --git a/game/core/treasure_items.gd b/game/core/treasure_items.gd index 8425d699..1a4d6c6b 100644 --- a/game/core/treasure_items.gd +++ b/game/core/treasure_items.gd @@ -79,14 +79,6 @@ func holding_count() -> int: # 持有件记录(迭代 10 追加批——奖励节点替换子交互数据源:只列未消耗件,handle 定位丢弃目标) -func holding_records() -> Array: - var out: Array = [] - for rec in _items: - if not bool(rec.get("used", false)): - out.append({"handle": int(rec.get("handle", 0)), "item_id": String(rec.get("item_id", ""))}) - return out - - # 丢弃(同调位替换交互节点渠道——腾位收新;与 Used 消费分轨的一次性移除,快照不再含) func discard_item(handle: int) -> bool: for i in _items.size(): diff --git a/game/presentation/curio_interact_controller.gd b/game/presentation/curio_interact_controller.gd index add98aed..85a13c76 100644 --- a/game/presentation/curio_interact_controller.gd +++ b/game/presentation/curio_interact_controller.gd @@ -5,7 +5,7 @@ # 装配纪律(ADR-0001,仿 treasure_interact_controller 先例):信号连接不在此发生——curio_overlay # 四信号与 adventure 两信号由组合根接至本类 on_* 入口;模态栈/演播后排窗/呈现刷新/终局面/ # 推进失败 fatal 经组合根回调注入;adventure/runner 随局更替——现值回调注入;兑付写调用唯一 -# 经 adventure.pick_curio/apply_node_curio/resolve_curio_replace(core 侧单口,本类零直写)。 +# 经 adventure.pick_curio/apply_node_curio(core 侧单口,本类零直写;替换子交互随迭代 22 T5 拆除——装配不可变契约 1,冒险中零更换途径)。 extends RefCounted const Narrator := preload("res://presentation/battle_narrator.gd") @@ -29,8 +29,6 @@ var _gp_summary: Callable # () → 战斗总结 GP 行文案(组 var _pending_curio := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用) var _curio_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路) -var _last_replace_items: Array = [] # 替换子态续玩重建缓存(treasure_replace_presented 载荷) -var _last_replace_incoming := {} # 同上——incoming 记录 func _init(db_ref, meta_get: Callable, curio_overlay_ref: CurioPickOverlay, end_overlay_ref: BattleEndOverlay, @@ -112,8 +110,7 @@ func on_presented(payload: Dictionary) -> void: toast.show_toast("上关剩余 %d 件秘宝已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈 _pending_curio = payload.duplicate(true) _curio_context = "level_clear" - if bool(payload.get("replace_pending", false)) or bool(payload.get("refreshed", false)): - # 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽) + if bool(payload.get("refreshed", false)): # 刷新重发(2026-10-06):refreshed 标直落选择面——不再重开胜利总结面 show_pick() return @@ -124,9 +121,8 @@ func on_presented(payload: Dictionary) -> void: _refresh_presentations.call() -# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态) -# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;CurioPickOverlay 共用面, -# 道具件满位替换子交互同构)。原「秘宝投放渠道」三选一(迭代 7 过渡形态)随批退役。 +# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;CurioPickOverlay 共用面)。 +# 原「秘宝投放渠道」三选一(迭代 7 过渡形态)随批退役;替换子交互随迭代 22 T5 拆除。 func present_node_curio() -> void: var adventure = _adventure_get.call() _curio_context = "reward_node" @@ -141,16 +137,16 @@ func present_node_curio() -> void: runner.continue_after_node() return _open_modal.call(curio_overlay) - curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(node_cands, node_draw.get("aux_detail", []) as Array)) - adventure.present_node_curio(node_cands, node_draw.get("aux_detail", []) as Array) + curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(node_cands)) + adventure.present_node_curio(node_cands) _feed_refresh_display(adventure) # 迭代 11 第①批 T5b + 2026-10-06 对齐商店——刷新面投喂单点 -# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态) +# 三选一面开出(战斗总结结算确认 / 续玩停点恢复) func show_pick() -> void: var payload: Dictionary = _pending_curio _open_modal.call(curio_overlay) - curio_overlay.open_pick(Narrator.curio_pick_header(payload), _curio_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array)) + curio_overlay.open_pick(Narrator.curio_pick_header(payload), _curio_card_texts(payload.get("candidates", []) as Array)) var adventure = _adventure_get.call() if adventure != null: _feed_refresh_display(adventure) # 迭代 11 第①批 T5b + 2026-10-06 对齐商店——刷新面投喂单点 @@ -165,11 +161,10 @@ func _feed_refresh_display(adventure) -> void: curio_overlay.set_refresh_display(adventure.attune_refresh_left(), cost, gp >= cost) -func _curio_card_texts(candidates: Array, treasure_detail: Array = []) -> Array: +func _curio_card_texts(candidates: Array) -> Array: # TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|feat_id" 复合键(同 kind # 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(on_pick_confirmed) var out: Array = [] - var treasure_i := 0 for tok in candidates: var kind := String((tok as Dictionary).get("kind", "")) var tier := String((tok as Dictionary).get("tier", "")) @@ -181,10 +176,6 @@ func _curio_card_texts(candidates: Array, treasure_detail: Array = []) -> Array: 选定即开一次奇物三选一(T5 门票——拿到即开不进持有集)" elif kind == "relic": text = Narrator.feat_card_text(db.feat_row(rid)) - elif kind == "treasure_item": - 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.curio_value(kind, tier)), _treasure_card_line(iid)) else: text = Narrator.bonus_card_text(kind, tier, int(db.adventure.curio_value(kind, tier))) out.append({"id": cid, "text": text}) @@ -194,17 +185,6 @@ func _curio_card_texts(candidates: Array, treasure_detail: Array = []) -> Array: # 道具件卡片行(掷定前置——具体道具名 + 效果摘要;空 iid = 无预掷回退态显示通用文案)。 # 内容数据驱动扩展批 T4 文案表值化:效果摘要=数据行 desc 字段直读(零 kind 硬编码映射—— # 新件写行即有名有姓) -func _treasure_card_line(iid: String) -> String: - if iid == "": - return "" - var row: Dictionary = db.treasure_item_row(iid) - if row.is_empty(): - return "" - var desc := String(row.get("desc", "")) - if desc != "": - return "%s · %s" % [String(row.get("display_name", iid)), desc] - return String(row.get("display_name", iid)) - # 三选一刷新(迭代 11 第①批 T5b X/Y 双源 + 2026-10-06 用户令对齐商店 MC-1——免费位用尽转 GP 付费档: # 同 shop_refresh_cost_base/curve 旋钮、同冒险钱包 runner.gp;通关停点经重发 curio_choice_presented 直落 @@ -227,7 +207,7 @@ func on_refresh() -> void: return if _curio_context == "reward_node": var st: Dictionary = adventure.node_curio_pending_state() - curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array)) + curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st.get("candidates", []) as Array)) _feed_refresh_display(adventure) @@ -251,30 +231,6 @@ func on_pick_confirmed(item_id: String) -> void: curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st2.get("candidates", []) as Array)) -func on_treasure_replace_presented(items: Array, incoming: Dictionary) -> void: - _last_replace_items = items.duplicate(true) - _last_replace_incoming = incoming.duplicate(true) - if not curio_overlay.visible: - _open_modal.call(curio_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律) - _open_replace(items, incoming) - - -func _open_replace(items: Array, incoming: Dictionary) -> void: - var named: Array = [] - for it in items: - var rec: Dictionary = it.duplicate() - rec["display_name"] = String(item_name_lookup().get(String(rec.get("item_id", "")), String(rec.get("item_id", "")))) - named.append(rec) - curio_overlay.open_replace(named, String(item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))), - named.size(), Attunement.slots(db, _save_get.call())) # 迭代 11 第①批 T7——呈现读数源切谓词表求值(过渡键退役) - - -func on_replace_chosen(discard: int) -> void: - var adventure = _adventure_get.call() - if adventure == null or not adventure.resolve_curio_replace(discard): - push_error("[bonus_interact] 同调位替换受理被拒——见输出日志") - - # 兑付反馈(curio_resolved 到达——结果行上屏,唯一出口 = 继续) func on_resolved(result: Dictionary) -> void: _pending_curio = {} diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 312ab523..7fcc3eea 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -294,7 +294,6 @@ func _build_ui() -> void: _open_modal, _open_modal_after_playback, _close_modal, _refresh_presentations, _fatal_screen, func() -> String: return _gp_summary_text({"gp_dropped": _last_gain_amount})) curio_overlay.pick_confirmed.connect(_bonus.on_pick_confirmed) - curio_overlay.replace_chosen.connect(_bonus.on_replace_chosen) curio_overlay.continue_pressed.connect(_bonus.on_continue) curio_overlay.refresh_requested.connect(_bonus.on_refresh) move_child(curio_overlay, _end_overlay.get_index()) @@ -488,7 +487,6 @@ func _wire_adventure() -> void: # 迭代 10 奖励重构批——big_reward_paid 退役,三选一信号族接线 adventure.curio_choice_presented.connect(_bonus.on_presented) adventure.curio_resolved.connect(_bonus.on_resolved) - 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 卡①——停点快照写盘;调试/模拟不经过此路径) diff --git a/game/presentation/views/curio_pick_overlay.gd b/game/presentation/views/curio_pick_overlay.gd index 637c2bb3..a47113ea 100644 --- a/game/presentation/views/curio_pick_overlay.gd +++ b/game/presentation/views/curio_pick_overlay.gd @@ -1,20 +1,19 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # Date: 2026-10-01(迭代 10 奖励重构批——level-clear.md v2 三选一选择面承载) -# 提升三选一覆盖层(模态):非末关通关的选择停点呈现——部分回血反馈 + 三卡点选 + 确认兑付 + -# 同调位替换子交互。高内聚边界:文案拼装归 Narrator(组合根喂入),选择意图经信号外发; -# 视图零核心引用。交互范式 = node_overlay choice_box 按钮族 + 选路两段式(点选→确认)复用,零新原语。 -# 状态机:pick(三卡点选→确认)→ [replace(满位 N+1 选 N)] → result(兑付反馈→继续)。 +# 提升三选一覆盖层(模态):非末关通关的选择停点呈现——部分回血反馈 + 三卡点选 + 确认兑付。 +# 高内聚边界:文案拼装归 Narrator(组合根喂入),选择意图经信号外发;视图零核心引用。 +# 交互范式 = node_overlay choice_box 按钮族 + 选路两段式(点选→确认)复用,零新原语。 +# 状态机:pick(三卡点选→确认)→ result(兑付反馈→继续)。〔replace 满位替换子态随迭代 22 T5 +# 拆除——装配不可变契约 1(冒险中零更换途径),core 写口与组合根接线同批摘除。〕 class_name CurioPickOverlay extends Control signal pick_confirmed(item_id: String) -signal replace_chosen(discard_index: int) # -1 = 放弃新件不获取 signal continue_pressed signal refresh_requested # 三选一刷新意图(迭代 11 第①批 T5b——P1 甲 X/Y 双源;重抽归组合根) var _label: Label var _choice_box: HBoxContainer # 三卡区(pick 态——M7 bcard 竖卡) -var _replace_box: VBoxContainer # 替换子面板(replace 态) var _confirm_btn: Button # 确认钮(点选后点亮——两段式第二拍) var _continue_btn: Button # 继续钮(兑付反馈态唯一出口) var _refresh_btn: Button # 刷新钮(迭代 11 第①批 T5b——余量>0 才显示;M7 升顶条图标钮) @@ -33,7 +32,7 @@ func _init() -> void: # T7(UI 设计对齐批 2026-10-06):全屏页形态(demo ⑥ #v-bonus——与商店 T6 同壳 page_shell, # 根底暗档)——56px 通栏顶条(标题+刷新图标钮/角标)+ 居中入账信息行(GP 汇总行承载)+ # bcard 三竖卡(D1 232×352)+ 底部确认主钮与免费选择注记。差异白名单=免费选择/✓章/无价签。 - # 状态机不变:pick→[replace]→result。M7 同构裁决遗产保留。 + # 状态机不变:pick→result。M7 同构裁决遗产保留。 var page: Dictionary = UiKit.page_shell(self, false) var top: HBoxContainer = page.top_row top.add_child(UiKit.label("战利品三选一",UiKit.FS_TITLE, true, UiKit.WARM)) @@ -79,11 +78,6 @@ func _init() -> void: foot.add_child(_continue_btn) _foot_hint = UiKit.label("免费选择 · 刷新不消耗 AP",UiKit.FS_HINT, false, UiKit.WARM_FAINT) foot.add_child(_foot_hint) - _replace_box = VBoxContainer.new() - _replace_box.add_theme_constant_override("separation", 8) - _replace_box.alignment = BoxContainer.ALIGNMENT_CENTER - _replace_box.size_flags_vertical = Control.SIZE_EXPAND_FILL - box.add_child(_replace_box) visible = false @@ -115,9 +109,8 @@ func open_pick(header: String, cards: Array) -> void: _bcards.append(card) _choice_box.visible = true _confirm_btn.visible = false - _replace_box.visible = false _continue_btn.visible = false - _foot_hint.visible = true # T7 底注=pick 态专属(replace/result 态让位) + _foot_hint.visible = true # T7 底注=pick 态专属(result 态让位) _refresh_btn.visible = false # 刷新面随 set_refresh_display 投喂后现形(open 先归零防残留;店内门票面不投喂=无刷新) _refresh_l.text = "" visible = true @@ -183,40 +176,15 @@ func show_result(text: String) -> void: _label.text = _header + "\n" + text _choice_box.visible = false _confirm_btn.visible = false - _replace_box.visible = false _continue_btn.visible = true _refresh_btn.visible = false # 刷新=选择态专属功能(结果态停点已兑付——残钮零意图可点) _foot_hint.visible = false visible = true -# 同调位替换子态(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] - for c in _replace_box.get_children(): - c.queue_free() - for it in items: - 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(_emit_replace_chosen.bind(tag)) - _replace_box.add_child(btn) - var decline := UiKit.button("放弃新件(不获取)") - decline.pressed.connect(_emit_replace_chosen.bind(-1)) - _replace_box.add_child(decline) - _choice_box.visible = false - _confirm_btn.visible = false - _replace_box.visible = true - _continue_btn.visible = false - _refresh_btn.visible = false # 刷新=选择态专属功能(替换子态不换候选) - _foot_hint.visible = false - visible = true - - # 收屏(组合根在 continue_pressed 处理毕调——推进下一关同拍) func close_pick() -> void: _choice_box.visible = false - _replace_box.visible = false _continue_btn.visible = false _confirm_btn.visible = false _refresh_btn.visible = false @@ -228,10 +196,6 @@ func _emit_continue_pressed() -> void: continue_pressed.emit() -func _emit_replace_chosen(tag: int) -> void: - replace_chosen.emit(tag) - - # M7:id → 手绘图标键(子串映射容错 id 词表演进;缺位=BCard 空图标槽不炸) func _icon_for_id(idl: String) -> String: if idl.contains("heal"): diff --git a/game/smoke/level_autoplay.gd b/game/smoke/level_autoplay.gd index 89b61532..1b847372 100644 --- a/game/smoke/level_autoplay.gd +++ b/game/smoke/level_autoplay.gd @@ -150,10 +150,9 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_ adr.node_entered.connect(_on_adventure_node_entered.bind(adr, st, logger)) adr.wallet_changed.connect(_on_adventure_wallet.bind(st, logger)) # 三选一停点驱动(迭代 10 奖励重构批——big_reward_paid 退役:确定性策略候选取首件、 - # 道具件满位替换放弃新件、兑付后推进下一关——零随机流自开,同种子逐位可复放) + # 兑付后推进下一关——零随机流自开,同种子逐位可复放;满位替换策略随迭代 22 T5 拆除) adr.curio_choice_presented.connect(_on_adventure_bonus.bind(st, adr)) adr.curio_resolved.connect(_on_adventure_curio_resolved.bind(st, adr)) - adr.treasure_replace_presented.connect(_on_adventure_treasure_replace.bind(st, adr)) adr.adventure_finished.connect(_on_adventure_finished.bind(st, logger)) adr.set_hero_override(atk_pct, hp_pct) # 同调分层注入(迭代 11 第①批 T8——缺省 {} 与既有调用方逐位一致零行为变更) @@ -226,7 +225,7 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr, if ncands.is_empty(): r.continue_after_node() # 牌库空合法态直通(池耗即枯竭——防御保留) else: - adr.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array) + adr.present_node_curio(ncands) var npick: Dictionary = _pick_token(ncands, String(st.get("feat_mode", "off"))) if adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))): # T5 门票拿到即开——候选已被替换为专长三选一,就地再选一轮(确定性策略同款 316-f); @@ -236,8 +235,6 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr, if not fcands.is_empty(): var fpick: Dictionary = _pick_token(fcands, String(st.get("feat_mode", "off"))) adr.apply_node_curio(String(fpick.get("kind", "")), String(fpick.get("tier", "")), String(fpick.get("feat_id", ""))) - if not adr._node_curio_pending.is_empty() and adr._node_curio_pending.has("incoming"): - adr.resolve_curio_replace(-1) # 满位替换:放弃新件 else: st.result = "advance_failed" elif t == "rest": @@ -310,13 +307,6 @@ static func _pick_token(candidates: Array, feat_mode: String = "off") -> Diction return best -# 同调位满位替换子交互(确定性策略:放弃新件不获取——分发面按停点上下文裁决节点/通关渠道) -static func _on_adventure_treasure_replace(items: Array, incoming: Dictionary, st: Dictionary, adr) -> void: - st.reward_events.append({"treasure_replace": incoming.duplicate(true)}) - if not adr.resolve_curio_replace(-1): - st.result = "advance_failed" - - # 三选一兑付完成 → 上下文分派(通关停点=推进下一关 / 奖励节点=回图选路) static func _on_adventure_curio_resolved(result: Dictionary, st: Dictionary, adr) -> void: st.reward_events.append({"curio_resolved": result.duplicate(true)}) diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index cb0c5b65..c39bcf9e 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -649,7 +649,7 @@ func _smoke_adventure() -> void: break check(not has_attack13, "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" % str(ncands13)) - a13.present_node_curio(ncands13, ndraw.get("aux_detail", []) as Array) + a13.present_node_curio(ncands13) a13.present_node_curio([{"kind": "gp", "tier": "common"}]) var gp13 := int(a13.ledger.get("gp", 0)) check(a13.apply_node_curio("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")), @@ -750,7 +750,7 @@ func _smoke_feats() -> void: var stock_before := {} for kk in ["attack", "gp", "heal"]: stock_before[kk] = a_t.pool_stock(kk) - a_t._node_curio_pending = {"candidates": [{"kind": "feat_ticket", "tier": "excellent"}, {"kind": "attack", "tier": "common"}], "aux_detail": []} + a_t._node_curio_pending = {"candidates": [{"kind": "feat_ticket", "tier": "excellent"}, {"kind": "attack", "tier": "common"}]} check(a_t.apply_node_curio("feat_ticket", "excellent"), "A5 门票兑付受理(节点渠道——拿到即开)") var tkt_cands: Array = (a_t.node_curio_pending_state().get("candidates", []) as Array) var tkt_all_relic := tkt_cands.size() > 0 @@ -1040,11 +1040,9 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool: var ndraw: Dictionary = a.node_curio_candidates() var ncands: Array = ndraw.get("candidates", []) as Array if not ncands.is_empty(): - a.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array) + a.present_node_curio(ncands) var npick: Dictionary = autoplay._pick_token(ncands) - if a.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))): - if not a._node_curio_pending.is_empty() and a._node_curio_pending.has("incoming"): - a.resolve_curio_replace(-1) + a.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))) r.continue_after_node() elif t == "rest": r.continue_after_node() diff --git a/game/smoke/ui_audit_probe.gd b/game/smoke/ui_audit_probe.gd index 4ed59fb4..5f06115b 100644 --- a/game/smoke/ui_audit_probe.gd +++ b/game/smoke/ui_audit_probe.gd @@ -34,7 +34,7 @@ func _ready() -> void: main.save_manager.set_breakpoint({ "master_seed": 42, "rng_counter": 0, "hero_hp": 20, "gp": 24, "keystone": 4, "level_index": 1, "bonus": {}, "shop_purchased": {}, "relics": [], - "bonus_aux_carry": [], "keystone_drops": [], "bonus_taken": {}, + "keystone_drops": [], "bonus_taken": {}, }) main._refresh_resume_button() await get_tree().process_frame diff --git a/game/smoke/ui_click_probe.gd b/game/smoke/ui_click_probe.gd index 322fb9a6..a4cd9149 100644 --- a/game/smoke/ui_click_probe.gd +++ b/game/smoke/ui_click_probe.gd @@ -262,14 +262,12 @@ func _ready() -> void: continue if main.curio_overlay.visible: # 迭代 10 三选一停点(CurioPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配—— - # 本探针此前未适配该层,走到即卡死)。三态分派:替换子态(满位——放弃新件)/ - # result 态(兑付反馈——点继续推进)/ 待选态(点首件+确认)。真实信号路径全线。 - # T1 D1——三选一停点输入锁定断言(停点态断言面:替换子/待选/兑付三态共用此层)。 + # 本探针此前未适配该层,走到即卡死)。两态分派:result 态(兑付反馈——点继续推进)/ + # 待选态(点首件+确认)。真实信号路径全线。〔替换子态随迭代 22 T5 拆除——装配不可变契约。〕 + # T1 D1——三选一停点输入锁定断言(停点态断言面:待选/兑付两态共用此层)。 if not main._input_locked(): _problems.append("三选一停点打开时输入未锁定") - if main.curio_overlay._replace_box.visible: - main.curio_overlay.replace_chosen.emit(-1) - elif main.curio_overlay._continue_btn.visible: + if main.curio_overlay._continue_btn.visible: main.curio_overlay.press_continue() elif main.adventure != null and not main.adventure._curio_pending.is_empty(): main.curio_overlay.press_card(0) @@ -409,9 +407,7 @@ func _ready() -> void: else: break elif main.curio_overlay.visible: - if main.curio_overlay._replace_box.visible: - main.curio_overlay.replace_chosen.emit(-1) - elif main.curio_overlay._continue_btn.visible: + if main.curio_overlay._continue_btn.visible: main.curio_overlay.press_continue() elif main.adventure != null and not main.adventure._curio_pending.is_empty(): main.curio_overlay.press_card(0) diff --git a/game/smoke/ui_struct_probe.gd b/game/smoke/ui_struct_probe.gd index cd15b8f0..cd4daa84 100644 --- a/game/smoke/ui_struct_probe.gd +++ b/game/smoke/ui_struct_probe.gd @@ -198,7 +198,7 @@ func _resume_bp_fixture() -> Dictionary: return { "master_seed": 42, "rng_counter": 0, "hero_hp": 20, "gp": 24, "keystone": 0, "level_index": 1, "bonus": {}, "shop_purchased": {}, "relics": [], - "bonus_aux_carry": [], "keystone_drops": [], "bonus_taken": {}, + "keystone_drops": [], "bonus_taken": {}, "bonus_pending": {"candidates": [{"kind": "heal", "tier": "common"}], "payload": {"gp_dropped": 0}}, } diff --git a/game/tests/unit/presentation/reward_overlay_test.gd b/game/tests/unit/presentation/reward_overlay_test.gd index 13fdd17b..3c27e989 100644 --- a/game/tests/unit/presentation/reward_overlay_test.gd +++ b/game/tests/unit/presentation/reward_overlay_test.gd @@ -1,7 +1,8 @@ extends SceneTree # 三选一覆盖层 UI 直验套件(迭代 7 T5-hotfix 转正;迭代 10 追加批随面迁移改写)—— # 原守 node_overlay.show_reward_items;奖励节点三选一与通关三选一合并共用 CurioPickOverlay 后, -# 本套件改守共用面的「功能存在 ≠ 功能可达」盲区:选择外发/强制确认两段式/替换子面板两路/结果态。 +# 本套件改守共用面的「功能存在 ≠ 功能可达」盲区:选择外发/强制确认两段式/结果态。 +# 〔替换子面板两路段随迭代 22 秘宝批 T5 拆除——装配不可变契约 1,overlay replace 态退役。〕 # 运行:godot --headless --path game --script res://tests/unit/presentation/reward_overlay_test.gd(expect 0) # T6 收口批:断言走 test_kit 计数(Kit.check)+ 失败即停早退协议保留(顺序状态机——坏态下后继断言无效)。 const Kit := preload("res://tests/unit/test_kit.gd") @@ -21,10 +22,8 @@ func _run() -> void: var ov = load("res://presentation/views/curio_pick_overlay.gd").new() root.add_child(ov) var picked := [] - var replaced := [] var continued := [] ov.pick_confirmed.connect(func(id: String): picked.append(id)) - ov.replace_chosen.connect(func(tag: int): replaced.append(tag)) ov.continue_pressed.connect(func(): continued.append(true)) # ① 选择态:三卡呈现、确认钮隐藏(两段式——点选前不给确认) ov.open_pick("第 1 层通过!", [ @@ -68,29 +67,11 @@ func _run() -> void: if not _req("点选确认未外发 pick_confirmed(gp)(%s)" % str(picked), ov._confirm_btn.visible and picked.size() == 1 and String(picked[0]) == "gp"): return - # ③ 替换子面板:丢旧收新与放弃新件两路外发(通关渠道=携带序号 / 节点渠道=库存句柄——tag 直通) - ov.open_replace([{"index": 0, "item_id": "incense_stick", "display_name": "线香"}], "天眼", 1, 1) - var replace_btns := 0 - for c in ov._replace_box.get_children(): - if c is Button: - replace_btns += 1 - if not _req("替换面板应 2 钮(N+1 选 N+放弃)且三卡隐藏(btns=%d)" % replace_btns, - replace_btns == 2 and not ov._choice_box.visible): - return - var replaced_first := false - for c in ov._replace_box.get_children(): - if c is Button: - c.pressed.emit() - replaced_first = true - break - if not _req("丢旧收新未外发 replace_chosen(0)(%s)" % str(replaced), - replaced_first and replaced.size() == 1 and int(replaced[0]) == 0): - return - # ④ 结果态:继续钮点亮、唯一出口(刷新钮随选择态收——停点已兑付残钮零意图可点) + # ③ 结果态:继续钮点亮、唯一出口(刷新钮随选择态收——停点已兑付残钮零意图可点) ov.set_refresh_display(2, 20, false) ov.show_result("已选择:攻击力提升 · 攻击 +1") - if not _req("结果态应点亮继续且收替换面板与刷新钮", - ov._continue_btn.visible and not ov._replace_box.visible and not ov._refresh_btn.visible): + if not _req("结果态应点亮继续且收三卡与刷新钮", + ov._continue_btn.visible and not ov._choice_box.visible and not ov._refresh_btn.visible): return ov.press_continue() if not _req("继续未外发 continue_pressed", continued.size() == 1): @@ -98,4 +79,4 @@ func _run() -> void: ov.close_pick() if not _req("close_pick 应收屏", not ov.visible): return - quit(Kit.finish("reward_verify", "PASS — 三卡两段式/确认外发/替换两路/结果态/收屏 全过")) + quit(Kit.finish("reward_verify", "PASS — 三卡两段式/确认外发/结果态/收屏 全过"))