奇物批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

+53 -53
View File
@@ -327,12 +327,12 @@ func _smoke_adventure() -> void:
var resolves: Array = []
var node_picks := 0
for ev in (st["reward_events"] as Array):
if (ev as Dictionary).has("bonus_pick"):
picks.append((ev as Dictionary)["bonus_pick"])
elif (ev as Dictionary).has("node_bonus_pick"):
if (ev as Dictionary).has("curio_pick"):
picks.append((ev as Dictionary)["curio_pick"])
elif (ev as Dictionary).has("node_curio_pick"):
node_picks += 1 # 奖励节点三选一(追加批——每关奖励节点各 1 次)
elif (ev as Dictionary).has("bonus_resolved"):
resolves.append((ev as Dictionary)["bonus_resolved"])
elif (ev as Dictionary).has("curio_resolved"):
resolves.append((ev as Dictionary)["curio_resolved"])
check(picks.size() == 2 and resolves.size() == 2 + node_picks,
"通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)")
check(node_picks >= 1, "奖励节点三选一 ≥1 次(L1 链必经 1 个;L2/L3 图可选路经若干——实际经停 %d)" % node_picks)
@@ -467,7 +467,7 @@ func _smoke_adventure() -> void:
for ev in wallet:
if String(ev["event"]) == "clear":
clears += 1
elif String(ev["event"]) == "gain" and String(ev.get("source", "")) != "bonus_pick":
elif String(ev["event"]) == "gain" and String(ev.get("source", "")) != "curio_pick":
gains += 1 # 战斗完成入账计数(三选一 GP 袋件入账另有 source 标记——不计战斗口径)
check(gains == int(st["battles"]), "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" % [gains, int(st["battles"])])
check(clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear",
@@ -505,13 +505,13 @@ func _smoke_adventure() -> void:
check(acc_shop >= 3, "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop)
a8._on_level_finished({"result": "victory", "hp": int(r8.hero_hp), "node_index": r8.node_index,
"node_id": r8.current_node_id(), "node_type": "shop"}) # 模拟 L2 通关(victory 载荷直调——非末关)
check(not a8._bonus_pending.is_empty(), "L2 通关三选一停点在案(直调兑付前——零自动推进)")
check(not a8._curio_pending.is_empty(), "L2 通关三选一停点在案(直调兑付前——零自动推进)")
check(not a8.continue_to_next_level(), "未兑付推进拒绝(选择停点零自动推进契约)")
var l2_cands: Array = a8._bonus_pending["candidates"]
var l2_cands: Array = a8._curio_pending["candidates"]
var l2_pick: Dictionary = l2_cands[0]
check(a8.pick_bonus(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", ""))), "三选一点选兑付受理(候选取首件)")
var picked2: String = String(a8._bonus_result.get("picked", ""))
var expect_acc: int = acc_shop + (int(a8._bonus_result.get("value", 0)) if picked2 == "max_hp" else 0)
check(a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", ""))), "三选一点选兑付受理(候选取首件)")
var picked2: String = String(a8._curio_result.get("picked", ""))
var expect_acc: int = acc_shop + (int(a8._curio_result.get("value", 0)) if picked2 == "max_hp" else 0)
check(int(a8.bonus.get("max_hp", -1)) == expect_acc,
"三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [int(a8.bonus.get("max_hp", -1)), acc_shop, picked2])
var max_eff8: int = a8.effective_max_hp() # T17 口径修复随批:账本生效上限含调试浮窗 pct(a8 已 set 400)
@@ -545,11 +545,11 @@ func _smoke_adventure() -> void:
var a11 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(11))
check(a11.start_adventure(11, {"first_script": true, "treasure_unlocked": {"blast_kit": true}}),
"剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 blast_kit)")
for di in range(a11._bonus_deck.size() - 1, -1, -1):
var dk := String((a11._bonus_deck[di] as Dictionary).get("kind", ""))
for di in range(a11._curio_deck.size() - 1, -1, -1):
var dk := String((a11._curio_deck[di] as Dictionary).get("kind", ""))
if dk == "attack" or dk == "ap":
a11._bonus_deck.remove_at(di)
var draw: Array = a11._draw_bonus_candidates()
a11._curio_deck.remove_at(di)
var draw: Array = a11._draw_curio_candidates()
var draw_kinds := {}
for t in draw:
draw_kinds[String((t as Dictionary).get("kind", ""))] = true
@@ -560,7 +560,7 @@ func _smoke_adventure() -> void:
# white-box 测试随停点渠道退役拆除——机械惰性承载,零投放零行为)
var treasure_seen := false
for i in 40:
for t in a11._draw_bonus_candidates():
for t in a11._draw_curio_candidates():
if String((t as Dictionary).get("kind", "")) == "treasure_item":
treasure_seen = true
break
@@ -569,7 +569,7 @@ func _smoke_adventure() -> void:
var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12))
a12.start_adventure(12, {"first_script": true})
var cap := {"payload": {}}
a12.bonus_choice_presented.connect(func(p): cap["payload"] = p)
a12.curio_choice_presented.connect(func(p): cap["payload"] = p)
var max_full := int(_db.hero.max_hp) + int(a12._base_override.get("max_hp", 0))
a12.ledger["hero_hp"] = max_full
a12._on_level_finished({"result": "victory", "hp": max_full, "node_index": 1, "node_id": 1, "node_type": "shop"})
@@ -579,10 +579,10 @@ func _smoke_adventure() -> void:
var a13 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(13))
check(a13.start_adventure(13, {"first_script": true, "treasure_unlocked": {"blast_kit": true}}),
"节点共享池场景:冒险启动(seed 13)")
for di13 in range(a13._bonus_deck.size() - 1, -1, -1):
if String((a13._bonus_deck[di13] as Dictionary).get("kind", "")) == "attack":
a13._bonus_deck.remove_at(di13)
var ndraw: Dictionary = a13.node_bonus_candidates()
for di13 in range(a13._curio_deck.size() - 1, -1, -1):
if String((a13._curio_deck[di13] as Dictionary).get("kind", "")) == "attack":
a13._curio_deck.remove_at(di13)
var ndraw: Dictionary = a13.node_curio_candidates()
var ncands13: Array = ndraw.get("candidates", []) as Array
var has_attack13 := false
for t in ncands13:
@@ -591,12 +591,12 @@ func _smoke_adventure() -> void:
break
check(not has_attack13,
"奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" % str(ncands13))
a13.present_node_bonus(ncands13, ndraw.get("aux_detail", []) as Array)
a13.present_node_bonus([{"kind": "gp", "tier": "common"}])
a13.present_node_curio(ncands13, ndraw.get("aux_detail", []) as Array)
a13.present_node_curio([{"kind": "gp", "tier": "common"}])
var gp13 := int(a13.ledger.get("gp", 0))
check(a13.apply_node_bonus("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")),
check(a13.apply_node_curio("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")),
"奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_value("gp", "common")))
check(a13._node_bonus_pending.is_empty(), "节点停点态兑付后清零(恰一次语义同构)")
check(a13._node_curio_pending.is_empty(), "节点停点态兑付后清零(恰一次语义同构)")
var s10: Dictionary = a10.summary
check(String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)),
"放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)")
@@ -612,12 +612,12 @@ func _smoke_relics() -> void:
check(a_r0.start_adventure(71, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 71)")
var relic_tokens := 0
var rarest_tokens := 0
for t in a_r0._bonus_deck:
for t in a_r0._curio_deck:
if String((t as Dictionary).get("kind", "")) == "relic":
relic_tokens += 1
if String((t as Dictionary).get("tier", "")) == "rarest":
rarest_tokens += 1
check(relic_tokens == 27 and rarest_tokens == 6 and a_r0._bonus_deck.size() == 125,
check(relic_tokens == 27 and rarest_tokens == 6 and a_r0._curio_deck.size() == 125,
"规则改写型特性带入池:27 件(rare 档 21 + rarest 锚点 6)——牌库 98+27=125")
check(_db.relics.validate().is_empty(), "relic_table.validate 零违例(有行流派扫雷端铁律——magic first_dig_spell 在案)")
# ② 档位激活引擎(纯函数直测——构造表 4 件 magic + 1 件 flag + 1 件 rarest 锚点)
@@ -646,23 +646,23 @@ func _smoke_relics() -> void:
var a_r = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(72))
check(a_r.start_adventure(72, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 72——注入 5 行临时表)")
var deck_relic := 0
for t in a_r._bonus_deck:
for t in a_r._curio_deck:
if String((t as Dictionary).get("kind", "")) == "relic":
deck_relic += 1
check(deck_relic == 5 and a_r._bonus_deck.size() == 103,
check(deck_relic == 5 and a_r._curio_deck.size() == 103,
"规则改写型特性带入池:5 行 relic 每件 1 张(common/rare→稀有档 rarest→罕见档)——牌库 98+5=103")
var relic_seen: Dictionary = {}
for i in 60:
for t in a_r._draw_bonus_candidates():
for t in a_r._draw_curio_candidates():
if String((t as Dictionary).get("kind", "")) == "relic":
relic_seen[String(t.get("relic_id", ""))] = true
check(relic_seen.size() >= 2, "抽签出 relic 候选(60 次重抽 ≥2 种不同 relic——身份键 relic_id 同档可多件)")
a_r._bonus_pending = {"candidates": [{"kind": "relic", "tier": "rare", "relic_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "relic_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
var deck_m3 := 0
for t in a_r._bonus_deck:
for t in a_r._curio_deck:
if String((t as Dictionary).get("relic_id", "")) == "m3":
deck_m3 += 1
check(a_r.pick_bonus("relic", "rare", "m3") and (a_r.relics_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1,
check(a_r.pick_curio("relic", "rare", "m3") and (a_r.relics_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1,
"relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1])
# 断点往返(持有集+余库 relic_id 随行)
var sm_r = load("res://core/save_manager.gd").new("user://_smoke_relics_bp.json")
@@ -771,10 +771,10 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
return false
if a._pending_next_level:
# 三选一停点确认(迭代 10——确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化)
if not a._bonus_pending.is_empty():
var bcands: Array = a._bonus_pending["candidates"]
if not a._curio_pending.is_empty():
var bcands: Array = a._curio_pending["candidates"]
var bpick: Dictionary = autoplay._pick_token(bcands)
a.pick_bonus(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("relic_id", "")))
a.pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("relic_id", "")))
a.continue_to_next_level()
continue
var r = a.runner()
@@ -788,14 +788,14 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
var t := String(r.current_node().get("type", ""))
if t == "reward":
# 奖励节点三选一(迭代 10 追加批——确定性选件、替换放弃新件、兑付确认回图;TP0 令牌化)
var ndraw: Dictionary = a.node_bonus_candidates()
var ndraw: Dictionary = a.node_curio_candidates()
var ncands: Array = ndraw.get("candidates", []) as Array
if not ncands.is_empty():
a.present_node_bonus(ncands, ndraw.get("aux_detail", []) as Array)
a.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array)
var npick: Dictionary = autoplay._pick_token(ncands)
if a.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))):
if not a._node_bonus_pending.is_empty() and a._node_bonus_pending.has("incoming"):
a.resolve_bonus_replace(-1)
if a.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))):
if not a._node_curio_pending.is_empty() and a._node_curio_pending.has("incoming"):
a.resolve_curio_replace(-1)
r.continue_after_node()
elif t == "rest":
r.continue_after_node()
@@ -3871,13 +3871,13 @@ func _smoke_shop_pool() -> void:
var tier := String(buy_c.get("tier", ""))
var val_map := {"attack": "atk", "max_hp": "max_hp", "armor": "armor_class", "ap": "ap"}
var gp0: int = int(r8.gp)
var taken0: int = int(a8._bonus_taken.get(kind, 0))
var taken0: int = int(a8._curio_taken.get(kind, 0))
var val0: int = int(a8.bonus.get(String(val_map[kind]), -999))
check(r8.buy_pool_item(kind), "购买受理(pool 口——%s|%s)" % [kind, tier])
check(int(r8.gp) == gp0 - int(buy_c.get("price", 0)), "钱包扣现价(%d → %d)" % [gp0, int(r8.gp)])
check(int(a8.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)),
"加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_value(kind, tier))])
check(int(a8._bonus_taken.get(kind, 0)) == taken0 + 1, "共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._bonus_taken.get(kind, 0))])
check(int(a8._curio_taken.get(kind, 0)) == taken0 + 1, "共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._curio_taken.get(kind, 0))])
# A 链:同visit 第二购现价 = ceil(base×b^(l2−1)×A^1)(货架已全重抽——新卡新价同公式)
var second: Dictionary = {}
for c in r8.shop_shelf():
@@ -3899,9 +3899,9 @@ func _smoke_shop_pool() -> void:
cut_seen = true
check(not cut_seen, "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)")
# ⑤ 池空硬闸(库存尽剔出牌库——TP0:white-box 耗尽 attack 令牌后货架零 attack + 消费口拒绝)
for di8 in range(a8._bonus_deck.size() - 1, -1, -1):
if String((a8._bonus_deck[di8] as Dictionary).get("kind", "")) == "attack":
a8._bonus_deck.remove_at(di8)
for di8 in range(a8._curio_deck.size() - 1, -1, -1):
if String((a8._curio_deck[di8] as Dictionary).get("kind", "")) == "attack":
a8._curio_deck.remove_at(di8)
var attack_seen := false
for i in 40:
r8._shop_draw_shelf()
@@ -3913,10 +3913,10 @@ func _smoke_shop_pool() -> void:
check(not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before,
"池空消费拒绝(handler 零副作用——买光即枯竭硬闸)")
# ⑥ 跨渠道共库 shop→reward 向:回填一枚 attack 令牌 → 消费口受理 → 奖励牌库同步摘牌
a8._bonus_deck.append({"kind": "attack", "tier": "common"})
check(a8._consume_pool_purchase("attack", "common"), "商店消费口受理(_apply_bonus_pick 同口——真单点库)")
a8._curio_deck.append({"kind": "attack", "tier": "common"})
check(a8._consume_pool_purchase("attack", "common"), "商店消费口受理(_apply_curio_pick 同口——真单点库)")
var reward_attack_seen := false
for t6 in a8._draw_bonus_candidates():
for t6 in a8._draw_curio_candidates():
if String((t6 as Dictionary).get("kind", "")) == "attack":
reward_attack_seen = true
break
@@ -4265,11 +4265,11 @@ func _meta_settle_and_workshop() -> void:
a12._attunement = ["pick_refresh"]
a12._attune_refresh_x = 1
a12._attune_refresh_y = 2
a12._bonus_pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}}
check(a12.refresh_bonus() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2,
a12._curio_pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}}
check(a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2,
"刷新双计数器——X 优先消耗(x1y2 → x0y2)")
check(a12.refresh_bonus() and a12.refresh_bonus() and a12._attune_refresh_y == 0
and a12.refresh_bonus() == false,
check(a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0
and a12.refresh_curio() == false,
"刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)")
a12._reset_refresh_x()
check(a12.attune_refresh_left() == 1,