feat(vouchers): 迭代20 T4 凭证件五件实装+F-1 刷新写错账修复(专长实装批 B段二)

五件路由(_apply_curio_pick 通道面):刷新券→刷新存量账 _refresh_stock(**F-1 修复**:
退役 _attune_refresh_x 写口——X 每停点重置吞存量=写错账缺陷正主;消耗序 X→Y→存量,
attune_refresh_left 扩第三源;旧早退路径零摘牌缺陷同批修正=选中即消尽)/储备金袋→
_pending_rap 战首 RAP 注入(action_economy.grant_reserve_ap 新口)/同调电池→
_battery_pending+choose_battery_target 玩家点选跨层写 attune_charges(禁自动替选——
装配集/主动充能件双校验拒绝)/时沙→_time_sand_queue 战首首轮 AP 队列(池增量不改上限,
hero_ap_bonus 同通道)/完美保险→_pending_insurance 战首豁免次数(flag_miss 首见判定
_perfect_hit_false 回退=完美谓词无痕,节点级未用即过期)。

注入链:runner set_voucher_grants 三账→enc voucher_*→编排器消费,节点级一次性注入即清零。
配额投放:凭证四件入稀有档(rap 2/battery 1/时沙 2/保险 1)→结构锚 rare 9/总量 104
(已裁换代授权内 provisional——偏离 GDD 占位记录随代拍板追认单)。
断点扩列(T7 前置):refresh_stock/battery_pending 随档+resume 回填(旧档缺键缺省 0)。

测试:feat_vouchers_test 新套 20 checks(V9 逐件路由+F-1 反例钉面/V10 留任路由+AP 属性轴)
入 run_all 21 套全绿;冒烟 863/0;smoke 牌库数 98→104 系换代。
This commit is contained in:
DKChan committed 2026-10-12 00:08:00 +08:00
1 parent 54ab330beb
commit 1387aedc94
8 files changed
+218 -22

No files matched your search

