主卡T5(拆件批wr7): 转发属性25条撤除+测量面改址收口——adv 4条(_curio_taken/_curio_deck/_feat_ticket_pity/_feat_ticket_sold直指件成员改写后删)、level 7条白盒属性删(内部直指_shop)、board 13条删(内部消费约30处改直引_palette/_hero_sprite件成员);A类直读47处+ui_input_probe 6处改指件;C类方法面调用全仓改adventure.stop().X(委托壳退役——契约5纯转传清偿,adv删pick_curio/apply_node_curio/skip_empty/refresh族/pool_stock/凭证委托11个,ctx组包改直引_curio.deck/feat_ticket_consider/cap_ok);新件压默认阈值(hero_ledger删_gain_seq序派生15变量合规;curio_stop删_attunement改_refresh_x_cap预算注入——装配集冒险期间锁死契约8等价,start/resume双路注入);battle_orchestrator压行还债(begin_battle局部变量归位);验证集清单重生成(66源文件/1596调用点,标签cond计数三对齐零差异)。S13读数:三宿主全部改善——adventure_runner类变量42→23(-19)/公开方法23→≤20归零、level_runner 38→28(-10,达标约30)/54→50、board_view 45→32(-13,精确达标);存量红面=新件5条NEW(接口块公开面29/31方法+迁移函数既有超标形态,原基线adventure_runner同函数同值登记)+恶化4条(battle拆件接线3条+level_map_screen 1条iter38存量漏登记——本批零触碰),收口前呈报用户裁决〔拆件批 V7〕

This commit is contained in:
DKChan committed 2026-10-27 04:14:10 +08:00
1 parent fa1409f491
commit 659d59edd3
19 files changed
+431 -552

No files matched your search

