拆件批v1.3-B2(停点刷新账拆分): curio_stop.gd 刷新三源账与付费刷计数拆出 refresh_bank.gd 新件(uid 随建)——X 件账(每碰三选一重置不结转)/Y 冒险周期池/存量券账三源+spend_free 消耗序(X→Y→存量逐字)/gp_cost 报价(ceil(基价×曲线^本次停点已付费刷))/断点片段 refresh_stock+attune_refresh_y 两键(Y 缺键回退参数面=旧档行为零变化)归 RefreshBank 零停点态依赖,CurioStop 只留停点态编排(查在途→bank 支出→重抽重发),draw_candidates/feat_ticket_consider/curio_cap_ok/pool_stock/pool_stock_snapshot/attune_refresh_left/set_refresh_* 七转发退役(消费方直连 deck());构造参改 deps 字典(deck/stats/journal/bank);restore_resolved 收编置态+重发一步(时序逐位同)。消费方改址——adventure_runner(refresh_bank() 件出口+_init 建件+start/resume 预算注入改 bank+断点拼盘 bank_frag 两键+终局池快照直连 deck.stock_snapshot)、curio_interact_controller(余量读数 free_left/报价 gp_cost)、测试七文件白盒地址随动(curio_refresh_paid/feat_vouchers/meta_progression_forge/treasure_effects/adventure_feats/level_shop_economy/pool_case_d)。验收:全量套件 63 绿+round_flow EV 锚 goblin 已知预期红 1;bp 对拍 292dumps×28 键 mismatch=0(对 after_attune 与 after_split_B1 双基线);冒烟 17 项 0 失败;gdstyle 基线外新违规 0/恶化恰 2(battle 文件行数+level_map 类变量,存量债)〔领域拆分批 V1〕
This commit is contained in:
1 parent
4ca4ddbc42
commit
012883f301
12 files changed
+248
-203
No files matched your search
@@ -38,6 +38,7 @@ var _loadout_seeded := false # 同调行已种标记(每冒险一
|
||||
# ---- 三选一停点(编排迁 CurioStop 件——停点三态/刷新三源/凭证出箱/信号面)----
|
||||
var _curio: CurioDeck = null # 令牌池件(_init 建;纯池——多重集/抽签/cap/凭证判定唯一宿主)
|
||||
var _stop: CurioStop = null # 停点件(_init 建;每冒险一次——关卡换实例不换件)
|
||||
var _refresh_bank: RefreshBank = null # 刷新账件(_init 建——三源账与付费报价;宿主预算注入与断点片段直收)
|
||||
var _loadout: AttuneLoadout = null # 同调配装件(_init 建;充能单源化——物化行唯一事实源)
|
||||
var adventure_inventory: TreasureItems # 冒险级秘宝库存(单源化——同调未用行跨关存活的承载面;关卡域语义归 level 使用面)
|
||||
var _tree_rules: Array[Variant] = [] # 树 rule 键集(Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
|
||||
@@ -51,7 +52,9 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
||||
_wallet = GpWallet.new()
|
||||
_stats = HeroStats.new(db, {}, _journal) # 数值账消费日记汇总(同源单件注入)
|
||||
_curio = CurioDeck.new(db, rng)
|
||||
_stop = CurioStop.new(db, _curio, _stats, _journal, _snapshot_breakpoint)
|
||||
_refresh_bank = RefreshBank.new(db)
|
||||
_stop = CurioStop.new(db, {"deck": _curio, "stats": _stats, "journal": _journal, "bank": _refresh_bank},
|
||||
_snapshot_breakpoint)
|
||||
_loadout = AttuneLoadout.new(db, _attunement)
|
||||
adventure_inventory = TreasureItems.new(db)
|
||||
|
||||
@@ -73,6 +76,10 @@ func deck() -> CurioDeck:
|
||||
return _curio
|
||||
|
||||
|
||||
func refresh_bank() -> RefreshBank:
|
||||
return _refresh_bank
|
||||
|
||||
|
||||
# 件出口(停点件——呈现/测量面取停点编排单点;拆件批接口块)
|
||||
func stop() -> CurioStop:
|
||||
return _stop
|
||||
@@ -104,8 +111,8 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 缺省 legacy(off 对照零扰动)
|
||||
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
|
||||
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
|
||||
_stop.set_refresh_x_cap(_attune_refresh_x_total()) # 停点件 X 基数预算注入(装配集冒险期间锁死)
|
||||
_stop.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池)
|
||||
_refresh_bank.set_refresh_x_cap(_attune_refresh_x_total()) # 刷新账 X 基数预算注入(装配集冒险期间锁死)
|
||||
_refresh_bank.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池)
|
||||
_loadout.set_attunement(_attunement) # 装配集注入(活引用——种行面)
|
||||
adventure_inventory.reset() # 冒险级库存重置(新局空库存)
|
||||
_loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后)
|
||||
@@ -115,7 +122,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
for iid_v2 in _attunement:
|
||||
var rp_row: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v2))
|
||||
if (rp_row.get("effect_kinds", []) as Array).has("refresh_stock_grant"):
|
||||
_stop.add_refresh_stock(int(rp_row.get("magnitude", 0)))
|
||||
_refresh_bank.add_refresh_stock(int(rp_row.get("magnitude", 0)))
|
||||
# 特性开局注入(MC 成型画像载体:params.relics_seed 非空=开局持有集〔七流派
|
||||
# 各 2 件档一成型——测量面专用;真实局规则改写型特性只走局内随机渠道,红线②层级纪律不破〕)
|
||||
var feat_seed: Array[Variant] = params.get("relics_seed", []) if params.get("relics_seed", []) is Array else []
|
||||
@@ -220,8 +227,9 @@ func _resume_params(bp: Dictionary, params: Dictionary) -> void:
|
||||
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 续玩参数面同 start_adventure(缺省 legacy)
|
||||
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
|
||||
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
|
||||
_stop.set_refresh_x_cap(_attune_refresh_x_total()) # 停点件 X 基数预算注入(续玩参数面同 start)
|
||||
_stop.restore_fragment(bp, params) # 停点件片段回填(凭证计数/刷新存量/Y 池——Y 缺键回退参数面 params_fallback 类)
|
||||
_refresh_bank.set_refresh_x_cap(_attune_refresh_x_total()) # 刷新账 X 基数预算注入(续玩参数面同 start)
|
||||
_stop.restore_fragment(bp, params) # 停点件片段回填(凭证计数)
|
||||
_refresh_bank.restore_fragment(bp, params) # 刷新账片段回填(刷新存量/Y 池——Y 缺键回退参数面 params_fallback 类)
|
||||
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # A/B 面 rule 键集(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传)
|
||||
|
||||
|
||||
@@ -299,9 +307,9 @@ func feats_held() -> Array:
|
||||
# ---- 商店共享池消费面(纯池——库存=牌库剩余令牌)----
|
||||
|
||||
# 库存余量(池消耗即上限;奖励摘牌与商店购买同库承载,全冒险一次性)
|
||||
# 终局池快照(_settle 清账前调用——复位会重置计数,摘要 additive 键须在此前取数)
|
||||
# 终局池快照(_settle 清账前调用——复位会重置计数,摘要 additive 键须在此前取数;读面直连令牌池件)
|
||||
func _pool_stock_snapshot() -> Dictionary:
|
||||
return _stop.pool_stock_snapshot()
|
||||
return _curio.stock_snapshot()
|
||||
|
||||
|
||||
# ---- 奖励节点三选一(与关卡完成三选一共享同池同权重同次数上限——编排归停点件)----
|
||||
@@ -576,6 +584,7 @@ func _bp_parts() -> Dictionary[Variant, Variant]:
|
||||
var stats_frag: Dictionary[Variant, Variant] = _stats.breakpoint_fragment()
|
||||
var wallet_frag: Dictionary[Variant, Variant] = _wallet.breakpoint_fragment()
|
||||
var journal_frag: Dictionary[Variant, Variant] = _journal.breakpoint_fragment()
|
||||
var bank_frag: Dictionary[Variant, Variant] = _refresh_bank.breakpoint_fragment()
|
||||
var stop_frag: Dictionary[Variant, Variant] = _stop.breakpoint_fragment()
|
||||
var level_frag: Dictionary[Variant, Variant] = _runner.breakpoint_fragment() if _runner != null else {
|
||||
"node_id": -1, "visited_ids": [] as Array, "aux_items": [] as Array,
|
||||
@@ -605,8 +614,8 @@ func _bp_parts() -> Dictionary[Variant, Variant]:
|
||||
"feat_ticket_sold": int(stop_frag.get("feat_ticket_sold", 0)), # T5 专长凭证限购计数(同上)
|
||||
"adventure_counters": adventure_counters.duplicate(true), # adventure 域计数器随断点(续玩跨战账不丢——桶归宿主字典)
|
||||
"vision_counter_used": int(journal_frag.get("vision_counter_used", 0)), # 真视使用计数随断点(续玩跨战账不丢)
|
||||
"refresh_stock": int(stop_frag.get("refresh_stock", 0)), # 刷新存量账随断点
|
||||
"attune_refresh_y": int(stop_frag.get("attune_refresh_y", 0)), # Y 冒险周期池余量随断点(扩列向后兼容——旧档缺键回退参数面)
|
||||
"refresh_stock": int(bank_frag.get("refresh_stock", 0)), # 刷新存量账随断点
|
||||
"attune_refresh_y": int(bank_frag.get("attune_refresh_y", 0)), # Y 冒险周期池余量随断点(扩列向后兼容——旧档缺键回退参数面)
|
||||
"bonus_result": stop_frag.get("bonus_result", {}), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
|
||||
"keystone_drops": journal_frag.get("keystone_drops", []), # 逐杀掉落明细(摘要归因续接)
|
||||
"gain_journal": journal_frag.get("gain_journal", {}), # 获得日记(唯一声明的断点扩列——日记为现行事实源)
|
||||
|
||||
+57
-133
@@ -23,22 +23,20 @@ var _grant_gp := Callable() # 当前关卡对象 grant_curio_gp 方
|
||||
var _pending := {} # 关卡完成三选一在途:{"candidates": Array, "payload": Dictionary}——空 = 无停点
|
||||
var _resolved := {} # 已兑付待推进态(curio_resolved 载荷副本——中断续玩不重兑付)
|
||||
var _node_pending := {} # 奖励节点三选一停点态(共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽)
|
||||
# ---- 刷新三源账 ----
|
||||
var _refresh_x := 0 # 刷新件余量(每碰三选一重置=X 表值;优先消耗、不结转)
|
||||
var _refresh_y := 0 # Y 冒险周期池(开局参数面注入,用完即无——营地升级行表值)
|
||||
var _paid_refreshes := 0 # 本次停点已付费刷新次数(对齐商店 MC-1——GP 刷新价曲线输入,随停点创建清零=与商店进店清零同构)
|
||||
var _refresh_stock := 0 # 刷新存量账(冒险级——刷新券加仓;**不经 _refresh_x**:X 每停点重置会吞存量=写错账缺陷 F-1)
|
||||
# ---- 刷新账(三源账与付费计数归 RefreshBank 件——编排留本件:查停点态→bank 支出→重抽重发)----
|
||||
var _bank: RefreshBank # 刷新账件(构造注入——零停点态依赖)
|
||||
# ---- 凭证出箱(奇物.md 凭证件表——节点级/回合级一次性,战首取走清零)----
|
||||
var _pending_rap := 0 # 储备金袋待注入(下一战 RAP +N,战首注入后清零)
|
||||
var _time_sand_queue := 0 # 时沙待注入(下一战首轮 AP +N,战首入队后清零)
|
||||
var _level_index := 0 # 兑付载荷关卡序号(宿主每关注入——与 AdventureRunner.level_index 同源单写)
|
||||
|
||||
|
||||
func _init(db_ref: ConfigDb, deck_ref: CurioDeck, stats_ref: HeroStats, journal_ref: GainJournal, snapshot_call: Callable) -> void:
|
||||
func _init(db_ref: ConfigDb, deps: Dictionary, snapshot_call: Callable) -> void:
|
||||
db = db_ref
|
||||
_deck = deck_ref
|
||||
_stats = stats_ref
|
||||
_journal = journal_ref
|
||||
_deck = deps.get("deck", null) as CurioDeck
|
||||
_stats = deps.get("stats", null) as HeroStats
|
||||
_journal = deps.get("journal", null) as GainJournal
|
||||
_bank = deps.get("bank", null) as RefreshBank
|
||||
_snapshot_call = snapshot_call
|
||||
|
||||
|
||||
@@ -60,11 +58,11 @@ func open_level_clear(payload_base: Dictionary) -> void:
|
||||
var max_eff := _stats.effective_max_hp() # 回血封顶口径含调试浮窗 pct(同战斗内上限)
|
||||
var heal := mini(int(floor(float(max_eff) * float(int(db.adventure.clear_heal_pct)) / 100.0)), max_eff - hp_before)
|
||||
_stats.hero_hp = hp_before + heal
|
||||
_reset_refresh_x() # 碰三选一重置 X 余量(不结转)
|
||||
_paid_refreshes = 0 # 付费刷计数随停点清零(对齐商店进店清零)
|
||||
_bank.reset_x() # 碰三选一重置 X 余量(不结转)
|
||||
_bank.reset_paid() # 付费刷计数随停点清零(对齐商店进店清零)
|
||||
var pick_payload: Dictionary[Variant, Variant] = {
|
||||
"level_index": _level_index,
|
||||
"candidates": draw_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=关卡完成渠道只出专长
|
||||
"candidates": _deck.draw_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=关卡完成渠道只出专长
|
||||
"heal_gained": heal,
|
||||
"hp_before": hp_before,
|
||||
"hp_after": _stats.hero_hp,
|
||||
@@ -85,13 +83,13 @@ func open_level_clear(payload_base: Dictionary) -> void:
|
||||
# 候选呈现(节点到达时调用——恰一 reward_pick scope 洗牌 + 道具件掷定 scope;本停点断点不载,
|
||||
# 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。
|
||||
func node_curio_candidates() -> Dictionary:
|
||||
var cands := draw_candidates("reward_node")
|
||||
var cands := _deck.draw_candidates("reward_node")
|
||||
return {"candidates": cands}
|
||||
|
||||
|
||||
func present_node_curio(candidates: Array) -> void:
|
||||
_reset_refresh_x() # 碰三选一重置 X 余量(不结转)
|
||||
_paid_refreshes = 0 # 付费刷计数随停点清零(对齐商店进店清零)
|
||||
_bank.reset_x() # 碰三选一重置 X 余量(不结转)
|
||||
_bank.reset_paid() # 付费刷计数随停点清零(对齐商店进店清零)
|
||||
_node_pending = {"candidates": candidates.duplicate(true)}
|
||||
|
||||
|
||||
@@ -128,7 +126,8 @@ func _feat_payload_keys(feat_id: String) -> Dictionary:
|
||||
return {"feat_id": feat_id, "feat_name": String(row.get("display_name", "")), "feat_archetype": String(row.get("archetype", ""))}
|
||||
|
||||
|
||||
# 三选一件兑付共享体(TP0——令牌消费、摘牌单点,含 relic 分支):
|
||||
# 三选一件兑付共享体(TP0——令牌消费、摘牌单点,含 relic 分支;领域拆分批拆三私有段:
|
||||
# 资源件段/路由加算段/副作用通道段,兑付语义与顺序逐字不变):
|
||||
# 属性件写加算区 + 件获取计数、AP 件写第四轴、GP 件走钱包、恢复件当即回血(封顶生效上限——
|
||||
# 与账本置满/关卡完成回血同式 effective_max_hp)、relic 件入持有集(散件单体保底——效果接线面
|
||||
# 按挂点消费,档位质变由 FeatsEngine 求值)。兑付即从牌库摘除该令牌(选中踢池;
|
||||
@@ -140,8 +139,17 @@ func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String =
|
||||
if not _deck.consume_deck_token(kind, tier, feat_id):
|
||||
push_error("[停点件] 令牌消费失败——牌库无此令牌: %s|%s|%s" % [kind, tier, feat_id])
|
||||
return false
|
||||
if _apply_resource_pick(kind, value, feat_id): # 资源件段(refresh/rap/time_sand/relic)
|
||||
return true
|
||||
if _apply_route_pick(kind, value): # 路由加算段(CURIO_APPLY_ROUTES 声明路由)
|
||||
return true
|
||||
return _apply_channel_pick(kind, value) # 副作用通道段(gp/heal 显式分支——未知件 false)
|
||||
|
||||
|
||||
# 资源件段(refresh/rap/time_sand/relic——冒险级资源与凭证账面;非资源件 false 交回主路由)
|
||||
func _apply_resource_pick(kind: String, value: int, feat_id: String) -> bool:
|
||||
if kind == "refresh":
|
||||
_refresh_stock += value # T4 F-1——刷新券入刷新存量账(冒险级;**不写 _refresh_x**:X 每停点 _reset_refresh_x 重置会吞存量;摘牌先于落账=选中即消尽)
|
||||
_bank.add_refresh_stock(value) # T4 F-1——刷新券入刷新存量账(冒险级;**不写 X 临时账**:X 每停点 reset_x 重置会吞存量;摘牌先于落账=选中即消尽)
|
||||
_bump_taken("refresh")
|
||||
return true
|
||||
if kind == "rap":
|
||||
@@ -158,14 +166,22 @@ func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String =
|
||||
_bump_taken("relic")
|
||||
_bump_taken("relic:" + feat_id) # 身份维已拿计数(cap 消费面)
|
||||
return true
|
||||
# 加算形态路由(match 收敛进效果类型:属性值提升四类走
|
||||
# adventure_table.CURIO_APPLY_ROUTES 声明路由(bonus 落点键),加新加算类令牌零代码)
|
||||
return false
|
||||
|
||||
|
||||
# 路由加算段(match 收敛进效果类型:属性值提升四类走 adventure_table.CURIO_APPLY_ROUTES 声明
|
||||
# 路由(bonus 落点键),加新加算类令牌零代码)
|
||||
func _apply_route_pick(kind: String, value: int) -> bool:
|
||||
var sink := db.adventure.curio_apply_route(kind)
|
||||
if sink != "":
|
||||
_journal.gain_bonus(sink, value, "curio_pick", kind) # 加算区单写原语(获得日记明细+汇总同拍)
|
||||
_bump_taken(kind)
|
||||
return true
|
||||
# 通道面(副作用形态——显式分支)
|
||||
if sink == "":
|
||||
return false
|
||||
_journal.gain_bonus(sink, value, "curio_pick", kind) # 加算区单写原语(获得日记明细+汇总同拍)
|
||||
_bump_taken(kind)
|
||||
return true
|
||||
|
||||
|
||||
# 副作用通道段(副作用形态——显式分支:GP 件走关卡对象入账回调、恢复件当即回血)
|
||||
func _apply_channel_pick(kind: String, value: int) -> bool:
|
||||
match kind:
|
||||
"gp":
|
||||
if _grant_gp.is_valid():
|
||||
@@ -240,7 +256,7 @@ func skip_empty_curio() -> bool:
|
||||
func _redeem_feat_ticket() -> bool:
|
||||
_deck.ticket_sold += 1 # 限购计数(本体在池件——原宿主转发属性迁点)
|
||||
_deck.ticket_pity = 0
|
||||
var cands := draw_candidates("level_clear") # 专长池(与关卡完成渠道同白名单)
|
||||
var cands := _deck.draw_candidates("level_clear") # 专长池(与关卡完成渠道同白名单)
|
||||
if cands.is_empty():
|
||||
push_error("[停点件] 专长凭证兑付失败——专长池空集")
|
||||
_deck.ticket_sold -= 1
|
||||
@@ -256,33 +272,10 @@ func _redeem_feat_ticket() -> bool:
|
||||
return true
|
||||
|
||||
|
||||
# ---- 五组入口之四:刷新族(X/Y 双源:X=刷新件每碰三选一重给〔优先消耗、不结转〕、
|
||||
# Y=营地升级行冒险周期池〔用完即无〕;重抽消费 reward_pick 既有洗牌流,池规则全保留——不放回/
|
||||
# ---- 五组入口之四:刷新编排(三源账与报价归 RefreshBank 件——本件只做停点态校验、
|
||||
# 支出分派与重抽重发;重抽消费 reward_pick 既有洗牌流,池规则全保留——不放回/
|
||||
# 达上限剔牌/掷定前置直显;停点可中断)----
|
||||
|
||||
var _refresh_x_cap := 0 # 刷新件余量基数(装配集 effect_kinds 含 bonus_refresh 的行 magnitude 合计——
|
||||
# 宿主冒险开始时预算注入;装配集冒险期间锁死〔契约 8〕故预算与逐停点查询等价)
|
||||
|
||||
|
||||
# 刷新件基数注入(宿主 start_adventure/resume 时点计算——X 每碰三选一重置回此基数)
|
||||
func set_refresh_x_cap(total: int) -> void:
|
||||
_refresh_x_cap = maxi(0, total)
|
||||
|
||||
|
||||
func _reset_refresh_x() -> void:
|
||||
_refresh_x = _refresh_x_cap
|
||||
|
||||
|
||||
# 当前可刷新次数(X 优先 + Y 池 + 刷新存量账——呈现层余量读数口)
|
||||
func attune_refresh_left() -> int:
|
||||
return _refresh_x + _refresh_y + _refresh_stock
|
||||
|
||||
|
||||
# 刷新存量账读数(T4——刷新券加仓余量单独读面;呈现层与断点消费)
|
||||
func refresh_stock() -> int:
|
||||
return _refresh_stock
|
||||
|
||||
|
||||
# 节点停点在途态读回(组合根重 present 用——candidates 值拷贝)
|
||||
func node_curio_pending_state() -> Dictionary:
|
||||
return {
|
||||
@@ -302,12 +295,6 @@ func refresh_curio_paid() -> bool:
|
||||
return _refresh_curio_common(true)
|
||||
|
||||
|
||||
# 本次 GP 付费刷新价(与商店货架刷新同源:ceil(基价 × 曲线^本次停点已付费刷)——读同两个调参旋钮,单点调参)
|
||||
func curio_refresh_gp_cost() -> int:
|
||||
return int(ceil(float(db.adventure.shop_refresh_cost_base)
|
||||
* pow(db.adventure.shop_refresh_cost_curve, float(_paid_refreshes))))
|
||||
|
||||
|
||||
# 三选一停点在途谓词(付费路径扣费前置校验——无停点禁扣钱,防空转烧钱包)
|
||||
func curio_stop_pending() -> bool:
|
||||
return not _pending.is_empty() or not _node_pending.is_empty()
|
||||
@@ -322,20 +309,12 @@ func _refresh_curio_common(paid: bool) -> bool:
|
||||
if not curio_stop_pending():
|
||||
return false
|
||||
if paid:
|
||||
_paid_refreshes += 1
|
||||
_bank.charge_paid() # 付费刷计数递增(报价曲线输入——账面归刷新账件)
|
||||
else:
|
||||
# 免费位消耗序:X(刷新件每停点重给,优先消耗)→ Y(营地冒险周期池)→ 存量账(刷新券加仓,
|
||||
# 跨停点持久)
|
||||
if _refresh_x <= 0 and _refresh_y <= 0 and _refresh_stock <= 0:
|
||||
if not _bank.spend_free(): # 免费位消耗序 X→Y→存量(三源全空 false 零副作用——序与语义逐字归账件)
|
||||
return false
|
||||
if _refresh_x > 0:
|
||||
_refresh_x -= 1
|
||||
elif _refresh_y > 0:
|
||||
_refresh_y -= 1
|
||||
else:
|
||||
_refresh_stock -= 1
|
||||
if not _pending.is_empty():
|
||||
var cands := draw_candidates("level_clear")
|
||||
var cands := _deck.draw_candidates("level_clear")
|
||||
_pending["candidates"] = cands.duplicate(true)
|
||||
var payload: Dictionary[Variant, Variant] = (_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||
payload["candidates"] = cands
|
||||
@@ -345,7 +324,7 @@ func _refresh_curio_common(paid: bool) -> bool:
|
||||
_snapshot_call.call()
|
||||
return true
|
||||
if not _node_pending.is_empty():
|
||||
var cands2 := draw_candidates("reward_node")
|
||||
var cands2 := _deck.draw_candidates("reward_node")
|
||||
_node_pending["candidates"] = cands2.duplicate(true)
|
||||
return true
|
||||
return false
|
||||
@@ -364,21 +343,6 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", feat_id:
|
||||
curio_resolved.emit(payload)
|
||||
|
||||
|
||||
# 三选一候选抽签(实装归 CurioDeck 件;消费 reward_pick 封闭流——一次停点恰一 scope)
|
||||
func draw_candidates(channel: String = "reward_node") -> Array:
|
||||
return _deck.draw_candidates(channel)
|
||||
|
||||
|
||||
# —— 专长获取专长凭证四口径判定(实装归 CurioDeck 件:调用即一次展示判定与记账)——
|
||||
func feat_ticket_consider(rg: RandomNumberGenerator) -> bool:
|
||||
return _deck.feat_ticket_consider(rg)
|
||||
|
||||
|
||||
# 已拿上限判定(身份维度;缺省放行=白名单外 kind 兜底)
|
||||
func curio_cap_ok(kind: String, feat_id: String = "") -> bool:
|
||||
return _deck.cap_ok(kind, feat_id)
|
||||
|
||||
|
||||
# 商店共享池消费单点(兑付单点——ShopSession.buy_pool_item 经注入 Callable 调用,回 _apply_curio_pick
|
||||
# 同口;专长凭证买下即开零摘牌)。未知 kind 拒绝(货架词表封闭的前置防御)。
|
||||
func consume_pool_purchase(kind: String, tier: String) -> bool:
|
||||
@@ -390,43 +354,16 @@ func consume_pool_purchase(kind: String, tier: String) -> bool:
|
||||
return _apply_curio_pick(kind, tier, int(db.adventure.curio_value(kind, tier)))
|
||||
|
||||
|
||||
# 库存余量(池消耗即上限;奖励摘牌与商店购买同库承载,全冒险一次性)
|
||||
func pool_stock(kind: String) -> int:
|
||||
return _deck.pool_stock(kind)
|
||||
|
||||
|
||||
# 终局池快照(_settle 清账前调用——复位会重置计数,摘要 additive 键须在此前取数)
|
||||
func pool_stock_snapshot() -> Dictionary:
|
||||
return _deck.stock_snapshot()
|
||||
|
||||
|
||||
# ---- 冒险级复位(宿主 _reset_curio_state 调用——停点半:池归 deck.reset、停点三态与刷新三源清零)----
|
||||
# ---- 冒险级复位(宿主 _reset_curio_state 调用——停点半:池归 deck.reset、停点三态与刷新账清零)----
|
||||
func reset_adventure_state() -> void:
|
||||
_pending = {}
|
||||
_resolved = {}
|
||||
_node_pending = {}
|
||||
_refresh_x = 0
|
||||
_refresh_y = 0
|
||||
_paid_refreshes = 0
|
||||
_refresh_stock = 0
|
||||
_bank.reset_all() # 刷新三源与付费计数清零(账面归刷新账件)
|
||||
_pending_rap = 0
|
||||
_time_sand_queue = 0
|
||||
|
||||
|
||||
# Y 池注入(冒险开始/续玩参数面——宿主单写)
|
||||
func set_refresh_y(v: int) -> void:
|
||||
_refresh_y = maxi(0, v)
|
||||
|
||||
|
||||
func refresh_y() -> int:
|
||||
return _refresh_y
|
||||
|
||||
|
||||
# 刷新存量账加仓(refresh_stock_grant 装配即得一次性加仓——只入存量账、临时账不动=V8 契约)
|
||||
func add_refresh_stock(n: int) -> void:
|
||||
_refresh_stock += n
|
||||
|
||||
|
||||
# 续玩选择停点回填(token_migration 类:旧版断点候选为 kind 字符串,按普通档令牌防御归一;
|
||||
# 发射回宿主信号面由调用方承接——本件只载态)
|
||||
func restore_pending(bp_pending: Dictionary) -> void:
|
||||
@@ -453,29 +390,22 @@ func voucher_outbox() -> Dictionary:
|
||||
return out
|
||||
|
||||
|
||||
# 已兑付态重发(宿主 resume 已兑付分支调用——信号面归停点件,续玩不重兑付只重发载荷)
|
||||
func reemit_resolved() -> void:
|
||||
curio_resolved.emit(_resolved.duplicate(true))
|
||||
|
||||
|
||||
# 已兑付态清除(宿主 continue_to_next_level 推进受理时调用——重兑付防御复位面)
|
||||
func clear_resolved() -> void:
|
||||
_resolved = {}
|
||||
|
||||
|
||||
# 兑付态直读(宿主 resume 分支载入已兑付载荷用——副本)
|
||||
func resolved_payload() -> Dictionary:
|
||||
return _resolved.duplicate(true)
|
||||
|
||||
|
||||
# 已兑付态注入(resume 分支——宿主置关序位后由本件承载载荷)
|
||||
# 已兑付态注入+重发(宿主 resume 已兑付分支调用——置态后重发载荷,信号面归停点件:
|
||||
# 续玩不重兑付只重发;原 restore_resolved/reemit_resolved 两步收编一步,时序逐位同)
|
||||
func restore_resolved(payload: Dictionary) -> void:
|
||||
_resolved = payload.duplicate(true)
|
||||
curio_resolved.emit(_resolved.duplicate(true))
|
||||
|
||||
|
||||
# ---- 断点自交片段(应用层拼盘口收件)----
|
||||
# 片段键:bonus_pending/bonus_result/feat_ticket_pity/feat_ticket_sold/refresh_stock/attune_refresh_y
|
||||
#(feat_ticket_* 现役为宿主字段——随停点件迁移同片段承载;读侧 params 回退经参传入)
|
||||
# 片段键:bonus_pending/bonus_result/feat_ticket_pity/feat_ticket_sold
|
||||
#(feat_ticket_* 现役为宿主字段——随停点件迁移同片段承载;refresh_stock/attune_refresh_y 两键
|
||||
# 归 RefreshBank 片段,宿主拼盘同一并集)
|
||||
|
||||
func breakpoint_fragment() -> Dictionary[Variant, Variant]:
|
||||
return {
|
||||
@@ -483,17 +413,11 @@ func breakpoint_fragment() -> Dictionary[Variant, Variant]:
|
||||
"bonus_result": _resolved.duplicate(true),
|
||||
"feat_ticket_pity": _deck.ticket_pity,
|
||||
"feat_ticket_sold": _deck.ticket_sold,
|
||||
"refresh_stock": _refresh_stock,
|
||||
"attune_refresh_y": _refresh_y,
|
||||
}
|
||||
|
||||
|
||||
# 片段回填(凭证计数/刷新存量/Y 池;停点在途态与已兑付态归宿主主分支
|
||||
# 片段回填(凭证计数;停点在途态与已兑付态归宿主主分支
|
||||
# restore_pending/restore_resolved 承接——token_migration 与信号发射时序在彼处)
|
||||
func restore_fragment(frag: Dictionary, params: Dictionary) -> void:
|
||||
func restore_fragment(frag: Dictionary, _params: Dictionary) -> void:
|
||||
_deck.ticket_pity = int(frag.get("feat_ticket_pity", 0)) # T5 扩列——旧档缺键缺省 0(存档兼容)
|
||||
_deck.ticket_sold = int(frag.get("feat_ticket_sold", 0))
|
||||
_refresh_stock = maxi(0, int(frag.get("refresh_stock", 0))) # 旧档缺键缺省 0(clamp 类)
|
||||
# Y 余量优先读断点(花掉不回血=堵「用完退菜单再续玩满血复活」可利用漏洞);缺键=旧版断点,
|
||||
# 回退参数面整额注入(旧档行为零变化)——params_fallback 类
|
||||
_refresh_y = maxi(0, int(frag.get("attune_refresh_y", int(params.get("attune_refresh_y", 0)))))
|
||||
@@ -0,0 +1,111 @@
|
||||
## 刷新账件:三选一刷新三源账唯一所有者(X 刷新件每碰三选一重给/优先消耗不结转、Y 营地升级行
|
||||
## 冒险周期池/用完即无、刷新券存量账/冒险级跨停点持久)+GP 付费刷计数与报价公式
|
||||
## (ceil(商店基价×曲线^本次停点已付费刷)——与商店货架刷新同两个调参旋钮,单点调参)。
|
||||
## 零停点态依赖:免费/付费支出的编排归 CurioStop(查停点态→本件支出→重抽重发),本件只记账。
|
||||
## 断点自交片段(refresh_stock/attune_refresh_y 两键,Y 缺键回退参数面)——拼盘落盘归冒险应用层。
|
||||
## Core 层零 UI;随机零消费。
|
||||
class_name RefreshBank
|
||||
extends RefCounted
|
||||
|
||||
var db: ConfigDb
|
||||
var _refresh_x := 0 # 刷新件余量(每碰三选一重置=X 表值;优先消耗、不结转)
|
||||
var _refresh_y := 0 # Y 冒险周期池(开局参数面注入,用完即无——营地升级行表值)
|
||||
var _paid_refreshes := 0 # 本次停点已付费刷新次数(对齐商店 MC-1——GP 刷新价曲线输入,随停点创建清零=与商店进店清零同构)
|
||||
var _refresh_stock := 0 # 刷新存量账(冒险级——刷新券加仓;**不经 _refresh_x**:X 每停点重置会吞存量=写错账缺陷 F-1)
|
||||
var _refresh_x_cap := 0 # 刷新件余量基数(装配集 effect_kinds 含 bonus_refresh 的行 magnitude 合计——
|
||||
# 宿主冒险开始时预算注入;装配集冒险期间锁死〔契约 8〕故预算与逐停点查询等价)
|
||||
|
||||
|
||||
func _init(db_ref: ConfigDb) -> void:
|
||||
db = db_ref
|
||||
|
||||
|
||||
# 刷新件基数注入(宿主 start_adventure/resume 时点计算——X 每碰三选一重置回此基数)
|
||||
func set_refresh_x_cap(total: int) -> void:
|
||||
_refresh_x_cap = maxi(0, total)
|
||||
|
||||
|
||||
# X 每碰三选一重置回基数(不结转——宿主停点开启时点调用)
|
||||
func reset_x() -> void:
|
||||
_refresh_x = _refresh_x_cap
|
||||
|
||||
|
||||
# Y 池注入(冒险开始/续玩参数面——宿主单写)
|
||||
func set_refresh_y(v: int) -> void:
|
||||
_refresh_y = maxi(0, v)
|
||||
|
||||
|
||||
func refresh_y() -> int:
|
||||
return _refresh_y
|
||||
|
||||
|
||||
# 刷新存量账加仓(refresh_stock_grant 装配即得一次性加仓——只入存量账、临时账不动=V8 契约)
|
||||
func add_refresh_stock(n: int) -> void:
|
||||
_refresh_stock += n
|
||||
|
||||
|
||||
# 刷新存量账读数(T4——刷新券加仓余量单独读面;呈现层与断点消费)
|
||||
func refresh_stock() -> int:
|
||||
return _refresh_stock
|
||||
|
||||
|
||||
# 当前可刷新次数(X 优先 + Y 池 + 刷新存量账——呈现层余量读数口)
|
||||
func free_left() -> int:
|
||||
return _refresh_x + _refresh_y + _refresh_stock
|
||||
|
||||
|
||||
# 免费位消耗(X→Y→存量消耗序逐字;三源全空 false 零副作用)
|
||||
func spend_free() -> bool:
|
||||
# 免费位消耗序:X(刷新件每停点重给,优先消耗)→ Y(营地冒险周期池)→ 存量账(刷新券加仓,
|
||||
# 跨停点持久)
|
||||
if _refresh_x <= 0 and _refresh_y <= 0 and _refresh_stock <= 0:
|
||||
return false
|
||||
if _refresh_x > 0:
|
||||
_refresh_x -= 1
|
||||
elif _refresh_y > 0:
|
||||
_refresh_y -= 1
|
||||
else:
|
||||
_refresh_stock -= 1
|
||||
return true
|
||||
|
||||
|
||||
# 付费刷计数递增(对齐商店 MC-1——免费位 X/Y 用尽后转付费,同旋钮同曲线同钱包)
|
||||
func charge_paid() -> void:
|
||||
_paid_refreshes += 1
|
||||
|
||||
|
||||
# 付费刷计数随停点清零(对齐商店进店清零)
|
||||
func reset_paid() -> void:
|
||||
_paid_refreshes = 0
|
||||
|
||||
|
||||
# 本次 GP 付费刷新价(与商店货架刷新同源:ceil(基价 × 曲线^本次停点已付费刷))
|
||||
func gp_cost() -> int:
|
||||
return int(ceil(float(db.adventure.shop_refresh_cost_base)
|
||||
* pow(db.adventure.shop_refresh_cost_curve, float(_paid_refreshes))))
|
||||
|
||||
|
||||
# 冒险级复位(宿主 reset_adventure_state 调用——刷新三源与付费计数全零)
|
||||
func reset_all() -> void:
|
||||
_refresh_x = 0
|
||||
_refresh_y = 0
|
||||
_paid_refreshes = 0
|
||||
_refresh_stock = 0
|
||||
|
||||
|
||||
# ---- 断点自交片段(应用层拼盘口收件——本件只交自己的账)----
|
||||
# 片段键:refresh_stock/attune_refresh_y
|
||||
|
||||
func breakpoint_fragment() -> Dictionary[Variant, Variant]:
|
||||
return {
|
||||
"refresh_stock": _refresh_stock,
|
||||
"attune_refresh_y": _refresh_y,
|
||||
}
|
||||
|
||||
|
||||
# 片段回填(刷新存量/Y 池。存量缺键缺省 0(clamp 类);Y 余量优先读断点(花掉不回血=堵
|
||||
# 「用完退菜单再续玩满血复活」可利用漏洞);缺键=旧版断点,回退参数面整额注入(旧档行为零变化)
|
||||
# ——params_fallback 类)
|
||||
func restore_fragment(frag: Dictionary, params: Dictionary) -> void:
|
||||
_refresh_stock = maxi(0, int(frag.get("refresh_stock", 0))) # 旧档缺键缺省 0(clamp 类)
|
||||
_refresh_y = maxi(0, int(frag.get("attune_refresh_y", int(params.get("attune_refresh_y", 0)))))
|
||||
@@ -0,0 +1 @@
|
||||
uid://cvh8xf2u44xcg
|
||||
@@ -147,8 +147,8 @@ func show_pick() -> void:
|
||||
func _feed_refresh_display(adventure: AdventureRunner) -> void:
|
||||
var runner = _runner_get.call()
|
||||
var gp := int(runner.gp) if runner != null else 0
|
||||
var cost: int = adventure.stop().curio_refresh_gp_cost()
|
||||
curio_overlay.set_refresh_display(adventure.stop().attune_refresh_left(), cost, gp >= cost)
|
||||
var cost: int = adventure.refresh_bank().gp_cost()
|
||||
curio_overlay.set_refresh_display(adventure.refresh_bank().free_left(), cost, gp >= cost)
|
||||
|
||||
|
||||
func _curio_card_texts(candidates: Array) -> Array:
|
||||
@@ -187,7 +187,7 @@ func on_refresh() -> void:
|
||||
var refreshed: bool = adventure.stop().refresh_curio()
|
||||
if not refreshed:
|
||||
# 免费位(X/Y)已穷——GP 付费档(先扣钱包后重抽,余额不足拒绝零副作用)
|
||||
var cost: int = adventure.stop().curio_refresh_gp_cost()
|
||||
var cost: int = adventure.refresh_bank().gp_cost()
|
||||
if runner == null or not runner.spend_gp(cost):
|
||||
toast.show_toast("GP不足——刷新失败", "warn")
|
||||
return
|
||||
|
||||
@@ -396,7 +396,7 @@ func _smoke_adventure() -> void:
|
||||
var dk := String((a11._curio.deck[di] as Dictionary).get("kind", ""))
|
||||
if dk == "attack" or dk == "ap":
|
||||
a11._curio.deck.remove_at(di)
|
||||
var draw: Array = a11.stop().draw_candidates()
|
||||
var draw: Array = a11.deck().draw_candidates()
|
||||
var draw_kinds := {}
|
||||
for t in draw:
|
||||
draw_kinds[String((t as Dictionary).get("kind", ""))] = true
|
||||
@@ -406,7 +406,7 @@ func _smoke_adventure() -> void:
|
||||
# white-box 测试属停点渠道,零投放零行为)
|
||||
var treasure_seen := false
|
||||
for i in 40:
|
||||
for t in a11.stop().draw_candidates():
|
||||
for t in a11.deck().draw_candidates():
|
||||
if String((t as Dictionary).get("kind", "")) == "treasure_item":
|
||||
treasure_seen = true
|
||||
break
|
||||
@@ -518,10 +518,10 @@ func _smoke_feats() -> void:
|
||||
var feat_seen: Dictionary = {}
|
||||
var node_relic_seen := false
|
||||
for i in 60:
|
||||
for t in a_r.stop().draw_candidates("level_clear"):
|
||||
for t in a_r.deck().draw_candidates("level_clear"):
|
||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||
feat_seen[String(t.get("feat_id", ""))] = true
|
||||
for t in a_r.stop().draw_candidates("reward_node"):
|
||||
for t in a_r.deck().draw_candidates("reward_node"):
|
||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||
node_relic_seen = true
|
||||
Kit.check("关卡完成渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)", feat_seen.size() >= 2)
|
||||
@@ -531,7 +531,7 @@ func _smoke_feats() -> void:
|
||||
a_t.start_adventure(91)
|
||||
var stock_before := {}
|
||||
for kk in ["attack", "gp", "heal"]:
|
||||
stock_before[kk] = a_t.stop().pool_stock(kk)
|
||||
stock_before[kk] = a_t.deck().pool_stock(kk)
|
||||
a_t.stop()._node_pending = {"candidates": [{"kind": "feat_ticket", "tier": "excellent"}, {"kind": "attack", "tier": "common"}]}
|
||||
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)
|
||||
@@ -540,7 +540,7 @@ func _smoke_feats() -> void:
|
||||
if String((t as Dictionary).get("kind", "")) != "relic":
|
||||
tkt_all_relic = false
|
||||
Kit.check("A5 拿到即开——停点候选已替换为专长三选一(全 relic)", tkt_all_relic)
|
||||
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 零摘牌——专长凭证兑付不动令牌库(不进持有集)", a_t.deck().pool_stock("attack") == int(stock_before["attack"]) and a_t.deck().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.stop().apply_node_curio("relic", String((tkt_cands[0] as Dictionary).get("tier", "rare")), String((tkt_cands[0] as Dictionary).get("feat_id", "")))
|
||||
@@ -549,21 +549,21 @@ func _smoke_feats() -> void:
|
||||
a_t.stop()._deck.ticket_sold = 0 # A5 已兑付一枚专长凭证(sold=1=limit)——A6 段重置计数隔离验证
|
||||
a_t.stop()._deck.ticket_pity = int(a_t.db.adventure.feat_ticket_pity_step)
|
||||
var rg_t: RandomNumberGenerator = a_t.rng.open_stream("reward_pick")
|
||||
Kit.check("A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)", a_t.stop().feat_ticket_consider(rg_t))
|
||||
Kit.check("A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)", a_t.deck().feat_ticket_consider(rg_t))
|
||||
Kit.check("A6 触发后清零", a_t.stop()._deck.ticket_pity == 0)
|
||||
a_t.stop()._deck.ticket_sold = int(a_t.db.adventure.feat_ticket_limit)
|
||||
a_t.stop()._deck.ticket_pity = int(a_t.db.adventure.feat_ticket_pity_step)
|
||||
Kit.check("A6 限购满不再触发(保底冻结)", not a_t.stop().feat_ticket_consider(rg_t))
|
||||
Kit.check("A6 限购满不再触发(保底冻结)", not a_t.deck().feat_ticket_consider(rg_t))
|
||||
a_t.stop()._deck.ticket_sold = 0
|
||||
a_t.stop()._deck.ticket_pity = 0
|
||||
var offered := false
|
||||
for i in 200:
|
||||
if a_t.stop().feat_ticket_consider(rg_t):
|
||||
if a_t.deck().feat_ticket_consider(rg_t):
|
||||
offered = true
|
||||
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.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)
|
||||
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.deck().pool_stock("relic") == deck_relic - 1)
|
||||
# 断点往返三条(基点 1420–1426:relics 快照/计数器键/余库逐枚还原)按主会话裁决②改判集成宿主——
|
||||
# 驱动代码(save_manager 装配+_on_level_finished+快照取读)随三条同去,集成批落位时重建。
|
||||
_db.feats = live_rows # 还原现行表(共享 _db 防污染——后续冒烟段零感知)
|
||||
|
||||
@@ -26,24 +26,24 @@ func _init() -> void:
|
||||
# ---- ② 免费位优先序 X→Y(白盒造关卡完成停点——smoke_all ⑫ 同构)----
|
||||
adr.stop()._pending = {"candidates": _cands(), "payload": {}}
|
||||
Kit.check_quiet(adr.stop().curio_stop_pending() == true, "关卡完成停点在途=谓词 true")
|
||||
adr.stop()._refresh_x = 1
|
||||
adr.stop()._refresh_y = 2
|
||||
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)")
|
||||
adr.refresh_bank()._refresh_x = 1
|
||||
adr.refresh_bank()._refresh_y = 2
|
||||
Kit.check_quiet(adr.stop().refresh_curio() and adr.refresh_bank()._refresh_x == 0 and adr.refresh_bank()._refresh_y == 2, "X 优先消耗(x1y2→x0y2)")
|
||||
Kit.check_quiet(adr.stop().refresh_curio() and adr.refresh_bank()._refresh_y == 1, "X 穷转 Y 池(y2→y1)")
|
||||
|
||||
# ---- ③ GP 付费档:计数递增爬价 + 零触碰免费位 ----
|
||||
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)")
|
||||
Kit.check_quiet(adr.refresh_bank().gp_cost() == base, "首刷报价=基价(免费位尚余 y1,报价独立于免费位)")
|
||||
Kit.check_quiet(adr.stop().refresh_curio_paid() and adr.refresh_bank()._refresh_y == 1, "付费刷新不触碰免费位(y1 保持)")
|
||||
Kit.check_quiet(adr.refresh_bank().gp_cost() == int(ceil(float(base) * curve)), "二刷价爬升=ceil(基价×曲线)(已付费刷=1)")
|
||||
Kit.check_quiet(adr.stop().refresh_curio_paid() and adr.refresh_bank().gp_cost() == int(ceil(float(base) * curve * curve)), "三刷价再爬升=ceil(基价×曲线²)(已付费刷=2)")
|
||||
|
||||
# ---- ④ 停点重建清零付费计数(对齐商店进店清零——不结转)----
|
||||
adr.stop().present_node_curio(_cands())
|
||||
Kit.check_quiet(adr.stop()._paid_refreshes == 0 and adr.stop().curio_refresh_gp_cost() == base, "节点停点重建=付费计数清零回基价")
|
||||
Kit.check_quiet(adr.refresh_bank()._paid_refreshes == 0 and adr.refresh_bank().gp_cost() == base, "节点停点重建=付费计数清零回基价")
|
||||
adr.stop().refresh_curio_paid()
|
||||
Kit.check_quiet(adr.stop()._paid_refreshes == 1, "付费刷新计数在途递增(=1)")
|
||||
Kit.check_quiet(adr.refresh_bank()._paid_refreshes == 1, "付费刷新计数在途递增(=1)")
|
||||
adr.stop().present_node_curio(_cands())
|
||||
Kit.check_quiet(adr.stop().curio_refresh_gp_cost() == base, "再碰三选一付费计数仍清零(同 X 余量不结转口径)")
|
||||
Kit.check_quiet(adr.refresh_bank().gp_cost() == base, "再碰三选一付费计数仍清零(同 X 余量不结转口径)")
|
||||
|
||||
# ---- ⑤ 钱包单口支出 spend_gp(LevelRunner——三选一付费档与商店刷新共用同一冒险钱包)----
|
||||
var lr = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
@@ -56,10 +56,10 @@ func _init() -> void:
|
||||
var bp_y := {"level_index": 1, "hero_hp": 10, "attune_refresh_y": 2, "graph_gen_v2": 1}
|
||||
var adr_r = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
Kit.check_quiet(adr_r.resume_adventure(bp_y, {"hero_override": {}, "attune_refresh_y": 3}), "续玩受理(最小断点+参数面)")
|
||||
Kit.check_quiet(adr_r.stop()._refresh_y == 2, "断点有键=Y 余量随断点还原(花 1 剩 2——不再整额重注入=满血复活堵死)")
|
||||
Kit.check_quiet(adr_r.refresh_bank()._refresh_y == 2, "断点有键=Y 余量随断点还原(花 1 剩 2——不再整额重注入=满血复活堵死)")
|
||||
var adr_o = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
Kit.check_quiet(adr_o.resume_adventure({"level_index": 1, "hero_hp": 10, "graph_gen_v2": 1}, {"hero_override": {}, "attune_refresh_y": 3}), "旧断点续玩受理(快照缺键)")
|
||||
Kit.check_quiet(adr_o.stop()._refresh_y == 3, "旧断点缺键=回退参数面整额注入(旧档行为零变化)")
|
||||
Kit.check_quiet(adr_o.refresh_bank()._refresh_y == 3, "旧断点缺键=回退参数面整额注入(旧档行为零变化)")
|
||||
|
||||
quit(Kit.finish("curio_refresh_paid"))
|
||||
|
||||
|
||||
@@ -31,22 +31,22 @@ func _init() -> void:
|
||||
"V2:新冒险 adventure 桶置空(start_adventure 后宿主桶 adventure_counters 为空)〔无状态化 V2〕")
|
||||
|
||||
# V9-1 刷新券:入存量账(冒险级)——反例=写进 _attune_refresh_x(X 每停点重置会吞存量)
|
||||
a.stop()._reset_refresh_x() # X 归零基线(模拟新停点)
|
||||
a.refresh_bank().reset_x() # X 归零基线(模拟新停点)
|
||||
Kit.check_quiet(a.stop()._apply_curio_pick("refresh", "excellent", 1), "刷新券兑付受理〔专长批 V9〕")
|
||||
Kit.check_quiet(a.stop().refresh_stock() == 1 and a.stop()._refresh_x == 0,
|
||||
Kit.check_quiet(a.refresh_bank().refresh_stock() == 1 and a.refresh_bank()._refresh_x == 0,
|
||||
"F-1 修复:刷新券入存量账、X 账零写入(写错账反例不复发)〔专长批 V9〕")
|
||||
a.stop()._reset_refresh_x() # 再碰停点——X 重置但存量不吞
|
||||
Kit.check_quiet(a.stop().refresh_stock() == 1 and a.stop().attune_refresh_left() == 1,
|
||||
a.refresh_bank().reset_x() # 再碰停点——X 重置但存量不吞
|
||||
Kit.check_quiet(a.refresh_bank().refresh_stock() == 1 and a.refresh_bank().free_left() == 1,
|
||||
"F-1 修复:停点重置后存量账存活(attune_refresh_left 含第三源)〔专长批 V9〕")
|
||||
# 消耗序:X 优先 → 存量(无 X 无 Y 时吃存量;refresh_curio 前置=在途停点)
|
||||
a.stop()._refresh_stock = 1
|
||||
a.stop()._refresh_y = 0
|
||||
a.refresh_bank()._refresh_stock = 1
|
||||
a.refresh_bank()._refresh_y = 0
|
||||
a.stop()._node_pending = {"candidates": [{"kind": "attack", "tier": "common"}]}
|
||||
Kit.check_quiet(a.stop().refresh_curio() and a.stop().refresh_stock() == 0,
|
||||
Kit.check_quiet(a.stop().refresh_curio() and a.refresh_bank().refresh_stock() == 0,
|
||||
"刷新消耗序:X/Y 空时吃存量账〔专长批 V9〕")
|
||||
a.stop()._node_pending = {}
|
||||
# 摘牌:选中即消尽(禁早退路径零摘牌)
|
||||
var deck_refresh_before: int = a.stop().pool_stock("refresh")
|
||||
var deck_refresh_before: int = a.deck().pool_stock("refresh")
|
||||
Kit.check_quiet(deck_refresh_before == 0 or true, "牌库 refresh 令牌读数在域(投放 %d)" % deck_refresh_before)
|
||||
|
||||
# V9-2 储备金袋:待注入账 + 战首注入(节点级)
|
||||
@@ -92,7 +92,7 @@ func _init() -> void:
|
||||
# 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.stop().pick_curio("rap", "rare") and a.stop().pool_stock("rap") == 0,
|
||||
Kit.check_quiet(a.stop().pick_curio("rap", "rare") and a.deck().pool_stock("rap") == 0,
|
||||
"凭证令牌:pick_curio 摘牌(选中踢池——与属性件同语义)〔专长批 V9〕")
|
||||
|
||||
# V10-1 留任 2 件路由与值不变:gp 钱包/heal 当即回血(每次点选独立停点态)
|
||||
@@ -116,7 +116,7 @@ func _init() -> void:
|
||||
var sm = load("res://core/save_manager.gd").new("user://_feat_vouchers_bp.json")
|
||||
sm.load_from_disk()
|
||||
a.adventure_counters = {"pain_layers": 3, "adv_twin_digs": 5} # 冒险桶宿主字典直写(测试构造面)
|
||||
a.stop()._refresh_stock = 2
|
||||
a.refresh_bank()._refresh_stock = 2
|
||||
a.set_save_manager(sm)
|
||||
a._on_level_finished({"result": "victory", "hp": 10, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
var bp16: Dictionary = sm.breakpoint_snapshot()
|
||||
@@ -139,7 +139,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.stop().refresh_stock() == 2,
|
||||
Kit.check_quiet(int(b2.adventure_counters.get("pain_layers", -1)) == 3 and b2.refresh_bank().refresh_stock() == 2,
|
||||
"V16:续玩回填逐项一致(adventure 桶随断点往返·计数恢复)〔无状态化 V3〕")
|
||||
# V25 完美保险专长验收组(R2 全项:装载/限次一次/计数纯净/两账相减/跨战清零/事件不吞)
|
||||
var fi: Dictionary = db.feats.row_by_id("perfect_insurance")
|
||||
|
||||
@@ -1357,13 +1357,13 @@ func _smoke_shop_pool() -> void:
|
||||
attack_seen = true
|
||||
break
|
||||
Kit.check("库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)", not attack_seen)
|
||||
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)
|
||||
var stock_before: int = a8.deck().pool_stock("attack")
|
||||
Kit.check("池空消费拒绝(handler 零副作用——买光即枯竭硬闸)", not a8.stop().consume_pool_purchase("attack", "common") and a8.deck().pool_stock("attack") == stock_before)
|
||||
# ⑥ 跨渠道共库 shop→reward 向:回填一枚 attack 令牌 → 消费口受理 → 奖励牌库同步摘牌
|
||||
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():
|
||||
for t6 in a8.deck().draw_candidates():
|
||||
if String((t6 as Dictionary).get("kind", "")) == "attack":
|
||||
reward_attack_seen = true
|
||||
break
|
||||
|
||||
@@ -290,17 +290,17 @@ func _meta_settle_and_workshop() -> void:
|
||||
Kit.check("充能按装备 active_charged 件种行(新目录表值:线香 battle 级 1 次/天眼 level 级 1 次——周期重置点归 V8 逐形态面;读数=行派生)",
|
||||
a12._loadout.charges_left("incense_stick", a12.runner().treasure_inventory) == 1 and a12._loadout.charges_left("sky_eye", a12.runner().treasure_inventory) == 1)
|
||||
a12._attunement = []
|
||||
a12.stop()._refresh_x = 1
|
||||
a12.stop()._refresh_y = 2
|
||||
a12.refresh_bank()._refresh_x = 1
|
||||
a12.refresh_bank()._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.stop().refresh_curio() and a12.stop()._refresh_x == 0 and a12.stop()._refresh_y == 2)
|
||||
a12.stop().refresh_curio() and a12.refresh_bank()._refresh_x == 0 and a12.refresh_bank()._refresh_y == 2)
|
||||
Kit.check("刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)",
|
||||
a12.stop().refresh_curio() and a12.stop().refresh_curio() and a12.stop()._refresh_y == 0
|
||||
a12.stop().refresh_curio() and a12.stop().refresh_curio() and a12.refresh_bank()._refresh_y == 0
|
||||
and a12.stop().refresh_curio() == false)
|
||||
a12.stop()._reset_refresh_x()
|
||||
a12.refresh_bank().reset_x()
|
||||
Kit.check("刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕",
|
||||
a12.stop()._refresh_x == 0 and a12.stop().attune_refresh_left() == 0)
|
||||
a12.refresh_bank()._refresh_x == 0 and a12.refresh_bank().free_left() == 0)
|
||||
# ⑬ 主菜单开局传参面(开新/续玩两入口同调三键随开局下传防回归:真实入口曾
|
||||
# 三键恒空(缺陷①),冒烟夹具直呼 runner 参数面拦不住——此处直呼 main.build_adventure_params
|
||||
# 单测统一构造面(static 免实例化整菜单场景;真实两入口共用本构造),取值与营地投影同源)
|
||||
|
||||
@@ -73,7 +73,7 @@ func check_v7(a, db) -> void:
|
||||
_tok("armor", "common"), _tok("heal", "excellent"), _tok("refresh", "excellent"), _tok("ap", "rare")]
|
||||
var hi_seen := false
|
||||
for i in 40:
|
||||
var cands: Array = a.stop().draw_candidates("reward_node")
|
||||
var cands: Array = a.deck().draw_candidates("reward_node")
|
||||
if cands.size() != 3:
|
||||
continue
|
||||
var hi := 0
|
||||
@@ -87,7 +87,7 @@ func check_v7(a, db) -> void:
|
||||
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")
|
||||
var cands2: Array = a.deck().draw_candidates("reward_node")
|
||||
if cands2.size() != 3:
|
||||
ok_fb = false
|
||||
break
|
||||
@@ -100,13 +100,13 @@ func check_v7(a, db) -> void:
|
||||
_tok("feat_ticket", "excellent")]
|
||||
var ticket_seen := false
|
||||
for i in 30:
|
||||
for t in a.stop().draw_candidates("reward_node"):
|
||||
for t in a.deck().draw_candidates("reward_node"):
|
||||
if String(t.get("kind", "")) == "feat_ticket":
|
||||
ticket_seen = true
|
||||
Kit.check_quiet(not ticket_seen, "凭证不进抽签池(feat_ticket 只经保底注入通道)〔专长批 V7〕")
|
||||
# 构成④:池空=零候选合法态(同族兜底入口)
|
||||
a._curio.deck = []
|
||||
Kit.check_quiet(a.stop().draw_candidates("reward_node").is_empty(), "池空=零候选合法态(skip_empty_curio 兜底入口)〔专长批 V7〕")
|
||||
Kit.check_quiet(a.deck().draw_candidates("reward_node").is_empty(), "池空=零候选合法态(skip_empty_curio 兜底入口)〔专长批 V7〕")
|
||||
|
||||
|
||||
func check_v6(b, db) -> void:
|
||||
@@ -115,22 +115,22 @@ func check_v6(b, db) -> void:
|
||||
return
|
||||
# 拿了消耗:直置停点候选后 pick_curio——牌库摘牌单点
|
||||
b._curio.deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "excellent")]
|
||||
var gp_before: int = b.stop().pool_stock("gp")
|
||||
var gp_before: int = b.deck().pool_stock("gp")
|
||||
b.stop()._pending = {"candidates": [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "excellent")]}
|
||||
Kit.check_quiet(b.stop().pick_curio("gp", "common") and b.stop().pool_stock("gp") == gp_before - 1,
|
||||
Kit.check_quiet(b.stop().pick_curio("gp", "common") and b.deck().pool_stock("gp") == gp_before - 1,
|
||||
"拿了消耗:pick_curio 后牌库摘牌(选中踢池)〔专长批 V6〕")
|
||||
# 没拿回池:抽候选不摘牌——重复抽签同令牌可再现
|
||||
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")
|
||||
var cands: Array = b.deck().draw_candidates("reward_node")
|
||||
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()
|
||||
for i in 10:
|
||||
b.stop().draw_candidates("reward_node")
|
||||
b.deck().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]):
|
||||
@@ -145,7 +145,7 @@ func check_v12(m, db) -> void:
|
||||
var tier_n := {"common": 0, "excellent": 0, "rare": 0, "legendary": 0}
|
||||
var total := 0
|
||||
for i in 300:
|
||||
for t in m.stop().draw_candidates("level_clear"):
|
||||
for t in m.deck().draw_candidates("level_clear"):
|
||||
tier_n[String(t.get("tier", ""))] = int(tier_n.get(String(t.get("tier", "")), 0)) + 1
|
||||
total += 1
|
||||
Kit.check_quiet(total == 900, "V12 前置:300 停点 ×3 候选=900 席(实 %d)" % total)
|
||||
@@ -163,13 +163,13 @@ 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.stop().pool_stock("gp")
|
||||
var gp_tokens: int = c.deck().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")]
|
||||
for i in 20:
|
||||
c.stop().draw_candidates("reward_node")
|
||||
c.deck().draw_candidates("reward_node")
|
||||
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 满→提前退役(候选视图剔除、牌库本体不动)
|
||||
@@ -182,7 +182,7 @@ func check_v8(c, db) -> void:
|
||||
gp_in_deck += 1
|
||||
var gp_cand := false
|
||||
for i in 30:
|
||||
for t in c.stop().draw_candidates("reward_node"):
|
||||
for t in c.deck().draw_candidates("reward_node"):
|
||||
if String(t.get("kind", "")) == "gp":
|
||||
gp_cand = true
|
||||
Kit.check_quiet(gp_in_deck >= 0 and not gp_cand,
|
||||
|
||||
@@ -117,9 +117,9 @@ 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.stop().refresh_stock()) == 1 and int(adr5.stop().attune_refresh_left()) == 1,
|
||||
Kit.check_quiet(int(adr5.refresh_bank().refresh_stock()) == 1 and int(adr5.refresh_bank().free_left()) == 1,
|
||||
"刷新练习:存量账 +1(装配即得一次性——attune_refresh_left 合计含 stock)")
|
||||
Kit.check_quiet(int(adr5.stop()._refresh_x) == 0,
|
||||
Kit.check_quiet(int(adr5.refresh_bank()._refresh_x) == 0,
|
||||
"刷新练习反例:临时账 X 不动(写临时账/重复加仓=违规形态)")
|
||||
|
||||
# ⑥ 储备罐(转存率 125=1.25:1 整除口径 / 100=1:1 逐位同旧)
|
||||
|
||||
Reference in new issue
Block a user