From ed0629236ae2aee65970e61924711a808685caa6 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 13 Oct 2026 00:09:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A323-T4):=20=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E6=8E=A5=E7=BA=BF=E2=80=94=E2=80=94hero=5Foverride=20?= =?UTF-8?q?=E5=85=AC=E5=BC=8F=E6=8D=A2=E4=BB=A3=EF=BC=88FORGE=5FROUTE=5FBO?= =?UTF-8?q?NUS=20=E9=80=80=E5=BD=B9=EF=BC=89+=E4=B8=A4=E8=BD=B4=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E9=9D=A2=E5=85=A8=E9=93=BE+=E5=85=A5=E6=88=98=20AC=20?= =?UTF-8?q?=E4=B8=89=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - meta_progression.hero_override:atk=forge_weapon_atk_bonus+树 flat / armor_class=forge_armor_ac_bonus+树 flat / max_hp=树 flat;FORGE_ROUTE_BONUS 预留常量退役(公式不再出现,注释留痕) - main.build_adventure_params:+attack_type/armor_type 两键(meta.hero_attack_type/hero_armor_type——存档选中路线类型轴,异常回退锻造配置默认值) - adventure_runner:params 两键快照入公有 hero_attack_type/hero_armor_type(契约 8 锁死可断言面;start/resume 双路同),_start_current_level 经 set_hero_axes 下传 - level_runner:set_hero_axes 注入+effective_ac 换代=甲型基础 AC(forge_armor_route_by_type.base_ac)+路线加值(base_override)+局内加算区(bonus)三层;enc 携带两键 - battle_orchestrator:spawn 传 enc 两轴(空串=锻造配置默认值回退——行为连续) - 冒烟 V5 后半/V6 锁死/V7 公式对拍+V10 树零攻防复核共 15 断言(含 enc 末端 hero_attack 行 pierce 键+spawn 显式注入) - 轻闸:冒烟 938/0(923 基线 +15)+run_all 23 套全绿;删除标识符 live 引用零残留(FORGE_ROUTE_BONUS/hero 两轴仅存退役注记与 README 文档面=归 T7) --- game/core/adventure_runner.gd | 9 ++++ game/core/battle_orchestrator.gd | 3 +- game/core/level_runner.gd | 28 +++++++++++- game/core/meta_progression.gd | 23 +++++----- game/presentation/main.gd | 4 ++ game/smoke/smoke_all.gd | 76 ++++++++++++++++++++++++++++++-- 6 files changed, 127 insertions(+), 16 deletions(-) diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index a84134eb..7cb22e12 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -62,6 +62,10 @@ var level_index := 0 # 当前关卡序号(1 起) var levels_total := 0 # 序列总关数(读 level 表行数) var keystone := 0 # 钥石入账累计(摘要第三件事的账面) var master_seed := 0 # 本局主种子(摘要第五件事) +# 两根类型轴冒险开始快照(迭代 23 T4——契约 8:营地选中态经参数面注入、冒险开始后锁死; +# 公有=参数面快照的可断言面〔契约 9 写口唯一,本字段只读不改〕。空串=未注入→消费面回退锻造配置默认值) +var hero_attack_type := "" +var hero_armor_type := "" var summary := {} # 最近一次结算的冒险摘要(idle 后保留——重开才新生成) var _runner: LevelRunner # 当前关卡行进器(每关新建——关卡域状态天然隔离) var _pending_next_level := false # 大奖励已兑付、等 continue_to_next_level 确认 @@ -128,6 +132,8 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool: level_index = 1 _pending_next_level = false _base_override = _normalize_override(params.get("hero_override", {})) + hero_attack_type = String(params.get("attack_type", "")) # 迭代 23 T4——两根类型轴快照(契约 8 锁死:开局后不随存档选中变化) + hero_armor_type = String(params.get("armor_type", "")) _first_script = bool(params.get("first_script", false)) # 缺省 = 非首次(抽签路径;真实侧由呈现层读存档布尔位传入) _shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 迭代 11 第②批 T3——缺省 legacy(off 对照零扰动) _treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {} @@ -211,6 +217,8 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: _feats.append(String(r)) _keystone_drops = (bp.get("keystone_drops", []) as Array).duplicate(true) _base_override = _normalize_override(params.get("hero_override", {})) + hero_attack_type = String(params.get("attack_type", "")) # 迭代 23 T4——续玩参数面同 start_adventure(账号层选中态随 resume 重注入) + hero_armor_type = String(params.get("armor_type", "")) # 生命账本越界钳制(T17——spawn 守卫 EC16 免疫):断点可能由旧口径版本写出(如调试上限 # 调高期带出的账本值 > 新口径生效上限,实例:hero_hp=28 > 基础 26)——恢复后钳到生效上限, # 否则首战 spawn「hero_hp 入参越界」拒收 → 「敌人系统 spawn 被拒」fatal。上限 = 表值+永久 @@ -936,6 +944,7 @@ func _start_current_level() -> bool: _runner.set_vision_counter(vision_counter) # 迭代 22 秘宝批 T3——真视使用计数共享 dict 下传(编排器受理成功 +1) _runner.set_feats(_feats) # 迭代 12 T1——特性持有集下传(begin_battle enc.feats 求值面) _runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费) + _runner.set_hero_axes(hero_attack_type, hero_armor_type) # 迭代 23 T4——两根类型轴快照下传(effective_ac 甲型基础 AC 层+enc 携带进 spawn) if _shop_mode == "pool": # 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动。 # 迭代 20 T2——cap 检查回调四参注入) diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index ab02386f..8c328c92 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -394,7 +394,8 @@ func begin_battle(enc: Dictionary) -> bool: # E4 收口:加成经 encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径) _hero_atk = int(enc.get("hero_atk", int(db.hero.atk))) if not _enemy_system.spawn(int(enc.level_index), int(enc.node_depth), int(enc.hero_hp), count_overrides, - _hero_atk, int(enc.get("hero_max_hp", -1)), atk_overrides): + _hero_atk, int(enc.get("hero_max_hp", -1)), atk_overrides, + String(enc.get("attack_type", "")), String(enc.get("armor_type", ""))): # 迭代 23 T4——两根类型轴(空串=锻造配置默认值回退,行为连续) _fatal("敌人系统 spawn 被拒") return false if not _install_new_board(): diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index aa524ccf..264868ac 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -68,6 +68,10 @@ var _attunement: Array = [] # 同调装配集(迭代 11 第 var _attune_charges: Dictionary = {} # 同调主动件充能账本(迭代 11 第①批 T6——adventure 共享 dict 引用;settlement use_treasure 扣减直改同引用) # 关卡末清空+快照计数入 level_finished 载荷) var _vision_counter: Dictionary = {"used": 0} # 真视类工具使用计数(迭代 22 秘宝批 T3——adventure 共享 dict 引用;编排器受理成功 +1,收束聚合入档) +# 两根类型轴(迭代 23 T4——adventure 参数面快照经 set_hero_axes 下传;空串=未注入→锻造配置默认值回退。 +# 消费面:effective_ac 甲型基础 AC 层 + begin_battle enc 携带进 unit_container.spawn 英雄快照) +var _hero_attack_type := "" +var _hero_armor_type := "" # ---- 商店共享池模式态(迭代 11 第②批 T3——缺省 legacy=固定三件加定价 off 对照承载,零 RNG 零池消费;TP0 令牌池换代)---- var _shop_pool_mode := false # pool 模式旗(set_shop_pool 注入时置位——AdventureRunner 按 params.shop_mode 下传) var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._curio_deck——库存=剩余令牌数单点) @@ -120,9 +124,21 @@ func effective_max_hp() -> int: return maxi(1, int(round(float(base) * (100.0 + float(hero_hp_pct)) / 100.0))) -# 护甲等级生效值(永久强化 flat + 加算区 armor_class 轴——无调试浮窗百分比通道,纯 flat) +# 护甲等级生效值(入战 AC 三层可加——迭代 23 T4:甲型基础 AC〔forge.tres 选中甲路线 base_ac〕+ +# 甲路线等级加值〔base_override.armor_class——hero_override 锻造分量〕+ 加算区 armor_class 轴; +# 无调试浮窗百分比通道,纯 flat) func effective_ac() -> int: - return maxi(0, int(db.hero.armor_class) + int(base_override.get("armor_class", 0)) + int(bonus.get("armor_class", 0))) + return maxi(0, _armor_base_ac() + int(base_override.get("armor_class", 0)) + int(bonus.get("armor_class", 0))) + + +# 甲型基础 AC(三层之第一层——选中甲路线行 base_ac;未注入/缺行=锻造配置默认值路线=轻甲 10, +# 与 hero.tres 两轴删除前 armor_class=10 行为连续) +func _armor_base_ac() -> int: + var at: String = _hero_armor_type if _hero_armor_type != "" else db.forge_default_armor_type() + var row: Dictionary = db.forge_armor_route_by_type(at) + if row.is_empty(): + return int(db.hero.armor_class) # 表缺失防御兜底(现役基准) + return int(row.get("base_ac", 0)) # 行动点加成生效值(迭代 10 奖励重构批——统一冒险加算区第四轴 ap;经战斗开始入参 hero_ap_bonus @@ -480,6 +496,7 @@ func _start_battle(node: Dictionary) -> bool: if not _settlement.begin_battle({"level_index": int(level_row.level_index), "node_depth": int(node.get("node_depth", 0)), "hero_hp": hero_hp, "hero_atk": effective_atk(), "hero_max_hp": effective_max_hp(), "hero_armor_class": effective_ac(), "hero_ap_bonus": effective_ap_bonus(), + "attack_type": _hero_attack_type, "armor_type": _hero_armor_type, # 迭代 23 T4——两根类型轴(spawn 英雄快照;空串=锻造配置默认值回退) "attunement": _attunement, "attune_charges": _attune_charges, "vision_counter": _vision_counter, # 迭代 22 秘宝批 T3——真视使用计数共享 dict(编排器受理成功 +1,收束入档) @@ -637,6 +654,13 @@ func set_tree_rules(rules: Array) -> void: _tree_rules = rules.duplicate() +# 两根类型轴注入(迭代 23 T4——adventure 参数面快照;空串=未注入→锻造配置默认值回退。 +# 消费面:effective_ac 甲型基础 AC 层 + begin_battle enc 携带进 unit_container.spawn 英雄快照) +func set_hero_axes(attack_type: String, armor_type: String) -> void: + _hero_attack_type = attack_type + _hero_armor_type = armor_type + + # adventure 域计数器共享引用注入(迭代 20 T6——AdventureRunner 冒险级账本引用下传; # begin_battle enc.adventure_counters 携带进效果引擎 rebuild 回灌与战末 counters_export 回写—— # **共享引用不复制**,attune_charges 同款;缺省空 dict=零扰动) diff --git a/game/core/meta_progression.gd b/game/core/meta_progression.gd index 4a07359e..a9139a7a 100644 --- a/game/core/meta_progression.gd +++ b/game/core/meta_progression.gd @@ -9,8 +9,9 @@ # 钥石直购价随定价批后补最终双渠道;六件局内效果归 Alpha 秘宝重设计批,本类只有解锁状态); # ③ 冒险收束入账(Boss 两档 = 表值 first_kill/repeat_kill,首杀标记读存档判first;调 SaveManager # apply_adventure_summary 入账并附 boss_drops 副本——复盘可归因,验收 5)。 -# 注入面:hero_override() = 三档 forge flat(全零 = 白板合法态;override 不写表——表运行期只读, -# 消费方 AdventureRunner/LevelRunner 经冒险开始参数面显式传参)。 +# 注入面:hero_override() = 树生命/命中淬炼 flat + 锻造路线加值(当前选中武器/甲——迭代 23 T4 换代, +# 全零 = 白板合法态;override 不写表——表运行期只读,消费方 AdventureRunner/LevelRunner 经冒险开始 +# 参数面显式传参)。 # 纪律:数值零字面量——价格/档位/阈值/两档金额只读 db.meta_* 访问器;存档只经 SaveManager 访问器, # 禁读内部结构;行集解析禁自扫表(一律走 ConfigDB 访问器)。 class_name MetaProgression @@ -255,18 +256,20 @@ func settle_adventure(summary: Dictionary) -> Dictionary: # ---- 跨局注入(冒险开始参数面)---- -# 迭代 21 T4 注入公式换代:基础值 + 树生命/命中淬炼 flat(命中线在账未注入——契约 6,消费面归 -# 数值批)+ 锻造路线加值位(本批预留 0——锻造工坊实装批按当前武器/甲路线等级填充)+ 局内加算区 -# (bonus 区域不经本面)。旧属性柜台 forge_flat 项退役移除(V7/V12);攻/AC 树来源随所有权切分 -# 砍除(契约 9——树零攻防来源,现表零 atk/armor_class 行,tree_stat_flat 二轴恒 0 保留通式)。 +# 迭代 23 T4 注入公式换代(锻造工坊实装批——契约 1 攻防所有权切分落地):基础值 + 树生命/命中淬炼 +# flat(命中线在账未注入——契约 6,消费面归数值批)+ **锻造路线加值**(当前选中武器攻击加值 + 当前甲 +# AC 加值——读存档 forge_routes 域+路线表)+ 局内加算区(bonus 区域不经本面)。入战 AC 三层可加: +# 甲型基础 AC(forge.tres 行 base_ac)+ 甲路线等级加值(本面 armor_class 分量)+ 局内加算区。 +# 旧属性柜台 forge_flat 项退役移除(V7/V12);攻/AC 树来源随所有权切分砍除(契约 9——树零攻防来源, +# 现表零 atk/armor_class 行,tree_stat_flat 二轴恒 0 保留通式)。原 FORGE_ROUTE_BONUS 预留常量随本批 +# 退役(公式不再出现——V7/V12)。 # 全零 = 白板合法态(无解锁不得改变任何局内规则);override 禁写表(表运行期只读)。 -const FORGE_ROUTE_BONUS := {"atk": 0, "max_hp": 0, "armor_class": 0} # 锻造路线加值位(预留 0——锻造工坊实装批填充) func hero_override() -> Dictionary: return { - "atk": int(FORGE_ROUTE_BONUS.get("atk", 0)) + tree_stat_flat("atk"), - "max_hp": int(FORGE_ROUTE_BONUS.get("max_hp", 0)) + tree_stat_flat("max_hp"), - "armor_class": int(FORGE_ROUTE_BONUS.get("armor_class", 0)) + tree_stat_flat("armor_class"), + "atk": forge_weapon_atk_bonus() + tree_stat_flat("atk"), + "max_hp": tree_stat_flat("max_hp"), + "armor_class": forge_armor_ac_bonus() + tree_stat_flat("armor_class"), } diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 7fcc3eea..4851e8b0 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -496,11 +496,15 @@ func _wire_adventure() -> void: # 取值口径与营地投影完全同源:装配集=save.attunement_equipped(save_manager.gd 访问器)、Y 刷新池= # meta.attune_refresh_charges(当前等级行表值)、树 rule 键集=meta.tree_rule_keys(Fork 激活侧 effects); # hero_override 沿用既有 meta 现算口径。static 形态=冒烟直呼单测本函数,免实例化整菜单场景)。 +# 迭代 23 T4:两根类型轴随开局下传(attack_type/armor_type——契约 2 所有权移交+契约 8 冒险开始快照; +# 取值=meta.hero_attack_type/hero_armor_type=存档选中路线类型轴,未解锁/异常选中态回退锻造配置默认值)。 # 注:runner 侧 resume_adventure 受理面现收 attunement/attune_refresh_y/tree_rules 三键 # (2026-10-08 尾裁微批b 卡⓪受理闭环——tree_rules 受理补齐,传参/受理两侧对齐,披露句退役)。 static func build_adventure_params(save_manager_ref, meta_ref, extra: Dictionary = {}) -> Dictionary: var params: Dictionary = { "hero_override": meta_ref.hero_override() if meta_ref != null else {}, + "attack_type": meta_ref.hero_attack_type() if meta_ref != null else "", + "armor_type": meta_ref.hero_armor_type() if meta_ref != null else "", "attunement": save_manager_ref.attunement_equipped() if save_manager_ref != null else [], "attune_refresh_y": meta_ref.attune_refresh_charges() if meta_ref != null else 0, "tree_rules": meta_ref.tree_rule_keys() if meta_ref != null else [], diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 82796d08..b3bfa71d 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -1909,7 +1909,7 @@ func _smoke_full_battle(L: int, D: int, label: String) -> void: check(hero_breakdown_ok, "%s 主角攻击行携带类型键与掷点读数(单分量——充能退役批)" % label) -func _run_battle(L: int, D: int) -> Dictionary: +func _run_battle(L: int, D: int, axes: Dictionary = {}) -> Dictionary: # 每遍全新 RngService(同 master 种子)——回放断言的隔离前提(地图战斗资源 AC"两套全新 RngService+地图战斗资源"同款) var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(42) @@ -1923,7 +1923,9 @@ func _run_battle(L: int, D: int) -> Dictionary: settlement.action_economy.round_end_stopped.connect(func(p): out.events.append({"ev": "round_end_stopped", "path_tag": p.path_tag})) var minesweeper = settlement.minesweeper var action_economy = settlement.action_economy - if not settlement.begin_battle({"level_index": L, "node_depth": D, "hero_hp": _db.hero.max_hp}): + var enc := {"level_index": L, "node_depth": D, "hero_hp": _db.hero.max_hp} + enc.merge(axes, true) # 迭代 23 T4——两根类型轴注入尾参(锻造工坊批对拍用;缺省零注入=回退面) + if not settlement.begin_battle(enc): out.stream.append({"type": "verdict", "value": "begin_failed"}) return out var max_rounds := 40 @@ -4502,6 +4504,73 @@ func _smoke_forge_axes() -> void: and mp2d.hero_armor_type() == "light", "类型轴当前生效值随选中走(ward;未解锁档回退默认 normal/light)〔锻造工坊批 V5/V6〕") + # ---- T4 注入接线(V5 后半 · V6 锁死 · V7 公式对拍 · V10 树零攻防复核)---- + # V5:build_adventure_params 注入默认值(新档=树钥匙未点亮)+点亮后读存档选中覆盖 + var sv4 = _meta_new_save(_META_SMOKE_PATHS[16]) + var mp4 = load("res://core/meta_progression.gd").new(_db, sv4) + var bp_params: Dictionary = load("res://presentation/main.gd").build_adventure_params(sv4, mp4) + check(String(bp_params.get("attack_type", "")) == "normal" + and String(bp_params.get("armor_type", "")) == "light", + "build_adventure_params 注入默认值 normal/light(树钥匙未点亮——新档初始持有态)〔锻造工坊批 V5〕") + sv4.add_keystone(1000) + check(mp4.forge_buy_unlock("spear") and mp4.forge_buy_level("spear") and mp4.forge_select_weapon("spear"), + "长枪直购解锁+首购+选中(注入面夹具——200+20 钥石)") + var bp_sel: Dictionary = load("res://presentation/main.gd").build_adventure_params(sv4, mp4) + check(String(bp_sel.get("attack_type", "")) == "pierce" + and String(bp_sel.get("armor_type", "")) == "light", + "build_adventure_params 读存档选中覆盖(选中长枪→pierce)〔锻造工坊批 V5〕") + # V7:hero_override 公式对拍=树 flat+锻造路线加值(旧路线练度待命不生效;切回即生效) + check(int(mp4.hero_override().get("atk", -1)) == 1 + and int(mp4.hero_override().get("max_hp", -1)) == 0 + and int(mp4.hero_override().get("armor_class", -1)) == 0 + and not mp4.hero_override().has("hit"), + "hero_override=锻造路线加值(选中长枪 Lv1→atk +1;甲 0 级→AC +0;树零分量;无 hit 键)〔锻造工坊批 V7〕") + sv4.set_forge_route_level("sword", 3) # 长剑练度待命(未选中) + check(int(mp4.hero_override().get("atk", -1)) == 1, + "换当前武器→攻击加值随切换(长剑 3 级待命不生效——只认选中)〔锻造工坊批 V7〕") + check(mp4.forge_select_weapon("sword") and int(mp4.hero_override().get("atk", -1)) == 3, + "切回长剑→加值=3 级量(旧路线练度保留且生效)〔锻造工坊批 V7〕") + # V7 三层 AC:重甲(基础 12)+路线 Lv1 加值+局内 0——经参数面起冒险断 runner 生效值 + var sv5 = _meta_new_save(_META_SMOKE_PATHS[17]) + var mp5 = load("res://core/meta_progression.gd").new(_db, sv5) + sv5.add_keystone(1000) + check(mp5.forge_buy_unlock("heavy") and mp5.forge_buy_level("heavy") and mp5.forge_select_armor("heavy"), + "重甲直购解锁+首购+选中(三层 AC 夹具)") + var adr5 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(77)) + var p5: Dictionary = load("res://presentation/main.gd").build_adventure_params(sv5, mp5) + check(adr5.start_adventure(77, p5), "注入参数冒险启动(两轴+override 同面)") + var r5 = adr5.runner() + check(int(r5.effective_ac()) == 12 + 1, + "入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕") + check(adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy", + "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕") + # V6 锁死:开冒险后改存档选中→参数面/注入值不变(快照冻结;light=初始持有已解锁可切) + check(mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy" + and int(r5.effective_ac()) == 12 + 1, + "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕") + # V5/V7 末端:enc 两轴直达 spawn 英雄快照(hero_attack 行携带选中类型键) + var run_ax = _run_battle(1, 0, {"attack_type": "pierce", "armor_type": "ward"}) + var pierce_rows := 0 + for row in run_ax.stream: + if row.get("source_class", "") == "hero_attack" and row.has("pierce"): + pierce_rows += 1 + check(pierce_rows >= 1, + "enc 两轴直达 spawn 英雄快照(hero_attack 行携带 pierce 键——注入链末端)〔锻造工坊批 V5〕") + # spawn 显式注入面(三参旧形态零注入已在上方覆盖——此处钉显式值) + var uc2 = load("res://core/unit_container.gd").new(_db) + check(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", + "spawn 显式两轴注入(magic/heavy 直入英雄快照)〔锻造工坊批 V5〕") + # V10 复核:树表 12 行 effects 键集无 atk/armor_class(契约 1——树零攻防来源) + var tree_atk_ac := false + for trow in _db.tree_rows(): + var eff: Dictionary = trow.get("effects", {}) + if eff.has("atk") or eff.has("armor_class"): + tree_atk_ac = true + check(not tree_atk_ac, + "树零攻防来源复核(12 行 effects 键集无 atk/armor_class——契约 1)〔锻造工坊批 V10〕") + # ---- 暂停层只读投影一致性(迭代 17 卡③——R4 数据只读同源:暂停层展示值逐项 == MetaProgression # 访问器读数;投影拼装与 main._pause_data 同构(同访问器族同序)。只读性连接巡检与零动效 @@ -4636,7 +4705,8 @@ const _META_SMOKE_PATHS := ["user://smoke_meta_buy.json", "user://smoke_meta_dry "user://smoke_meta_attune.json", "user://smoke_meta_attune2.json", "user://smoke_meta_tree.json", "user://smoke_meta_career.json", "user://smoke_meta_career_v5.json", "user://smoke_meta_treasure_unlock.json", "user://smoke_meta_treasure_blank.json", - "user://smoke_meta_forge.json", "user://smoke_meta_forge2.json", "user://smoke_meta_forge3.json"] + "user://smoke_meta_forge.json", "user://smoke_meta_forge2.json", "user://smoke_meta_forge3.json", + "user://smoke_meta_forge4.json", "user://smoke_meta_forge5.json"] func _meta_smoke_cleanup() -> void: