主卡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

+19 -68
View File
@@ -35,12 +35,6 @@ var _resume_bp := {} # 续玩快照暂存(resume_adventur
# ---- 三选一停点(编排迁 CurioStop 件——停点三态/刷新三源/凭证出箱/信号面)----
var _curio: CurioDeck = null # 令牌池件(_init 建;纯池——多重集/抽签/cap/凭证判定唯一宿主)
var _stop: CurioStop = null # 停点件(_init 建;每冒险一次——关卡换实例不换件)
var _curio_taken: Dictionary[Variant, Variant]: # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载)
get: return _curio.taken
set(v): _curio.taken = v
var _curio_deck: Array[Variant]: # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};商店货架同库共账)
get: return _curio.deck
set(v): _curio.deck = v
var _tree_rules: Array[Variant] = [] # 树 rule 键集(Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
var _shop_mode := "legacy" # 商店模式("legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool)
@@ -87,7 +81,7 @@ 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_attunement(_attunement) # 停点件 X 基数查询消费面(活引用)
_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 冒险周期池)
_seed_attune_charges() # 冒险开始充能账本按装备主动件重置(每冒险充能语义)
adventure_counters = {} # 冒险桶清零(效果引擎 adv_ 计数器宿主——冒险开始归零)
@@ -166,10 +160,10 @@ func _resume_ledger(bp: Dictionary, params: Dictionary) -> void:
func _resume_curio_state(bp: Dictionary) -> void:
_reset_curio_state()
var bp_taken: Dictionary[Variant, Variant] = bp.get("bonus_taken", {}) as Dictionary
for id in _curio_taken.keys():
_curio_taken[id] = maxi(0, int(bp_taken.get(id, 0)))
for id in _curio.taken.keys():
_curio.taken[id] = maxi(0, int(bp_taken.get(id, 0)))
for id in bp_taken.keys():
_curio_taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:<feat_id>)随快照还原
_curio.taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:<feat_id>)随快照还原
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
# relic 令牌 feat_id 随还原)
var bp_deck = _bp_read("bonus_deck", bp)
@@ -181,7 +175,7 @@ func _resume_curio_state(bp: Dictionary) -> void:
if String(rtok["kind"]) == "relic":
rtok["feat_id"] = String(tok.get("feat_id", "")) # relic 令牌 feat_id 随还原
restored.append(rtok)
_curio_deck = restored
_curio.deck = restored
# 特性持有集还原(缺键 = 旧版断点防御空集——主角账字段单点)
_hero.feats = []
for r in (bp.get("relics", []) as Array):
@@ -202,6 +196,7 @@ func _resume_params(bp: Dictionary, params: Dictionary) -> void:
_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
_seed_attune_charges() # 冒险开始充能账本按装备主动件重置(每冒险充能语义)
_stop.set_refresh_x_cap(_attune_refresh_x_total()) # 停点件 X 基数预算注入(续玩参数面同 start)
_stop.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 下传)
@@ -263,10 +258,6 @@ func continue_to_next_level() -> bool:
# 停点空候选消化(池空=三选一环节整体跳过——呈现层跳开选择面;编排归停点件)
func skip_empty_curio() -> bool:
return _stop.skip_empty_curio()
# ---- 提升三选一(选择停点零自动推进、重复兑付被拒;兑付恰一件、未选项零生效)----
# 候选令牌匹配(身份键恒等——属性加成型特性 kind+tier、规则改写型特性 feat_id)
@@ -275,13 +266,6 @@ func _find_token(candidates: Array, kind: String, tier: String, feat_id: String
# 玩家点选兑付(state 守卫留应用层——编排归停点件;呈现层直调面 T5 改 stop())
func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
if state != "running":
push_error("[冒险结构] pick_curio 拒绝——state=%s(非 running 不可兑付)" % state)
return false
return _stop.pick_curio(kind, tier, feat_id)
# 特性持有集读面(效果接线/求值/摘要唯一取数口——副本防外部改写)
func feats_held() -> Array:
return _hero.feats_held()
@@ -290,10 +274,6 @@ func feats_held() -> Array:
# ---- 商店共享池消费面(纯池——库存=牌库剩余令牌)----
# 库存余量(池消耗即上限;奖励摘牌与商店购买同库承载,全冒险一次性)
func pool_stock(kind: String) -> int:
return _stop.pool_stock(kind)
# 终局池快照(_settle 清账前调用——复位会重置计数,摘要 additive 键须在此前取数)
func _pool_stock_snapshot() -> Dictionary:
return _stop.pool_stock_snapshot()
@@ -312,8 +292,15 @@ func present_node_curio(candidates: Array) -> void:
# 点选兑付(节点域——编排归停点件;呈现层直调面 T5 改 stop())
func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
return _stop.apply_node_curio(kind, tier, feat_id)
# 刷新件余量基数(kind 分发查询——装配集内 effect_kinds 含 bonus_refresh 的行 magnitude 合计;
# 刷新件类加件零 id 分支;停点件 X 重置基数经 start/resume 预算注入)
func _attune_refresh_x_total() -> int:
var total := 0
for iid_v in _attunement:
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v))
if (arow.get("effect_kinds", []) as Array).has("bonus_refresh"):
total += int(arow.get("magnitude", 0))
return total
# 冒险开始充能账本重置(T6——装备 active_charged 件各回 charges_per_adventure;被动件不入账)
@@ -328,50 +315,14 @@ func _seed_attune_charges() -> void:
# ---- 三选一刷新与凭证(编排归停点件——本类留组合根委托读面)----
# 当前可刷新次数(X 优先 + Y 池 + 刷新存量账——呈现层余量读数口)
func attune_refresh_left() -> int:
return _stop.attune_refresh_left()
# 刷新存量账读数(T4——刷新券加仓余量单独读面;呈现层与断点消费)
func refresh_stock() -> int:
return _stop.refresh_stock()
# 节点停点在途态读回(组合根重 present 用——candidates 值拷贝)
func node_curio_pending_state() -> Dictionary:
return _stop.node_curio_pending_state()
# 三选一刷新(免费位 X/Y——组合根唯一调用口;无停点/无余量 false 零副作用)
func refresh_curio() -> bool:
return _stop.refresh_curio()
# 三选一 GP 付费刷新(对齐商店 MC-1——组合根先经 curio_stop_pending+钱包支出再调,本口只做计数与重抽)
func refresh_curio_paid() -> bool:
return _stop.refresh_curio_paid()
# 本次 GP 付费刷新价(与商店货架刷新同源——单点调参)
func curio_refresh_gp_cost() -> int:
return _stop.curio_refresh_gp_cost()
# 三选一停点在途谓词(付费路径扣费前置校验——无停点禁扣钱,防空转烧钱包)
func curio_stop_pending() -> bool:
return _stop.curio_stop_pending()
# —— 专长获取专长凭证四口径判定(实装归 CurioDeck 件:调用即一次展示判定与记账)——
func _feat_ticket_consider(rg: RandomNumberGenerator) -> bool:
return _curio.feat_ticket_consider(rg)
# 已拿上限判定(身份维度;缺省放行=白名单外 kind 兜底)
func _curio_cap_ok(kind: String, feat_id: String = "") -> bool:
return _curio.cap_ok(kind, feat_id)
# Boss 钥石逐杀掉落(掉落渠道 = Boss/精英击杀;首杀/复杀两档读 meta.tres boss_keystone;
# 首杀判据 = 存档 boss_first_kill 标记位〔断点未注入 = 恒首杀——模拟/冒烟零扰动;标记仍在收束
# apply_adventure_summary 逐关卡完成关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因)
@@ -403,8 +354,8 @@ func _start_current_level() -> bool:
# 关卡对象构造注入一次给齐(拆件批 T3——ctx 键集见 LevelRunner._init 头注;
# 凭证出箱在组包时点取走〔节点级一次性——未用即过期语义不变〕)
var outbox: Dictionary[Variant, Variant] = _stop.voucher_outbox()
var pool_wiring: Dictionary[Variant, Variant] = {"deck": _curio_deck, "consume": _stop.consume_pool_purchase,
"ticket": _feat_ticket_consider, "cap": _curio_cap_ok} if _shop_mode == "pool" else {}
var pool_wiring: Dictionary[Variant, Variant] = {"deck": _curio.deck, "consume": _stop.consume_pool_purchase,
"ticket": _curio.feat_ticket_consider, "cap": _curio.cap_ok} if _shop_mode == "pool" else {}
var ctx: Dictionary[Variant, Variant] = {
"hero": _hero,
"ledger": run_ledger,
@@ -607,8 +558,8 @@ func _bp_parts() -> Dictionary[Variant, Variant]:
"keystone": int(hero_frag.get("keystone", 0)),
"visited_ids": level_frag.get("visited_ids", []),
"aux_items": level_frag.get("aux_items", []),
"bonus_taken": _curio_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
"bonus_deck": _curio_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
"bonus_taken": _curio.taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
"bonus_deck": _curio.deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
"relics": hero_frag.get("relics", []), # 特性持有集(局内随机渠道获得态续玩防丢失)
"bonus_pending": stop_frag.get("bonus_pending", {}), # 三选一停点态(空 = 无停点——选择停点可中断契约)
"feat_ticket_pity": int(stop_frag.get("feat_ticket_pity", 0)), # T5 专长凭证保底计数(扩列向后兼容——旧档缺键缺省 0)
+3 -5
View File
@@ -385,12 +385,10 @@ func begin_battle(enc: Dictionary) -> bool:
_fx_new_resource_types = {}
_fx_pending_reveals = []
_fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
var ledger_ref = enc.get("ledger", null)
var ledger_ref: Variant = enc.get("ledger", null)
_ledger = ledger_ref if ledger_ref is RunLedger else RunLedger.new() # 缺省新账=调试/单场零扰动面
var hero_ref = enc.get("hero", null)
_hero = hero_ref if hero_ref is HeroLedger else null # null=调试/单场直构——账面消费面跳过(空账守卫同语义)
var bucket_ref: Variant = enc.get("adv_bucket", null)
counters_ctx.adventure_bucket = bucket_ref if bucket_ref is Dictionary else {} # 挂冒险桶引用(归宿主字典——跨战持久=同一字典引用;直构兜底自有空字典)
_hero = enc.get("hero", null) if enc.get("hero", null) is HeroLedger else null # null=调试直构——账面消费跳过
counters_ctx.adventure_bucket = enc.get("adv_bucket", null) if enc.get("adv_bucket", null) is Dictionary else {} # 冒险桶宿主字典(直构兜底自有空字典)
var level_ref: Variant = enc.get("level_counters")
if level_ref is Dictionary:
counters_ctx.level_bucket = level_ref # 挂关卡桶引用(LevelRunner 每关 new 下传)
+6 -15
View File
@@ -257,26 +257,17 @@ func _redeem_feat_ticket() -> bool:
# Y=营地升级行冒险周期池〔用完即无〕;重抽消费 reward_pick 既有洗牌流,池规则全保留——不放回/
# 达上限剔牌/掷定前置直显;停点可中断)----
# 刷新件余量基数(kind 分发查询——装配集内 effect_kinds 含 bonus_refresh 的行 magnitude 合计;
# 刷新件类加件零 id 分支)
func _attune_refresh_x_total() -> int:
var total := 0
for iid_v in _attunement:
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v))
if (arow.get("effect_kinds", []) as Array).has("bonus_refresh"):
total += int(arow.get("magnitude", 0))
return total
var _refresh_x_cap := 0 # 刷新件余量基数(装配集 effect_kinds 含 bonus_refresh 的行 magnitude 合计——
# 宿主冒险开始时预算注入;装配集冒险期间锁死〔契约 8〕故预算与逐停点查询等价)
var _attunement: Array[Variant] = [] # 同调装配集(活引用——宿主冒险开始注入,刷新件基数查询消费面)
func set_attunement(ids: Array[Variant]) -> void:
_attunement = ids
# 刷新件基数注入(宿主 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 = _attune_refresh_x_total()
_refresh_x = _refresh_x_cap
# 当前可刷新次数(X 优先 + Y 池 + 刷新存量账——呈现层余量读数口)
+3 -5
View File
@@ -21,7 +21,6 @@ var armor_type := "" # 甲类型轴快照(同上)
var _base_override: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0}
var _override: Array[int] = [0, 0] # 调试浮窗百分比通道 [atk_pct, hp_pct](每关新 runner 应用面)
var _gain_log: Array[Variant] = [] # 获得记录列表(三选一件/商店购买/战末奖励各一条)
var _gain_seq := 0 # 获得记录 id 发号(1 起)
var _bonus_detail: Array[Variant] = [] # 属性增益明细(带来源指回 gain_id)
var _bonus_total: Dictionary[Variant, Variant] = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
var _keystone := 0 # 钥石入账累计(摘要第三件事的账面)
@@ -129,7 +128,6 @@ func reset_adventure_accounts() -> void:
gp = 0
_bonus_total = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
_gain_log = []
_gain_seq = 0
_bonus_detail = []
_purchased = {}
_keystone = 0
@@ -144,11 +142,11 @@ func reset_adventure_accounts() -> void:
# 获得记录列表追加(三通道各一条——source_class 词表:curio_pick/shop_product/battle_export;
# 返回记录 id 供明细来源指回)
func record_gain(source_class: String, kind: String, detail: Dictionary) -> int:
_gain_seq += 1
var rec: Dictionary[Variant, Variant] = {"id": _gain_seq, "source_class": source_class, "kind": kind}
var rec_id := _gain_log.size() + 1 # 记录 id = 列表序派生(1 起——恢复面按 max(id) 校正)
var rec: Dictionary[Variant, Variant] = {"id": rec_id, "source_class": source_class, "kind": kind}
rec.merge(detail.duplicate(true))
_gain_log.append(rec)
return _gain_seq
return rec_id
# 统一冒险加算区单写原语(三通道统一入口:三选一属性件 source_class="curio_pick";
+1 -18
View File
@@ -49,23 +49,6 @@ var _attunement: Array[Variant] = [] # 同调装配集(adve
# 消费面:effective_ac 甲型基础 AC 层 + begin_battle enc 携带进 unit_container.spawn 英雄快照)
var _hero_attack_type := ""
var _hero_armor_type := ""
# ---- 商店共享池模式态(缺省 legacy=固定三件加定价 off 对照承载,零 RNG 零池消费;TP0 令牌池)----
# 件内状态白盒读面(断点写侧与既有取数面沿用原字段名;状态本体单点在 ShopSession)
var _shop_pool_mode: bool:
get: return _shop.pool_mode
var _pool_deck: Array[Variant]:
get: return _shop.pool_deck
var _pool_consume: Callable:
get: return _shop.pool_consume
var _pool_ticket: Callable:
get: return _shop.pool_ticket
var _shop_shelf: Array[Variant]:
get: return _shop.shelf
var _shop_visit_buys: int:
get: return _shop.visit_buys
var _shop_paid_refreshes: int:
get: return _shop.paid_refreshes
# ---- 关卡相位轴(显式 Phase enum:内部 enum + 守卫换算,对齐 行动经济 Phase 先例;
# level_result:String 与 awaiting_choice() 是 AdventureRunner/呈现层消费的外部契约面)----
enum Phase { IDLE, RUNNING, AWAITING_CHOICE, ENDED }
@@ -508,7 +491,7 @@ func _enter_node(node: Dictionary) -> bool:
"shop":
# 商店房兑付:到达即开店(覆盖层归呈现层),购买出账/
# 离开归玩家意图——零自动推进;离开确认经 continue_after_node 回选路口
if _shop_pool_mode:
if _shop.pool_mode:
_shop.open_pool() # 进店抽 3(恰一 shop_draw scope;买后全重抽同签)
node_entered.emit(node, {"gp": gp, "shelf": true})
return true
+19 -63
View File
@@ -78,51 +78,7 @@ var _cell := 46.0 # 当前格边长(set_cell_area 按容器尺寸重
var _palette = _TILE_PALETTE.new() # 瓦片件:装载缓存+查表+明度/墨族静态算法
var _hero_sprite = _HERO_SPRITE.new() # 英雄精灵件:_init 挂为本件子级(tween 需在树上)
# ── 件成员同名转发(探针直读面零改动:读 _seal_tex、读写 _hero_tex_idle;其余为随迁状态单点再导出)──
var _tiles: Dictionary[String, Texture2D]:
get:
return _palette.tiles
var _floor_tiles: Array[Texture2D]:
get:
return _palette.floor_tiles
var _tiles_ok: bool:
get:
return _palette.tiles_ok
var _seal_tex: Texture2D:
get:
return _palette.seal_tex
var _tiles_board_type: String:
get:
return _palette.tiles_board_type
var _digit_dark_ink: bool:
get:
return _palette.digit_dark_ink
var _digit_plate: bool:
get:
return _palette.digit_plate
var _hero_tex_idle: Texture2D:
get:
return _hero_sprite.tex_idle
set(v):
_hero_sprite.tex_idle = v
var _hero_tex_walk: Texture2D:
get:
return _hero_sprite.tex_walk
var _hero_last: Vector2i:
get:
return _hero_sprite.last
set(v):
_hero_sprite.last = v
var _hero_move_tw: Tween:
get:
return _hero_sprite.move_tw
var _hero_bump_tw: Tween:
get:
return _hero_sprite.bump_tw
var _hero_dig_tw: Tween:
get:
return _hero_sprite.dig_tw
# ── 件成员同名转发(探针直读面零改动:读 _palette.seal_tex、读写 _hero_sprite.tex_idle;其余为随迁状态单点再导出)──
# ---- 视野迷雾(纯呈现层;现行事实源=HUD框架「视野迷雾」节 v0.13,渲染规格=art-bible §6.5 v3.5)----
const FOG_RADIUS := 8 # 菱形灯光区基准半径(曼哈顿距离 ≤R——与微光环「周围 8 格」同圈起步;半径基准值改动=非白名单须用户)
var _fog_enabled := true # 迷雾开关(呈现态布尔——真实游戏恒 on;探针/夹具可 off=无迷雾基线,两态结算逐位一致断言面)
@@ -161,8 +117,8 @@ func _init() -> void:
_desat_mat = ShaderMaterial.new()
_desat_mat.shader = load("res://presentation/views/desaturation.gdshader")
_desat_mat.set_shader_parameter("strength", 0.0)
_hero_tex_idle = UiKit.load_tex(UiKit.TEX_HERO_IDLE)
_hero_tex_walk = UiKit.load_tex(UiKit.TEX_HERO_WALK)
_hero_sprite.tex_idle = UiKit.load_tex(UiKit.TEX_HERO_IDLE)
_hero_sprite.tex_walk = UiKit.load_tex(UiKit.TEX_HERO_WALK)
_palette.load_board_tiles() # 高墙/地面瓦片整批守卫装载(缺位静默回退——英雄同款纪律,禁刷屏)
@@ -247,8 +203,8 @@ func set_cell_area(avail: Vector2) -> void:
var u: Node2D = _cells[c]
u.edge = _cell
u.position = Vector2(c) * stride
if _world.hero != null and _hero_last.x >= 0:
_place_hero(_hero_last, _hero_last, true) # 格边长变更(窗口缩放)——英雄即时随格重置(杀位移 tween 防飞陈旧落点)
if _world.hero != null and _hero_sprite.last.x >= 0:
_place_hero(_hero_sprite.last, _hero_sprite.last, true) # 格边长变更(窗口缩放)——英雄即时随格重置(杀位移 tween 防飞陈旧落点)
_refresh_slab()
_update_world_size()
@@ -276,15 +232,15 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
_glow.clear()
_glow_errors.clear()
_contact_unit = null
_hero_last = Vector2i(-99, -99)
_hero_sprite.last = Vector2i(-99, -99)
_r0.clear()
_hover = Vector2i(-9, -9)
for c in _board.compute_r0():
_r0[c] = true
# 瓦片套随板图型切换(board_gen 词表 v1.1 dungeon/underdark/deep_underdark;
# 同图型重复 rebuild 零重载,_tiles_board_type 守卫)
# 同图型重复 rebuild 零重载,_palette.tiles_board_type 守卫)
var bt: String = _board.board_type if _board.board_type != "" else "dungeon"
if bt != _tiles_board_type:
if bt != _palette.tiles_board_type:
_palette.load_board_tiles(bt)
var stride := _cell + float(SEP)
for y in _board.height:
@@ -296,7 +252,7 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
u.position = Vector2(c) * stride
u.bevel = DEPTH_BEVEL # 新建格单元继承
u.use_parent_material = true # 去饱和材质挂 BoardWorld——单元随世界材质灰化
if _tiles_ok:
if _palette.tiles_ok:
# NEAREST 采样(32×32 源 × 45/36px 格边非整数缩放接受档,
# 与英雄小人 1.5× 豁免同级(圣经 §8.2);只在瓦片
# 模式启用——程序化回退路径渲染零扰动(含格面文字采样不回退不变化)
@@ -629,7 +585,7 @@ func _badge_glyph(category: String) -> String:
#(dungeon 瓦片实测达标维持);深墨族 1-3 青墨/4-5 赭墨/6-8 锈墨(色温语义同构——underdark/
# deep_underdark 亮白筋纹瓦片上亮墨对亮斑 CR 1.0–1.9 融字,深墨族 5.7–11.1;选族判据见 TilePalette.digit_ink_mode)
func _digit_ink(d: int) -> Color:
if _digit_dark_ink:
if _palette.digit_dark_ink:
if d <= 3:
return UiKit.INK_COLD
if d <= 5:
@@ -655,7 +611,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
var digit_col := COL_DIGIT
# 封印槽统一维护:封印格=封印印记纹理;其余格清 null(封印格挖开后残留在 tex_seal
# 会让 _draw 无条件把封印印画在已开格上——tex_seal 绘制无 revealed 条件)。缺盘走字形回退。
u.tex_seal = _seal_tex if (seal and not revealed and _seal_tex != null) else null
u.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null
if removed:
revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;危险格本无数字,周边数字冻结)
seal = false
@@ -678,7 +634,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
else:
if seal:
bg = COL_BRICK
if _seal_tex == null:
if _palette.seal_tex == null:
glyph = "封"
digit_col = COL_SEAL
else:
@@ -692,7 +648,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
#(绘制路径零像素变化),仅英雄格保留蓝框载体。
var sb: StyleBoxFlat = null
var tile: Texture2D = null
if _tiles_ok:
if _palette.tiles_ok:
tile = _floor_tile_for(c) if revealed else _wall_tile_for(c)
else:
# 素材整批缺位回退 = 现行程序化平色路径(fresh checkout 冒烟不炸——需求卡片 §2.2)
@@ -702,7 +658,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
sb = UiKit.panel_flat(Color.TRANSPARENT, Color.TRANSPARENT,UiKit.RADIUS_OBJECT, 0)
sb.set_border_width_all(3)
sb.border_color = COL_HERO
if glyph == "" and _hero_tex_idle == null:
if glyph == "" and _hero_sprite.tex_idle == null:
# 贴图缺位回退字形「人」(项目纹理槽惯例——贴图在位=格内字形退役,单位本体由独立精灵承载)
glyph = "人"
digit_col = COL_HERO
@@ -711,8 +667,8 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
u.glyph = glyph
u.glyph_color = digit_col
# 色牌后备(数字直画退牌;「危」/「?」维持牌载保读性)+ v3.4:双峰纹理板
#(_digit_plate——underdark 系亮白筋+近黑斑并存的直画不可救纹理)数字落牌,牌上深墨 12:1
var plate_digit := _digit_plate and glyph != "" and glyph != "危" and glyph != "?" and glyph != "封" and glyph != "人"
#(_palette.digit_plate——underdark 系亮白筋+近黑斑并存的直画不可救纹理)数字落牌,牌上深墨 12:1
var plate_digit: bool = _palette.digit_plate and glyph != "" and glyph != "危" and glyph != "?" and glyph != "封" and glyph != "人"
if plate_digit:
digit_col = COL_DIGIT # 牌上深墨族(素牌 12:1——双峰纹理免疫)
u.glyph_color = digit_col
@@ -739,7 +695,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
var code := 0 if d.y < 0 else (1 if d.y > 0 else (2 if d.x < 0 else 3))
if code == 1 and u.facade_south:
continue # 南缘由立面件承载(高光+双段暗带)——2px 描边让位防三重叠黑
if code == 0 and _tiles_ok:
if code == 0 and _palette.tiles_ok:
continue # v3.5 冻结① (b)-(c) 交界无边框:墙面直贴接墙顶(E 格墙面件在邻格承载)——北向描边不画(平色回退态保留描边)
u.outline_sides.append(code)
else:
@@ -748,9 +704,9 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
u.proj_band = u.edge * WALL_PROJECTION_FRAC
# E 格高墙面(圣经 v3.5 冻结①——方向稿 is_facade_cell 口径):已开格南邻未开墙 → 南部 30%
# 承接墙面(材质=同板墙顶 center 瓦片,原亮度直贴);仅瓦片态启用——平色回退保持原语义
if south_in and not _minesweeper.is_revealed(south_n) and _tiles_ok:
if south_in and not _minesweeper.is_revealed(south_n) and _palette.tiles_ok:
u.facade_strip = true
u.tex_facade = _tiles.get("wall_center")
u.tex_facade = _palette.tiles.get("wall_center")
# ②( 秘宝叠加层:真视格=红内缩框;预读格=奖励身份徽章(identity_resolver 既有
# 只读面取字形——零数据复制,badge 位复用);已开格标记自然退位(refresh 逐拍重算)
if not revealed:
+6 -6
View File
@@ -265,8 +265,8 @@ func _ready() -> void:
# `u.tex_seal = null` 会覆盖条件赋值=素材在位时封印不可见(headless 缺盘走字形回退
# 掩盖)——有盘断言纹理在槽、缺盘断言字形回退,两分支防双写踩踏复活 ----
var seal_unit = bv.cells_probe().get(seal_t)
if bv._seal_tex != null:
if seal_unit == null or seal_unit.tex_seal != bv._seal_tex:
if bv._palette.seal_tex != null:
if seal_unit == null or seal_unit.tex_seal != bv._palette.seal_tex:
_problems.append("case④b 封印纹理槽未挂封印印记纹理(tex_seal=%s)" % ["无单元" if seal_unit == null else str(seal_unit.tex_seal)])
elif seal_unit == null or seal_unit.glyph != "封":
_problems.append("case④b 缺盘字形回退缺失(glyph=%s)" % ["无单元" if seal_unit == null else String(seal_unit.glyph)])
@@ -332,7 +332,7 @@ func _ready() -> void:
var sp9: Sprite2D = bv._world.hero
if not sp9.flip_h:
_problems.append("case⑨ 右移后未翻转朝向(flip_h=false,素材基准朝左)")
if sp9.texture != bv._hero_tex_idle:
if sp9.texture != bv._hero_sprite.tex_idle:
_problems.append("case⑨ 到位未回站帧(texture=%s)" % str(sp9.texture))
if sp9.scale != Vector2.ONE * bv.HERO_SPRITE_SCALE:
_problems.append("case⑨ 缩放档漂移(%s 应=%s 均匀)" % [str(sp9.scale), str(bv.HERO_SPRITE_SCALE)])
@@ -355,8 +355,8 @@ func _ready() -> void:
if bv._world.hero != null:
var hp11: Vector2i = ms.hero_pos()
var u11 = bv.cells_probe()[hp11]
var tex_saved: Texture2D = bv._hero_tex_idle
bv._hero_tex_idle = null
var tex_saved: Texture2D = bv._hero_sprite.tex_idle
bv._hero_sprite.tex_idle = null
bv.refresh()
var digit11: int = int(ms.reveal_record(hp11).get("digit", 0))
if digit11 == 0:
@@ -364,7 +364,7 @@ func _ready() -> void:
_problems.append("case⑪ 贴图缺位未回退字形「人」(glyph=%s)" % u11.glyph)
elif u11.glyph != str(digit11):
_problems.append("case⑪ 缺位态数字格字形被破坏(glyph=%s 应=%d)" % [u11.glyph, digit11])
bv._hero_tex_idle = tex_saved
bv._hero_sprite.tex_idle = tex_saved
bv.refresh()
# ---- case⑭ 变异角标(flat_damage 未开格视觉标记;resolver 注入面+落位+复位) ----
var mark_t: Vector2i = _pick_forbidden(bv, ms) # 任一未开格(禁入=未开非相邻)
+6 -6
View File
@@ -228,22 +228,22 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr,
elif t == "reward":
# 奖励节点三选一(共享池确定性策略;TP0 起 316-f 判读:1 级最稀有、
# 同档并列取最左;替换放弃新件;兑付后 continue_after_node 回图选路)
var ndraw: Dictionary = adr.node_curio_candidates()
var ndraw: Dictionary = adr.stop().node_curio_candidates()
var ncands: Array = ndraw.get("candidates", []) as Array
st.reward_events.append({"node_curio_pick": ncands.duplicate(true)})
if ncands.is_empty():
r.continue_after_node() # 牌库空合法态直通(池耗即枯竭——防御保留)
else:
adr.present_node_curio(ncands)
adr.stop().present_node_curio(ncands)
var npick: Dictionary = _pick_token(ncands, String(st.get("feat_mode", "off")))
if adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))):
if adr.stop().apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))):
# 专长凭证拿到即开——候选已被替换为专长三选一,就地再选一轮(确定性策略同款 316-f);
# 不作此分支则节点停点悬空→选路卡死(MC timeout 形态)
if String(npick.get("kind", "")) == "feat_ticket":
var fcands: Array = (adr.node_curio_pending_state().get("candidates", []) as Array)
var fcands: Array = (adr.stop().node_curio_pending_state().get("candidates", []) as Array)
if not fcands.is_empty():
var fpick: Dictionary = _pick_token(fcands, String(st.get("feat_mode", "off")))
adr.apply_node_curio(String(fpick.get("kind", "")), String(fpick.get("tier", "")), String(fpick.get("feat_id", "")))
adr.stop().apply_node_curio(String(fpick.get("kind", "")), String(fpick.get("tier", "")), String(fpick.get("feat_id", "")))
else:
st.result = "advance_failed"
elif t == "rest":
@@ -281,7 +281,7 @@ static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> voi
st.result = "advance_failed"
return
var pick: Dictionary = _pick_token(candidates, String(st.get("feat_mode", "off")))
if not adr.pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("feat_id", ""))):
if not adr.stop().pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("feat_id", ""))):
st.result = "advance_failed"
+4 -4
View File
@@ -68,14 +68,14 @@ func _drive_dump_run(db, seed_val: int) -> Dictionary:
_dump(ctx, "node_curio_pending")
var ncands: Array = (adr.stop()._node_pending.get("candidates", []) as Array)
var npick: Dictionary = Autoplay._pick_token(ncands, "off")
if not adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))):
if not adr.stop().apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))):
ctx["result"] = "node_advance_failed"
continue
if not (adr.stop()._pending as Dictionary).is_empty():
_dump(ctx, "curio_pending")
var cands: Array = (adr.stop()._pending.get("candidates", []) as Array)
var pick: Dictionary = Autoplay._pick_token(cands, "off")
if not adr.pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("feat_id", ""))):
if not adr.stop().pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("feat_id", ""))):
ctx["result"] = "advance_failed"
continue
if bool(adr._pending_next_level):
@@ -159,12 +159,12 @@ static func _on_node_entered(node: Dictionary, _info: Dictionary, ctx: Dictionar
if t == "rest" or t == "shop":
runner.continue_after_node()
elif t == "reward":
var ndraw: Dictionary = adr.node_curio_candidates()
var ndraw: Dictionary = adr.stop().node_curio_candidates()
var ncands: Array = ndraw.get("candidates", []) as Array
if ncands.is_empty():
runner.continue_after_node() # 牌库空合法态直通(池耗即枯竭)
else:
adr.present_node_curio(ncands)
adr.stop().present_node_curio(ncands)
# 节点三选一兑付完成回图(context=reward_node → continue_after_node 回选路)
@@ -78,7 +78,7 @@ func _feats_breakpoint_roundtrip() -> void:
var a_r = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(72))
a_r.start_adventure(72) # 源 :723(受理断言留单元宿主)
a_r.stop()._pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "x3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
a_r.pick_curio("relic", "rare", "x3") # 源 :781(兑付断言留单元宿主)
a_r.stop().pick_curio("relic", "rare", "x3") # 源 :781(兑付断言留单元宿主)
# 断点往返(持有集+余库 feat_id 随行)
var bp_path := "user://_smoke_feats_bp.json" # 段首清场(存档域隔离纪律——上轮残留断点先删)
if FileAccess.file_exists(bp_path):
+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)")
+258 -258
View File
@@ -1,6 +1,6 @@
# 验证集清单(断言快照·冻结)
> 生成日期:2026-10-26 · 基点 commit:`5b5214f40f206498422e1bcd603a21a8393bf560` · 源:cross_system_flow_test.gd、node_map_determinism_test.gd、save_rng_roundtrip_test.gd、system_test.gd、damage_controller_test.gd、damage_math_test.gd、extended_row_violation_test.gd、roll_multiplier_test.gd、round_flow_skill_pool_test.gd、seal_polarity_test.gd、skill_unlock_test.gd、variant_placement_test.gd、source_guard_test.gd、system_contracts_test.gd、adventure_feats_test.gd、board_gen_reward_test.gd、board_generator_test.gd、breakpoint_resume_test.gd、config_db_cross_validator_test.gd、config_db_resolver_test.gd、curio_refresh_paid_test.gd、effect_words_t1_test.gd、feat_vouchers_test.gd、level_map_type_vis_test.gd、level_shop_economy_test.gd、level_table_validate_test.gd、meta_progression_forge_test.gd、minesweeper_rules_test.gd、node_map_generator_test.gd、node_map_growth_test.gd、pool_case_d_test.gd、save_boot_no_activate_test.gd、save_manager_domain_test.gd、save_slot_isolation_test.gd、save_slot_summary_test.gd、settle_segments_test.gd、treasure_effects_test.gd、treasure_items_test.gd、tree_requires_level_test.gd、tree_rules_v11_test.gd、config_db_fatal_oob_test.gd、config_db_test.gd、counter_prefix_fatal_test.gd、forge_table_test.gd、level_gen_params_test.gd、reward_dangling_treasure_test.gd、rng_service_test.gd、rng_stream_boot_test.gd、table_structure_guard_test.gd、treasure_target_violation_test.gd、example_test.gd、battle_narrator_test.gd、forge_workbench_test.gd、hud_presenter_test.gd、level_map_info_fog_test.gd、modal_flow_test.gd、pacing_queue_test.gd、player_copy_sweep_test.gd、reward_overlay_test.gd、standee_fx_sweep_test.gd、terrain_tile_sets_test.gd、theme_projection_test.gd、type_glow_test.gd、ui_font_asset_test.gd、test_kit.gd
> 生成日期:2026-10-27 · 基点 commit:`fa1409f491c5a77371230ed5136797182493d236` · 源:cross_system_flow_test.gd、node_map_determinism_test.gd、save_rng_roundtrip_test.gd、system_test.gd、damage_controller_test.gd、damage_math_test.gd、extended_row_violation_test.gd、roll_multiplier_test.gd、round_flow_skill_pool_test.gd、seal_polarity_test.gd、skill_unlock_test.gd、variant_placement_test.gd、source_guard_test.gd、system_contracts_test.gd、adventure_feats_test.gd、board_gen_reward_test.gd、board_generator_test.gd、breakpoint_resume_test.gd、config_db_cross_validator_test.gd、config_db_resolver_test.gd、curio_refresh_paid_test.gd、effect_words_t1_test.gd、feat_vouchers_test.gd、hero_ledger_test.gd、level_map_type_vis_test.gd、level_shop_economy_test.gd、level_table_validate_test.gd、meta_progression_forge_test.gd、minesweeper_rules_test.gd、node_map_generator_test.gd、node_map_growth_test.gd、pool_case_d_test.gd、save_boot_no_activate_test.gd、save_manager_domain_test.gd、save_slot_isolation_test.gd、save_slot_summary_test.gd、settle_segments_test.gd、treasure_effects_test.gd、treasure_items_test.gd、tree_requires_level_test.gd、tree_rules_v11_test.gd、config_db_fatal_oob_test.gd、config_db_test.gd、counter_prefix_fatal_test.gd、forge_table_test.gd、level_gen_params_test.gd、reward_dangling_treasure_test.gd、rng_service_test.gd、rng_stream_boot_test.gd、table_structure_guard_test.gd、treasure_target_violation_test.gd、example_test.gd、battle_narrator_test.gd、forge_workbench_test.gd、hud_presenter_test.gd、level_map_info_fog_test.gd、modal_flow_test.gd、pacing_queue_test.gd、player_copy_sweep_test.gd、reward_overlay_test.gd、standee_fx_sweep_test.gd、terrain_tile_sets_test.gd、theme_projection_test.gd、type_glow_test.gd、ui_font_asset_test.gd、test_kit.gd
> 提取口径:静态 `check(` 调用点,括号配平归集(可跨行);缺省源=三宿主遍历
> (unit/**+integration/**+system_test.gd)。本基点实测 1596 个静态调用点;行级
> `grep -c "check("` 是另一口径(含 `func check(` 定义行与注释行),两口径各自浮动,
@@ -56,7 +56,7 @@
| 21 | 169 | "诊断转储文件生成(%s)" ↩ % diag | diag.contains("diag_") | (待填) | | |
| 22 | 182 | "JSONL 全部行可解析(%d 行 = session + 2 结算)" ↩ % lines | lines >= 3 and parse_ok | (待填) | | |
## S05 _smoke_generated_map_determinism(行 43–112 · 21 条 · 源 game/tests/integration/node_map_determinism_test.gd · 去向:待分类回填)
## S05 _smoke_generated_map_determinism(行 43–113 · 21 条 · 源 game/tests/integration/node_map_determinism_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
@@ -65,22 +65,22 @@
| 25 | 47 | "产图节点携图内唯一句柄 id 与自入口 depth(消费侧寻址面)" | _not_empty_graph(nodes_a) and _handles_unique(nodes_a) | (待填) | | |
| 26 | 51 | "单关起关受理(种子 910004——生成图直供,无兜底图)" | runner1.start_level(2, 910004) | (待填) | | |
| 27 | 53 | "起关生效图 = 生成器同档同种子直取逐位一致(消费侧无第二图源)〔v7〕" | runner1.level_row.get("nodes", []) == (gen.generate(params2, 910004) as Dictionary).get("nodes", []) | (待填) | | |
| 28 | 59 | "冒险模式起关受理(入口停点)" | runner2.start_level_external(3, {"hero_hp": 20, "gp": 0}, {}, Callable(self, "_smoke_rest_stub"), {}, true) | (待填) | | |
| 29 | 61 | "起关产图零计数器消费(同图不依赖取数号位)" | rng_c.counter() == counter_before | (待填) | | |
| 30 | 64 | "入口停点候选数=参数档入口个数(L3 档 %d——多入口首跳即选路,禁写死单入口)〔v2〕" ↩ % entry_count | entry_count >= 1 and (runner2.next_candidates() as Array).size() == entry_count | (待填) | | |
| 31 | 68 | "第二入口经句柄显式进入(句柄 %d)〔v2〕" ↩ % int(eids[1]) | runner2.pick_next_node(int(eids[1])) and int(runner2.current_node_id()) == int(eids[1]) | (待填) | | |
| 32 | 75 | "首局口径种子 42 开局受理(defer_entry 路径)" | adr_x.start_adventure(42, {"hero_override": {}, "treasure_unlocked": {}}) | (待填) | | |
| 33 | 78 | "同种子第二次开局受理" | adr_y.start_adventure(42, {"hero_override": {}, "treasure_unlocked": {}}) | (待填) | | |
| 34 | 80 | "种子 42 两次开局图逐位同图(教学局恒同图——首局固定种子的确定性承载)〔v7〕" | adr_x.runner().nodes() == adr_y.runner().nodes() | (待填) | | |
| 35 | 82 | "开局图 = L1 参数档在种子 42 下的生成图(开局无隐式取数分叉)" | adr_x.runner().nodes() == (gen.generate(_db.level_gen_params_row(1), 42) as Dictionary).get("nodes", []) | (待填) | | |
| 36 | 85 | "停点断点落盘在档" | sv_w.has_breakpoint() | (待填) | | |
| 37 | 88 | "断点续玩受理(代差标记键在档)〔v8〕" | adr_z.resume_adventure(bp, {"hero_override": {}, "treasure_unlocked": {}}) | (待填) | | |
| 38 | 90 | "续玩重生成图与中断前逐位同图〔v7〕" | adr_z.runner().nodes() == adr_x.runner().nodes() | (待填) | | |
| 39 | 94 | "缺生成图代差标记键的旧断点被显式拒绝(禁按新语义静默回放)〔v8〕" | adr_old.resume_adventure(bp_old, {}) == false | (待填) | | |
| 40 | 101 | "level 表三行均无 templates 键(模板数据已清出)〔v9〕" | tpl_free and _db.level_count() == 3 | (待填) | | |
| 41 | 106 | "新档首次冒险布尔位 = false(默认域——首局恒走固定种子 42)" | sv.is_first_adventure_done() == false | (待填) | | |
| 42 | 110 | "冒险收束点置位(三结局同写——写入只在收束挂点)" | sv.is_first_adventure_done() == true | (待填) | | |
| 43 | 112 | "置位幂等(重复收束不翻转)" | sv.is_first_adventure_done() == true | (待填) | | |
| 28 | 60 | "冒险模式起关受理(入口停点)" | runner2.start_level_external(3, Callable(self, "_smoke_rest_stub"), true) | (待填) | | |
| 29 | 62 | "起关产图零计数器消费(同图不依赖取数号位)" | rng_c.counter() == counter_before | (待填) | | |
| 30 | 65 | "入口停点候选数=参数档入口个数(L3 档 %d——多入口首跳即选路,禁写死单入口)〔v2〕" ↩ % entry_count | entry_count >= 1 and (runner2.next_candidates() as Array).size() == entry_count | (待填) | | |
| 31 | 69 | "第二入口经句柄显式进入(句柄 %d)〔v2〕" ↩ % int(eids[1]) | runner2.pick_next_node(int(eids[1])) and int(runner2.current_node_id()) == int(eids[1]) | (待填) | | |
| 32 | 76 | "首局口径种子 42 开局受理(defer_entry 路径)" | adr_x.start_adventure(42, {"hero_override": {}, "treasure_unlocked": {}}) | (待填) | | |
| 33 | 79 | "同种子第二次开局受理" | adr_y.start_adventure(42, {"hero_override": {}, "treasure_unlocked": {}}) | (待填) | | |
| 34 | 81 | "种子 42 两次开局图逐位同图(教学局恒同图——首局固定种子的确定性承载)〔v7〕" | adr_x.runner().nodes() == adr_y.runner().nodes() | (待填) | | |
| 35 | 83 | "开局图 = L1 参数档在种子 42 下的生成图(开局无隐式取数分叉)" | adr_x.runner().nodes() == (gen.generate(_db.level_gen_params_row(1), 42) as Dictionary).get("nodes", []) | (待填) | | |
| 36 | 86 | "停点断点落盘在档" | sv_w.has_breakpoint() | (待填) | | |
| 37 | 89 | "断点续玩受理(代差标记键在档)〔v8〕" | adr_z.resume_adventure(bp, {"hero_override": {}, "treasure_unlocked": {}}) | (待填) | | |
| 38 | 91 | "续玩重生成图与中断前逐位同图〔v7〕" | adr_z.runner().nodes() == adr_x.runner().nodes() | (待填) | | |
| 39 | 95 | "缺生成图代差标记键的旧断点被显式拒绝(禁按新语义静默回放)〔v8〕" | adr_old.resume_adventure(bp_old, {}) == false | (待填) | | |
| 40 | 102 | "level 表三行均无 templates 键(模板数据已清出)〔v9〕" | tpl_free and _db.level_count() == 3 | (待填) | | |
| 41 | 107 | "新档首次冒险布尔位 = false(默认域——首局恒走固定种子 42)" | sv.is_first_adventure_done() == false | (待填) | | |
| 42 | 111 | "冒险收束点置位(三结局同写——写入只在收束挂点)" | sv.is_first_adventure_done() == true | (待填) | | |
| 43 | 113 | "置位幂等(重复收束不翻转)" | sv.is_first_adventure_done() == true | (待填) | | |
## S06 _init(行 27–27 · 1 条 · 源 game/tests/integration/save_rng_roundtrip_test.gd · 去向:待分类回填)
@@ -827,140 +827,140 @@
| 457 | 81 | "放弃结算:result=abandoned · cleared=false(已获面不追回——钥石累计 %d)" ↩ % int(sum.get("keystone_total", -1)) | String(sum.get("result", "")) == "abandoned" and not bool(sum.get("cleared", true)) | (待填) | | |
| 458 | 82 | "结算后重复 abandon 拒绝(结算受理只算一次——state=idle)" | not adr.abandon() | (待填) | | |
## S72 _smoke_adventure(行 113–438 · 57 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填)
## S72 _smoke_adventure(行 118–443 · 57 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
| 459 | 113 | "序列关数 = 行集关数(三关正式序列生效)" | int(_db.level.rows.size()) == 3 | (待填) | | |
| 460 | 130 | "生成图每个战斗/Boss 节点经解析链命中敌行集与棋盘行(扫描 %d 节点,缺失 %d)〔fv24〕" ↩ % [cov_scan, cov_miss] | cov_scan > 0 and cov_miss == 0 | (待填) | | |
| 461 | 135 | "满配冒险成功(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)" | String(st["result"]) == "completed" and bool(sum.get("cleared", false)) | (待填) | | |
| 462 | 136 | "完成几关 = 3(levels_cleared)" | int(sum.get("levels_cleared", -1)) == 3 | (待填) | | |
| 463 | 140 | "钥石合计 = ΣBoss 首杀行值(%d——逐杀掉落新口径;断点未注入恒首杀)" ↩ % int(sum.get("keystone_total", -1)) | int(sum.get("keystone_total", -1)) == ks_exp | (待填) | | |
| 464 | 142 | "逐杀掉落明细恰 3 笔(L1/L2/L3——additive 摘要键 keystone_drops)" | drops.size() == 3 | (待填) | | |
| 465 | 143 | "摘要携带主种子(42)" | int(sum.get("master_seed", -1)) == 42 | (待填) | | |
| 466 | 154 | "关卡完成三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)" | picks.size() == 2 and resolves.size() == 2 + node_picks | (待填) | | |
| 467 | 165 | "三关生成图各含 ≥1 奖励节点(节点三选一候选位在图)〔v4〕" | reward_levels == 3 | (待填) | | |
| 468 | 169 | "奖励分支点选→三选一候选可取且停点在途(候选 %s 件)" ↩ % str((rw.get("cands", []) as Array).size()) | bool(rw.get("pending", false)) and (rw.get("cands", []) as Array).size() >= 1 | (待填) | | |
| 469 | 182 | "奖励三选一点选兑付受理(件=%s)" ↩ % String(rw_pc.get("kind", "")) | rw_pick_ok | (待填) | | |
| 470 | 188 | "进战载荷两样=关卡序号+节点深度(实测 depth=%s 句柄=%s)〔v6〕" ↩ % [str(enc_probe.get("node_depth", -1)), str(node_probe.get("id", -1))] | not enc_probe.is_empty() and int(enc_probe.get("node_depth", -1)) == int(node_probe.get("depth", -1)) and int(enc_probe.get("level_index", -1)) == 1 and int(node_probe.get("id", -1)) != int(node_probe.get("depth", -1)) | (待填) | | |
| 471 | 197 | "奖励节点兑付回执与经停逐笔相等(context=reward_node——同账计数 %d/%d)" ↩ % [node_resolves, node_picks] | node_resolves == node_picks | (待填) | | |
| 472 | 244 | "三选一候选 2–3 枚且零重复(令牌洗牌不放回——kind+tier 复合身份)" | cand_ok | (待填) | | |
| 473 | 245 | "关卡完成部分回血封顶(floor(观测上限×比例) 封顶——购血观测值精确断言,核代码增补)" | heal_ok | (待填) | | |
| 474 | 246 | "钥石逐杀入账(首杀档位 + 累计逐位)" | ks_gain_ok | (待填) | | |
| 475 | 247 | "点选恰兑付选中一件(值=令牌档位表值;未选项零生效)" | resolve_ok | (待填) | | |
| 476 | 258 | "各战上限入参 ∈ [基础, 基础+3×件值+购血余量](覆盖值通道——双渠道共享上限 3;模拟档贪心购血合法叠加〔数值修复批 v3.4〕;首战=基础 %d)" ↩ % exp_base | seq_ok | (待填) | | |
| 477 | 261 | "同种子两局逐位可复现(summary/上限序列/行进序列/三选一载荷——reward_pick 封闭流)" | st2["summary"] == sum and st2["encounter_max_hp_seq"] == seq and st2["visited_ids"] == st["visited_ids"] and st2["reward_events"] == st["reward_events"] | (待填) | | |
| 478 | 271 | "零配 L1 败北种子可达(seed=%s)" ↩ % str(lose.get("s", -1)) | not lose.is_empty() | (待填) | | |
| 479 | 274 | "L1 败北摘要:未打穿 · 完成 0 关 · 钥石 0 · 止步第 1 层" | not bool(sl["cleared"]) and int(sl["levels_cleared"]) == 0 \\ and int(sl["keystone_total"]) == 0 and int(sl["stopped_at"]) == 1 | (待填) | | |
| 480 | 278 | "L1 败北败退明细:层号/节点编号/节点类型在案(数值验证取数面 2026-09-22)" | int(dl.get("level_index", -1)) == 1 and int(dl.get("node_id", -1)) >= 0 \\ and String(dl.get("node_type", "")) in ["battle", "boss"] | (待填) | | |
| 481 | 287 | "半配(atk+50)L2 败北种子可达(seed=%s)" ↩ % str(half.get("s", -1)) | not half.is_empty() | (待填) | | |
| 482 | 290 | "L2 败北摘要:L1 已过(钥石=L1 首杀掉落)· 止步第 2 层" | not bool(sh["cleared"]) and int(sh["levels_cleared"]) == 1 \\ and int(sh["keystone_total"]) == int(_db.meta_boss_keystone_row(1).get("first_kill", 0)) \\ and int(sh["stopped_at"]) == 2 | (待填) | | |
| 483 | 295 | "L2 败北败退明细:层号=2 · 节点在案(数值验证取数面 2026-09-22)" | int(dh.get("level_index", -1)) == 2 and int(dh.get("node_id", -1)) >= 0 \\ and String(dh.get("node_type", "")) in ["battle", "boss"] | (待填) | | |
| 484 | 301 | "重复拒绝场景:冒险启动(seed 7——生成图确定同上)" | adr6.start_adventure(7) | (待填) | | |
| 485 | 309 | "重复拒绝场景就位:选路态放弃 → 已结算" | adr6.abandon() | (待填) | | |
| 486 | 312 | "重复关卡结束宣布拒绝(账本与摘要零变化——契约 7)" | adr6.summary == sum6_before | (待填) | | |
| 487 | 322 | "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" ↩ % [gains, int(st["battles"])] | gains == int(st["battles"]) | (待填) | | |
| 488 | 323 | "钱包 clear 恰一次且为末事件(关卡收束不清——GP 冒险收束清零归冒险结构)" | clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear" | (待填) | | |
| 489 | 325 | "三关商店房各到达一次(每关入口→终点路径恰穿一店的驱动投影)且首达余额 > 0(%d——GP 跨关延续)" ↩ % (shop_seq[0] if shop_seq.size() > 0 else -1) | shop_seq.size() == 3 and int(shop_seq[0]) > 0 | (待填) | | |
| 490 | 336 | "同账场景种子可达(seed=%s——L2 商店房就位)" ↩ % str(same.get("s", -1)) | not same.is_empty() | (待填) | | |
| 491 | 349 | "进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" ↩ % [hp_price + l2_price_bonus, hp_price, l2_price_bonus] | gp0 >= hp_price + l2_price_bonus | (待填) | | |
| 492 | 350 | "购买 hp_up 受理(出账 + 加算区)" | r8.buy_product("hp_up") | (待填) | | |
| 493 | 352 | "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" ↩ % acc_shop | acc_shop >= 3 | (待填) | | |
| 494 | 355 | "L2 关卡完成三选一停点在案(直调兑付前——零自动推进)" | not a8._curio_pending.is_empty() | (待填) | | |
| 495 | 356 | "未兑付推进拒绝(选择停点零自动推进契约)" | not a8.continue_to_next_level() | (待填) | | |
| 496 | 359 | "三选一点选兑付受理(候选取首件)" | a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))) | (待填) | | |
| 497 | 362 | "三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" ↩ % [int(a8.run_ledger.bonus.get("max_hp", -1)), acc_shop, picked2] | int(a8.run_ledger.bonus.get("max_hp", -1)) == expect_acc | (待填) | | |
| 498 | 364 | "关卡完成部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" ↩ % [int(a8.ledger["hero_hp"]), max_eff8] | int(a8.ledger["hero_hp"]) <= max_eff8 | (待填) | | |
| 499 | 365 | "兑付后推进受理(下一关开始)" | a8.continue_to_next_level() | (待填) | | |
| 500 | 368 | "恢复点数学:floor(100×30%)=30 · 封顶缺口 5 · 满血回 0 合法" | int(a9.apply_rest(30, 70, 100)) == 30 and int(a9.apply_rest(30, 95, 100)) == 5 \\ and int(a9.apply_rest(30, 100, 100)) == 0 | (待填) | | |
| 501 | 372 | "同一公式两个落点逐位一致(LevelRunner.rest_heal_amount 内置默认)" | int(lr9.rest_heal_amount(30, 70, 100)) == 30 and int(lr9.rest_heal_amount(30, 95, 100)) == 5 | (待填) | | |
| 502 | 376 | "放弃场景:冒险启动(seed 15——生成图确定同上)" | a10.start_adventure(15) | (待填) | | |
| 503 | 377 | "放弃场景:入口停点 pick 进首战(停点前移批 2026-09-23 驱动口径)" | a10.runner().pick_next_node(int(a10.runner().next_candidate_ids()[0])) | (待填) | | |
| 504 | 378 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not a10.abandon() | (待填) | | |
| 505 | 385 | "非战斗期(选路态)abandon 受理" | a10.runner().current_battle() == null and a10.abandon() | (待填) | | |
| 506 | 389 | "剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 wide_scout_band)" | a11.start_adventure(11, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | |
| 507 | 398 | "池耗即枯竭(attack/ap 令牌耗尽不出候选——%s)" ↩ % str(draw) | not draw_kinds.has("attack") and not draw_kinds.has("ap") | (待填) | | |
| 508 | 399 | "池耗后候选恒 ≥2(稀有档余量——防「三选项全不可选」死角)" | (draw as Array).size() >= 2 | (待填) | | |
| 509 | 408 | "池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)" | not treasure_seen | (待填) | | |
| 510 | 417 | "满血关卡完成回血 0 合法(封顶口径——heal_gained=0 非负溢出)" | int(cap["payload"].get("heal_gained", -1)) == 0 | (待填) | | |
| 511 | 420 | "节点共享池场景:冒险启动(seed 13)" | a13.start_adventure(13, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | |
| 512 | 431 | "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" ↩ % str(ncands13) | not has_attack13 | (待填) | | |
| 513 | 435 | "奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" ↩ % int(_db.adventure.curio_value("gp", "common")) | a13.apply_node_curio("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")) | (待填) | | |
| 514 | 436 | "节点停点态兑付后清零(恰一次语义同构)" | a13._node_curio_pending.is_empty() | (待填) | | |
| 515 | 438 | "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)" | String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)) | (待填) | | |
| 459 | 118 | "序列关数 = 行集关数(三关正式序列生效)" | int(_db.level.rows.size()) == 3 | (待填) | | |
| 460 | 135 | "生成图每个战斗/Boss 节点经解析链命中敌行集与棋盘行(扫描 %d 节点,缺失 %d)〔fv24〕" ↩ % [cov_scan, cov_miss] | cov_scan > 0 and cov_miss == 0 | (待填) | | |
| 461 | 140 | "满配冒险成功(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)" | String(st["result"]) == "completed" and bool(sum.get("cleared", false)) | (待填) | | |
| 462 | 141 | "完成几关 = 3(levels_cleared)" | int(sum.get("levels_cleared", -1)) == 3 | (待填) | | |
| 463 | 145 | "钥石合计 = ΣBoss 首杀行值(%d——逐杀掉落新口径;断点未注入恒首杀)" ↩ % int(sum.get("keystone_total", -1)) | int(sum.get("keystone_total", -1)) == ks_exp | (待填) | | |
| 464 | 147 | "逐杀掉落明细恰 3 笔(L1/L2/L3——additive 摘要键 keystone_drops)" | drops.size() == 3 | (待填) | | |
| 465 | 148 | "摘要携带主种子(42)" | int(sum.get("master_seed", -1)) == 42 | (待填) | | |
| 466 | 159 | "关卡完成三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)" | picks.size() == 2 and resolves.size() == 2 + node_picks | (待填) | | |
| 467 | 170 | "三关生成图各含 ≥1 奖励节点(节点三选一候选位在图)〔v4〕" | reward_levels == 3 | (待填) | | |
| 468 | 174 | "奖励分支点选→三选一候选可取且停点在途(候选 %s 件)" ↩ % str((rw.get("cands", []) as Array).size()) | bool(rw.get("pending", false)) and (rw.get("cands", []) as Array).size() >= 1 | (待填) | | |
| 469 | 187 | "奖励三选一点选兑付受理(件=%s)" ↩ % String(rw_pc.get("kind", "")) | rw_pick_ok | (待填) | | |
| 470 | 193 | "进战载荷两样=关卡序号+节点深度(实测 depth=%s 句柄=%s)〔v6〕" ↩ % [str(enc_probe.get("node_depth", -1)), str(node_probe.get("id", -1))] | not enc_probe.is_empty() and int(enc_probe.get("node_depth", -1)) == int(node_probe.get("depth", -1)) and int(enc_probe.get("level_index", -1)) == 1 and int(node_probe.get("id", -1)) != int(node_probe.get("depth", -1)) | (待填) | | |
| 471 | 202 | "奖励节点兑付回执与经停逐笔相等(context=reward_node——同账计数 %d/%d)" ↩ % [node_resolves, node_picks] | node_resolves == node_picks | (待填) | | |
| 472 | 249 | "三选一候选 2–3 枚且零重复(令牌洗牌不放回——kind+tier 复合身份)" | cand_ok | (待填) | | |
| 473 | 250 | "关卡完成部分回血封顶(floor(观测上限×比例) 封顶——购血观测值精确断言,核代码增补)" | heal_ok | (待填) | | |
| 474 | 251 | "钥石逐杀入账(首杀档位 + 累计逐位)" | ks_gain_ok | (待填) | | |
| 475 | 252 | "点选恰兑付选中一件(值=令牌档位表值;未选项零生效)" | resolve_ok | (待填) | | |
| 476 | 263 | "各战上限入参 ∈ [基础, 基础+3×件值+购血余量](覆盖值通道——双渠道共享上限 3;模拟档贪心购血合法叠加〔数值修复批 v3.4〕;首战=基础 %d)" ↩ % exp_base | seq_ok | (待填) | | |
| 477 | 266 | "同种子两局逐位可复现(summary/上限序列/行进序列/三选一载荷——reward_pick 封闭流)" | st2["summary"] == sum and st2["encounter_max_hp_seq"] == seq and st2["visited_ids"] == st["visited_ids"] and st2["reward_events"] == st["reward_events"] | (待填) | | |
| 478 | 276 | "零配 L1 败北种子可达(seed=%s)" ↩ % str(lose.get("s", -1)) | not lose.is_empty() | (待填) | | |
| 479 | 279 | "L1 败北摘要:未打穿 · 完成 0 关 · 钥石 0 · 止步第 1 层" | not bool(sl["cleared"]) and int(sl["levels_cleared"]) == 0 \\ and int(sl["keystone_total"]) == 0 and int(sl["stopped_at"]) == 1 | (待填) | | |
| 480 | 283 | "L1 败北败退明细:层号/节点编号/节点类型在案(数值验证取数面 2026-09-22)" | int(dl.get("level_index", -1)) == 1 and int(dl.get("node_id", -1)) >= 0 \\ and String(dl.get("node_type", "")) in ["battle", "boss"] | (待填) | | |
| 481 | 292 | "半配(atk+50)L2 败北种子可达(seed=%s)" ↩ % str(half.get("s", -1)) | not half.is_empty() | (待填) | | |
| 482 | 295 | "L2 败北摘要:L1 已过(钥石=L1 首杀掉落)· 止步第 2 层" | not bool(sh["cleared"]) and int(sh["levels_cleared"]) == 1 \\ and int(sh["keystone_total"]) == int(_db.meta_boss_keystone_row(1).get("first_kill", 0)) \\ and int(sh["stopped_at"]) == 2 | (待填) | | |
| 483 | 300 | "L2 败北败退明细:层号=2 · 节点在案(数值验证取数面 2026-09-22)" | int(dh.get("level_index", -1)) == 2 and int(dh.get("node_id", -1)) >= 0 \\ and String(dh.get("node_type", "")) in ["battle", "boss"] | (待填) | | |
| 484 | 306 | "重复拒绝场景:冒险启动(seed 7——生成图确定同上)" | adr6.start_adventure(7) | (待填) | | |
| 485 | 314 | "重复拒绝场景就位:选路态放弃 → 已结算" | adr6.abandon() | (待填) | | |
| 486 | 317 | "重复关卡结束宣布拒绝(账本与摘要零变化——契约 7)" | adr6.summary == sum6_before | (待填) | | |
| 487 | 327 | "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" ↩ % [gains, int(st["battles"])] | gains == int(st["battles"]) | (待填) | | |
| 488 | 328 | "钱包 clear 恰一次且为末事件(关卡收束不清——GP 冒险收束清零归冒险结构)" | clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear" | (待填) | | |
| 489 | 330 | "三关商店房各到达一次(每关入口→终点路径恰穿一店的驱动投影)且首达余额 > 0(%d——GP 跨关延续)" ↩ % (shop_seq[0] if shop_seq.size() > 0 else -1) | shop_seq.size() == 3 and int(shop_seq[0]) > 0 | (待填) | | |
| 490 | 341 | "同账场景种子可达(seed=%s——L2 商店房就位)" ↩ % str(same.get("s", -1)) | not same.is_empty() | (待填) | | |
| 491 | 354 | "进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" ↩ % [hp_price + l2_price_bonus, hp_price, l2_price_bonus] | gp0 >= hp_price + l2_price_bonus | (待填) | | |
| 492 | 355 | "购买 hp_up 受理(出账 + 加算区)" | r8.buy_product("hp_up") | (待填) | | |
| 493 | 357 | "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" ↩ % acc_shop | acc_shop >= 3 | (待填) | | |
| 494 | 360 | "L2 关卡完成三选一停点在案(直调兑付前——零自动推进)〔拆件批 V2〕" | not a8.stop()._pending.is_empty() | (待填) | | |
| 495 | 361 | "未兑付推进拒绝(选择停点零自动推进契约)" | not a8.continue_to_next_level() | (待填) | | |
| 496 | 364 | "三选一点选兑付受理(候选取首件)" | a8.stop().pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))) | (待填) | | |
| 497 | 367 | "三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" ↩ % [a8.hero().bonus_total("max_hp"), acc_shop, picked2] | a8.hero().bonus_total("max_hp") == expect_acc | (待填) | | |
| 498 | 369 | "关卡完成部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" ↩ % [a8.hero().hero_hp, max_eff8] | a8.hero().hero_hp <= max_eff8 | (待填) | | |
| 499 | 370 | "兑付后推进受理(下一关开始)" | a8.continue_to_next_level() | (待填) | | |
| 500 | 373 | "恢复点数学:floor(100×30%)=30 · 封顶缺口 5 · 满血回 0 合法" | int(a9.apply_rest(30, 70, 100)) == 30 and int(a9.apply_rest(30, 95, 100)) == 5 \\ and int(a9.apply_rest(30, 100, 100)) == 0 | (待填) | | |
| 501 | 377 | "同一公式两个落点逐位一致(LevelRunner.rest_heal_amount 内置默认)" | int(lr9.rest_heal_amount(30, 70, 100)) == 30 and int(lr9.rest_heal_amount(30, 95, 100)) == 5 | (待填) | | |
| 502 | 381 | "放弃场景:冒险启动(seed 15——生成图确定同上)" | a10.start_adventure(15) | (待填) | | |
| 503 | 382 | "放弃场景:入口停点 pick 进首战(停点前移批 2026-09-23 驱动口径)" | a10.runner().pick_next_node(int(a10.runner().next_candidate_ids()[0])) | (待填) | | |
| 504 | 383 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not a10.abandon() | (待填) | | |
| 505 | 390 | "非战斗期(选路态)abandon 受理" | a10.runner().current_battle() == null and a10.abandon() | (待填) | | |
| 506 | 394 | "剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 wide_scout_band)" | a11.start_adventure(11, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | |
| 507 | 403 | "池耗即枯竭(attack/ap 令牌耗尽不出候选——%s)" ↩ % str(draw) | not draw_kinds.has("attack") and not draw_kinds.has("ap") | (待填) | | |
| 508 | 404 | "池耗后候选恒 ≥2(稀有档余量——防「三选项全不可选」死角)" | (draw as Array).size() >= 2 | (待填) | | |
| 509 | 413 | "池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)" | not treasure_seen | (待填) | | |
| 510 | 422 | "满血关卡完成回血 0 合法(封顶口径——heal_gained=0 非负溢出)" | int(cap["payload"].get("heal_gained", -1)) == 0 | (待填) | | |
| 511 | 425 | "节点共享池场景:冒险启动(seed 13)" | a13.start_adventure(13, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | |
| 512 | 436 | "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" ↩ % str(ncands13) | not has_attack13 | (待填) | | |
| 513 | 440 | "奖励节点 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")) | (待填) | | |
| 514 | 441 | "节点停点态兑付后清零(恰一次语义同构)" | a13.stop()._node_pending.is_empty() | (待填) | | |
| 515 | 443 | "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)" | String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)) | (待填) | | |
## S73 _smoke_feats(行 449–761 · 68 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填)
## S73 _smoke_feats(行 454–766 · 68 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
| 516 | 449 | "feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)" | _db.feats != null and (_db.feats.rows as Array).size() == 30 | (待填) | | |
| 517 | 450 | "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)" | _db.feats.validate().is_empty() | (待填) | | |
| 518 | 452 | "专长带场景:冒险启动(seed 71)" | a_r0.start_adventure(71) | (待填) | | |
| 519 | 460 | "专长带入池: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 | (待填) | | |
| 520 | 462 | "旧 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() | (待填) | | |
| 521 | 463 | "迁出收尾: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() | (待填) | | |
| 522 | 470 | "层级纪律:牌库 kind 全集=CURIO_KINDS+relic(秘宝 item_id 零入池)〔专长批 V18〕" | kinds_ok | (待填) | | |
| 523 | 476 | "棋盘事实不可变:新 30 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕" | rewrite_rows == 0 | (待填) | | |
| 524 | 483 | "对外命名纪律:30 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕" | bad_words == 0 | (待填) | | |
| 525 | 484 | "数值纪律:陷阱爆发倍率>1 · 血蜜回复<典型陷阱伤 · 石肤上限 3 在册(结构锚形态内 provisional)〔专长批 V22 机检半〕" | float((_db.feats.row_by_id("trap_burst").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("mult", 0)) > 1.0 and int((_db.feats.row_by_id("blood_honey").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("value", 99)) < 5 and int((_db.feats.row_by_id("stone_skin").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("cap", 0)) == 3 | (待填) | | |
| 526 | 502 | "新 schema 构造表 validate 零违例(四级 rarity/cap 字段/词表全过——流派标签已退役零 archetype)" | rt.validate().is_empty() | (待填) | | |
| 527 | 506 | "专长带场景:冒险启动(seed 72——注入 5 行临时表)" | a_r.start_adventure(72) | (待填) | | |
| 528 | 511 | "专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107" | deck_relic == 5 and a_r._curio_deck.size() == 107 | (待填) | | |
| 529 | 522 | "关卡完成渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)" | feat_seen.size() >= 2 | (待填) | | |
| 530 | 523 | "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以专长凭证形态出现)" | not node_relic_seen | (待填) | | |
| 531 | 531 | "A5 专长凭证兑付受理(节点渠道——拿到即开)" | a_t.apply_node_curio("feat_ticket", "excellent") | (待填) | | |
| 532 | 537 | "A5 拿到即开——停点候选已替换为专长三选一(全 relic)" | tkt_all_relic | (待填) | | |
| 533 | 538 | "A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)" | a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]) | (待填) | | |
| 534 | 539 | "A5 无专长凭证残留持有集" | not (a_t.feats_held() as Array).has("feat_ticket") | (待填) | | |
| 535 | 542 | "A5 专长面兑付受理(专长凭证开出候选可正常入持有集)" | (a_t.feats_held() as Array).size() == feats_n + 1 | (待填) | | |
| 536 | 547 | "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)" | a_t._feat_ticket_consider(rg_t) | (待填) | | |
| 537 | 548 | "A6 触发后清零" | a_t._feat_ticket_pity == 0 | (待填) | | |
| 538 | 551 | "A6 限购满不再触发(保底冻结)" | not a_t._feat_ticket_consider(rg_t) | (待填) | | |
| 539 | 559 | "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)" | offered | (待填) | | |
| 540 | 561 | "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 | (待填) | | |
| 541 | 585 | "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" ↩ % (i + 1) | refund | (待填) | | |
| 542 | 587 | "双管镐反例:第 %d 次不返(未满 2 次)" ↩ % (i + 1) | not refund | (待填) | | |
| 543 | 591 | "地质学家:首开新类型集 1 点〔专长批 V3〕" | orch._hits_of(geo_hits, "counter_add").size() == 1 | (待填) | | |
| 544 | 595 | "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕" | orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0 | (待填) | | |
| 545 | 597 | "地质学家反例:同类型重复首开不叠点〔专长批 V3〕" | orch._hits_of(geo_hits3, "counter_add").is_empty() | (待填) | | |
| 546 | 601 | "安全帽:开安全格攒层 ×2〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2 | (待填) | | |
| 547 | 607 | "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕" | hh_reduce == 2 | (待填) | | |
| 548 | 611 | "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕" | orch._hits_of(sk_zero, "counter_add").size() == 1 | (待填) | | |
| 549 | 615 | "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕" | orch._hits_of(sk_full, "reveal_tiles").size() == 1 | (待填) | | |
| 550 | 616 | "跳格采掘反例:普通开格不计点〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty() | (待填) | | |
| 551 | 623 | "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | |
| 552 | 625 | "边陲矿工:边缘格产出 +1·中心格反例零产出〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": true}), "grant_material").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": false}), "grant_material").is_empty() | (待填) | | |
| 553 | 633 | "封印回响:标中计数≥2→轮末返 1 AP(下轮 queue)〔专长批 V3〕" | orch._hits_of(se_hits, "grant_ap").size() == 1 and String((orch._hits_of(se_hits, "grant_ap")[0] as Dictionary).get("mode", "")) == "queue" | (待填) | | |
| 554 | 642 | "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕" | sm_remove == 1 | (待填) | | |
| 555 | 644 | "封印桩:标中本轮 AC +N(round 域——round_start 清零)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus").size() == 1 and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus")[0] as Dictionary).get("mode", "")) == "round" | (待填) | | |
| 556 | 648 | "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1 | (待填) | | |
| 557 | 657 | "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕" | pm_bonus == 2 | (待填) | | |
| 558 | 664 | "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty() | (待填) | | |
| 559 | 668 | "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕" | fw_shield == 0 | (待填) | | |
| 560 | 670 | "见血开窍:踩雷揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1 | (待填) | | |
| 561 | 677 | "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕" | ti_val == 2 | (待填) | | |
| 562 | 682 | "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2 | (待填) | | |
| 563 | 685 | "陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled——施加面值=damage×mult)〔专长批 V3〕" | orch._hits_of(tb_hits, "blast_scaled").size() == 1 and float((orch._hits_of(tb_hits, "blast_scaled")[0] as Dictionary).get("mult", 0)) > 1.0 | (待填) | | |
| 564 | 689 | "血蜜:踩雷回复 N 生命〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1 | (待填) | | |
| 565 | 692 | "石肤:受伤 AC+1(本场域 cap 3——达上限不再涨由累积器封顶)〔专长批 V3〕" | orch._hits_of(ss_hits, "ac_bonus").size() == 1 and int((orch._hits_of(ss_hits, "ac_bonus")[0] as Dictionary).get("cap", 0)) == 3 | (待填) | | |
| 566 | 696 | "背水:生命≤50% 减伤·过半血反例零减伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1, "hp_ratio": 40}), "damage_reduce").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 2, "hp_ratio": 80}), "damage_reduce").is_empty() | (待填) | | |
| 567 | 701 | "破封一击:挖开带封印危险格 +1 层·无封印反例零触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": true}), "counter_add").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": false}), "counter_add").is_empty() | (待填) | | |
| 568 | 709 | "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕" | orch._hits_of(me_hits, "counter_add").size() == 1 | (待填) | | |
| 569 | 713 | "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕" | me_atk == 4 | (待填) | | |
| 570 | 714 | "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty() | (待填) | | |
| 571 | 717 | "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕" | orch._hits_of(ld_hits, "grant_ap").size() == 1 | (待填) | | |
| 572 | 718 | "一滴不剩反例:上轮有余不发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty() | (待填) | | |
| 573 | 724 | "收盘:本轮开格≥8→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | |
| 574 | 727 | "斩首学者:击杀揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1 | (待填) | | |
| 575 | 730 | "处刑台:敌血量≤30% 挂单位标记加伤·阈值外反例零命中〔专长批 V3〕" | orch._hits_of(ex_hits, "target_attack_bonus").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty() | (待填) | | |
| 576 | 734 | "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1 | (待填) | | |
| 577 | 737 | "血税:双向同局账本(踩陷阱−2max_hp/封印命中+1atk——bonus_delta 战末汇入)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "bonus_delta").size() == 1 and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "bonus_delta")[0] as Dictionary).get("axis", "")) == "atk" | (待填) | | |
| 578 | 742 | "淬毒刃纹(迁入):克制正面(magic_vs_heavy 矩阵值>0)追加·中性面反例零触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "normal_vs_light"}), "modify_ticket").is_empty() | (待填) | | |
| 579 | 746 | "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1 | (待填) | | |
| 580 | 750 | "零持有订阅=零命中(relic 关着零扰动——伤害管线既有行为逐位不变)" | orch0._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}).is_empty() and orch0._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty() and orch0._fx_emit("mine_hit", {"damage": 5}).is_empty() and orch0._fx_emit("battle_won", {"round": 1}).is_empty() | (待填) | | |
| 581 | 758 | "特性段空态隐藏(视图空态纪律同特性段)" | not panel._feat_section.visible | (待填) | | |
| 582 | 760 | "特性段持有呈现(逐件行常驻可读——「局内奖励选了什么看不到」缺口承载)" | panel._feat_section.visible and panel._feat_list.get_child_count() == 2 | (待填) | | |
| 583 | 761 | "特性卡文案表值化:desc 行字段直读(零硬编码映射)" | String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "pain_memory", "display_name": "疼痛记忆", "desc": "触陷阱攒 1 层"})).contains("触陷阱攒 1 层") | (待填) | | |
| 516 | 454 | "feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)" | _db.feats != null and (_db.feats.rows as Array).size() == 30 | (待填) | | |
| 517 | 455 | "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)" | _db.feats.validate().is_empty() | (待填) | | |
| 518 | 457 | "专长带场景:冒险启动(seed 71)" | a_r0.start_adventure(71) | (待填) | | |
| 519 | 465 | "专长带入池: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 | (待填) | | |
| 520 | 467 | "旧 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() | (待填) | | |
| 521 | 468 | "迁出收尾: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() | (待填) | | |
| 522 | 475 | "层级纪律:牌库 kind 全集=CURIO_KINDS+relic(秘宝 item_id 零入池)〔专长批 V18〕" | kinds_ok | (待填) | | |
| 523 | 481 | "棋盘事实不可变:新 30 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕" | rewrite_rows == 0 | (待填) | | |
| 524 | 488 | "对外命名纪律:30 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕" | bad_words == 0 | (待填) | | |
| 525 | 489 | "数值纪律:陷阱爆发倍率>1 · 血蜜回复<典型陷阱伤 · 石肤上限 3 在册(结构锚形态内 provisional)〔专长批 V22 机检半〕" | float((_db.feats.row_by_id("trap_burst").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("mult", 0)) > 1.0 and int((_db.feats.row_by_id("blood_honey").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("value", 99)) < 5 and int((_db.feats.row_by_id("stone_skin").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("cap", 0)) == 3 | (待填) | | |
| 526 | 507 | "新 schema 构造表 validate 零违例(四级 rarity/cap 字段/词表全过——流派标签已退役零 archetype)" | rt.validate().is_empty() | (待填) | | |
| 527 | 511 | "专长带场景:冒险启动(seed 72——注入 5 行临时表)" | a_r.start_adventure(72) | (待填) | | |
| 528 | 516 | "专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107" | deck_relic == 5 and a_r._curio.deck.size() == 107 | (待填) | | |
| 529 | 527 | "关卡完成渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)" | feat_seen.size() >= 2 | (待填) | | |
| 530 | 528 | "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以专长凭证形态出现)" | not node_relic_seen | (待填) | | |
| 531 | 536 | "A5 专长凭证兑付受理(节点渠道——拿到即开)" | a_t.stop().apply_node_curio("feat_ticket", "excellent") | (待填) | | |
| 532 | 542 | "A5 拿到即开——停点候选已替换为专长三选一(全 relic)" | tkt_all_relic | (待填) | | |
| 533 | 543 | "A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)" | a_t.stop().pool_stock("attack") == int(stock_before["attack"]) and a_t.stop().pool_stock("gp") == int(stock_before["gp"]) | (待填) | | |
| 534 | 544 | "A5 无专长凭证残留持有集" | not (a_t.feats_held() as Array).has("feat_ticket") | (待填) | | |
| 535 | 547 | "A5 专长面兑付受理(专长凭证开出候选可正常入持有集)" | (a_t.feats_held() as Array).size() == feats_n + 1 | (待填) | | |
| 536 | 552 | "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)" | a_t.stop().feat_ticket_consider(rg_t) | (待填) | | |
| 537 | 553 | "A6 触发后清零" | a_t.stop()._deck.ticket_pity == 0 | (待填) | | |
| 538 | 556 | "A6 限购满不再触发(保底冻结)" | not a_t.stop().feat_ticket_consider(rg_t) | (待填) | | |
| 539 | 564 | "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)" | offered | (待填) | | |
| 540 | 566 | "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 | (待填) | | |
| 541 | 590 | "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" ↩ % (i + 1) | refund | (待填) | | |
| 542 | 592 | "双管镐反例:第 %d 次不返(未满 2 次)" ↩ % (i + 1) | not refund | (待填) | | |
| 543 | 596 | "地质学家:首开新类型集 1 点〔专长批 V3〕" | orch._hits_of(geo_hits, "counter_add").size() == 1 | (待填) | | |
| 544 | 600 | "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕" | orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0 | (待填) | | |
| 545 | 602 | "地质学家反例:同类型重复首开不叠点〔专长批 V3〕" | orch._hits_of(geo_hits3, "counter_add").is_empty() | (待填) | | |
| 546 | 606 | "安全帽:开安全格攒层 ×2〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2 | (待填) | | |
| 547 | 612 | "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕" | hh_reduce == 2 | (待填) | | |
| 548 | 616 | "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕" | orch._hits_of(sk_zero, "counter_add").size() == 1 | (待填) | | |
| 549 | 620 | "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕" | orch._hits_of(sk_full, "reveal_tiles").size() == 1 | (待填) | | |
| 550 | 621 | "跳格采掘反例:普通开格不计点〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty() | (待填) | | |
| 551 | 628 | "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | |
| 552 | 630 | "边陲矿工:边缘格产出 +1·中心格反例零产出〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": true}), "grant_material").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": false}), "grant_material").is_empty() | (待填) | | |
| 553 | 638 | "封印回响:标中计数≥2→轮末返 1 AP(下轮 queue)〔专长批 V3〕" | orch._hits_of(se_hits, "grant_ap").size() == 1 and String((orch._hits_of(se_hits, "grant_ap")[0] as Dictionary).get("mode", "")) == "queue" | (待填) | | |
| 554 | 647 | "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕" | sm_remove == 1 | (待填) | | |
| 555 | 649 | "封印桩:标中本轮 AC +N(round 域——round_start 清零)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus").size() == 1 and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus")[0] as Dictionary).get("mode", "")) == "round" | (待填) | | |
| 556 | 653 | "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1 | (待填) | | |
| 557 | 662 | "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕" | pm_bonus == 2 | (待填) | | |
| 558 | 669 | "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty() | (待填) | | |
| 559 | 673 | "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕" | fw_shield == 0 | (待填) | | |
| 560 | 675 | "见血开窍:踩雷揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1 | (待填) | | |
| 561 | 682 | "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕" | ti_val == 2 | (待填) | | |
| 562 | 687 | "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2 | (待填) | | |
| 563 | 690 | "陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled——施加面值=damage×mult)〔专长批 V3〕" | orch._hits_of(tb_hits, "blast_scaled").size() == 1 and float((orch._hits_of(tb_hits, "blast_scaled")[0] as Dictionary).get("mult", 0)) > 1.0 | (待填) | | |
| 564 | 694 | "血蜜:踩雷回复 N 生命〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1 | (待填) | | |
| 565 | 697 | "石肤:受伤 AC+1(本场域 cap 3——达上限不再涨由累积器封顶)〔专长批 V3〕" | orch._hits_of(ss_hits, "ac_bonus").size() == 1 and int((orch._hits_of(ss_hits, "ac_bonus")[0] as Dictionary).get("cap", 0)) == 3 | (待填) | | |
| 566 | 701 | "背水:生命≤50% 减伤·过半血反例零减伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1, "hp_ratio": 40}), "damage_reduce").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 2, "hp_ratio": 80}), "damage_reduce").is_empty() | (待填) | | |
| 567 | 706 | "破封一击:挖开带封印危险格 +1 层·无封印反例零触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": true}), "counter_add").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": false}), "counter_add").is_empty() | (待填) | | |
| 568 | 714 | "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕" | orch._hits_of(me_hits, "counter_add").size() == 1 | (待填) | | |
| 569 | 718 | "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕" | me_atk == 4 | (待填) | | |
| 570 | 719 | "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty() | (待填) | | |
| 571 | 722 | "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕" | orch._hits_of(ld_hits, "grant_ap").size() == 1 | (待填) | | |
| 572 | 723 | "一滴不剩反例:上轮有余不发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty() | (待填) | | |
| 573 | 729 | "收盘:本轮开格≥8→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | |
| 574 | 732 | "斩首学者:击杀揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1 | (待填) | | |
| 575 | 735 | "处刑台:敌血量≤30% 挂单位标记加伤·阈值外反例零命中〔专长批 V3〕" | orch._hits_of(ex_hits, "target_attack_bonus").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty() | (待填) | | |
| 576 | 739 | "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1 | (待填) | | |
| 577 | 742 | "血税:双向同局账本(踩陷阱−2max_hp/封印命中+1atk——bonus_delta 战末汇入)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "bonus_delta").size() == 1 and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "bonus_delta")[0] as Dictionary).get("axis", "")) == "atk" | (待填) | | |
| 578 | 747 | "淬毒刃纹(迁入):克制正面(magic_vs_heavy 矩阵值>0)追加·中性面反例零触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "normal_vs_light"}), "modify_ticket").is_empty() | (待填) | | |
| 579 | 751 | "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1 | (待填) | | |
| 580 | 755 | "零持有订阅=零命中(relic 关着零扰动——伤害管线既有行为逐位不变)" | orch0._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}).is_empty() and orch0._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty() and orch0._fx_emit("mine_hit", {"damage": 5}).is_empty() and orch0._fx_emit("battle_won", {"round": 1}).is_empty() | (待填) | | |
| 581 | 763 | "特性段空态隐藏(视图空态纪律同特性段)" | not panel._feat_section.visible | (待填) | | |
| 582 | 765 | "特性段持有呈现(逐件行常驻可读——「局内奖励选了什么看不到」缺口承载)" | panel._feat_section.visible and panel._feat_list.get_child_count() == 2 | (待填) | | |
| 583 | 766 | "特性卡文案表值化:desc 行字段直读(零硬编码映射)" | String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "pain_memory", "display_name": "疼痛记忆", "desc": "触陷阱攒 1 层"})).contains("触陷阱攒 1 层") | (待填) | | |
## S74 _smoke_layout_cell_formula(行 65–68 · 2 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填)
@@ -1238,7 +1238,7 @@
| 708 | 383 | "关卡步骤流非空(%d 行)" ↩ % rep1.rows.size() | rep1.rows.size() > 0 | (待填) | | |
| 709 | 385 | "模拟后 hero 表恢复基线(atk=%d hp=%d ac=%d——对配置快照)" ↩ % [int(_db.hero.atk), int(_db.hero.max_hp), int(_db.hero.armor_class)] | int(_db.hero.atk) == int(hero_base.atk) and int(_db.hero.max_hp) == int(hero_base.hp) \\ and int(_db.hero.armor_class) == int(hero_base.ac) | (待填) | | |
## S104 _smoke_level_graph(行 442–762 · 70 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
## S104 _smoke_level_graph(行 442–761 · 70 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
@@ -1306,131 +1306,131 @@
| 771 | 710 | "混编各行行值独立投影(display_name/hp/atk 按各自行值——同场不同种不同值)" | String(mix_units[0].get("display_name")) == "地精" and int(mix_units[1].get("hp")) == 20 and int(mix_units[1].get("atk")) == 3 | (待填) | | |
| 772 | 714 | "行集实例总数守卫:两行各定值 3(总 6 > 4)spawn 被拒(行集构成纪律③——R3 fail-fast 运行面)" | not over_sys.spawn(1, 6, int(_db.hero.max_hp)) | (待填) | | |
| 773 | 719 | "混编断言段注入行全量还原(表回 28 行——迭代 35 生成图深度扩面覆盖批后的基线行数;首批混编投放基线 20 行+覆盖 8 行)" | int(_db.enemy.rows.size()) == 28 | (待填) | | |
| 774 | 726 | "可见性冒烟:L2 入口停点就位(defer_entry——无当前节点,锚层 0)" | vis_ok and vis_runner.awaiting_choice() and vis_runner.current_node_id() == -1 | (待填) | | |
| 775 | 732 | "可见性冒烟:入口停点候选集 ⊆ 类型可见集(下一跳恒可见)〔fv4〕" | vis_cand_in | (待填) | | |
| 776 | 737 | "可见性冒烟:L2 全部终点 Boss 句柄 %s 恒在可见集(终点不入雾)〔fv3〕" ↩ % str(_ids_of_type(nodes2, "boss")) | vis_boss_ok | (待填) | | |
| 777 | 739 | "可见性冒烟:N 读数据表 = %d(provisional 2——代码零硬编码)〔fv6〕" ↩ % vis_n | vis_n == 2 | (待填) | | |
| 778 | 761 | "可见性冒烟:入口锚层 0 时层距 ≤ %d 的节点全在可见集(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, near_count] | near_count >= 1 and near_ok | (待填) | | |
| 779 | 762 | "可见性冒烟:入口锚层 0 时层距 > %d 的非终点节点全入雾(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, fog_count] | fog_count >= 1 and fog_ok | (待填) | | |
| 774 | 725 | "可见性冒烟:L2 入口停点就位(defer_entry——无当前节点,锚层 0)" | vis_ok and vis_runner.awaiting_choice() and vis_runner.current_node_id() == -1 | (待填) | | |
| 775 | 731 | "可见性冒烟:入口停点候选集 ⊆ 类型可见集(下一跳恒可见)〔fv4〕" | vis_cand_in | (待填) | | |
| 776 | 736 | "可见性冒烟:L2 全部终点 Boss 句柄 %s 恒在可见集(终点不入雾)〔fv3〕" ↩ % str(_ids_of_type(nodes2, "boss")) | vis_boss_ok | (待填) | | |
| 777 | 738 | "可见性冒烟:N 读数据表 = %d(provisional 2——代码零硬编码)〔fv6〕" ↩ % vis_n | vis_n == 2 | (待填) | | |
| 778 | 760 | "可见性冒烟:入口锚层 0 时层距 ≤ %d 的节点全在可见集(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, near_count] | near_count >= 1 and near_ok | (待填) | | |
| 779 | 761 | "可见性冒烟:入口锚层 0 时层距 > %d 的非终点节点全入雾(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, fog_count] | fog_count >= 1 and fog_ok | (待填) | | |
## S105 _smoke_gp_wallet(行 799–835 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
## S105 _smoke_gp_wallet(行 798–834 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
| 780 | 799 | "GP 到账:L1 启动(普通战档)" | runner.start_level(1, 42) | (待填) | | |
| 781 | 809 | "L1 首战胜利(battle_finished gp_dropped 载荷在案)" | fin.has("p") and String(fin["p"].get("result", "")) == "victory" | (待填) | | |
| 782 | 812 | "L1 普通战 gp_dropped − escrow ∈ [2+0, 4+0](掷定+关级加值 %d,escrow %+d)" ↩ % [gp_dropped - escrow_sum, escrow_sum] | gp_dropped - escrow_sum >= 2 and gp_dropped - escrow_sum <= 4 | (待填) | | |
| 783 | 817 | "钱包恰入账一次(gain 事件 ×%d)" ↩ % gains.size() | gains.size() == 1 | (待填) | | |
| 784 | 819 | "入账金额 = gp_dropped 载荷(%d)" ↩ % gp_dropped | int(gains[0]["amount"]) == gp_dropped | (待填) | | |
| 785 | 820 | "钱包读数 = 入账后投影(gp=%d,破财负值场景经 clamp ≥0)" ↩ % runner.gp | runner.gp == maxi(0, gp_dropped) and runner.gp == int(gains[0]["gp"]) | (待填) | | |
| 786 | 827 | "L2 Boss 战启动((2,9)——Boss 档掷定 [6,10] + 关级加值 1)" | boss.begin_battle({"level_index": 2, "node_depth": 9, "node_type": "boss", "hero_hp": int(_db.hero.max_hp)}) | (待填) | | |
| 787 | 832 | "L2 Boss 战终局(%s——gp_dropped 于战斗末掷定上抛)" ↩ % String(boss.battle_result) | fin_b.has("p") | (待填) | | |
| 788 | 835 | "L2 Boss 战 gp_dropped − escrow ∈ [6+1, 10+1](掷定+关级加值 %d,escrow %+d)" ↩ % [gp_b - escrow_b, escrow_b] | gp_b - escrow_b >= 7 and gp_b - escrow_b <= 11 | (待填) | | |
| 780 | 798 | "GP 到账:L1 启动(普通战档)" | runner.start_level(1, 42) | (待填) | | |
| 781 | 808 | "L1 首战胜利(battle_finished gp_dropped 载荷在案)" | fin.has("p") and String(fin["p"].get("result", "")) == "victory" | (待填) | | |
| 782 | 811 | "L1 普通战 gp_dropped − escrow ∈ [2+0, 4+0](掷定+关级加值 %d,escrow %+d)" ↩ % [gp_dropped - escrow_sum, escrow_sum] | gp_dropped - escrow_sum >= 2 and gp_dropped - escrow_sum <= 4 | (待填) | | |
| 783 | 816 | "钱包恰入账一次(gain 事件 ×%d)" ↩ % gains.size() | gains.size() == 1 | (待填) | | |
| 784 | 818 | "入账金额 = gp_dropped 载荷(%d)" ↩ % gp_dropped | int(gains[0]["amount"]) == gp_dropped | (待填) | | |
| 785 | 819 | "钱包读数 = 入账后投影(gp=%d,破财负值场景经 clamp ≥0)" ↩ % runner.gp | runner.gp == maxi(0, gp_dropped) and runner.gp == int(gains[0]["gp"]) | (待填) | | |
| 786 | 826 | "L2 Boss 战启动((2,9)——Boss 档掷定 [6,10] + 关级加值 1)" | boss.begin_battle({"level_index": 2, "node_depth": 9, "node_type": "boss", "hero_hp": int(_db.hero.max_hp)}) | (待填) | | |
| 787 | 831 | "L2 Boss 战终局(%s——gp_dropped 于战斗末掷定上抛)" ↩ % String(boss.battle_result) | fin_b.has("p") | (待填) | | |
| 788 | 834 | "L2 Boss 战 gp_dropped − escrow ∈ [6+1, 10+1](掷定+关级加值 %d,escrow %+d)" ↩ % [gp_b - escrow_b, escrow_b] | gp_b - escrow_b >= 7 and gp_b - escrow_b <= 11 | (待填) | | |
## S106 _smoke_gp_escrow(行 873–937 · 12 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
## S106 _smoke_gp_escrow(行 872–936 · 12 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
| 789 | 873 | "GP 袋(gp 型安全格)可挖掘局种子可达(seed=%s)" ↩ % str(scan.get("seed", -1)) | not scan.is_empty() | (待填) | | |
| 790 | 886 | "GP局战斗终局(先手 999 收束)" | settlement2.battle_result == "victory" | (待填) | | |
| 791 | 891 | "GP 袋兑付行在案(GP 袋 +3 → 战斗内缓冲,origin 指回来源格)" | gain_ok | (待填) | | |
| 792 | 892 | "escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" ↩ % int(settlement2.escrow) | int(settlement2.escrow) >= 3 | (待填) | | |
| 793 | 894 | "格面兑付计入 gp_dropped(%d − escrow %d ∈ [2,4]——战斗末一次到账,随上抛即弃)" ↩ % [gp_d, int(settlement2.escrow)] | gp_d - int(settlement2.escrow) >= 2 and gp_d - int(settlement2.escrow) <= 4 | (待填) | | |
| 794 | 900 | "破财直验战斗启动" | settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999, "node_type": "boss"}) | (待填) | | |
| 795 | 904 | "破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)" | int(settle_l.escrow) == -6 | (待填) | | |
| 796 | 909 | "破财压制 gp_dropped 负值上抛(%d + 6 = %d ∈ Boss 档掷定 [%d,%d] + 关级加值 %d——掷定不受破财污染)" ↩ % [ gp_l, gp_l + 6, int(boss_drop.get("roll_min", 0)), int(boss_drop.get("roll_max", 0)), boss_bonus1] | gp_l + 6 >= int(boss_drop.get("roll_min", 0)) + boss_bonus1 and gp_l + 6 <= int(boss_drop.get("roll_max", 0)) + boss_bonus1 | (待填) | | |
| 797 | 919 | "clamp 场景:L1 启动(冒险钱包开局 0)" | runner.start_level(1, 4242) | (待填) | | |
| 798 | 931 | "破财压制入账载荷为负(gp_dropped=%d)" ↩ % gp3 | gp3 < 0 | (待填) | | |
| 799 | 936 | "破财入账恰一次且投影读数 0(amount=%d,clamp 后 gp=0)" ↩ % gp3 | gain3.size() == 1 and int(gain3[0]["amount"]) == gp3 and int(gain3[0]["gp"]) == 0 | (待填) | | |
| 800 | 937 | "钱包全程不透支(gp=%d,事件投影最小读数 %d ≥ 0)" ↩ % [runner.gp, int(ev_box["min_gp"])] | runner.gp == 0 and int(ev_box["min_gp"]) >= 0 | (待填) | | |
| 789 | 872 | "GP 袋(gp 型安全格)可挖掘局种子可达(seed=%s)" ↩ % str(scan.get("seed", -1)) | not scan.is_empty() | (待填) | | |
| 790 | 885 | "GP局战斗终局(先手 999 收束)" | settlement2.battle_result == "victory" | (待填) | | |
| 791 | 890 | "GP 袋兑付行在案(GP 袋 +3 → 战斗内缓冲,origin 指回来源格)" | gain_ok | (待填) | | |
| 792 | 891 | "escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" ↩ % int(settlement2.escrow) | int(settlement2.escrow) >= 3 | (待填) | | |
| 793 | 893 | "格面兑付计入 gp_dropped(%d − escrow %d ∈ [2,4]——战斗末一次到账,随上抛即弃)" ↩ % [gp_d, int(settlement2.escrow)] | gp_d - int(settlement2.escrow) >= 2 and gp_d - int(settlement2.escrow) <= 4 | (待填) | | |
| 794 | 899 | "破财直验战斗启动" | settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999, "node_type": "boss"}) | (待填) | | |
| 795 | 903 | "破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)" | int(settle_l.escrow) == -6 | (待填) | | |
| 796 | 908 | "破财压制 gp_dropped 负值上抛(%d + 6 = %d ∈ Boss 档掷定 [%d,%d] + 关级加值 %d——掷定不受破财污染)" ↩ % [ gp_l, gp_l + 6, int(boss_drop.get("roll_min", 0)), int(boss_drop.get("roll_max", 0)), boss_bonus1] | gp_l + 6 >= int(boss_drop.get("roll_min", 0)) + boss_bonus1 and gp_l + 6 <= int(boss_drop.get("roll_max", 0)) + boss_bonus1 | (待填) | | |
| 797 | 918 | "clamp 场景:L1 启动(冒险钱包开局 0)" | runner.start_level(1, 4242) | (待填) | | |
| 798 | 930 | "破财压制入账载荷为负(gp_dropped=%d)" ↩ % gp3 | gp3 < 0 | (待填) | | |
| 799 | 935 | "破财入账恰一次且投影读数 0(amount=%d,clamp 后 gp=0)" ↩ % gp3 | gain3.size() == 1 and int(gain3[0]["amount"]) == gp3 and int(gain3[0]["gp"]) == 0 | (待填) | | |
| 800 | 936 | "钱包全程不透支(gp=%d,事件投影最小读数 %d ≥ 0)" ↩ % [runner.gp, int(ev_box["min_gp"])] | runner.gp == 0 and int(ev_box["min_gp"]) >= 0 | (待填) | | |
## S107 _smoke_shop_room(行 1003–1089 · 25 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
## S107 _smoke_shop_room(行 1002–1088 · 25 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
| 801 | 1003 | "商店房到达种子可达(seed=%s——进店 GP ≥ 16)" ↩ % str(shop_run.get("seed", -1)) | not shop_run.is_empty() | (待填) | | |
| 802 | 1016 | "L2 行进到商店房:当前节点型别 = shop 且出边恰一条指终点 Boss(句柄 %d·出边 %s)" ↩ % [shop_id, str(shop_outs)] | String(runner.current_node().get("type", "")) == "shop" and shop_outs.size() == 1 \\ and String(s_byid.get(int(shop_outs[0]), {}).get("type", "")) == "boss" \\ and (s_byid.get(int(shop_outs[0]), {}).get("next", []) as Array).is_empty() | (待填) | | |
| 803 | 1020 | "L2 到店路径穿商店恰 1 家(已到句柄 %s 型别计数——必经恰一店的行进面)" ↩ % str(runner.visited_ids()) | _path_shop_count(s_nodes, runner.visited_ids()) == 1 | (待填) | | |
| 804 | 1022 | "L2 商店节点(句柄 %d)入边恰一条(店位归终点的直接前驱——行进面复核)" ↩ % shop_id | shop_preds == 1 | (待填) | | |
| 805 | 1024 | "到店商店节点无出边(夹具停摆——去终点方向无法从图解析)" | false | (待填) | | |
| 806 | 1026 | "到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)" | not runner.awaiting_choice() and runner.next_candidate_ids().is_empty() | (待填) | | |
| 807 | 1029 | "三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)" | prods.size() == 3 | (待填) | | |
| 808 | 1031 | "L2 首购现价 = 基价 + 关级加值 2(atk_up 6 / hp_up 4 / armor_up 6)" | int(by_id["atk_up"]["price"]) == 6 and int(by_id["hp_up"]["price"]) == 4 and int(by_id["armor_up"]["price"]) == 6 | (待填) | | |
| 809 | 1034 | "购买 atk_up 受理(现价 6)" | runner.buy_product("atk_up") | (待填) | | |
| 810 | 1035 | "钱包扣现价 6(%d → %d)" ↩ % [gp0, runner.gp] | runner.gp == gp0 - 6 | (待填) | | |
| 811 | 1037 | "统一冒险加算区 atk +1(bonus={atk,max_hp,armor_class} 账本)" | int(runner.run_ledger.bonus.get("atk", -1)) == 1 and int(runner.run_ledger.bonus.get("max_hp", -1)) == 0 and int(runner.run_ledger.bonus.get("armor_class", -1)) == 0 | (待填) | | |
| 812 | 1039 | "atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)" | int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1 | (待填) | | |
| 813 | 1046 | "购买 hp_up 受理(现价 4)" | runner.buy_product("hp_up") | (待填) | | |
| 814 | 1047 | "钱包扣 4(%d)" ↩ % runner.gp | runner.gp == gp0 - 6 - 4 | (待填) | | |
| 815 | 1048 | "生命上限 +3(%d → %d——加算区 max_hp 轴)" ↩ % [mhp_before, runner.effective_max_hp()] | runner.effective_max_hp() == mhp_before + 3 | (待填) | | |
| 816 | 1049 | "当前生命同步 +3 封顶新上限(%d → %d)" ↩ % [hp_before, runner.hero_hp] | runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()) | (待填) | | |
| 817 | 1051 | "购买 armor_up 受理(现价 6)" | runner.buy_product("armor_up") | (待填) | | |
| 818 | 1052 | "AC = 10+1(armor_class 加算区轴,无百分比通道)" | runner.effective_ac() == int(_db.hero.armor_class) + 1 | (待填) | | |
| 819 | 1067 | "余额不足购买被拒(gp=%d < 现价 %d——零扣费零档位变动)" ↩ % [gp_dry, hp_dry] | not runner.buy_product("hp_up") and runner.gp == gp_dry and runner.purchased_tier("hp_up") == tier_dry | (待填) | | |
| 820 | 1069 | "离开商店 → 回选路口(候选 = 店出边 %s——零自动推进收口)" ↩ % str(shop_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(shop_outs) | (待填) | | |
| 821 | 1072 | "主角攻击生效值 = 2+1(基础表值 + 加算区)" | runner.effective_atk() == int(_db.hero.atk) + 1 | (待填) | | |
| 822 | 1073 | "选终点 Boss(句柄 %d)开战" ↩ % int(shop_outs[0]) | runner.pick_next_node(int(shop_outs[0])) and runner.current_battle() != null | (待填) | | |
| 823 | 1077 | "战斗开始参数 = 基础表值 + 加算区(atk %d = 2+1 / max_hp %d = %d+%d×3)" ↩ % [int(snap["hero"].get("atk", -1)), int(snap["hero"].get("max_hp", -1)), int(_db.hero.max_hp), hp_buys] | int(snap["hero"].get("atk", -1)) == int(_db.hero.atk) + 1 and int(snap["hero"].get("max_hp", -1)) == int(_db.hero.max_hp) + 3 * hp_buys | (待填) | | |
| 824 | 1088 | "冒险收束(Boss 战终局:%s)" ↩ % String(fin_end.get("p", {}).get("result", "")) | fin_end.has("p") and String(fin_end["p"].get("result", "")) in ["victory", "defeat"] | (待填) | | |
| 825 | 1089 | "冒险收束钱包清零(局内货币生命周期终点——不入存档不入摘要)" | runner.gp == 0 | (待填) | | |
| 801 | 1002 | "商店房到达种子可达(seed=%s——进店 GP ≥ 16)" ↩ % str(shop_run.get("seed", -1)) | not shop_run.is_empty() | (待填) | | |
| 802 | 1015 | "L2 行进到商店房:当前节点型别 = shop 且出边恰一条指终点 Boss(句柄 %d·出边 %s)" ↩ % [shop_id, str(shop_outs)] | String(runner.current_node().get("type", "")) == "shop" and shop_outs.size() == 1 \\ and String(s_byid.get(int(shop_outs[0]), {}).get("type", "")) == "boss" \\ and (s_byid.get(int(shop_outs[0]), {}).get("next", []) as Array).is_empty() | (待填) | | |
| 803 | 1019 | "L2 到店路径穿商店恰 1 家(已到句柄 %s 型别计数——必经恰一店的行进面)" ↩ % str(runner.visited_ids()) | _path_shop_count(s_nodes, runner.visited_ids()) == 1 | (待填) | | |
| 804 | 1021 | "L2 商店节点(句柄 %d)入边恰一条(店位归终点的直接前驱——行进面复核)" ↩ % shop_id | shop_preds == 1 | (待填) | | |
| 805 | 1023 | "到店商店节点无出边(夹具停摆——去终点方向无法从图解析)" | false | (待填) | | |
| 806 | 1025 | "到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)" | not runner.awaiting_choice() and runner.next_candidate_ids().is_empty() | (待填) | | |
| 807 | 1028 | "三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)" | prods.size() == 3 | (待填) | | |
| 808 | 1030 | "L2 首购现价 = 基价 + 关级加值 2(atk_up 6 / hp_up 4 / armor_up 6)" | int(by_id["atk_up"]["price"]) == 6 and int(by_id["hp_up"]["price"]) == 4 and int(by_id["armor_up"]["price"]) == 6 | (待填) | | |
| 809 | 1033 | "购买 atk_up 受理(现价 6)" | runner.buy_product("atk_up") | (待填) | | |
| 810 | 1034 | "钱包扣现价 6(%d → %d)" ↩ % [gp0, runner.gp] | runner.gp == gp0 - 6 | (待填) | | |
| 811 | 1036 | "统一冒险加算区 atk +1(加算区住主角账)" | runner.hero().bonus_total("atk") == 1 and runner.hero().bonus_total("max_hp") == 0 and runner.hero().bonus_total("armor_class") == 0 | (待填) | | |
| 812 | 1038 | "atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)" | int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1 | (待填) | | |
| 813 | 1045 | "购买 hp_up 受理(现价 4)" | runner.buy_product("hp_up") | (待填) | | |
| 814 | 1046 | "钱包扣 4(%d)" ↩ % runner.gp | runner.gp == gp0 - 6 - 4 | (待填) | | |
| 815 | 1047 | "生命上限 +3(%d → %d——加算区 max_hp 轴)" ↩ % [mhp_before, runner.effective_max_hp()] | runner.effective_max_hp() == mhp_before + 3 | (待填) | | |
| 816 | 1048 | "当前生命同步 +3 封顶新上限(%d → %d)" ↩ % [hp_before, runner.hero_hp] | runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()) | (待填) | | |
| 817 | 1050 | "购买 armor_up 受理(现价 6)" | runner.buy_product("armor_up") | (待填) | | |
| 818 | 1051 | "AC = 10+1(armor_class 加算区轴,无百分比通道)" | runner.effective_ac() == int(_db.hero.armor_class) + 1 | (待填) | | |
| 819 | 1066 | "余额不足购买被拒(gp=%d < 现价 %d——零扣费零档位变动)" ↩ % [gp_dry, hp_dry] | not runner.buy_product("hp_up") and runner.gp == gp_dry and runner.purchased_tier("hp_up") == tier_dry | (待填) | | |
| 820 | 1068 | "离开商店 → 回选路口(候选 = 店出边 %s——零自动推进收口)" ↩ % str(shop_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(shop_outs) | (待填) | | |
| 821 | 1071 | "主角攻击生效值 = 2+1(基础表值 + 加算区)" | runner.effective_atk() == int(_db.hero.atk) + 1 | (待填) | | |
| 822 | 1072 | "选终点 Boss(句柄 %d)开战" ↩ % int(shop_outs[0]) | runner.pick_next_node(int(shop_outs[0])) and runner.current_battle() != null | (待填) | | |
| 823 | 1076 | "战斗开始参数 = 基础表值 + 加算区(atk %d = 2+1 / max_hp %d = %d+%d×3)" ↩ % [int(snap["hero"].get("atk", -1)), int(snap["hero"].get("max_hp", -1)), int(_db.hero.max_hp), hp_buys] | int(snap["hero"].get("atk", -1)) == int(_db.hero.atk) + 1 and int(snap["hero"].get("max_hp", -1)) == int(_db.hero.max_hp) + 3 * hp_buys | (待填) | | |
| 824 | 1087 | "冒险收束(Boss 战终局:%s)" ↩ % String(fin_end.get("p", {}).get("result", "")) | fin_end.has("p") and String(fin_end["p"].get("result", "")) in ["victory", "defeat"] | (待填) | | |
| 825 | 1088 | "冒险收束钱包清零(局内货币生命周期终点——不入存档不入摘要)" | runner.gp == 0 | (待填) | | |
## S108 _smoke_shop_graph(行 1111–1168 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
## S108 _smoke_shop_graph(行 1110–1167 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
| 826 | 1111 | "L%d 参数档缺行(商店编排断言无源可扫)" ↩ % L | false | (待填) | | |
| 827 | 1116 | "L%d 种子 %d 生成图为空(编排断言扫描面空转)" ↩ % [L, seed_val] | false | (待填) | | |
| 828 | 1142 | "编排断言扫描面非空(三关参数档 × 种子 %d–%d=图 %d 张·终点 %d·路径 %d)〔v5〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, graph_total, term_total, path_total] | graph_total == 3 * (SWEEP_SEED_TO - SWEEP_SEED_FROM) and term_total >= 3 and path_total >= 3 | (待填) | | |
| 829 | 1144 | "性质① 每个终点的直接前驱恰一家商店(入边一条且源型别=shop·违例 %d/%d 终点)〔v5〕" ↩ % [pred_bad, term_total] | pred_bad == 0 | (待填) | | |
| 830 | 1145 | "性质② 每图商店节点数 = 终点数(违例 %d/%d 图)〔v5〕" ↩ % [count_bad, graph_total] | count_bad == 0 | (待填) | | |
| 831 | 1146 | "性质③ 每条入口→终点路径恰穿一家商店(违例 %d/%d 条路径)〔v5〕" ↩ % [path_bad, path_total] | path_bad == 0 | (待填) | | |
| 832 | 1152 | "L%d 现行图终点 %d 个 = 商店 %d 个·入口→终点路径 %d 条全穿恰一店" ↩ % [L, cur_terms.size(), cur_shops.size(), _all_entry_terminal_paths(cur_nodes).size()] | cur_terms.size() == cur_shops.size() and not cur_terms.is_empty() and _all_entry_terminal_paths(cur_nodes).size() >= cur_terms.size() and _paths_all_one_shop(cur_nodes) | (待填) | | |
| 833 | 1164 | "L%d 现行图终点深度 %s 逐点 Boss 行集解析命中且落带 ≥ 终点深度" ↩ % [L, str(tdepths)] | hit_ok | (待填) | | |
| 834 | 1167 | "L1 现行图跨表覆盖(战斗/Boss 节点 %d 颗非空·敌侧违例 %d·板侧违例 %d)" ↩ % [int(l1_cov["nodes"]), int(l1_cov["enemy_bad"]), int(l1_cov["board_bad"])] | int(l1_cov["nodes"]) >= 1 and int(l1_cov["enemy_bad"]) == 0 and int(l1_cov["board_bad"]) == 0 | (待填) | | |
| 826 | 1110 | "L%d 参数档缺行(商店编排断言无源可扫)" ↩ % L | false | (待填) | | |
| 827 | 1115 | "L%d 种子 %d 生成图为空(编排断言扫描面空转)" ↩ % [L, seed_val] | false | (待填) | | |
| 828 | 1141 | "编排断言扫描面非空(三关参数档 × 种子 %d–%d=图 %d 张·终点 %d·路径 %d)〔v5〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, graph_total, term_total, path_total] | graph_total == 3 * (SWEEP_SEED_TO - SWEEP_SEED_FROM) and term_total >= 3 and path_total >= 3 | (待填) | | |
| 829 | 1143 | "性质① 每个终点的直接前驱恰一家商店(入边一条且源型别=shop·违例 %d/%d 终点)〔v5〕" ↩ % [pred_bad, term_total] | pred_bad == 0 | (待填) | | |
| 830 | 1144 | "性质② 每图商店节点数 = 终点数(违例 %d/%d 图)〔v5〕" ↩ % [count_bad, graph_total] | count_bad == 0 | (待填) | | |
| 831 | 1145 | "性质③ 每条入口→终点路径恰穿一家商店(违例 %d/%d 条路径)〔v5〕" ↩ % [path_bad, path_total] | path_bad == 0 | (待填) | | |
| 832 | 1151 | "L%d 现行图终点 %d 个 = 商店 %d 个·入口→终点路径 %d 条全穿恰一店" ↩ % [L, cur_terms.size(), cur_shops.size(), _all_entry_terminal_paths(cur_nodes).size()] | cur_terms.size() == cur_shops.size() and not cur_terms.is_empty() and _all_entry_terminal_paths(cur_nodes).size() >= cur_terms.size() and _paths_all_one_shop(cur_nodes) | (待填) | | |
| 833 | 1163 | "L%d 现行图终点深度 %s 逐点 Boss 行集解析命中且落带 ≥ 终点深度" ↩ % [L, str(tdepths)] | hit_ok | (待填) | | |
| 834 | 1166 | "L1 现行图跨表覆盖(战斗/Boss 节点 %d 颗非空·敌侧违例 %d·板侧违例 %d)" ↩ % [int(l1_cov["nodes"]), int(l1_cov["enemy_bad"]), int(l1_cov["board_bad"])] | int(l1_cov["nodes"]) >= 1 and int(l1_cov["enemy_bad"]) == 0 and int(l1_cov["board_bad"]) == 0 | (待填) | | |
## S109 _smoke_multi_terminal_settlement(行 1176–1223 · 5 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
## S109 _smoke_multi_terminal_settlement(行 1175–1222 · 5 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
| 835 | 1176 | "L2 现行图为多终点(Boss 终点 %s 两颗以上——大岔口承载面)" ↩ % str(boss_ids) | boss_ids.size() >= 2 | (待填) | | |
| 836 | 1188 | "L2 min/max 两路各打进各自终点 Boss(末句柄 %d / %d 互异且都在图 Boss 集 %s 内;两路 result=%s/%s)" ↩ % [min_last, max_last, str(boss_ids), String(run_min.get("result", "")), String(run_max.get("result", ""))] | String(run_min.get("result", "")) == "victory" and String(run_max.get("result", "")) == "victory" and boss_ids.has(min_last) and boss_ids.has(max_last) and min_last != max_last | (待填) | | |
| 837 | 1192 | "min 路未到 max 终点 %d、max 路未到 min 终点 %d(区域隔离——走 A 区不误兑付 B 区 Boss)" ↩ % [max_last, min_last] | not min_vis.has(max_last) and not max_vis.has(min_last) | (待填) | | |
| 838 | 1199 | "L2 全程驱动启动受理" | runner.start_level(2, 42) | (待填) | | |
| 839 | 1221 | "任一 Boss 胜即本关收束恰一次(count=%d·载荷 result=%s·node_id=%s 在图 Boss 集 %s 内)" ↩ % [int(fin["count"]), String(payload.get("result", "")), str(payload.get("node_id", -1)), str(boss_ids)] | int(fin["count"]) == 1 and String(payload.get("result", "")) == "victory" and boss_ids.has(int(payload.get("node_id", -1))) | (待填) | | |
| 835 | 1175 | "L2 现行图为多终点(Boss 终点 %s 两颗以上——大岔口承载面)" ↩ % str(boss_ids) | boss_ids.size() >= 2 | (待填) | | |
| 836 | 1187 | "L2 min/max 两路各打进各自终点 Boss(末句柄 %d / %d 互异且都在图 Boss 集 %s 内;两路 result=%s/%s)" ↩ % [min_last, max_last, str(boss_ids), String(run_min.get("result", "")), String(run_max.get("result", ""))] | String(run_min.get("result", "")) == "victory" and String(run_max.get("result", "")) == "victory" and boss_ids.has(min_last) and boss_ids.has(max_last) and min_last != max_last | (待填) | | |
| 837 | 1191 | "min 路未到 max 终点 %d、max 路未到 min 终点 %d(区域隔离——走 A 区不误兑付 B 区 Boss)" ↩ % [max_last, min_last] | not min_vis.has(max_last) and not max_vis.has(min_last) | (待填) | | |
| 838 | 1198 | "L2 全程驱动启动受理" | runner.start_level(2, 42) | (待填) | | |
| 839 | 1220 | "任一 Boss 胜即本关收束恰一次(count=%d·载荷 result=%s·node_id=%s 在图 Boss 集 %s 内)" ↩ % [int(fin["count"]), String(payload.get("result", "")), str(payload.get("node_id", -1)), str(boss_ids)] | int(fin["count"]) == 1 and String(payload.get("result", "")) == "victory" and boss_ids.has(int(payload.get("node_id", -1))) | (待填) | | |
## S110 _smoke_shop_pool(行 1258–1392 · 29 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
## S110 _smoke_shop_pool(行 1257–1391 · 29 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
| 840 | 1258 | "池模式商店房场景可达(seed=%s)" ↩ % str(scen.get("s", -1)) | not scen.is_empty() | (待填) | | |
| 841 | 1266 | "进店抽 3(货架 %d 卡——同构交互)" ↩ % first_shelf_a.size() | first_shelf_a.size() == 3 | (待填) | | |
| 842 | 1272 | "刷新首价=基价(%d)" ↩ % cost0 | cost0 == int(_db.adventure.shop_refresh_cost_base) | (待填) | | |
| 843 | 1273 | "基础免费 0 次(Q2——天赋增益位后续添加)" | r8.shop_refresh_free_left() == 0 | (待填) | | |
| 844 | 1275 | "GP 刷新受理(gp=%d cost=%d)" ↩ % [gp0, cost0] | r8.shop_refresh() | (待填) | | |
| 845 | 1276 | "GP 刷新扣费(%d→%d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - cost0 | (待填) | | |
| 846 | 1277 | "刷新爬价 ×curve(%d)" ↩ % int(r8.shop_refresh_cost()) | int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.adventure.shop_refresh_cost_curve))) | (待填) | | |
| 847 | 1285 | "同种子同货架逐位(确定性重放承载——断点货架续玩语义)" | same_shelf | (待填) | | |
| 848 | 1295 | "relic 现价 = ceil(15.0×b^(关序−1))(读表与迁移前字面量逐位一致,%d)〔wd11〕" ↩ % relic_price_now | relic_price_now == relic_price_pre | (待填) | | |
| 849 | 1301 | "货架 kind 在封闭词表或保底 relic 卡(%s)" ↩ % ckind | is_relic or shelf_kinds.has(ckind) | (待填) | | |
| 850 | 1308 | "首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" ↩ % [ckind, int(c.get("price", -1)), expect_p] | int(c.get("price", -1)) == expect_p | (待填) | | |
| 851 | 1315 | "可购卡在架(GP %d——种子扫描保证首购空间)" ↩ % int(r8.gp) | not buy_c.is_empty() | (待填) | | |
| 852 | 1323 | "购买受理(pool 口——%s\|%s)" ↩ % [kind, tier] | r8.buy_pool_item(kind) | (待填) | | |
| 853 | 1324 | "钱包扣现价(%d → %d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - int(buy_c.get("price", 0)) | (待填) | | |
| 854 | 1325 | "加算区 +令牌档位件值(池件=奖励件本体——%s\|%s %+d)" ↩ % [kind, tier, int(_db.adventure.curio_value(kind, tier))] | int(a8.run_ledger.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)) | (待填) | | |
| 855 | 1326 | "共享池账本 -1(跨渠道单点——taken %d → %d)" ↩ % [taken0, int(a8._curio_taken.get(kind, 0))] | int(a8._curio_taken.get(kind, 0)) == taken0 + 1 | (待填) | | |
| 856 | 1338 | "A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" ↩ % expect_p2 | int(second.get("price", -1)) == expect_p2 | (待填) | | |
| 857 | 1339 | "同 visit 第二购受理(A 链软闸在价不在限)" | r8.buy_pool_item(String(second.get("kind_id", ""))) | (待填) | | |
| 858 | 1341 | "A 链爬价场景未构造(抬 GP 后货架仍无可购第二卡)——场景绑定失败" | false | (待填) | | |
| 859 | 1349 | "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)" | not cut_seen | (待填) | | |
| 860 | 1360 | "库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)" | not attack_seen | (待填) | | |
| 861 | 1362 | "池空消费拒绝(handler 零副作用——买光即枯竭硬闸)" | not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before | (待填) | | |
| 862 | 1365 | "商店消费口受理(_apply_curio_pick 同口——真单点库)" | a8._consume_pool_purchase("attack", "common") | (待填) | | |
| 863 | 1371 | "商店购买进奖励牌库摘牌(买一件=奖励面少一件——shop→reward 向共库)" | not reward_attack_seen | (待填) | | |
| 864 | 1376 | "离开商店 → 回选路口(货架态清零落档——零自动推进收口)" | r8.continue_after_node() and r8.awaiting_choice() | (待填) | | |
| 865 | 1378 | "断点扩键 shop_shelf 在案(离开店=空架合法态——P2 甲扩列)" | bp.has("shop_shelf") and (bp["shop_shelf"] as Array).is_empty() | (待填) | | |
| 866 | 1379 | "断点扩键 shop_visit_buys 在案(离开店清零——A 链输入不入档语义)" | bp.has("shop_visit_buys") and int(bp["shop_visit_buys"]) == 0 | (待填) | | |
| 867 | 1384 | "终局池读数六键出讫(run_adventure pool 面——四池种+T5 专长凭证+T6 heal 行,读数只锚一次载体)" | ps.size() == 6 | (待填) | | |
| 868 | 1392 | "终局池余量在域 [0,配额](%s)" ↩ % str(ps) | ps_ok | (待填) | | |
| 840 | 1257 | "池模式商店房场景可达(seed=%s)" ↩ % str(scen.get("s", -1)) | not scen.is_empty() | (待填) | | |
| 841 | 1265 | "进店抽 3(货架 %d 卡——同构交互)" ↩ % first_shelf_a.size() | first_shelf_a.size() == 3 | (待填) | | |
| 842 | 1271 | "刷新首价=基价(%d)" ↩ % cost0 | cost0 == int(_db.adventure.shop_refresh_cost_base) | (待填) | | |
| 843 | 1272 | "基础免费 0 次(Q2——天赋增益位后续添加)" | r8.shop_refresh_free_left() == 0 | (待填) | | |
| 844 | 1274 | "GP 刷新受理(gp=%d cost=%d)" ↩ % [gp0, cost0] | r8.shop_refresh() | (待填) | | |
| 845 | 1275 | "GP 刷新扣费(%d→%d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - cost0 | (待填) | | |
| 846 | 1276 | "刷新爬价 ×curve(%d)" ↩ % int(r8.shop_refresh_cost()) | int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.adventure.shop_refresh_cost_curve))) | (待填) | | |
| 847 | 1284 | "同种子同货架逐位(确定性重放承载——断点货架续玩语义)" | same_shelf | (待填) | | |
| 848 | 1294 | "relic 现价 = ceil(15.0×b^(关序−1))(读表与迁移前字面量逐位一致,%d)〔wd11〕" ↩ % relic_price_now | relic_price_now == relic_price_pre | (待填) | | |
| 849 | 1300 | "货架 kind 在封闭词表或保底 relic 卡(%s)" ↩ % ckind | is_relic or shelf_kinds.has(ckind) | (待填) | | |
| 850 | 1307 | "首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" ↩ % [ckind, int(c.get("price", -1)), expect_p] | int(c.get("price", -1)) == expect_p | (待填) | | |
| 851 | 1314 | "可购卡在架(GP %d——种子扫描保证首购空间)" ↩ % int(r8.gp) | not buy_c.is_empty() | (待填) | | |
| 852 | 1322 | "购买受理(pool 口——%s\|%s)" ↩ % [kind, tier] | r8.buy_pool_item(kind) | (待填) | | |
| 853 | 1323 | "钱包扣现价(%d → %d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - int(buy_c.get("price", 0)) | (待填) | | |
| 854 | 1324 | "加算区 +令牌档位件值(池件=奖励件本体——%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)) | (待填) | | |
| 855 | 1325 | "共享池账本 -1(跨渠道单点——taken %d → %d)" ↩ % [taken0, int(a8._curio.taken.get(kind, 0))] | int(a8._curio.taken.get(kind, 0)) == taken0 + 1 | (待填) | | |
| 856 | 1337 | "A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" ↩ % expect_p2 | int(second.get("price", -1)) == expect_p2 | (待填) | | |
| 857 | 1338 | "同 visit 第二购受理(A 链软闸在价不在限)" | r8.buy_pool_item(String(second.get("kind_id", ""))) | (待填) | | |
| 858 | 1340 | "A 链爬价场景未构造(抬 GP 后货架仍无可购第二卡)——场景绑定失败" | false | (待填) | | |
| 859 | 1348 | "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)" | not cut_seen | (待填) | | |
| 860 | 1359 | "库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)" | not attack_seen | (待填) | | |
| 861 | 1361 | "池空消费拒绝(handler 零副作用——买光即枯竭硬闸)" | not a8.stop().consume_pool_purchase("attack", "common") and a8.stop().pool_stock("attack") == stock_before | (待填) | | |
| 862 | 1364 | "商店消费口受理(停点件兑付单点同口——真单点库)〔拆件批 V3〕" | a8.stop().consume_pool_purchase("attack", "common") | (待填) | | |
| 863 | 1370 | "商店购买进奖励牌库摘牌(买一件=奖励面少一件——shop→reward 向共库)" | not reward_attack_seen | (待填) | | |
| 864 | 1375 | "离开商店 → 回选路口(货架态清零落档——零自动推进收口)" | r8.continue_after_node() and r8.awaiting_choice() | (待填) | | |
| 865 | 1377 | "断点扩键 shop_shelf 在案(离开店=空架合法态——P2 甲扩列)" | bp.has("shop_shelf") and (bp["shop_shelf"] as Array).is_empty() | (待填) | | |
| 866 | 1378 | "断点扩键 shop_visit_buys 在案(离开店清零——A 链输入不入档语义)" | bp.has("shop_visit_buys") and int(bp["shop_visit_buys"]) == 0 | (待填) | | |
| 867 | 1383 | "终局池读数六键出讫(run_adventure pool 面——四池种+T5 专长凭证+T6 heal 行,读数只锚一次载体)" | ps.size() == 6 | (待填) | | |
| 868 | 1391 | "终局池余量在域 [0,配额](%s)" ↩ % str(ps) | ps_ok | (待填) | | |
## S111 _test_positive(行 46–56 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填)
@@ -1508,9 +1508,9 @@
| 918 | 284 | "装配关系读回持久(reload 后投影一致——写新件 id)〔秘宝批 V15〕" | (mp_a2.attune_camp_status()["equipped"] as Array).has("morning_order") | (待填) | | |
| 919 | 288 | "充能重置场景:装配冒险启动(seed 12)〔秘宝批 V8 换主语〕" | a12.start_adventure(12, {"attunement": ["incense_stick", "sky_eye"]}) | (待填) | | |
| 920 | 290 | "充能账本按装备 active_charged 件重置(新目录表值:线香 battle 级 1 次/天眼 level 级 1 次——周期重置点归 V8 逐形态面)" | int(a12.run_ledger.attune_charges.get("incense_stick", -1)) == 1 and int(a12.run_ledger.attune_charges.get("sky_eye", -1)) == 1 | (待填) | | |
| 921 | 296 | "刷新双计数器——X 优先消耗(x1y2 → x0y2)" | a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2 | (待填) | | |
| 922 | 298 | "刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)" | a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0 and a12.refresh_curio() == false | (待填) | | |
| 923 | 302 | "刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕" | a12._attune_refresh_x == 0 and a12.attune_refresh_left() == 0 | (待填) | | |
| 921 | 296 | "刷新双计数器——X 优先消耗(x1y2 → x0y2)" | a12.stop().refresh_curio() and a12.stop()._refresh_x == 0 and a12.stop()._refresh_y == 2 | (待填) | | |
| 922 | 298 | "刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)" | a12.stop().refresh_curio() and a12.stop().refresh_curio() and a12.stop()._refresh_y == 0 and a12.stop().refresh_curio() == false | (待填) | | |
| 923 | 302 | "刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕" | a12.stop()._refresh_x == 0 and a12.stop().attune_refresh_left() == 0 | (待填) | | |
| 924 | 313 | "开局传参夹具前置:Fork 侧 a 激活 rule 键非空 + Y=Lv2 行表值 2(防断言空转)" | rules_e.has("posture_first_strike") and mp_e.attune_refresh_charges() == 2 | (待填) | | |
| 925 | 317 | "主菜单开新冒险参数面:同调装配 = 存档装配集(营地装了什么开局就带什么)" | (p_new.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) | (待填) | | |
| 926 | 319 | "主菜单开新冒险参数面:Y 刷新池 = 当前等级行表值(营地升几档开局就给几次)" | int(p_new.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() | (待填) | | |
@@ -1529,7 +1529,7 @@
| 934 | 357 | "命中线在账未注入(契约 6——override 无 hit 字段〔天赋树批 V13〕)" | not mp.hero_override().has("hit") | (待填) | | |
| 935 | 360 | "注入参数冒险启动(start_adventure params 通道)" | adr.start_adventure(920251, {"hero_override": mp.hero_override()}) | (待填) | | |
| 936 | 364 | "首战 hero_atk = 表值 + 永久强化 flat(begin_battle 同表达式——下一局能用到强化)" | int(r.effective_atk()) == int(_db.hero.atk) + int(ov.get("atk", 0)) | (待填) | | |
| 937 | 366 | "首战 hero_max_hp 与开局生命账本 = 表值 + 注入档" | int(r.effective_max_hp()) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)) and int(adr.ledger["hero_hp"]) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)) | (待填) | | |
| 937 | 366 | "首战 hero_max_hp 与开局生命账本 = 表值 + 注入档" | int(r.effective_max_hp()) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)) and int(adr.hero().hero_hp) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)) | (待填) | | |
| 938 | 371 | "白板冒险启动(无 params 实参——B1 空参数路径)" | adr_w.start_adventure(920251) | (待填) | | |
| 939 | 374 | "零强化参数冒险启动(新档 hero_override 全零)" | adr_z.start_adventure(920251, {"hero_override": load("res://core/meta_progression.gd").new(_db, sv_white).hero_override()}) | (待填) | | |
| 940 | 378 | "白板成立:空参数 = 零强化参数 = 纯表值(atk/max_hp/ac 三轴逐位一致——验收 2/3)" | int(rw.effective_atk()) == int(_db.hero.atk) and int(rw.effective_max_hp()) == int(_db.hero.max_hp) and int(rz.effective_atk()) == int(rw.effective_atk()) and int(rz.effective_max_hp()) == int(rw.effective_max_hp()) and int(rz.effective_ac()) == int(rw.effective_ac()) | (待填) | | |
@@ -1567,8 +1567,8 @@
| 967 | 505 | "重甲直购解锁+首购+选中(三层 AC 夹具)" | mp5.forge_buy_unlock("heavy") and mp5.forge_buy_level("heavy") and mp5.forge_select_armor("heavy") | (待填) | | |
| 968 | 509 | "注入参数冒险启动(两轴+override 同面)" | adr5.start_adventure(77, p5) | (待填) | | |
| 969 | 511 | "入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕" | int(r5.effective_ac()) == 12 + 1 | (待填) | | |
| 970 | 513 | "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕" | adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy" | (待填) | | |
| 971 | 516 | "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕" | mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy" and int(r5.effective_ac()) == 12 + 1 | (待填) | | |
| 970 | 513 | "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕" | adr5.hero().attack_type == "normal" and adr5.hero().armor_type == "heavy" | (待填) | | |
| 971 | 516 | "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕" | mp5.forge_select_armor("light") and adr5.hero().armor_type == "heavy" and int(r5.effective_ac()) == 12 + 1 | (待填) | | |
| 972 | 525 | "enc 两轴直达 spawn 英雄快照(hero_attack 行携带 pierce 键——注入链末端)〔锻造工坊批 V5〕" | pierce_rows >= 1 | (待填) | | |
| 973 | 529 | "spawn 显式两轴注入(magic/heavy 直入英雄快照)〔锻造工坊批 V5〕" | uc2.spawn(1, 0, int(_db.hero.max_hp), [], -1, -1, [], "magic", "heavy") and String(uc2.hero_unit().get("attack_type", "")) == "magic" and String(uc2.hero_unit().get("armor_type", "")) == "heavy" | (待填) | | |
| 974 | 539 | "树零攻防来源复核(12 行 effects 键集无 atk/armor_class——契约 1)〔锻造工坊批 V10〕" | not tree_atk_ac | (待填) | | |
@@ -1603,7 +1603,7 @@
| 998 | 618 | "两线拉满+同调位全解锁 → 地脉共鸣自动点亮(零币特化判定)" | sv.tree_level("easter_ley") == 1 | (待填) | | |
| 999 | 621 | "觉醒零消费(钥石余额不动)" | int(sv.keystone_balance()) == bal_now | (待填) | | |
## S119 _smoke_tree_rules(行 633–757 · 21 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填)
## S119 _smoke_tree_rules(行 633–758 · 21 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填)
| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |
|---|---|---|---|---|---|---|
@@ -1624,10 +1624,10 @@
| 1014 | 721 | "守⑤ rig:次战编排启动(新战新队列实例)" | orch2.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["sense_guard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | (待填) | | |
| 1015 | 727 | "守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)" | int(orch2.action_economy.pending_ap_delta()) == 0 | (待填) | | |
| 1016 | 733 | "散未激活:商品标价原价(无 economy_spend 键)" | int(lr._spend_discounted(100)) == 100 | (待填) | | |
| 1017 | 735 | "散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)" | int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1 | (待填) | | |
| 1018 | 739 | "聚 rig:基线编排启动(无增益侧)" | orch3.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": [], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | (待填) | | |
| 1019 | 748 | "聚 rig:增益编排启动(tree_rules=economy_hoard)" | orch4.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["economy_hoard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | (待填) | | |
| 1020 | 756 | "聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)" | gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base | (待填) | | |
| 1017 | 736 | "散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)" | int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1 | (待填) | | |
| 1018 | 740 | "聚 rig:基线编排启动(无增益侧)" | orch3.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": [], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | (待填) | | |
| 1019 | 749 | "聚 rig:增益编排启动(tree_rules=economy_hoard)" | orch4.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["economy_hoard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | (待填) | | |
| 1020 | 757 | "聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)" | gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base | (待填) | | |
## S120 _init(行 22–30 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填)