奇物批T2:运行层bonus_*→curio_*全族换词(_curio_pending/deck/taken/result/candidates+pick_curio/apply_node_curio/curio_choice_presented/curio_resolved等+两文件改名curio_interact_controller/curio_pick_overlay含.uid+CurioPickOverlay/CurioInteractController类名);T2裁点甲案自裁=五存档键引号字面量禁改(全仓对拍六形态全等);_bonus_treasure_carry按T0澄清剔除(秘宝语义);非奇物bonus语义保全(加算区bonus/price_level_bonus/attack_bonus/bp_bonus等);reward_overlay_test红转绿适配(M7 bcard换PanelContainer致c is Button恒0——改数_choice_box子节点数断言语义不变,run_all 16套全绿历史首次)

This commit is contained in:
DKChan committed 2026-10-05 18:44:51 +08:00
1 parent d4cde73976
commit 8e2694de49
17 files changed
+298 -299

No files matched your search

+113 -113
View File
@@ -41,9 +41,9 @@ class_name AdventureRunner
extends RefCounted
signal level_started(level_index: int, display_name: String) # 每关开始(含冒险首关)——呈现层重取 runner()、刷新图条与层名行
signal bonus_choice_presented(payload: Dictionary) # 提升三选一停点(非末关通关恰一次;迭代 10 大奖励重构——big_reward_paid 退役)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared}
signal bonus_resolved(result: Dictionary) # 三选一兑付完成(pick_bonus/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index}
signal treasure_replace_presented(items: Array, incoming: Dictionary) # 同调位满位替换子交互(pick_bonus 道具件满位时)——items=现有持有件记录、incoming=新件记录
signal curio_choice_presented(payload: Dictionary) # 提升三选一停点(非末关通关恰一次;迭代 10 大奖励重构——big_reward_paid 退役)——{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)
@@ -79,16 +79,16 @@ var attune_charges := {} # 同调主动件冒险充能账本(
var _save_manager = null # 断点承载注入(迭代 8 卡①——null = 不写断点;模拟/冒烟缺省)
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
# ---- 三选一停点态(迭代 10 奖励重构批)----
var _bonus_taken := {} # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载;池库存正主=_bonus_deck)
var _bonus_deck: Array = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起规则改写型特性带=relic 令牌随表追加)
var _curio_taken := {} # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载;池库存正主=_curio_deck)
var _curio_deck: Array = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起规则改写型特性带=relic 令牌随表追加)
var _relics: Array[String] = [] # 特性持有集(迭代 12 T1——局内随机渠道获得、冒险级跨关携带、收束清零;🔴 层级纪律承载面:不入同调位不入局外树)
var _tree_rules: Array = [] # 树 rule 键集(迭代 12 T10——Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
var _bonus_pending := {} # 停点态:{"candidates": Array, "payload": Dictionary, "chosen"/"incoming": 替换在途}——空 = 无停点
var _bonus_result := {} # 已兑付待推进态(bonus_resolved 载荷副本——中断续玩不重兑付)
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_bonus_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽)
var _node_curio_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽)
var _shop_mode := "legacy" # 商店模式(迭代 11 第②批 T3——"legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool)
@@ -114,7 +114,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
purchased = {}
keystone = 0
summary = {}
_reset_bonus_state()
_reset_curio_state()
levels_total = db.level_count() # 表访问收口(2026-10-02——行集直扫退役)
level_index = 1
_pending_next_level = false
@@ -174,10 +174,10 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
levels_total = db.level_count() # 表访问收口(2026-10-02——行集直扫退役)
level_index = int(bp.get("level_index", 1))
_pending_next_level = false
_reset_bonus_state()
_reset_curio_state()
var bp_taken: Dictionary = bp.get("bonus_taken", {}) as Dictionary
for id in _bonus_taken.keys():
_bonus_taken[id] = maxi(0, int(bp_taken.get(id, 0)))
for id in _curio_taken.keys():
_curio_taken[id] = maxi(0, int(bp_taken.get(id, 0)))
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
# 迭代 12 T1——relic 令牌 relic_id 随还原)
var bp_deck = bp.get("bonus_deck", null)
@@ -189,7 +189,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
if String(rtok["kind"]) == "relic":
rtok["relic_id"] = String(tok.get("relic_id", ""))
restored.append(rtok)
_bonus_deck = restored
_curio_deck = restored
# 特性持有集还原(迭代 12 T1——缺键 = 旧版断点防御空集)
_relics = []
for r in (bp.get("relics", []) as Array):
@@ -223,28 +223,28 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
migrated.append({"kind": String((c as Dictionary).get("kind", "")), "tier": String((c as Dictionary).get("tier", ""))})
else:
migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON})
_bonus_pending = {"candidates": migrated}
_curio_pending = {"candidates": migrated}
if bp_pending.has("aux_detail"):
_bonus_pending["aux_detail"] = (bp_pending.get("aux_detail", []) as Array).duplicate(true) # 掷定前置随停点快照续接
_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"):
_bonus_pending["chosen"] = "treasure_item"
_curio_pending["chosen"] = "treasure_item"
var incoming: Array = (bp_pending.get("incoming", []) as Array).duplicate(true)
_bonus_pending["incoming"] = incoming
_curio_pending["incoming"] = incoming
state = "running"
bonus_choice_presented.emit(payload)
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"
bonus_choice_presented.emit(payload)
curio_choice_presented.emit(payload)
return true
if not bp_result.is_empty():
_pending_next_level = true
_bonus_result = bp_result.duplicate(true)
_curio_result = bp_result.duplicate(true)
state = "running"
bonus_resolved.emit(_bonus_result.duplicate(true))
curio_resolved.emit(_curio_result.duplicate(true))
return true
_resume_bp = bp.duplicate(true)
# 迭代 11 第①批 T6——充能账本随断点回局(缺键 = 旧档/无断点,保持 _seed_attune_charges 种子态)
@@ -256,21 +256,21 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
# 三选一停点态归零(冒险开始/结算收束共用;令牌池按表配额重建——池消耗即上限,账本随池重生)
func _reset_bonus_state() -> void:
_bonus_taken = {"attack": 0, "max_hp": 0, "armor": 0, "gp": 0, "ap": 0, "heal": 0}
_bonus_deck = _build_bonus_deck()
func _reset_curio_state() -> void:
_curio_taken = {"attack": 0, "max_hp": 0, "armor": 0, "gp": 0, "ap": 0, "heal": 0}
_curio_deck = _build_curio_deck()
_relics = []
_bonus_pending = {}
_bonus_result = {}
_curio_pending = {}
_curio_result = {}
_bonus_treasure_carry = []
_keystone_drops = []
_node_bonus_pending = {}
_node_curio_pending = {}
# 令牌池构建(TP0 v2.2——按表 kind×tier 配额展开为持久多重集;构建序确定 = CURIO_KINDS×TIERS 逐层,
# 随机性只发生在洗牌消费 reward_pick 封闭流的时刻。迭代 12 T1:规则改写型特性带=relic 活行每件 1 张追加
# (rarity common/rare→稀有令牌档、rarest→罕见令牌档——relic 永不入普通档;空表=零追加零扰动))
func _build_bonus_deck() -> Array:
func _build_curio_deck() -> Array:
var deck: Array = []
for kind in db.adventure.CURIO_KINDS:
for tier in db.adventure.TIERS:
@@ -326,11 +326,11 @@ func continue_to_next_level() -> bool:
if not _pending_next_level:
push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进")
return false
if not _bonus_pending.is_empty():
if not _curio_pending.is_empty():
push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)")
return false
_pending_next_level = false
_bonus_result = {}
_curio_result = {}
level_index += 1
return _start_current_level()
@@ -353,22 +353,22 @@ func _find_token(candidates: Array, kind: String, tier: String, relic_id: String
# 另须 relic_id)。属性件写加算区、AP 件写第四轴、GP 件走钱包「奖励入账」入口、恢复件当即回血、
# relic 件入特性持有集(🔴 层级纪律:只走局内随机渠道)、道具件受同调位约束(随池种退役不可达)。
# 消费=选中令牌踢池(未选回池由「抽签不摘牌、兑付才摘牌」语义承载——TP0 v2.2)。
func pick_bonus(kind: String, tier: String = "", relic_id: String = "") -> bool:
if state != "running" or _bonus_pending.is_empty():
push_error("[冒险结构] pick_bonus 拒绝——无待选停点(state=%s)" % state)
func pick_curio(kind: String, tier: String = "", relic_id: String = "") -> bool:
if state != "running" or _curio_pending.is_empty():
push_error("[冒险结构] pick_curio 拒绝——无待选停点(state=%s)" % state)
return false
var candidates: Array = _bonus_pending.get("candidates", []) as Array
var candidates: Array = _curio_pending.get("candidates", []) as Array
if _find_token(candidates, kind, tier, relic_id).is_empty():
push_error("[冒险结构] pick_bonus 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
push_error("[冒险结构] pick_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
return false
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_bonus_pick(kind, tier, value, relic_id):
push_error("[冒险结构] pick_bonus 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id])
if not _apply_curio_pick(kind, tier, value, relic_id):
push_error("[冒险结构] pick_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id])
return false
_finish_bonus_pick(kind, value, hp_before, tier, relic_id)
_finish_curio_pick(kind, value, hp_before, tier, relic_id)
return true
@@ -383,9 +383,9 @@ func _relic_payload_keys(relic_id: String) -> Dictionary:
# 三选一件兑付共享体(2026-10-02 修复批双胞胎收口;TP0 改令牌消费——摘牌单点;迭代 12 T1 relic 分支):
# 属性件写加算区 + 件获取计数、AP 件写第四轴、GP 件走钱包、恢复件当即回血(封顶生效上限——
# 与账本置满/通关回血同式 effective_max_hp)、relic 件入持有集(散件单体保底——效果接线面
# 按挂点消费,档位质变由 RelicsEngine 求值)。兑付即从 _bonus_deck 摘除该令牌(选中踢池;
# 按挂点消费,档位质变由 RelicsEngine 求值)。兑付即从 _curio_deck 摘除该令牌(选中踢池;
# 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。
func _apply_bonus_pick(kind: String, tier: String, value: int, relic_id: String = "") -> bool:
func _apply_curio_pick(kind: String, tier: String, value: int, relic_id: String = "") -> bool:
if kind == "relic" and db.relic_row(relic_id).is_empty():
push_error("[冒险结构] relic 兑付失败——表无此件: %s" % relic_id)
return false # 先校验后摘牌(令牌零丢失)
@@ -395,28 +395,28 @@ func _apply_bonus_pick(kind: String, tier: String, value: int, relic_id: String
if kind == "relic":
if not _relics.has(relic_id):
_relics.append(relic_id)
_bonus_taken["relic"] = int(_bonus_taken.get("relic", 0)) + 1
_curio_taken["relic"] = int(_curio_taken.get("relic", 0)) + 1
return true
match kind:
"attack":
bonus["atk"] = int(bonus.get("atk", 0)) + value
_bonus_taken["attack"] = int(_bonus_taken.get("attack", 0)) + 1
_curio_taken["attack"] = int(_curio_taken.get("attack", 0)) + 1
"max_hp":
bonus["max_hp"] = int(bonus.get("max_hp", 0)) + value
_bonus_taken["max_hp"] = int(_bonus_taken.get("max_hp", 0)) + 1
_curio_taken["max_hp"] = int(_curio_taken.get("max_hp", 0)) + 1
"armor":
bonus["armor_class"] = int(bonus.get("armor_class", 0)) + value
_bonus_taken["armor"] = int(_bonus_taken.get("armor", 0)) + 1
_curio_taken["armor"] = int(_curio_taken.get("armor", 0)) + 1
"ap":
bonus["ap"] = int(bonus.get("ap", 0)) + value
_bonus_taken["ap"] = int(_bonus_taken.get("ap", 0)) + 1
_curio_taken["ap"] = int(_curio_taken.get("ap", 0)) + 1
"gp":
if _runner != null:
_runner.grant_bonus_gp(value)
_bonus_taken["gp"] = int(_bonus_taken.get("gp", 0)) + 1
_curio_taken["gp"] = int(_curio_taken.get("gp", 0)) + 1
"heal":
ledger["hero_hp"] = mini(int(ledger.get("hero_hp", 0)) + value, effective_max_hp())
_bonus_taken["heal"] = int(_bonus_taken.get("heal", 0)) + 1
_curio_taken["heal"] = int(_curio_taken.get("heal", 0)) + 1
_:
return false
return true
@@ -424,13 +424,13 @@ func _apply_bonus_pick(kind: String, tier: String, value: int, relic_id: String
# 从牌库摘除一枚令牌(TP0——选中踢池单点;迭代 12 T1 relic 按 relic_id 精确摘;找不到 = false)
func _consume_deck_token(kind: String, tier: String, relic_id: String = "") -> bool:
for i in _bonus_deck.size():
var tok: Dictionary = _bonus_deck[i] as Dictionary
for i in _curio_deck.size():
var tok: Dictionary = _curio_deck[i] as Dictionary
if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier:
continue
if kind == "relic" and String(tok.get("relic_id", "")) != relic_id:
continue
_bonus_deck.remove_at(i)
_curio_deck.remove_at(i)
return true
return false
@@ -443,27 +443,27 @@ func relics_held() -> Array:
# ---- 商店共享池消费面(迭代 11 第②批 T3——P1 共享池单点账本;TP0 令牌池换代——库存=牌库剩余令牌)----
# 库存余量(TP0:stock = 牌库内该 kind 剩余令牌数——「池消耗即上限」,种类次数上限退役;
# 奖励三选一兑付摘牌 与 商店购买消费 由同一 _bonus_deck 承载——两渠道跨关共库,全冒险一次性)。
# 奖励三选一兑付摘牌 与 商店购买消费 由同一 _curio_deck 承载——两渠道跨关共库,全冒险一次性)。
func pool_stock(kind: String) -> int:
var n := 0
for t in _bonus_deck:
for t in _curio_deck:
if String((t as Dictionary).get("kind", "")) == kind:
n += 1
return n
# 商店购买消费 handler(LevelRunner.buy_pool_item 经 Callable 调用——ADR-0001 S6 方法引用):
# 消费在架令牌本体(kind+tier 精确——货架卡即令牌,TP0 令牌级货架)→ _apply_bonus_pick 同口
# 消费在架令牌本体(kind+tier 精确——货架卡即令牌,TP0 令牌级货架)→ _apply_curio_pick 同口
# 写加算区+账本+摘牌(与奖励三选一真单点——买一件=奖励面少一件的库存语义由同库成立)。
# 未知 kind 拒绝(货架词表封闭的前置防御)。
func _consume_pool_purchase(kind: String, tier: String) -> bool:
if not db.adventure.CURIO_KINDS.has(kind):
push_error("[冒险结构] 商店购买消费拒绝——件不在池词表: %s" % kind)
return false
return _apply_bonus_pick(kind, tier, int(db.adventure.curio_value(kind, tier)))
return _apply_curio_pick(kind, tier, int(db.adventure.curio_value(kind, tier)))
# 终局池快照(_settle 清账前调用——_reset_bonus_state 会重置 _bonus_taken,摘要 additive 键
# 终局池快照(_settle 清账前调用——_reset_curio_state 会重置 _curio_taken,摘要 additive 键
# pool_stock 须在此前取数;货架四种 stock=cap−taken,读数面 = 商店买走 vs 奖励领走 vs 枯竭)
func _pool_stock_snapshot() -> Dictionary:
var out := {}
@@ -474,30 +474,30 @@ func _pool_stock_snapshot() -> Dictionary:
# 道具件兑付(同调位约束):有位直接入跨关携带;满位转替换子交互(incoming 暂存、等 resolve_treasure_replace)。
# 掷定前置(追加批二):具体道具随候选抽定(_last_draw_treasure / _bonus_pending.aux_detail)——本函数消费预掷值,
# 掷定前置(追加批二):具体道具随候选抽定(_last_draw_treasure / _curio_pending.aux_detail)——本函数消费预掷值,
# 断点续玩/无预掷值时回退当场掷定(流序不同但消费数不变,确定性由调用序保证)。
# 持有容器 = 跨关携带 _bonus_treasure_carry(与节点渠道的关卡域库存分轨——容器差异保守不合,注记在案)。
func _pick_treasure_item(count: int) -> bool:
var picked: Array = _draw_pending_treasure(_bonus_pending, count, true)
var picked: Array = _draw_pending_treasure(_curio_pending, count, true)
if picked.is_empty():
push_error("[冒险结构] pick_bonus(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
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_bonus_pick("treasure_item", picked.size(), hp_before)
_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(_bonus_pending):
if not _replace_in_transit(_curio_pending):
push_error("[冒险结构] resolve_treasure_replace 拒绝——无替换在途")
return false
var incoming: Array = _bonus_pending.get("incoming", []) as Array
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():
@@ -507,15 +507,15 @@ func resolve_treasure_replace(discard_index: int) -> bool:
for iid in incoming:
_bonus_treasure_carry.append(String(iid))
# 放弃新件(discard_index < 0)= 原持有集不动、新件不获取
_finish_bonus_pick("treasure_item", incoming.size(), hp_before)
_finish_curio_pick("treasure_item", incoming.size(), hp_before)
return true
# 兑付收口(恰一次):清停点态 → 记已兑付态 → emit → 停点快照(等继续确认——中断续玩不重兑付)。
# picked = kind;tier 随载荷(TP0——大小档位读数面);relic 伴随键随载荷(迭代 12 T1——反馈行取数)
func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String = "", relic_id: String = "") -> void:
_bonus_pending = {}
_bonus_result = {
func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String = "", relic_id: String = "") -> void:
_curio_pending = {}
_curio_result = {
"picked": picked,
"tier": tier,
"value": value,
@@ -523,8 +523,8 @@ func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String
"hp_after": int(ledger.get("hero_hp", 0)),
"level_index": level_index,
}
_bonus_result.merge(_relic_payload_keys(relic_id))
bonus_resolved.emit(_bonus_result.duplicate(true))
_curio_result.merge(_relic_payload_keys(relic_id))
curio_resolved.emit(_curio_result.duplicate(true))
_snapshot_breakpoint()
@@ -533,30 +533,30 @@ func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String
# 候选呈现(节点到达时调用——恰一 reward_pick scope 洗牌 + 道具件掷定 scope;本停点断点不载,
# 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。
# 掷定前置(追加批二):返回 dict 带 aux_detail(道具件具体道具)供卡片直显。
func node_bonus_candidates() -> Dictionary:
var cands: Array = _draw_bonus_candidates()
func node_curio_candidates() -> Dictionary:
var cands: Array = _draw_curio_candidates()
return {"candidates": cands, "aux_detail": _last_draw_treasure.duplicate()}
func present_node_bonus(candidates: Array, treasure_detail: Array = []) -> void:
func present_node_curio(candidates: Array, treasure_detail: Array = []) -> void:
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
_node_bonus_pending = {"candidates": candidates.duplicate(true), "aux_detail": treasure_detail.duplicate(true)}
_node_curio_pending = {"candidates": candidates.duplicate(true), "aux_detail": treasure_detail.duplicate(true)}
# 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与通关三选一同库;relic 同权入持有集)
func apply_node_bonus(kind: String, tier: String = "", relic_id: String = "") -> bool:
if _node_bonus_pending.is_empty():
push_error("[冒险结构] apply_node_bonus 拒绝——无待选停点")
func apply_node_curio(kind: String, tier: String = "", relic_id: String = "") -> bool:
if _node_curio_pending.is_empty():
push_error("[冒险结构] apply_node_curio 拒绝——无待选停点")
return false
var candidates: Array = _node_bonus_pending.get("candidates", []) as Array
var candidates: Array = _node_curio_pending.get("candidates", []) as Array
if _find_token(candidates, kind, tier, relic_id).is_empty():
push_error("[冒险结构] apply_node_bonus 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
push_error("[冒险结构] apply_node_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_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_bonus_pick(kind, tier, value, relic_id):
push_error("[冒险结构] apply_node_bonus 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id])
if not _apply_curio_pick(kind, tier, value, relic_id):
push_error("[冒险结构] apply_node_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id])
return false
_finish_node_bonus(kind, value, tier, relic_id)
return true
@@ -564,11 +564,11 @@ func apply_node_bonus(kind: String, tier: String = "", relic_id: String = "") ->
# 节点渠道道具件(同调位约束):持有=关卡域库存+跨关携带,有位直接入本关库存(当关可用);
# 满位转替换子交互(丢弃目标=库存件 handle——与通关渠道的 carry 序号分轨,容器语义保守不合)。
# 掷定前置:消费 _node_bonus_pending.aux_detail 预掷值(缺省回退当场掷定)。
# 掷定前置:消费 _node_curio_pending.aux_detail 预掷值(缺省回退当场掷定)。
func _apply_node_treasure(count: int) -> bool:
var picked: Array = _draw_pending_treasure(_node_bonus_pending, count, false)
var picked: Array = _draw_pending_treasure(_node_curio_pending, count, false)
if picked.is_empty():
push_error("[冒险结构] apply_node_bonus(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
push_error("[冒险结构] apply_node_curio(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
return false
var records: Array = []
if _runner != null and _runner.treasure_inventory != null:
@@ -585,10 +585,10 @@ func _apply_node_treasure(count: int) -> bool:
func _resolve_node_replace(discard_handle: int) -> bool:
if not _replace_in_transit(_node_bonus_pending):
if not _replace_in_transit(_node_curio_pending):
push_error("[冒险结构] resolve_node_replace 拒绝——无替换在途")
return false
var incoming: Array = _node_bonus_pending.get("incoming", []) as Array
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)
@@ -629,40 +629,40 @@ func attune_refresh_left() -> int:
# 节点停点在途态读回(组合根重 present 用——candidates/aux_detail 值拷贝)
func node_bonus_pending_state() -> Dictionary:
func node_curio_pending_state() -> Dictionary:
return {
"candidates": (_node_bonus_pending.get("candidates", []) as Array).duplicate(true),
"aux_detail": (_node_bonus_pending.get("aux_detail", []) as Array).duplicate(true),
"candidates": (_node_curio_pending.get("candidates", []) as Array).duplicate(true),
"aux_detail": (_node_curio_pending.get("aux_detail", []) as Array).duplicate(true),
}
# 三选一刷新(组合根唯一调用口;无停点/无余量 false 零副作用)。通关停点重抽后重发
# bonus_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_bonus_pending_state 重 present。
func refresh_bonus() -> bool:
# curio_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_curio_pending_state 重 present。
func refresh_curio() -> bool:
if _attune_refresh_x <= 0 and _attune_refresh_y <= 0:
return false
if not _bonus_pending.is_empty():
if not _curio_pending.is_empty():
if _attune_refresh_x > 0:
_attune_refresh_x -= 1
else:
_attune_refresh_y -= 1
var cands: Array = _draw_bonus_candidates()
_bonus_pending["candidates"] = cands.duplicate(true)
_bonus_pending["aux_detail"] = _last_draw_treasure.duplicate()
var payload: Dictionary = (_bonus_pending.get("payload", {}) as Dictionary).duplicate(true)
var cands: Array = _draw_curio_candidates()
_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()
bonus_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途)
curio_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途)
_snapshot_breakpoint()
return true
if not _node_bonus_pending.is_empty():
if not _node_curio_pending.is_empty():
if _attune_refresh_x > 0:
_attune_refresh_x -= 1
else:
_attune_refresh_y -= 1
var cands2: Array = _draw_bonus_candidates()
_node_bonus_pending["candidates"] = cands2.duplicate(true)
_node_bonus_pending["aux_detail"] = _last_draw_treasure.duplicate()
var cands2: Array = _draw_curio_candidates()
_node_curio_pending["candidates"] = cands2.duplicate(true)
_node_curio_pending["aux_detail"] = _last_draw_treasure.duplicate()
return true
return false
@@ -694,14 +694,14 @@ func _draw_pending_treasure(pending: Dictionary, count: int, allow_detail_fallba
# 替换受理分发(单入口——节点停点在途优先按库存句柄,否则按通关停点的携带序号)
func resolve_bonus_replace(discard: int) -> bool:
if not _node_bonus_pending.is_empty() and _node_bonus_pending.has("incoming"):
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 = "", relic_id: String = "") -> void:
_node_bonus_pending = {}
_node_curio_pending = {}
var payload := {
"context": "reward_node",
"picked": picked,
@@ -710,7 +710,7 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id:
"level_index": level_index,
}
payload.merge(_relic_payload_keys(relic_id))
bonus_resolved.emit(payload)
curio_resolved.emit(payload)
# 令牌池洗牌不放回抽 3(TP0 v2.2——Fisher-Yates 全库洗牌后按序收集;**同 kind 去重每 kind 至多
@@ -719,9 +719,9 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id:
# 自动回池;池耗即枯竭——余库不足 N kind 时出齐剩余,空库 = 零候选合法态)。候选 = 令牌字典
# {"kind","tier"}。消费 reward_pick 封闭流——一次停点恰一 scope,同种子同候选;道具件掷定前置
# 随池种退役拆除(_last_draw_treasure 恒空集,机械惰性承载)。
func _draw_bonus_candidates() -> Array:
func _draw_curio_candidates() -> Array:
_last_draw_treasure = []
var pool := _bonus_deck.duplicate()
var pool := _curio_deck.duplicate()
var rg: RandomNumberGenerator = rng.open_stream("reward_pick")
for i in pool.size():
var j := rg.randi_range(i, pool.size() - 1)
@@ -832,7 +832,7 @@ func _start_current_level() -> bool:
_runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费)
if _shop_mode == "pool":
# 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动)
_runner.set_shop_pool(_bonus_deck, Callable(self, "_consume_pool_purchase"))
_runner.set_shop_pool(_curio_deck, Callable(self, "_consume_pool_purchase"))
# 三选一道具件跨关携带注入(迭代 10)——迭代 11 第①批 T7 退役:aux 卡出池后 carry 恒空集,
# 注入面随停点渠道退役拆除(容器字段保留=替换子交互机械惰性承载,零投放零行为)
level_started.emit(level_index, String(_runner.level_row.get("display_name", "")))
@@ -876,7 +876,7 @@ func _on_level_finished(payload: Dictionary) -> void:
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
var pick_payload: Dictionary = {
"level_index": level_index,
"candidates": _draw_bonus_candidates(),
"candidates": _draw_curio_candidates(),
"aux_detail": _last_draw_treasure.duplicate(), # 掷定前置(追加批二——道具件具体道具随候选抽定,卡片直显)
"heal_gained": heal,
"hp_before": hp_before,
@@ -886,10 +886,10 @@ func _on_level_finished(payload: Dictionary) -> void:
"gp": int(ledger.get("gp", 0)),
"aux_cleared": int(payload.get("aux_cleared", 0)), # additive 扩签(迭代 7 T4——关卡末清空反馈取数)
}
_bonus_pending = {"candidates": (pick_payload["candidates"] as Array).duplicate(true),
_curio_pending = {"candidates": (pick_payload["candidates"] as Array).duplicate(true),
"aux_detail": _last_draw_treasure.duplicate(),
"payload": pick_payload.duplicate(true)}
bonus_choice_presented.emit(pick_payload)
curio_choice_presented.emit(pick_payload)
_snapshot_breakpoint() # 选择停点可中断(契约 5——续玩不重抽不静默丢失)
return
# 末关通关:不发三选一与部分回血(没有下一战可受益)、Boss 钥石照常入账——冒险完成结算
@@ -920,7 +920,7 @@ func _settle(result: String) -> void:
}
if _runner != null:
_runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现)
_reset_bonus_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
_reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
if _save_manager != null:
_save_manager.clear_breakpoint() # 三结局清断点(迭代 8 卡①/T3——退出冒险=放弃流程+清除断点)
_save_manager.write_to_disk()
@@ -974,11 +974,11 @@ func _snapshot_breakpoint() -> void:
"keystone": keystone,
"visited_ids": _runner.visited_ids() if _runner != null else [],
"aux_items": treasure_snapshot,
"bonus_taken": _bonus_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
"bonus_deck": _bonus_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
"bonus_taken": _curio_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
"bonus_deck": _curio_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
"relics": _relics.duplicate(), # 特性持有集(迭代 12 T1——局内随机渠道获得态续玩防丢失)
"bonus_pending": _bonus_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约)
"bonus_result": _bonus_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
"bonus_pending": _curio_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约)
"bonus_result": _curio_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
"bonus_aux_carry": _bonus_treasure_carry.duplicate(true), # 道具件跨关携带(注入前中断防静默丢失)
"keystone_drops": _keystone_drops.duplicate(true), # 逐杀掉落明细(摘要归因续接——迭代 10)
}
+1 -1
View File
@@ -154,7 +154,7 @@ func relic_row(relic_id: String) -> Dictionary:
return relics.row_by_id(relic_id) if relics != null else {}
# 规则改写型特性带令牌构建源(池接线——adventure_runner._build_bonus_deck 消费;行 rarity→投放档映射)
# 规则改写型特性带令牌构建源(池接线——adventure_runner._build_curio_deck 消费;行 rarity→投放档映射)
func relic_rows() -> Array:
return relics.rows if relics != null else []
+4 -4
View File
@@ -69,8 +69,8 @@ var _attune_charges: Dictionary = {} # 同调主动件充能账本(
# 关卡末清空+快照计数入 level_finished 载荷)
# ---- 商店共享池模式态(迭代 11 第②批 T3——缺省 legacy=固定三件加定价 off 对照承载,零 RNG 零池消费;TP0 令牌池换代)----
var _shop_pool_mode := false # pool 模式旗(set_shop_pool 注入时置位——AdventureRunner 按 params.shop_mode 下传)
var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._bonus_deck——库存=剩余令牌数单点)
var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_bonus_pick 同口写加算区+账本+摘牌)
var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._curio_deck——库存=剩余令牌数单点)
var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_curio_pick 同口写加算区+账本+摘牌)
var _shop_shelf: Array = [] # 当前货架(进店抽 3/买后全重抽——令牌字典 {"kind","tier"} 序,同 kind 去重每 kind 至多一卡)
var _shop_visit_buys := 0 # 本次进店已购次数(A 链定价输入——离开清零)
var _shop_paid_refreshes := 0 # 本次进店已付费刷新次数(MC-1——GP 刷新价曲线输入,离开清零)
@@ -640,7 +640,7 @@ func grant_treasure_item(item_id: String, source: String) -> Dictionary:
# wallet_changed 载荷带 source 供呈现归因)
func grant_bonus_gp(amount: int) -> void:
gp = maxi(0, gp + amount)
wallet_changed.emit("gain", gp, {"amount": amount, "source": "bonus_pick"})
wallet_changed.emit("gain", gp, {"amount": amount, "source": "curio_pick"})
# ---- 玩家输入转发(呈现层只与 runner 对话)----
@@ -877,7 +877,7 @@ func shop_refresh() -> bool:
# 购买(pool 模式唯一购买口):在架(kind→在架令牌)/余额前置校验零副作用 → handler 消费在架
# 令牌本体(kind+tier——_apply_bonus_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买
# 令牌本体(kind+tier——_apply_curio_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买
# 次计数 → wallet 事件 → 三件全重抽(未买不保留——重抽绑购买,无独立刷新动作)
func buy_pool_item(kind: String) -> bool:
var shelf_tok := {}