diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 3136eaaa..138fe7e3 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -36,10 +36,10 @@ const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward", " var db: ConfigDb var rng: RngServiceCore -# E1 封装收口:协作对象 board_generator/enemy_system/status_effects/reward_economy 私有化——跨层读取一律经 presentation_snapshot()/diagnostic_snapshot() 成文出口。 +# E1 封装收口:协作对象 enemy_system/status_effects/reward_economy 私有化——跨层读取一律经 presentation_snapshot()/diagnostic_snapshot() 成文出口。 # minesweeper/action_economy 保持公有 = 架构文档 API Boundaries 的 HUD框架消费面(意图提交 settle_batch + compute_move_path # 只读;ap/rap/相位读取)——非本文档登记的契约面,勿增新用途。 -var _board_generator: BoardGenerator +# 装板域(BoardGenerator+装板六函数族)已拆 BoardInstaller——生成器实例随件自建,宿主零成员。 var minesweeper: MinesweeperRules var action_economy: ActionEconomy var _enemy_system: UnitContainer @@ -230,13 +230,12 @@ var _hero_atk := 0 # 主角攻击基值快照(encounte func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void: db = db_ref rng = rng_ref - _board_generator = BoardGenerator.new() action_economy = ActionEconomy.new(db) minesweeper = MinesweeperRules.new(db, action_economy) _enemy_system = UnitContainer.new(db) _status_effects = StatusContainer.new(db) _reward_economy = RewardEconomy.new(db, rng) - _board_generator.fatal_handler = _fatal; _reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线) + _reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线) _damage = DamageController.new(db, rng, _enemy_system, _status_effects) _damage.emit_row_cb = _emit_row # 行协议归属本类(9 类行协议 + 来源断言不外泄) _fx_host = BattleFxHost.new({"db": db, "rng": rng, "minesweeper": minesweeper, "action_economy": action_economy, @@ -268,7 +267,7 @@ func begin_battle(enc: Dictionary) -> bool: return false if not _begin_battle_accept(enc): return false - if not _install_new_board(): + if not _install_and_reset(): return false _reset_round_facts() # 首板 install 的初始展开事件不入轮 facts(resource 恒 false 的双保险) _begin_battle_fx_state(enc) @@ -411,34 +410,14 @@ func _begin_battle_preview_economy(enc: Dictionary) -> void: action_economy.grant_reserve_ap(voucher_rap) -func _install_new_board(during_round := false) -> bool: - var stream: RandomNumberGenerator = rng.open_stream("board_gen") - var board: BoardData = _board_generator.generate(int(encounter.level_index), int(encounter.node_depth), stream, db) - if board == null: +# ---- 装板受理薄壳(BoardInstaller 拆件后的宿主承接面——begin_battle/中途刷新/轮内刷新三调用点唯一入口)---- +# 件内装板(生成→全局变异决议→装板受理→资源摇号→变异布置)成功后执行宿主态复位:五处计数清零+ +# 板序推进+board_changed 发射+秘宝叠加层退役——时序逐字;复位职责留宿主(成员单点),不随件。 +func _install_and_reset(during_round := false) -> bool: + var installer := BoardInstaller.new({"db": db, "rng": rng, "minesweeper": minesweeper, + "reward_economy": _reward_economy, "encounter": encounter, "fatal_cb": _fatal}) + if not installer.install(during_round): return false - # 全局作用域变异决议(装板序内前置于 roll_board:图级偏斜旋钮须在资源摇号前定型——详见 _resolve_global_variant) - _resolve_global_variant(board) - var res: Dictionary[Variant, Variant] = minesweeper.install_board(board, during_round) - if not res.accepted: - _fatal("排雷玩法 install 拒绝(不变式违例): %s" % str(res)) - return false - # 地图战斗资源 义务⑥:roll_board 恰一次(install 后、挖掘前);资格格 = 全部非封锁格(安全格 + 危险格,雷安同池) - var r0 := {} - for c in board.compute_r0(): - r0[c] = true - var eligible: Array[Dictionary] = [] - for c in board.safe_cells(): - if not r0.has(c): - eligible.append({"cell": c}) - for c in board.mines.keys(): - if not r0.has(c): - eligible.append({"cell": c}) - if not _reward_economy.roll_board(eligible): - _fatal("地图战斗资源 roll_board 被拒") - return false - # 地形变异 布置段(棋盘设置顺序第 4 步:生成棋盘 → 打开状态初始化 → - # 战斗资源格设置 → 变异格设置)——布置在初始展开与资源格之后,变异天然不落已开区 - _place_variants(board) _seal_consumed.clear() # 换图新雷 = 新实例,资格自然重置(行动经济 Rule 8) _seal_miss_seen.clear() _perfect_hit_true = 0 # 整图完美计数随 install 重置 @@ -452,103 +431,6 @@ func _install_new_board(during_round := false) -> bool: return true -# ---- 全局作用域变异 决议(装板序内 roll_board 之前)---- -# boss/elite 图命中全局型:写整图 board_variants(恰一行)+ 注入资源面偏斜旋钮(图级投影在 -# RewardEconomy.roll_board 消费);不消费 variant_place 流(全局取用确定性——零取号合法态)。 -# 全局池空(含 L1 Boss 免疫/unlock 门控未达):字段留空、旋钮归零——_place_variants 原路走 -# region 布置 = 兜底回落合法态(防配置缺行静默裸奔——provisional)。 -func _resolve_global_variant(board: BoardData) -> void: - board.board_variants.clear() - _reward_economy.board_skew = 0 - var row: Dictionary[Variant, Variant] = db.combat.variation.global_variant_row(int(encounter.level_index), String(board.board_type), - String(encounter.get("node_type", ""))) - if row.is_empty(): - return - board.board_variants = [String(row.variant_id)] - _reward_economy.board_skew = int(row.get("effect_value", 0)) - - -# ---- 地形变异 布置段(棋盘设置顺序第 4 步)---- -# 候选池门控(unlock_level ≤ 关卡序号 + board_types 与本地形交集,∅ 通配)经 config_db.variant_pool; -# 池空 = 合法态,静默零布置(框架 P0 口径不变)。零特判:本段只写格子变异数据快照,不改扫雷规则本体。 -# 候选格域 = 安全格 − 初始展开区(伤害型 placement=closed_only 的结构保障——布置发生在初始展开之后, -# 已开格天然排除);危险格不吃变异(挖雷一层惩罚 + 变异伤害双层叠罚污染逐行归因,首版排除——provisional)。 -# 每图至多一种类型(候选池内均匀选一);数量 = round(候选池格数×percent) + -# (关卡序号−1)×per_level_bonus——基数为候选池而非总格数(percent 语义 = 占可挖格比例, -# 关卡递增由 per_level_bonus 绝对格数承载);连通片生长(8 邻),单片 ≤ pack_max。 -# 替换制:整图 board_variants 非空 = 本图已带全局型,跳过 region 布置(详见 _resolve_global_variant)。 -func _place_variants(board: BoardData) -> void: - if not board.board_variants.is_empty(): - return - var pool: Array[Variant] = db.combat.variation.variant_pool(int(encounter.level_index), String(board.board_type)) - if pool.is_empty(): - return - var r0 := {} - for c in board.compute_r0(): - r0[c] = true - var candidates: Array[Vector2i] = [] - for c in board.safe_cells(): - if not r0.has(c): - candidates.append(c) - if candidates.is_empty(): - return - var stream: RandomNumberGenerator = rng.open_stream("variant_place") # 一板恰一 scope(换图/刷新图各取号一次) - var vrow: Dictionary[Variant, Variant] = pool[stream.randi_range(0, pool.size() - 1)] - var total: int = int(round(float(candidates.size()) * float(db.combat.variation.percent_of_cells) / 100.0)) \ - + (int(encounter.level_index) - 1) * int(db.combat.variation.per_level_bonus) - total = mini(total, candidates.size()) - _place_variants_apply(board, candidates, stream, vrow, total) - - -# 变异片生长施加面(布置域=候选格,数量=total,vrow 单型贯穿;取号序=stream 顺序消费) -func _place_variants_apply(board: BoardData, candidates: Array[Vector2i], stream: RandomNumberGenerator, vrow: Dictionary, total: int) -> void: - var placed := {} - while placed.size() < total: - var pack_size: int = mini(total - placed.size(), stream.randi_range(2, int(db.combat.variation.pack_max))) - var free: Array[Vector2i] = [] - for c in candidates: - # 新片种子/生长格禁与其他已布片 8 邻贴接——否则两片粘连成一个超 pack_max 的连通分量 - # (R3 随机流下实测暴露:独立生长的相邻片合流击穿单片上限断言) - if placed.has(c) or _adjacent_to_packs(board, placed, c): - continue - free.append(c) - if free.is_empty(): - break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发) - var seed_c: Vector2i = free[stream.randi_range(0, free.size() - 1)] - placed[seed_c] = true - var pack: Array[Variant] = [seed_c] - var pack_set := {seed_c: true} - while pack.size() < pack_size: - var fringe := _variants_fringe(board, pack, candidates, placed, pack_set) - if fringe.is_empty(): - break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发) - var nxt: Vector2i = fringe[stream.randi_range(0, fringe.size() - 1)] - placed[nxt] = true - pack_set[nxt] = true - pack.append(nxt) - for c in placed.keys(): - board.variants[c] = [String(vrow.variant_id)] - - -# 片生长待选格收集(本片内各生长格的 8 邻候选并集——未布置、未在途、不与他片贴接) -func _variants_fringe(board: BoardData, pack: Array, candidates: Array[Vector2i], placed: Dictionary, pack_set: Dictionary) -> Array[Vector2i]: - var fringe: Array[Vector2i] = [] - for c in pack: - for n in board.neighbors8(c): - if candidates.has(n) and not placed.has(n) and not fringe.has(n) \ - and not _adjacent_to_packs(board, placed, n, pack_set): - fringe.append(n) - return fringe - - -# 候选格是否与其他已布片(own_pack 之外的 placed 格)8 邻贴接——片间隔离保证连通分量 = 单片 ≤ pack_max -func _adjacent_to_packs(board: BoardData, placed: Dictionary, c: Vector2i, own_pack: Dictionary = {}) -> bool: - for m in board.neighbors8(c): - if placed.has(m) and not own_pack.has(m): - return true - return false - - # ---- 事件缓冲(轮 facts 收集;结算触发 = Waiting 相位编排点,不依赖到达序)---- func _on_cell_revealed(evt: Dictionary) -> void: @@ -798,7 +680,7 @@ func _exit_distribution() -> void: if mode == "new_board": _reward_economy.retire_mapping() minesweeper.retire_board() - if not _install_new_board(): + if not _install_and_reset(): _fatal("换图编排失败") return # 工兵晨令续队直调拆除(内容数据驱动扩展批 T4——round_start 订阅行承载,见 begin_battle 注) @@ -832,7 +714,7 @@ func player_request_refresh() -> bool: _refresh_used_this_round += 1 _reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提) minesweeper.retire_board(true) # 旧板退役(轮内通道) - return _install_new_board(true) # 当场换图——失败即编排违例 fail-fast + return _install_and_reset(true) # 当场换图——失败即编排违例 fail-fast func _battle_over(result: String) -> void: diff --git a/game/core/board_installer.gd b/game/core/board_installer.gd new file mode 100644 index 00000000..96f7d921 --- /dev/null +++ b/game/core/board_installer.gd @@ -0,0 +1,167 @@ +## 棋盘安装与变异布置域件(B1 域·BoardInstaller):生成受理、全局作用域变异决议、装板受理、 +## 资源摇号资格域组装(roll_board 恰一次——install 后、挖掘前)、region 变异决议与连通片布置。 +## 棋盘设置顺序 = 生成棋盘 → 打开状态初始化 → 战斗资源格设置 → 变异格设置(布置在初始展开与 +## 资源格之后,变异天然不落已开区)。宿主侧装板后状态复位(封印/完美计数清零、板序推进、 +## board_changed 发射、秘宝叠加层退役)留宿主薄壳——本件零复位职责。零 UI 零 await; +## 随机只经注入 RngServiceCore 的 board_gen/variant_place 两封闭流(流名与开流时点不变, +## 生成器只消费传入流——同种子同输入逐位复现)。 +class_name BoardInstaller +extends RefCounted + +var db: ConfigDb +var rng: RngServiceCore +var minesweeper: MinesweeperRules +var reward_economy: RewardEconomy +var encounter: Dictionary[String, Variant] # 宿主 encounter 同对象引用(assign 原地变异——跨战活引用) +var fatal_cb: Callable # 协作件违约→宿主 _fatal 封印(单点接线——BoardGenerator 同款) +var generator: BoardGenerator # 件内自建自接线(原宿主 _board_generator 成员随件迁移) + + +func _init(bundle: Dictionary) -> void: + for k in ["db", "rng", "minesweeper", "reward_economy", "encounter", "fatal_cb"]: + assert(bundle.has(k), "[装板域件] 引用束缺键: %s" % k) + db = bundle.db + rng = bundle.rng + minesweeper = bundle.minesweeper + reward_economy = bundle.reward_economy + encounter = bundle.encounter + fatal_cb = bundle.fatal_cb + generator = BoardGenerator.new() + generator.fatal_handler = fatal_cb # 协作件违约→宿主 _fatal 封印(单点接线) + + +func _fatal(msg: String) -> void: + if fatal_cb.is_valid(): + fatal_cb.call(msg) # 组装层注入 orchestrator._fatal(封印传导,不绕第二中止通道) + else: + push_error(msg) + assert(false, msg) + + +# 装板单入口(生成 → 全局变异决议 → 装板受理 → 资源摇号 → 变异布置); +# 宿主态复位归宿主薄壳(_install_and_reset)——本件返回即装板完成、复位未行。 +func install(during_round := false) -> bool: + var stream: RandomNumberGenerator = rng.open_stream("board_gen") + var board: BoardData = generator.generate(int(encounter.level_index), int(encounter.node_depth), stream, db) + if board == null: + return false + # 全局作用域变异决议(装板序内前置于 roll_board:图级偏斜旋钮须在资源摇号前定型——详见 _resolve_global_variant) + _resolve_global_variant(board) + var res: Dictionary[Variant, Variant] = minesweeper.install_board(board, during_round) + if not res.accepted: + _fatal("排雷玩法 install 拒绝(不变式违例): %s" % str(res)) + return false + # 地图战斗资源 义务⑥:roll_board 恰一次(install 后、挖掘前);资格格 = 全部非封锁格(安全格 + 危险格,雷安同池) + var r0 := {} + for c in board.compute_r0(): + r0[c] = true + var eligible: Array[Dictionary] = [] + for c in board.safe_cells(): + if not r0.has(c): + eligible.append({"cell": c}) + for c in board.mines.keys(): + if not r0.has(c): + eligible.append({"cell": c}) + if not reward_economy.roll_board(eligible): + _fatal("地图战斗资源 roll_board 被拒") + return false + # 地形变异 布置段(棋盘设置顺序第 4 步)——布置在初始展开与资源格之后,变异天然不落已开区 + _place_variants(board) + return true + + +# ---- 全局作用域变异 决议(装板序内 roll_board 之前)---- +# boss/elite 图命中全局型:写整图 board_variants(恰一行)+ 注入资源面偏斜旋钮(图级投影在 +# RewardEconomy.roll_board 消费);不消费 variant_place 流(全局取用确定性——零取号合法态)。 +# 全局池空(含 L1 Boss 免疫/unlock 门控未达):字段留空、旋钮归零——_place_variants 原路走 +# region 布置 = 兜底回落合法态(防配置缺行静默裸奔——provisional)。 +func _resolve_global_variant(board: BoardData) -> void: + board.board_variants.clear() + reward_economy.board_skew = 0 + var row: Dictionary[Variant, Variant] = db.combat.variation.global_variant_row(int(encounter.level_index), String(board.board_type), + String(encounter.get("node_type", ""))) + if row.is_empty(): + return + board.board_variants = [String(row.variant_id)] + reward_economy.board_skew = int(row.get("effect_value", 0)) + + +# ---- 地形变异 布置段(棋盘设置顺序第 4 步)---- +# 候选池门控(unlock_level ≤ 关卡序号 + board_types 与本地形交集,∅ 通配)经 config_db.variant_pool; +# 池空 = 合法态,静默零布置(框架 P0 口径不变)。零特判:本段只写格子变异数据快照,不改扫雷规则本体。 +# 候选格域 = 安全格 − 初始展开区(伤害型 placement=closed_only 的结构保障——布置发生在初始展开之后, +# 已开格天然排除);危险格不吃变异(挖雷一层惩罚 + 变异伤害双层叠罚污染逐行归因,首版排除——provisional)。 +# 每图至多一种类型(候选池内均匀选一);数量 = round(候选池格数×percent) + +# (关卡序号−1)×per_level_bonus——基数为候选池而非总格数(percent 语义 = 占可挖格比例, +# 关卡递增由 per_level_bonus 绝对格数承载);连通片生长(8 邻),单片 ≤ pack_max。 +# 替换制:整图 board_variants 非空 = 本图已带全局型,跳过 region 布置(详见 _resolve_global_variant)。 +func _place_variants(board: BoardData) -> void: + if not board.board_variants.is_empty(): + return + var pool: Array[Variant] = db.combat.variation.variant_pool(int(encounter.level_index), String(board.board_type)) + if pool.is_empty(): + return + var r0 := {} + for c in board.compute_r0(): + r0[c] = true + var candidates: Array[Vector2i] = [] + for c in board.safe_cells(): + if not r0.has(c): + candidates.append(c) + if candidates.is_empty(): + return + var stream: RandomNumberGenerator = rng.open_stream("variant_place") # 一板恰一 scope(换图/刷新图各取号一次) + var vrow: Dictionary[Variant, Variant] = pool[stream.randi_range(0, pool.size() - 1)] + var total: int = int(round(float(candidates.size()) * float(db.combat.variation.percent_of_cells) / 100.0)) \ + + (int(encounter.level_index) - 1) * int(db.combat.variation.per_level_bonus) + total = mini(total, candidates.size()) + _place_variants_apply(board, candidates, stream, vrow, total) + + +# 变异片生长施加面(布置域=候选格,数量=total,vrow 单型贯穿;取号序=stream 顺序消费) +func _place_variants_apply(board: BoardData, candidates: Array[Vector2i], stream: RandomNumberGenerator, vrow: Dictionary, total: int) -> void: + var placed := {} + while placed.size() < total: + var pack_size: int = mini(total - placed.size(), stream.randi_range(2, int(db.combat.variation.pack_max))) + var free: Array[Vector2i] = [] + for c in candidates: + # 新片种子/生长格禁与其他已布片 8 邻贴接——否则两片粘连成一个超 pack_max 的连通分量 + # (R3 随机流下实测暴露:独立生长的相邻片合流击穿单片上限断言) + if placed.has(c) or _adjacent_to_packs(board, placed, c): + continue + free.append(c) + if free.is_empty(): + break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发) + var seed_c: Vector2i = free[stream.randi_range(0, free.size() - 1)] + placed[seed_c] = true + var pack: Array[Variant] = [seed_c] + var pack_set := {seed_c: true} + while pack.size() < pack_size: + var fringe := _variants_fringe(board, pack, candidates, placed, pack_set) + if fringe.is_empty(): + break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发) + var nxt: Vector2i = fringe[stream.randi_range(0, fringe.size() - 1)] + placed[nxt] = true + pack_set[nxt] = true + pack.append(nxt) + for c in placed.keys(): + board.variants[c] = [String(vrow.variant_id)] + + +# 片生长待选格收集(本片内各生长格的 8 邻候选并集——未布置、未在途、不与他片贴接) +func _variants_fringe(board: BoardData, pack: Array, candidates: Array[Vector2i], placed: Dictionary, pack_set: Dictionary) -> Array[Vector2i]: + var fringe: Array[Vector2i] = [] + for c in pack: + for n in board.neighbors8(c): + if candidates.has(n) and not placed.has(n) and not fringe.has(n) \ + and not _adjacent_to_packs(board, placed, n, pack_set): + fringe.append(n) + return fringe + + +# 候选格是否与其他已布片(own_pack 之外的 placed 格)8 邻贴接——片间隔离保证连通分量 = 单片 ≤ pack_max +func _adjacent_to_packs(board: BoardData, placed: Dictionary, c: Vector2i, own_pack: Dictionary = {}) -> bool: + for m in board.neighbors8(c): + if placed.has(m) and not own_pack.has(m): + return true + return false diff --git a/game/core/board_installer.gd.uid b/game/core/board_installer.gd.uid new file mode 100644 index 00000000..7f2e6b17 --- /dev/null +++ b/game/core/board_installer.gd.uid @@ -0,0 +1 @@ +uid://di6u170vqf8ae diff --git a/game/tests/static_scan.sh b/game/tests/static_scan.sh index 9f3e5ac6..df9951d6 100644 --- a/game/tests/static_scan.sh +++ b/game/tests/static_scan.sh @@ -308,13 +308,13 @@ fi # 可回放纯逻辑步骤流(同种子同输入逐位复现),await 会打断确定性——「模块归属」B1 行已写明 # 「禁 await——切片 88 冒烟实证」。本闸为防回归防线(2026-10-24 人工 grep 盘面干净)。 # 扫描面:B1 域文件集(与 architecture.md「模块归属」B1 行同口径)=battle_orchestrator.gd -# + battle_fx_host.gd + reward_grant.gd 两拆件 + settlement/ 五段段类 + damage_controller.gd,共 9 文件,显式清单见 S8_FILES。 +# + battle_fx_host.gd + reward_grant.gd + board_installer.gd 三拆件 + settlement/ 五段段类 + damage_controller.gd,共 10 文件,显式清单见 S8_FILES。 # 执行侧字面值豁免:脚本必须带字面值才能工作;文件集与架构文档冲突时以 B1 域文件集为准。 # 判据:文件集内出现 await 关键字即红(词边界 \bawait\b,任何形态——await 信号/协程/ # get_tree());排除注释行(行首 #/##)与字符串字面量(双引号包裹段内的 await 非代码), # 口径仿 S1/S2 的 grep 排除手法。 # 自检:清单内文件缺位(路径写错/被改名)即判红——防扫面为空静默绿。 -S8_FILES="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/damage_controller.gd" +S8_FILES="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/damage_controller.gd" S8_MISS=0 for f in $S8_FILES; do if [ ! -f "$f" ]; then @@ -356,7 +356,7 @@ fi # 自检:扫描面为空即判红(require_scan_face);B1 文件集与三族自家文件缺位即判红 # (文件被改名=豁免名单失效,宁可红不可静默放行)。 if require_scan_face "S7" "$DIR_CORE"; then -S7_B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/damage_controller.gd" +S7_B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/damage_controller.gd" S7_MISS=0 for f in $S7_B1_SET game/core/unit_container.gd game/core/status_container.gd game/core/reward_economy.gd; do if [ ! -f "$f" ]; then