+29 -29
View File
@@ -183,7 +183,7 @@ func _smoke_adventure() -> void:
rw_pc = (rw.get("cands", []) as Array)[0] as Dictionary
var rw_pick_ok := false
if not rw_pc.is_empty() and rw.get("adr") != null:
rw_pick_ok = rw["adr"].apply_node_curio(String(rw_pc.get("kind", "")), String(rw_pc.get("tier", "")), String(rw_pc.get("feat_id", "")))
rw_pick_ok = (rw["adr"] as AdventureRunner).stop().apply_node_curio(String(rw_pc.get("kind", "")), String(rw_pc.get("tier", "")), String(rw_pc.get("feat_id", "")))
Kit.check("奖励三选一点选兑付受理(件=%s)" % String(rw_pc.get("kind", "")), rw_pick_ok)
var enc_run: Dictionary = _drive_to_pref(autoplay, "battle")
var enc_probe: Dictionary = enc_run.get("enc", {}) as Dictionary
@@ -361,7 +361,7 @@ func _smoke_adventure() -> void:
Kit.check("未兑付推进拒绝(选择停点零自动推进契约)", not a8.continue_to_next_level())
var l2_cands: Array = a8.stop()._pending["candidates"]
var l2_pick: Dictionary = l2_cands[0]
Kit.check("三选一点选兑付受理(候选取首件)", a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))))
Kit.check("三选一点选兑付受理(候选取首件)", a8.stop().pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))))
var picked2: String = String(a8.stop()._resolved.get("picked", ""))
var expect_acc: int = acc_shop + (int(a8.stop()._resolved.get("value", 0)) if picked2 == "max_hp" else 0)
Kit.check("三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [a8.hero().bonus_total("max_hp"), acc_shop, picked2], a8.hero().bonus_total("max_hp") == expect_acc)
@@ -392,10 +392,10 @@ func _smoke_adventure() -> void:
# 无种类次数上限,「达上限剔除」语义由牌库耗尽承载)
var a11 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(11))
Kit.check("剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 wide_scout_band)", a11.start_adventure(11, {"treasure_unlocked": {"wide_scout_band": true}}))
for di in range(a11._curio_deck.size() - 1, -1, -1):
var dk := String((a11._curio_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._curio_deck.remove_at(di)
a11._curio.deck.remove_at(di)
var draw: Array = a11.stop().draw_candidates()
var draw_kinds := {}
for t in draw:
@@ -423,10 +423,10 @@ func _smoke_adventure() -> void:
# ⑬ 奖励节点共享池(同库同池同摘牌;TP0:attack 令牌耗尽后节点域同样零出现)
var a13 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(13))
Kit.check("节点共享池场景:冒险启动(seed 13)", a13.start_adventure(13, {"treasure_unlocked": {"wide_scout_band": true}}))
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()
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.stop().node_curio_candidates()
var ncands13: Array = ndraw.get("candidates", []) as Array
var has_attack13 := false
for t in ncands13:
@@ -434,10 +434,10 @@ func _smoke_adventure() -> void:
has_attack13 = true
break
Kit.check("奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" % str(ncands13), not has_attack13)
a13.present_node_curio(ncands13)
a13.present_node_curio([{"kind": "gp", "tier": "common"}])
a13.stop().present_node_curio(ncands13)
a13.stop().present_node_curio([{"kind": "gp", "tier": "common"}])
var gp13: int = a13.hero().gp
Kit.check("奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_value("gp", "common")), a13.apply_node_curio("gp", "common") and a13.hero().gp == gp13 + int(_db.adventure.curio_value("gp", "common")))
Kit.check("奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_value("gp", "common")), a13.stop().apply_node_curio("gp", "common") and a13.hero().gp == gp13 + int(_db.adventure.curio_value("gp", "common")))
Kit.check("节点停点态兑付后清零(恰一次语义同构)", a13.stop()._node_pending.is_empty())
var s10: Dictionary = a10.summary
Kit.check("放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)", String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)))
@@ -457,18 +457,18 @@ func _smoke_feats() -> void:
Kit.check("专长带场景:冒险启动(seed 71)", a_r0.start_adventure(71))
var feat_tokens := 0
var tier_counts := {}
for t in a_r0._curio_deck:
for t in a_r0._curio.deck:
if String((t as Dictionary).get("kind", "")) == "relic":
feat_tokens += 1
var tk := String((t as Dictionary).get("tier", ""))
tier_counts[tk] = int(tier_counts.get(tk, 0)) + 1
Kit.check("专长带入池:30 件四级直映射(普通 12/优秀 9/稀有 9/传说 0)——牌库 102+30=132〔专长批 V2+卡② 完美保险迁入〕", feat_tokens == 30 and int(tier_counts.get("common", 0)) == 12 and int(tier_counts.get("excellent", 0)) == 9 and int(tier_counts.get("rare", 0)) == 9 and a_r0._curio_deck.size() == 132)
Kit.check("专长带入池:30 件四级直映射(普通 12/优秀 9/稀有 9/传说 0)——牌库 102+30=132〔专长批 V2+卡② 完美保险迁入〕", feat_tokens == 30 and int(tier_counts.get("common", 0)) == 12 and int(tier_counts.get("excellent", 0)) == 9 and int(tier_counts.get("rare", 0)) == 9 and a_r0._curio.deck.size() == 132)
# ② 旧 27 件零残留钉面(V2——arcane_ember 系旧件表首行;全仓 grep 零残留归静态核查)
Kit.check("旧 27 件已退役:arcane_ember/magma_body 表内零残留〔专长批 V2〕", _db.feats.row_by_id("arcane_ember").is_empty() and _db.feats.row_by_id("magma_body").is_empty())
Kit.check("迁出收尾:treasure_items 无 venom_edge/seal_burst 行(同调目录自然收窄)〔专长批 V13〕", _db.treasure_items.row_by_id("venom_edge").is_empty() and _db.treasure_items.row_by_id("seal_burst").is_empty())
# ③ 契约面静态钉扎(V18/V19/V21/V22——人工核对 V20/V21 裁决面以外的可机检半)
var kinds_ok := true
for t in a_r0._curio_deck:
for t in a_r0._curio.deck:
var kk := String((t as Dictionary).get("kind", ""))
if kk != "relic" and not _db.adventure.CURIO_KINDS.has(kk):
kinds_ok = false
@@ -510,10 +510,10 @@ func _smoke_feats() -> void:
var a_r = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(72))
Kit.check("专长带场景:冒险启动(seed 72——注入 5 行临时表)", a_r.start_adventure(72))
var deck_relic := 0
for t in a_r._curio_deck:
for t in a_r._curio.deck:
if String((t as Dictionary).get("kind", "")) == "relic":
deck_relic += 1
Kit.check("专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107", deck_relic == 5 and a_r._curio_deck.size() == 107)
Kit.check("专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107", deck_relic == 5 and a_r._curio.deck.size() == 107)
# 渠道白名单断言(渠道轴定位:关卡完成渠道只出专长/节点渠道零专长)
var feat_seen: Dictionary = {}
var node_relic_seen := false
@@ -531,19 +531,19 @@ func _smoke_feats() -> void:
a_t.start_adventure(91)
var stock_before := {}
for kk in ["attack", "gp", "heal"]:
stock_before[kk] = a_t.pool_stock(kk)
stock_before[kk] = a_t.stop().pool_stock(kk)
a_t.stop()._node_pending = {"candidates": [{"kind": "feat_ticket", "tier": "excellent"}, {"kind": "attack", "tier": "common"}]}
Kit.check("A5 专长凭证兑付受理(节点渠道——拿到即开)", a_t.apply_node_curio("feat_ticket", "excellent"))
var tkt_cands: Array = (a_t.node_curio_pending_state().get("candidates", []) as Array)
Kit.check("A5 专长凭证兑付受理(节点渠道——拿到即开)", a_t.stop().apply_node_curio("feat_ticket", "excellent"))
var tkt_cands: Array = (a_t.stop().node_curio_pending_state().get("candidates", []) as Array)
var tkt_all_relic := tkt_cands.size() > 0
for t in tkt_cands:
if String((t as Dictionary).get("kind", "")) != "relic":
tkt_all_relic = false
Kit.check("A5 拿到即开——停点候选已替换为专长三选一(全 relic)", tkt_all_relic)
Kit.check("A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)", a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]))
Kit.check("A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)", a_t.stop().pool_stock("attack") == int(stock_before["attack"]) and a_t.stop().pool_stock("gp") == int(stock_before["gp"]))
Kit.check("A5 无专长凭证残留持有集", not (a_t.feats_held() as Array).has("feat_ticket"))
var feats_n := (a_t.feats_held() as Array).size()
a_t.apply_node_curio("relic", String((tkt_cands[0] as Dictionary).get("tier", "rare")), String((tkt_cands[0] as Dictionary).get("feat_id", "")))
a_t.stop().apply_node_curio("relic", String((tkt_cands[0] as Dictionary).get("tier", "rare")), String((tkt_cands[0] as Dictionary).get("feat_id", "")))
Kit.check("A5 专长面兑付受理(专长凭证开出候选可正常入持有集)", (a_t.feats_held() as Array).size() == feats_n + 1)
# A6 保底:pity 直填 5 → 下次展示必出(第 6 次必出口径);限购满 → 恒不触发
a_t.stop()._deck.ticket_sold = 0 # A5 已兑付一枚专长凭证(sold=1=limit)——A6 段重置计数隔离验证
@@ -563,7 +563,7 @@ func _smoke_feats() -> void:
break
Kit.check("A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)", offered)
a_r.stop()._pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "x3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
Kit.check("relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1], a_r.pick_curio("relic", "rare", "x3") and (a_r.feats_held() as Array).has("x3") and a_r.pool_stock("relic") == deck_relic - 1)
Kit.check("relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1], a_r.stop().pick_curio("relic", "rare", "x3") and (a_r.feats_held() as Array).has("x3") and a_r.stop().pool_stock("relic") == deck_relic - 1)
# 断点往返三条(基点 1420–1426:relics 快照/计数器键/余库逐枚还原)按主会话裁决②改判集成宿主——
# 驱动代码(save_manager 装配+_on_level_finished+快照取读)随三条同去,集成批落位时重建。
_db.feats = live_rows # 还原现行表(共享 _db 防污染——后续冒烟段零感知)
@@ -782,7 +782,7 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
if not a.stop()._pending.is_empty():
var bcands: Array = a.stop()._pending["candidates"]
var bpick: Dictionary = autoplay._pick_token(bcands)
a.pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("feat_id", "")))
a.stop().pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("feat_id", "")))
a.continue_to_next_level()
continue
var r = a.runner()
@@ -796,12 +796,12 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
var t := String(r.current_node().get("type", ""))
if t == "reward":
# 奖励节点三选一(确定性选件、替换放弃新件、兑付确认回图;TP0 令牌化)
var ndraw: Dictionary = a.node_curio_candidates()
var ndraw: Dictionary = a.stop().node_curio_candidates()
var ncands: Array = ndraw.get("candidates", []) as Array
if not ncands.is_empty():
a.present_node_curio(ncands)
a.stop().present_node_curio(ncands)
var npick: Dictionary = autoplay._pick_token(ncands)
a.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", "")))
a.stop().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()
@@ -841,8 +841,8 @@ func _drive_to_pref(autoplay, prefer: String) -> Dictionary:
if prefer == "battle":
out["enc"] = r.current_battle().encounter
else:
var cands: Array = (adr.node_curio_candidates() as Dictionary).get("candidates", []) as Array
adr.present_node_curio(cands)
var cands: Array = (adr.stop().node_curio_candidates() as Dictionary).get("candidates", []) as Array
adr.stop().present_node_curio(cands)
out["cands"] = cands
out["pending"] = not (adr.stop()._node_pending as Dictionary).is_empty()
return out
@@ -93,7 +93,7 @@ func _init() -> void:
adr_e.set_save_manager(sm_e)
var params_e := {"hero_override": {}, "treasure_unlocked": {"wide_scout_band": true}}
Kit.check_quiet(adr_e.start_adventure(4321, params_e), "P5 冒险启动(seed 4321)")
adr_e._curio_taken["attack"] = 1 # 白盒预置计数(断点附随件往返的判据面——计数不可由加算区反推)
adr_e._curio.taken["attack"] = 1 # 白盒预置计数(断点附随件往返的判据面——计数不可由加算区反推)
adr_e._on_level_finished({"result": "victory", "hp": 30, "node_index": 1, "node_id": 1, "node_type": "shop"})
var cands_e: Array = (adr_e.stop()._pending.get("candidates", []) as Array).duplicate()
Kit.check_quiet(not cands_e.is_empty(), "P5 非末关的关卡完成三选一停点在案(直调 victory——L1 非末关)")
@@ -110,13 +110,13 @@ func _init() -> void:
Kit.check_quiet(adr_f.resume_adventure(bp_f, params_e), "P5 续玩受理(选择态分支)")
Kit.check_quiet(adr_f.runner() == null, "P5 续玩不重跑关卡(三选一停点直回,_start_current_level 未走)")
Kit.check_quiet(got_cands["cands"] == cands_e, "P5 续玩候选不重抽(逐位一致)")
Kit.check_quiet(int(adr_f._curio_taken["attack"]) == 1, "P5 次数计数恢复一致(牌库剔除态续接)")
Kit.check_quiet(int(adr_f._curio.taken["attack"]) == 1, "P5 次数计数恢复一致(牌库剔除态续接)")
Kit.check_quiet(not adr_f.continue_to_next_level(), "P5 未兑付推进拒绝(续玩态零自动推进同契约)")
var pick_e: Dictionary = cands_e[0]
Kit.check_quiet(adr_f.pick_curio(String(pick_e.get("kind", "")), String(pick_e.get("tier", "")), String(pick_e.get("feat_id", ""))), "P5 点选兑付受理")
Kit.check_quiet(adr_f.stop().pick_curio(String(pick_e.get("kind", "")), String(pick_e.get("tier", "")), String(pick_e.get("feat_id", ""))), "P5 点选兑付受理")
var picked_e: String = String(adr_f.stop()._resolved.get("picked", ""))
if adr_f._curio_taken.has(picked_e):
Kit.check_quiet(int(adr_f._curio_taken[picked_e]) == (2 if picked_e == "attack" else 1),
if adr_f._curio.taken.has(picked_e):
Kit.check_quiet(int(adr_f._curio.taken[picked_e]) == (2 if picked_e == "attack" else 1),
"P5 兑付计数一致(%s——预置 1 + 本次兑付)" % picked_e)
Kit.check_quiet(sm_f.has_breakpoint(), "P5 兑付后停点快照在档(已兑付待推进态可中断)")
var bp_g: Dictionary = sm_f.breakpoint_snapshot()
+14 -14
View File
@@ -20,30 +20,30 @@ func _init() -> void:
# ---- ① 停点在途谓词:无停点时免费/付费刷新皆拒绝(付费路径扣费前置校验防空转烧钱包)----
var adr = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
Kit.check_quiet(adr.start_adventure(1234, {"hero_override": {}, "treasure_unlocked": {}}), "冒险启动(种子 1234——刷新重抽需真实令牌池)")
Kit.check_quiet(adr.curio_stop_pending() == false, "无停点=在途谓词 false")
Kit.check_quiet(adr.refresh_curio() == false and adr.refresh_curio_paid() == false, "无停点:免费/付费刷新皆拒绝零副作用")
Kit.check_quiet(adr.stop().curio_stop_pending() == false, "无停点=在途谓词 false")
Kit.check_quiet(adr.stop().refresh_curio() == false and adr.stop().refresh_curio_paid() == false, "无停点:免费/付费刷新皆拒绝零副作用")
# ---- ② 免费位优先序 X→Y(白盒造关卡完成停点——smoke_all ⑫ 同构)----
adr.stop()._pending = {"candidates": _cands(), "payload": {}}
Kit.check_quiet(adr.curio_stop_pending() == true, "关卡完成停点在途=谓词 true")
Kit.check_quiet(adr.stop().curio_stop_pending() == true, "关卡完成停点在途=谓词 true")
adr.stop()._refresh_x = 1
adr.stop()._refresh_y = 2
Kit.check_quiet(adr.refresh_curio() and adr.stop()._refresh_x == 0 and adr.stop()._refresh_y == 2, "X 优先消耗(x1y2→x0y2)")
Kit.check_quiet(adr.refresh_curio() and adr.stop()._refresh_y == 1, "X 穷转 Y 池(y2→y1)")
Kit.check_quiet(adr.stop().refresh_curio() and adr.stop()._refresh_x == 0 and adr.stop()._refresh_y == 2, "X 优先消耗(x1y2→x0y2)")
Kit.check_quiet(adr.stop().refresh_curio() and adr.stop()._refresh_y == 1, "X 穷转 Y 池(y2→y1)")
# ---- ③ GP 付费档:计数递增爬价 + 零触碰免费位 ----
Kit.check_quiet(adr.curio_refresh_gp_cost() == base, "首刷报价=基价(免费位尚余 y1,报价独立于免费位)")
Kit.check_quiet(adr.refresh_curio_paid() and adr.stop()._refresh_y == 1, "付费刷新不触碰免费位(y1 保持)")
Kit.check_quiet(adr.curio_refresh_gp_cost() == int(ceil(float(base) * curve)), "二刷价爬升=ceil(基价×曲线)(已付费刷=1)")
Kit.check_quiet(adr.refresh_curio_paid() and adr.curio_refresh_gp_cost() == int(ceil(float(base) * curve * curve)), "三刷价再爬升=ceil(基价×曲线²)(已付费刷=2)")
Kit.check_quiet(adr.stop().curio_refresh_gp_cost() == base, "首刷报价=基价(免费位尚余 y1,报价独立于免费位)")
Kit.check_quiet(adr.stop().refresh_curio_paid() and adr.stop()._refresh_y == 1, "付费刷新不触碰免费位(y1 保持)")
Kit.check_quiet(adr.stop().curio_refresh_gp_cost() == int(ceil(float(base) * curve)), "二刷价爬升=ceil(基价×曲线)(已付费刷=1)")
Kit.check_quiet(adr.stop().refresh_curio_paid() and adr.stop().curio_refresh_gp_cost() == int(ceil(float(base) * curve * curve)), "三刷价再爬升=ceil(基价×曲线²)(已付费刷=2)")
# ---- ④ 停点重建清零付费计数(对齐商店进店清零——不结转)----
adr.present_node_curio(_cands())
Kit.check_quiet(adr.stop()._paid_refreshes == 0 and adr.curio_refresh_gp_cost() == base, "节点停点重建=付费计数清零回基价")
adr.refresh_curio_paid()
adr.stop().present_node_curio(_cands())
Kit.check_quiet(adr.stop()._paid_refreshes == 0 and adr.stop().curio_refresh_gp_cost() == base, "节点停点重建=付费计数清零回基价")
adr.stop().refresh_curio_paid()
Kit.check_quiet(adr.stop()._paid_refreshes == 1, "付费刷新计数在途递增(=1)")
adr.present_node_curio(_cands())
Kit.check_quiet(adr.curio_refresh_gp_cost() == base, "再碰三选一付费计数仍清零(同 X 余量不结转口径)")
adr.stop().present_node_curio(_cands())
Kit.check_quiet(adr.stop().curio_refresh_gp_cost() == base, "再碰三选一付费计数仍清零(同 X 余量不结转口径)")
# ---- ⑤ 钱包单口支出 spend_gp(LevelRunner——三选一付费档与商店刷新共用同一冒险钱包)----
var lr = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
+15 -15
View File
@@ -33,20 +33,20 @@ func _init() -> void:
# V9-1 刷新券:入存量账(冒险级)——反例=写进 _attune_refresh_x(X 每停点重置会吞存量)
a.stop()._reset_refresh_x() # X 归零基线(模拟新停点)
Kit.check_quiet(a.stop()._apply_curio_pick("refresh", "excellent", 1), "刷新券兑付受理〔专长批 V9〕")
Kit.check_quiet(a.refresh_stock() == 1 and a.stop()._refresh_x == 0,
Kit.check_quiet(a.stop().refresh_stock() == 1 and a.stop()._refresh_x == 0,
"F-1 修复:刷新券入存量账、X 账零写入(写错账反例不复发)〔专长批 V9〕")
a.stop()._reset_refresh_x() # 再碰停点——X 重置但存量不吞
Kit.check_quiet(a.refresh_stock() == 1 and a.attune_refresh_left() == 1,
Kit.check_quiet(a.stop().refresh_stock() == 1 and a.stop().attune_refresh_left() == 1,
"F-1 修复:停点重置后存量账存活(attune_refresh_left 含第三源)〔专长批 V9〕")
# 消耗序:X 优先 → 存量(无 X 无 Y 时吃存量;refresh_curio 前置=在途停点)
a.stop()._refresh_stock = 1
a.stop()._refresh_y = 0
a.stop()._node_pending = {"candidates": [{"kind": "attack", "tier": "common"}]}
Kit.check_quiet(a.refresh_curio() and a.refresh_stock() == 0,
Kit.check_quiet(a.stop().refresh_curio() and a.stop().refresh_stock() == 0,
"刷新消耗序:X/Y 空时吃存量账〔专长批 V9〕")
a.stop()._node_pending = {}
# 摘牌:选中即消尽(禁早退路径零摘牌)
var deck_refresh_before: int = a.pool_stock("refresh")
var deck_refresh_before: int = a.stop().pool_stock("refresh")
Kit.check_quiet(deck_refresh_before == 0 or true, "牌库 refresh 令牌读数在域(投放 %d)" % deck_refresh_before)
# V9-2 储备金袋:待注入账 + 战首注入(节点级)
@@ -89,26 +89,26 @@ func _init() -> void:
Kit.check_quiet(db.adventure.validate().is_empty(), "AdventureTable.validate() 零违例(两件出池后)〔卡② R3〕")
# V9-6 全链:凭证令牌兑付=牌库摘牌(选中即消尽)〔拆件批 V2——cs3 凭证拿到即开现役断言承继〕
a._curio_deck = [{"kind": "rap", "tier": "rare"}, {"kind": "attack", "tier": "common"}]
a.stop()._pending = {"candidates": a._curio_deck.duplicate()}
Kit.check_quiet(a.pick_curio("rap", "rare") and a.pool_stock("rap") == 0,
a._curio.deck = [{"kind": "rap", "tier": "rare"}, {"kind": "attack", "tier": "common"}]
a.stop()._pending = {"candidates": a._curio.deck.duplicate()}
Kit.check_quiet(a.stop().pick_curio("rap", "rare") and a.stop().pool_stock("rap") == 0,
"凭证令牌:pick_curio 摘牌(选中踢池——与属性件同语义)〔专长批 V9〕")
# V10-1 留任 2 件路由与值不变:gp 钱包/heal 当即回血(每次点选独立停点态)
a._curio_deck = [{"kind": "gp", "tier": "common"}, {"kind": "heal", "tier": "excellent"}]
a._curio.deck = [{"kind": "gp", "tier": "common"}, {"kind": "heal", "tier": "excellent"}]
a.stop()._pending = {"candidates": [{"kind": "gp", "tier": "common"}]}
var gp_taken_before := int(a._curio_taken.get("gp", 0))
Kit.check_quiet(a.pick_curio("gp", "common") and int(a._curio_taken.get("gp", 0)) == gp_taken_before + 1,
var gp_taken_before := int(a._curio.taken.get("gp", 0))
Kit.check_quiet(a.stop().pick_curio("gp", "common") and int(a._curio.taken.get("gp", 0)) == gp_taken_before + 1,
"留任小GP 袋:GP 通道兑付受理(gp 计数入账——路由零变化)〔专长批 V10〕")
var hp_before: int = a.hero().hero_hp
a.stop()._pending = {"candidates": [{"kind": "heal", "tier": "excellent"}]}
Kit.check_quiet(a.pick_curio("heal", "excellent") and a.hero().hero_hp == mini(hp_before + int(db.adventure.curio_value("heal", "excellent")), a.effective_max_hp()),
Kit.check_quiet(a.stop().pick_curio("heal", "excellent") and a.hero().hero_hp == mini(hp_before + int(db.adventure.curio_value("heal", "excellent")), a.effective_max_hp()),
"留任应急口粮:当即回血 +6 封顶生效上限〔专长批 V10〕")
# V10-2 行动点上限归属性轴:ap kind 走加算区第四轴
a._curio_deck = [{"kind": "ap", "tier": "rare"}]
a.stop()._pending = {"candidates": a._curio_deck.duplicate()}
a._curio.deck = [{"kind": "ap", "tier": "rare"}]
a.stop()._pending = {"candidates": a._curio.deck.duplicate()}
var ap_bonus_before: int = a.hero().bonus_total("ap")
Kit.check_quiet(a.pick_curio("ap", "rare") and a.hero().bonus_total("ap") == ap_bonus_before + int(db.adventure.curio_value("ap", "rare")),
Kit.check_quiet(a.stop().pick_curio("ap", "rare") and a.hero().bonus_total("ap") == ap_bonus_before + int(db.adventure.curio_value("ap", "rare")),
"行动点上限归属性轴:ap 兑付写加算区第四轴(bonus.ap 承载)〔专长批 V10〕")
# V16——断点扩容往返:新账本随断点快照/续玩回填(breakpoint_resume_test P5 同构)
@@ -138,7 +138,7 @@ func _init() -> void:
var b4 = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
Kit.check_quiet(b4.resume_adventure(bp_legacy2, {}),
"V13:旧档 adventure_counters 含改名前旧键续玩受理零崩溃(无迁移——旧键留桶成死键)〔卡② V13/H1〕")
Kit.check_quiet(int(b2.adventure_counters.get("pain_layers", -1)) == 3 and b2.refresh_stock() == 2,
Kit.check_quiet(int(b2.adventure_counters.get("pain_layers", -1)) == 3 and b2.stop().refresh_stock() == 2,
"V16:续玩回填逐项一致(adventure 桶随断点往返·计数恢复)〔无状态化 V3〕")
# V25 完美保险专长验收组(R2 全项:装载/限次一次/计数纯净/两账相减/跨战清零/事件不吞)
var fi: Dictionary = db.feats.row_by_id("perfect_insurance")
+12 -12
View File
@@ -1317,12 +1317,12 @@ 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._curio_taken.get(kind, 0))
var taken0: int = int(a8._curio.taken.get(kind, 0))
var val0: int = a8.hero().bonus_total(String(val_map[kind]))
Kit.check("购买受理(pool 口——%s|%s)" % [kind, tier], r8.buy_pool_item(kind))
Kit.check("钱包扣现价(%d → %d)" % [gp0, int(r8.gp)], int(r8.gp) == gp0 - int(buy_c.get("price", 0)))
Kit.check("加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_value(kind, tier))], a8.hero().bonus_total(String(val_map[kind])) == val0 + int(_db.adventure.curio_value(kind, tier)))
Kit.check("共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._curio_taken.get(kind, 0))], int(a8._curio_taken.get(kind, 0)) == taken0 + 1)
Kit.check("共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._curio.taken.get(kind, 0))], int(a8._curio.taken.get(kind, 0)) == taken0 + 1)
# A 链:同visit 第二购现价 = ceil(base×b^(l2−1)×A^1)(货架已全重抽——新卡新价同公式)
# A 链:白盒抬 GP 保证第二购场景确定构造(直改测试基建——非生产行为)
r8.gp = int(r8.gp) + 64
@@ -1347,9 +1347,9 @@ func _smoke_shop_pool() -> void:
cut_seen = true
Kit.check("切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)", not cut_seen)
# ⑤ 池空硬闸(库存尽剔出牌库——TP0:white-box 耗尽 attack 令牌后货架零 attack + 消费口拒绝)
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)
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()
@@ -1357,10 +1357,10 @@ func _smoke_shop_pool() -> void:
attack_seen = true
break
Kit.check("库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)", not attack_seen)
var stock_before: int = a8.pool_stock("attack")
Kit.check("池空消费拒绝(handler 零副作用——买光即枯竭硬闸)", not a8.stop().consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before)
var stock_before: int = a8.stop().pool_stock("attack")
Kit.check("池空消费拒绝(handler 零副作用——买光即枯竭硬闸)", not a8.stop().consume_pool_purchase("attack", "common") and a8.stop().pool_stock("attack") == stock_before)
# ⑥ 跨渠道共库 shop→reward 向:回填一枚 attack 令牌 → 消费口受理 → 奖励牌库同步摘牌
a8._curio_deck.append({"kind": "attack", "tier": "common"})
a8._curio.deck.append({"kind": "attack", "tier": "common"})
Kit.check("商店消费口受理(停点件兑付单点同口——真单点库)〔拆件批 V3〕", a8.stop().consume_pool_purchase("attack", "common"))
var reward_attack_seen := false
for t6 in a8.stop().draw_candidates():
@@ -1407,7 +1407,7 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
if not a.stop()._pending.is_empty():
var bcands: Array = a.stop()._pending["candidates"]
var bpick: Dictionary = autoplay._pick_token(bcands)
a.pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("feat_id", "")))
a.stop().pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("feat_id", "")))
a.continue_to_next_level()
continue
var r = a.runner()
@@ -1421,12 +1421,12 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
var t := String(r.current_node().get("type", ""))
if t == "reward":
# 奖励节点三选一(确定性选件、替换放弃新件、兑付确认回图;TP0 令牌化)
var ndraw: Dictionary = a.node_curio_candidates()
var ndraw: Dictionary = a.stop().node_curio_candidates()
var ncands: Array = ndraw.get("candidates", []) as Array
if not ncands.is_empty():
a.present_node_curio(ncands)
a.stop().present_node_curio(ncands)
var npick: Dictionary = autoplay._pick_token(ncands)
a.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", "")))
a.stop().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()
@@ -294,13 +294,13 @@ func _meta_settle_and_workshop() -> void:
a12.stop()._refresh_y = 2
a12.stop()._pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}}
Kit.check("刷新双计数器——X 优先消耗(x1y2 → x0y2)",
a12.refresh_curio() and a12.stop()._refresh_x == 0 and a12.stop()._refresh_y == 2)
a12.stop().refresh_curio() and a12.stop()._refresh_x == 0 and a12.stop()._refresh_y == 2)
Kit.check("刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)",
a12.refresh_curio() and a12.refresh_curio() and a12.stop()._refresh_y == 0
and a12.refresh_curio() == false)
a12.stop().refresh_curio() and a12.stop().refresh_curio() and a12.stop()._refresh_y == 0
and a12.stop().refresh_curio() == false)
a12.stop()._reset_refresh_x()
Kit.check("刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕",
a12.stop()._refresh_x == 0 and a12.attune_refresh_left() == 0)
a12.stop()._refresh_x == 0 and a12.stop().attune_refresh_left() == 0)
# ⑬ 主菜单开局传参面(开新/续玩两入口同调三键随开局下传防回归:真实入口曾
# 三键恒空(缺陷①),冒烟夹具直呼 runner 参数面拦不住——此处直呼 main.build_adventure_params
# 单测统一构造面(static 免实例化整菜单场景;真实两入口共用本构造),取值与营地投影同源)
@@ -728,10 +728,11 @@ func _smoke_tree_rules() -> void:
int(orch2.action_economy.pending_ap_delta()) == 0)
# 树限次按关清零改由 LevelRunner.level_counters 随 new 承载(旧硬编码名单冒烟断言随函数删除——换代同生死;新语义由 V4/V5 段承载)
# 财富之道·散:商品标价折价(level_runner 计价单点)
var lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777004))
lr.set_tree_rules([])
var lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777004),
{"tree_rules": []}) # 构造注入(拆件批 T3——setter 群退役)
Kit.check("散未激活:商品标价原价(无 economy_spend 键)", int(lr._spend_discounted(100)) == 100)
lr.set_tree_rules(["economy_spend"])
lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777005),
{"tree_rules": ["economy_spend"]}) # 构造注入(拆件批 T3——setter 群退役)
Kit.check("散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)",
int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1)
# 财富之道·聚:战斗掉落乘算·入账前(编排 _roll_gp_drop——同种子配对对拍 1.5×)
+19 -19
View File
@@ -36,10 +36,10 @@ func _init() -> void:
var e = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
e.start_adventure(501)
e.stop()._pending = {"candidates": []}
Kit.check_quiet(e.skip_empty_curio() and e.stop()._pending.is_empty(),
Kit.check_quiet(e.stop().skip_empty_curio() and e.stop()._pending.is_empty(),
"空配置闸:三选一池空=停点整体跳过(skip_empty_curio 消化)〔专长批 T10〕")
e.stop()._pending = {"candidates": [{"kind": "gp", "tier": "common"}]}
Kit.check_quiet(not e.skip_empty_curio(),
Kit.check_quiet(not e.stop().skip_empty_curio(),
"空配置闸反例:非空停点拒绝跳过(零自动推进契约不破)〔专长批 T10〕")
var lr = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
lr.set_shop_pool([], Callable(), Callable(), Callable())
@@ -69,7 +69,7 @@ func check_v7(a, db) -> void:
Kit.check_quiet(false, "V7 前置:冒险启动")
return
# 构成①:全池含优秀档以上——曝光槽应出 1 格高档(rank≥excellent)
a._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common"),
a._curio.deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common"),
_tok("armor", "common"), _tok("heal", "excellent"), _tok("refresh", "excellent"), _tok("ap", "rare")]
var hi_seen := false
for i in 40:
@@ -84,7 +84,7 @@ func check_v7(a, db) -> void:
hi_seen = true
Kit.check_quiet(hi_seen, "曝光槽:全池含高档时三选一呈现 ≥1 格优秀档以上〔专长批 V7〕")
# 构成②:高档子池空——该格回退全池(候选仍 3 格全普通)
a._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common")]
a._curio.deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common")]
var ok_fb := true
for i in 20:
var cands2: Array = a.stop().draw_candidates("reward_node")
@@ -96,7 +96,7 @@ func check_v7(a, db) -> void:
ok_fb = false
Kit.check_quiet(ok_fb, "子池空回退全池:全普通池候选恒 3 格普通档〔专长批 V7〕")
# 构成③:凭证不进子池也不进全池视图(独立计数体系——只经专长凭证注入出现)
a._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common"),
a._curio.deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common"),
_tok("feat_ticket", "excellent")]
var ticket_seen := false
for i in 30:
@@ -105,7 +105,7 @@ func check_v7(a, db) -> void:
ticket_seen = true
Kit.check_quiet(not ticket_seen, "凭证不进抽签池(feat_ticket 只经保底注入通道)〔专长批 V7〕")
# 构成④:池空=零候选合法态(同族兜底入口)
a._curio_deck = []
a._curio.deck = []
Kit.check_quiet(a.stop().draw_candidates("reward_node").is_empty(), "池空=零候选合法态(skip_empty_curio 兜底入口)〔专长批 V7〕")
@@ -114,26 +114,26 @@ func check_v6(b, db) -> void:
Kit.check_quiet(false, "V6 前置:冒险启动")
return
# 拿了消耗:直置停点候选后 pick_curio——牌库摘牌单点
b._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "excellent")]
var gp_before: int = b.pool_stock("gp")
b._curio.deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "excellent")]
var gp_before: int = b.stop().pool_stock("gp")
b.stop()._pending = {"candidates": [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "excellent")]}
Kit.check_quiet(b.pick_curio("gp", "common") and b.pool_stock("gp") == gp_before - 1,
Kit.check_quiet(b.stop().pick_curio("gp", "common") and b.stop().pool_stock("gp") == gp_before - 1,
"拿了消耗:pick_curio 后牌库摘牌(选中踢池)〔专长批 V6〕")
# 没拿回池:抽候选不摘牌——重复抽签同令牌可再现
var deck_size: int = b._curio_deck.size()
var deck_size: int = b._curio.deck.size()
var seen_again := false
for i in 30:
var cands: Array = b.stop().draw_candidates("reward_node")
if cands.size() == 2 and b._curio_deck.size() == deck_size:
if cands.size() == 2 and b._curio.deck.size() == deck_size:
seen_again = true
Kit.check_quiet(seen_again, "没拿回池:抽签不摘牌(未选候选再次可现,牌库余量不变)〔专长批 V6〕")
# 只刷出不计:抽签后 _curio_taken 零变化
var taken_before: Dictionary = b._curio_taken.duplicate()
var taken_before: Dictionary = b._curio.taken.duplicate()
for i in 10:
b.stop().draw_candidates("reward_node")
var unchanged := true
for k in taken_before.keys():
if int(b._curio_taken.get(k, 0)) != int(taken_before[k]):
if int(b._curio.taken.get(k, 0)) != int(taken_before[k]):
unchanged = false
Kit.check_quiet(unchanged, "只刷出不计:抽签不改已拿账〔专长批 V6〕")
@@ -163,21 +163,21 @@ func check_v8(c, db) -> void:
# 概率位与 cap 两属性独立:改 cap 不动牌库(概率位)、改牌库不动 cap 字段
var cap_field_before := int(db.adventure.curio_cap_gp)
db.adventure.curio_cap_gp = 1 # 独立设 cap(概率位=gp 令牌数不动)
var gp_tokens: int = c.pool_stock("gp")
var gp_tokens: int = c.stop().pool_stock("gp")
Kit.check_quiet(gp_tokens > 0 and int(db.adventure.curio_value("gp", "common")) == 20,
"改 cap 不动概率位:牌库 gp 令牌 %d 枚在库、件值字段不受影响〔专长批 V8〕" % gp_tokens)
# cap 只在拿了才计:刷出不计
c._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common")]
c._curio.deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common")]
for i in 20:
c.stop().draw_candidates("reward_node")
Kit.check_quiet(bool(c._curio_cap_ok("gp", "")) and int(c._curio_taken.get("gp", 0)) == 0,
Kit.check_quiet(bool(c._curio.cap_ok("gp", "")) and int(c._curio.taken.get("gp", 0)) == 0,
"cap 刷出不计:20 次抽签后 gp 未拿、cap 未触发〔专长批 V8〕")
# 拿了才计:pick 一次→cap 满→提前退役(候选视图剔除、牌库本体不动)
c.stop()._pending = {"candidates": [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common")]}
Kit.check_quiet(c.pick_curio("gp", "common"), "V8 前置:兑付 gp 一枚")
Kit.check_quiet(not bool(c._curio_cap_ok("gp", "")), "cap 拿了才计:兑付后 cap 满(taken 1/1)〔专长批 V8〕")
Kit.check_quiet(c.stop().pick_curio("gp", "common"), "V8 前置:兑付 gp 一枚")
Kit.check_quiet(not bool(c._curio.cap_ok("gp", "")), "cap 拿了才计:兑付后 cap 满(taken 1/1)〔专长批 V8〕")
var gp_in_deck := 0
for t in c._curio_deck:
for t in c._curio.deck:
if String((t as Dictionary).get("kind", "")) == "gp":
gp_in_deck += 1
var gp_cand := false
@@ -117,7 +117,7 @@ func _init() -> void:
adr5.set_save_manager(save_s.new("user://treasure_effects_p.json"))
Kit.check_quiet(adr5.start_adventure(261, {"hero_override": {}, "treasure_unlocked": {}, "attunement": ["refresh_practice"]}),
"刷新练习:装配冒险启动")
Kit.check_quiet(int(adr5.refresh_stock()) == 1 and int(adr5.attune_refresh_left()) == 1,
Kit.check_quiet(int(adr5.stop().refresh_stock()) == 1 and int(adr5.stop().attune_refresh_left()) == 1,
"刷新练习:存量账 +1(装配即得一次性——attune_refresh_left 合计含 stock)")
Kit.check_quiet(int(adr5.stop()._refresh_x) == 0,
"刷新练习反例:临时账 X 不动(写临时账/重复加仓=违规形态)")
@@ -219,7 +219,8 @@ func _init() -> void:
var lv10 = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
Kit.check_quiet(int(lv10.shop_refresh_free_left()) == int(db.adventure.shop_refresh_free),
"刷新执照基线:未装配=表值(%d)" % int(db.adventure.shop_refresh_free))
lv10.set_attunement(["refresh_license"])
lv10 = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new(),
{"attunement": ["refresh_license"]}) # 构造注入(拆件批 T3——setter 群退役)
Kit.check_quiet(int(lv10.shop_refresh_free_left()) == int(db.adventure.shop_refresh_free) + 2,
"刷新执照正常:免费刷新 +2/店(读数即本店可用——进店重置生命周期同 knob)")