+5
View File
@@ -179,6 +179,11 @@ func request_stop() -> bool:
return true
# 储备池注入(迭代 20 T4——储备金袋凭证:战首 RAP +N,节点级一次性;任意相位受理=战首注入时 Idle/Active 均合法)
func grant_reserve_ap(n: int) -> void:
_rap += maxi(0, n)
# 轮末停手转存决策(round_end 发射点调用——编排器授予段尾):transfer=true=剩余 AP 转蓄力层
# (上限 3,整备军备面);false=1:1 转存 RAP(缺省)。非停手轮(cleared/耗尽)_last_stop_ap=0
# 两分支均零操作。转蓄后达上限的沿发 charge_full(R6 框架先行——现无订阅者)。
+76 -7
View File
@@ -78,6 +78,12 @@ var _attune_refresh_y := 0 # Y 冒险周期池(开局参数面
var _curio_paid_refreshes := 0 # 本次停点已付费刷新次数(2026-10-06 用户令对齐商店 MC-1——GP 刷新价曲线输入,随停点创建清零=与商店进店清零同构)
var attune_charges := {} # 同调主动件冒险充能账本(迭代 11 第①批 T6——跨关卡共享 dict;settlement use_treasure 扣减直改同引用;断点随 attunement_charges 键入档)
var adventure_counters := {} # adventure 域计数器账本(迭代 20 T6——攒层/付费计数等跨战持久件账;效果引擎 rebuild 回灌/战末导出回写共享引用;断点随 adventure_counters 键入档)
# ---- 凭证件三本新账(迭代 20 T4——奇物.md 凭证件表新 5 件;F-1 刷新写错账修复承载面)----
var _refresh_stock := 0 # 刷新存量账(冒险级——刷新券加仓;**不经 _attune_refresh_x**:X 每停点重置会吞存量=旧写错账缺陷 F-1)
var _pending_rap := 0 # 储备金袋待注入(节点级——下一战 RAP +N,战首注入后清零)
var _battery_pending := 0 # 同调电池待分配充能(冒险级——choose_battery_target 玩家点选消费,禁自动替选)
var _time_sand_queue := 0 # 时沙待注入(回合级——下一战首轮 AP +N,战首入队后清零)
var _pending_insurance := 0 # 完美保险待注入(节点级——下一战标错豁免 N 次,战首注入后清零;战内未用即过期)
var _save_manager = null # 断点承载注入(迭代 8 卡①——null = 不写断点;模拟/冒烟缺省)
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
# ---- 三选一停点态(迭代 10 奖励重构批)----
@@ -224,6 +230,8 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
var bp_counters = bp.get("adventure_counters", null)
if bp_counters is Dictionary:
adventure_counters = (bp_counters as Dictionary).duplicate(true)
_refresh_stock = maxi(0, int(bp.get("refresh_stock", 0))) # 迭代 20 T4/T7——旧档缺键缺省 0
_battery_pending = maxi(0, int(bp.get("battery_pending", 0)))
var bp_result: Dictionary = bp.get("bonus_result", {}) as Dictionary
if not bp_pending.is_empty():
_pending_next_level = true
@@ -282,6 +290,11 @@ func _reset_curio_state() -> void:
_keystone_drops = []
_node_curio_pending = {}
adventure_counters = {}
_refresh_stock = 0
_pending_rap = 0
_battery_pending = 0
_time_sand_queue = 0
_pending_insurance = 0
# 令牌池构建(TP0 v2.2——按表 kind×tier 配额展开为持久多重集;构建序确定 = CURIO_KINDS×TIERS 逐层,
@@ -421,15 +434,32 @@ func _feat_payload_keys(feat_id: String) -> Dictionary:
# 按挂点消费,档位质变由 FeatsEngine 求值)。兑付即从 _curio_deck 摘除该令牌(选中踢池;
# 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。
func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String = "") -> bool:
if kind == "refresh":
_attune_refresh_x += value # T6——刷新次数件兑付=X 余量增量(作用三选一界面非角色,不进加算区——registry refresh_boundary)
return true
if kind == "relic" and db.feat_row(feat_id).is_empty():
push_error("[冒险结构] relic 兑付失败——表无此件: %s" % feat_id)
return false # 先校验后摘牌(令牌零丢失)
if not _consume_deck_token(kind, tier, feat_id):
push_error("[冒险结构] 令牌消费失败——牌库无此令牌: %s|%s|%s" % [kind, tier, feat_id])
return false
if kind == "refresh":
_refresh_stock += value # T4 F-1 修复——刷新券入刷新存量账(冒险级;**退役 _attune_refresh_x 写口**:X 每停点 _reset_refresh_x 重置会吞存量=写错账缺陷正主;摘牌先于落账=选中即消尽,旧早退路径零摘牌缺陷同批修正)
_curio_taken["refresh"] = int(_curio_taken.get("refresh", 0)) + 1
return true
if kind == "rap":
_pending_rap += value # T4——储备金袋:下一战 RAP +N(节点级,战首注入后清零)
_curio_taken["rap"] = int(_curio_taken.get("rap", 0)) + 1
return true
if kind == "attune_battery":
_battery_pending += value # T4——同调电池:待分配充能(choose_battery_target 玩家点选消费——禁自动替选)
_curio_taken["attune_battery"] = int(_curio_taken.get("attune_battery", 0)) + 1
return true
if kind == "time_sand":
_time_sand_queue += value # T4——时沙:下一战首轮 AP +N(回合级池增量,不改上限)
_curio_taken["time_sand"] = int(_curio_taken.get("time_sand", 0)) + 1
return true
if kind == "perfect_insurance":
_pending_insurance += value # T4——完美保险:下一战标错豁免 N 次(节点级,未用即过期)
_curio_taken["perfect_insurance"] = int(_curio_taken.get("perfect_insurance", 0)) + 1
return true
if kind == "relic":
if not _feats.has(feat_id):
_feats.append(feat_id)
@@ -670,9 +700,37 @@ func _reset_refresh_x() -> void:
_attune_refresh_x = _attune_refresh_x_total()
# 当前可刷新次数(X 优先 + Y 池——呈现层余量读数口)
# 当前可刷新次数(X 优先 + Y 池 + 刷新存量账——呈现层余量读数口;T4 F-1 修复扩第三源)
func attune_refresh_left() -> int:
return _attune_refresh_x + _attune_refresh_y
return _attune_refresh_x + _attune_refresh_y + _refresh_stock
# 刷新存量账读数(T4——刷新券加仓余量单独读面;呈现层与断点消费)
func refresh_stock() -> int:
return _refresh_stock
# 同调电池充能分配(T4——玩家点选哪件已装配主动秘宝 +N;**禁自动替选**:
# 无待分配/件不在装配集/非主动充能件=拒绝零副作用)
func choose_battery_target(item_id: String) -> bool:
if _battery_pending <= 0:
push_error("[冒险结构] 电池分配拒绝——无待分配充能")
return false
if not _attunement.has(item_id):
push_error("[冒险结构] 电池分配拒绝——件不在装配集: %s" % item_id)
return false
var arow: Dictionary = db.treasure_item_row(item_id)
if String(arow.get("form", "")) != "active_charged":
push_error("[冒险结构] 电池分配拒绝——非主动充能件: %s" % item_id)
return false
_battery_pending -= 1
attune_charges[item_id] = int(attune_charges.get(item_id, 0)) + 1
return true
# 待分配充能读数(呈现层电池选件面数据源)
func battery_pending() -> int:
return _battery_pending
# 节点停点在途态读回(组合根重 present 用——candidates/aux_detail 值拷贝)
@@ -712,12 +770,16 @@ func _refresh_curio_common(paid: bool) -> bool:
if paid:
_curio_paid_refreshes += 1
else:
if _attune_refresh_x <= 0 and _attune_refresh_y <= 0:
# 免费位消耗序:X(刷新件每停点重给,优先消耗)→ Y(营地冒险周期池)→ 存量账(刷新券加仓,
# 迭代 20 T4 F-1 修复新账——跨停点持久)
if _attune_refresh_x <= 0 and _attune_refresh_y <= 0 and _refresh_stock <= 0:
return false
if _attune_refresh_x > 0:
_attune_refresh_x -= 1
else:
elif _attune_refresh_y > 0:
_attune_refresh_y -= 1
else:
_refresh_stock -= 1
if not _curio_pending.is_empty():
var cands: Array = _draw_curio_candidates("level_clear")
_curio_pending["candidates"] = cands.duplicate(true)
@@ -998,6 +1060,11 @@ func _start_current_level() -> bool:
# 迭代 20 T2——cap 检查回调四参注入)
_runner.set_shop_pool(_curio_deck, Callable(self, "_consume_pool_purchase"), Callable(self, "_feat_ticket_consider"), Callable(self, "_curio_cap_ok"))
_runner.set_adventure_counters(adventure_counters) # 迭代 20 T6——adventure 域计数器共享引用下传
# 迭代 20 T4——凭证件三账注入(节点级/回合级一次性:注入即清零,未用即过期=寿命档语义)
_runner.set_voucher_grants(_pending_rap, _time_sand_queue, _pending_insurance)
_pending_rap = 0
_time_sand_queue = 0
_pending_insurance = 0
# 三选一道具件跨关携带注入(迭代 10)——迭代 11 第①批 T7 退役:aux 卡出池后 carry 恒空集,
# 注入面随停点渠道退役拆除(容器字段保留=替换子交互机械惰性承载,零投放零行为)
level_started.emit(level_index, String(_runner.level_row.get("display_name", "")))
@@ -1147,6 +1214,8 @@ func _snapshot_breakpoint() -> void:
"feat_ticket_pity": _feat_ticket_pity, # T5 门票保底计数(扩列向后兼容——旧档缺键缺省 0)
"feat_ticket_sold": _feat_ticket_sold, # T5 门票限购计数(同上)
"adventure_counters": adventure_counters.duplicate(true), # 迭代 20 T6/T7——adventure 域计数器随断点(续玩跨战账不丢)
"refresh_stock": _refresh_stock, # 迭代 20 T4/T7——刷新存量账随断点(F-1 新账)
"battery_pending": _battery_pending, # 迭代 20 T4/T7——电池待分配充能随断点
"bonus_result": _curio_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
"bonus_aux_carry": _bonus_treasure_carry.duplicate(true), # 道具件跨关携带(注入前中断防静默丢失)
"keystone_drops": _keystone_drops.duplicate(true), # 逐杀掉落明细(摘要归因续接——迭代 10)
+13 -1
View File
@@ -86,6 +86,7 @@ var _fx_bonus_delta := {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} #
var _fx_new_resource_types := {} # 本战已见资源类型集(地质学家 fact new_resource_type 判定——battle 域)
var _fx_pending_reveals: Array[Dictionary] = [] # 揭示效果待兑队列(见血开窍/斩首学者/地质学家/跳格采掘——授予段末统一兑付,防轮 facts 迭代中追加重入)
var _fx_revealing := false # fx 揭示兑付中标记(_on_cell_revealed 缓冲旁路——fx 揭示的奖励由兑付点直接入账,防二次缓冲二次兑付)
var _fx_insurance := 0 # 完美保险余量(迭代 20 T4——本战标错豁免次数;消费于 flag_miss 首见判定)
# 特性运行态(内容数据驱动扩展批 T3——relic 持有集经 enc.feats 注入:效果行为=数据行 triggers 经
# 效果引擎分发;魔矢每战首次计数 battle 域。空集=零扰动)
var _feat_first_dig_used := false
@@ -342,9 +343,14 @@ func begin_battle(enc: Dictionary) -> bool:
# 迭代 10 奖励重构批——跨冒险行动点加成轴(统一冒险加算区第四轴):经排队修正通道兑现
# 「每场战斗开始首轮 AP +加成值」(queue_ap_delta = 下轮开轮基线加算、notify_round_start 兑现后
# 清零——首轮即消费,后续轮次不重复兑现;语义锚 = 行动经济 GDD「跨冒险行动点加成轴」节)
var ap_bonus: int = int(enc.get("hero_ap_bonus", 0))
var ap_bonus: int = int(enc.get("hero_ap_bonus", 0)) + int(enc.get("voucher_time_sand", 0)) # 迭代 20 T4——时沙同通道(首轮池增量,不改上限)
if ap_bonus > 0:
action_economy.queue_ap_delta(ap_bonus)
# 迭代 20 T4——储备金袋(节点级 RAP 注入)与完美保险(标错豁免次数)接收
var voucher_rap: int = int(enc.get("voucher_rap", 0))
if voucher_rap > 0:
action_economy.grant_reserve_ap(voucher_rap)
_fx_insurance = int(enc.get("voucher_insurance", 0))
# 工兵晨令直调拆除(内容数据驱动扩展批 T4——round_start grant_ap(mode:queue) 订阅行承载,
# when round_index ≤ effective_rounds 行字段 value_from 定影;发射位先于轮切换=当轮兑现)
# round_start 发射+施加(内容数据驱动扩展批 T3——发射位先于行动经济轮切换:树规则 grant_ap
@@ -1112,6 +1118,12 @@ func _judge_flag(evt: Dictionary, ri: int) -> void:
if _flag_miss_seen.has(cell):
return # 每格实例一次去重(归因行与负向兑付同键——防逐轮惩罚轰炸)
_flag_miss_seen[cell] = true
# 完美保险(迭代 20 T4——本战下一次标错不破坏完美资格:_perfect_hit_false 回退=本次标错
# 对完美谓词无痕;只保一次/件,节点级用完即止)
if _fx_insurance > 0:
_fx_insurance -= 1
_perfect_hit_false -= 1
_emit_row("insurance_used", "", cell, ri, "完美保险·标错豁免", 0, {"left": _fx_insurance}, "")
var hl := ""
if _perfect_hit_false == 1:
hl = "perfect_broken" # 本板完美谓词已破的归因锚
+19
View File
@@ -485,9 +485,15 @@ func _start_battle(node: Dictionary) -> bool:
"tree_rules": _tree_rules,
"bonus_ref": bonus,
"adventure_counters": _adventure_counters_ref,
"voucher_rap": _voucher_rap,
"voucher_time_sand": _voucher_time_sand,
"voucher_insurance": _voucher_insurance,
"node_type": String(node.get("type", ""))}):
push_error("[关卡节点地图] 战斗启动失败(depth %s)" % str(node.get("node_depth", -1)))
return false
_voucher_rap = 0 # 迭代 20 T4——节点级一次性:注入即消费(战斗未启动则保留至下次注入前不清)
_voucher_time_sand = 0
_voucher_insurance = 0
return true
@@ -627,6 +633,19 @@ func set_adventure_counters(ref: Dictionary) -> void:
_adventure_counters_ref = ref
# 凭证件三账注入(迭代 20 T4——储备金袋 RAP/时沙 首轮 AP/完美保险 标错豁免;节点级一次性,
# battle begin 消费后归零;缺省 0=零扰动)
var _voucher_rap := 0
var _voucher_time_sand := 0
var _voucher_insurance := 0
func set_voucher_grants(rap: int, time_sand: int, insurance: int) -> void:
_voucher_rap = maxi(0, rap)
_voucher_time_sand = maxi(0, time_sand)
_voucher_insurance = maxi(0, insurance)
# 同调主动件充能账本注入(迭代 11 第①批 T6——共享 dict 引用直改;能力件骑库存面注入:
# 差额注入防续玩双份——resume 路径 restore_inventory 已含断点内 attune 记录)
func set_attune_charges(charges: Dictionary) -> void: