diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 06842c9c..94b29869 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -47,11 +47,6 @@ var action_economy: ActionEconomy var _enemy_system: UnitContainer var _status_effects: StatusContainer var _reward_economy: RewardEconomy -var treasure_inventory: TreasureItems = null: - set(v): - treasure_inventory = v - if _reward_grant != null: - _reward_grant.treasure_inventory = v # 库存注入转发(注入时序在宿主构造之后——外部赋值路径零改址) var _damage: DamageController # 伤害控制器(单据唯一兑付管线) var _fx_host: BattleFxHost # 效果引擎宿主件(fx 族持有与兑付——拆件定稿轴件一) var _reward_grant: RewardGrant # 奖励授予件(授予管线与秘宝使用——拆件定稿轴件二) @@ -98,8 +93,8 @@ func _battle_hooks() -> Dictionary[Variant, Variant]: func _hook_attune_consume(item_id: String) -> void: - if _attune_loadout() != null and treasure_inventory != null: - _attune_loadout().consume_charge(item_id, treasure_inventory) + if _attune_loadout() != null and _reward_grant.treasure_inventory != null: + _attune_loadout().consume_charge(item_id, _reward_grant.treasure_inventory) func _hook_vision_add() -> void: @@ -190,9 +185,9 @@ func _attune_charge_safe_row() -> Dictionary: # (used handle 不回收=本轮已耗充能作废,补新 handle=「回满」;差额注入防双份=level_runner # set_attune_charges 同构)。reset ∈ {battle, level}——adventure 周期归冒险开始种子面) func _reseed_attune_charges(reset: String) -> void: - if _attune_loadout() == null or treasure_inventory == null: + if _attune_loadout() == null or _reward_grant.treasure_inventory == null: return # 直构零装配/库存未就位——充能回满跳过(空装配同语义) - _attune_loadout().reset_cycle(reset, treasure_inventory) # 写时种行/补行(按周期筛——语义逐字承原循环体) + _attune_loadout().reset_cycle(reset, _reward_grant.treasure_inventory) # 写时种行/补行(按周期筛——语义逐字承原循环体) # 积点制件进度读数(自动兑付无玩家发起入口,读数口=C 区秘宝槽悬停浮窗进度行: @@ -409,8 +404,8 @@ func _install_and_reset(during_round := false) -> bool: _facts.board_from_midround = during_round # 轮内刷新装板 = 刷新图(清图奖励不发);常规换图/开战装板 = false _facts.board_seq += 1 board_changed.emit() - if treasure_inventory != null: - treasure_inventory.retire_overlays() # 叠加层随图消亡(换图/刷新图装板点;已花道具不返还) + if _reward_grant.treasure_inventory != null: + _reward_grant.treasure_inventory.retire_overlays() # 叠加层随图消亡(换图/刷新图装板点;已花道具不返还) return true @@ -643,8 +638,8 @@ func _battle_over(result: String) -> void: _reward_economy.retire_mapping() _enemy_system.despawn_all() _status_effects.notify_battle_end() - if treasure_inventory != null: - treasure_inventory.retire_overlays() # 叠加层随战斗结束清空(库存不动——关卡域跨战斗) + if _reward_grant.treasure_inventory != null: + _reward_grant.treasure_inventory.retire_overlays() # 叠加层随战斗结束清空(库存不动——关卡域跨战斗) action_economy.notify_combat_end() # → Idle,RAP 清零 db.set_reload_gate(false) _facts.reset_round() diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index 05d110d5..a2f0a102 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -517,7 +517,7 @@ func _start_battle(node: Dictionary) -> bool: _settlement = BattleOrchestrator.new(db, rng) if treasure_inventory == null: treasure_inventory = TreasureItems.new(db) # 惰性建——关卡域单实例(跨战斗延续) - _settlement.treasure_inventory = treasure_inventory # 库存注入(授予路由+use_treasure 编排消费面) + _settlement.reward_grant().treasure_inventory = treasure_inventory # 库存注入(经授予件出口直注——注入时序在宿主构造之后不变) if _settlement != null: _settlement.set_treasure_unlocked(_treasure_unlocked) # 解锁门控下传(装板摇号前) # core↔core 接线只许方法 Callable(静态规则 S6——lambda 捕获 self 会延长 diff --git a/game/core/settlement/grant_segment.gd b/game/core/settlement/grant_segment.gd index 0904c527..1c396e51 100644 --- a/game/core/settlement/grant_segment.gd +++ b/game/core/settlement/grant_segment.gd @@ -367,8 +367,8 @@ func _seal_preview_area(cell: Vector2i, ri: int) -> void: var n_snap = oc._reward_economy.query_full_snapshot(n) if n_snap is Dictionary and not types.has(String((n_snap as Dictionary).get("cell_type", ""))): types.append(String((n_snap as Dictionary).get("cell_type", ""))) - if oc.treasure_inventory != null and not types.is_empty(): - oc.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell)) + if reward_grant.treasure_inventory != null and not types.is_empty(): + reward_grant.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell)) oc._emit_row({"type": "extended", "source_class": "fx_warning_beacon", "origin_cell": cell}, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types}) diff --git a/game/tests/unit/battle_settlement/roll_multiplier_test.gd b/game/tests/unit/battle_settlement/roll_multiplier_test.gd index ac3c8980..ac4365af 100644 --- a/game/tests/unit/battle_settlement/roll_multiplier_test.gd +++ b/game/tests/unit/battle_settlement/roll_multiplier_test.gd @@ -34,9 +34,31 @@ func _init() -> void: _smoke_pool_wiring() _smoke_damage_types() _smoke_attack_chain_fresh_pool() + _smoke_hero_atk_derived() quit(Kit.finish("roll_multiplier_test")) +# ---- 主角攻击基值派生读(注册面批同代编排归约 V4——_hero_atk 快照成员退役改派生读: +# enc 携 hero_atk 键受理后经派生读供 spawn 与攻击单 base_value 两消费点;缺省回退表值—— +# 受理后不变量(encounter 受理后只读)。攻击单 base_value 贯通由 _smoke_damage_types 的 +# hero_atk_val=100 全链路行值承载(同夹具既有覆盖——派生读在攻击链上工作即行值随 override 走)---- +func _smoke_hero_atk_derived() -> void: + var rng_a = load("res://core/rng_service.gd").new() + rng_a.set_master_seed(4242) + var s_a = load("res://core/battle_orchestrator.gd").new(_db, rng_a) + Kit.check_quiet(s_a.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp), "hero_atk": 100}), + "起战受理(override 100)〔编排归约 V4〕") + Kit.check_quiet(int(s_a._enemy_system.hero_unit().get("atk", -1)) == 100, + "主角攻击单基值==enc 携带 override(_hero_atk 派生读贯通 spawn 消费点)〔编排归约 V4〕") + var rng_b = load("res://core/rng_service.gd").new() + rng_b.set_master_seed(4242) + var s_b = load("res://core/battle_orchestrator.gd").new(_db, rng_b) + Kit.check_quiet(s_b.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp)}), + "起战受理(缺省)〔编排归约 V4〕") + Kit.check_quiet(int(s_b._enemy_system.hero_unit().get("atk", -1)) == int(_db.combat.hero.atk), + "缺省主角攻击单基值==hero 表值(派生读回退面)〔编排归约 V4〕") + + # ---- 敌方攻击类型轴(表列/单位快照/单据载荷/矩阵命中证词)---- # 克制矩阵命中证词 = 同种子三甲型配对实测(不是查表存在性证词):同种子下 armor_roll 流消费逐位一致、 # 阈值(hero armor_class)不变,三甲型唯一差异 = 克制点数 cdelta(normal_vs_light 0 / heavy −1 / ward +1) diff --git a/game/tests/unit/core/treasure_effects_test.gd b/game/tests/unit/core/treasure_effects_test.gd index 69074176..fdceacb8 100644 --- a/game/tests/unit/core/treasure_effects_test.gd +++ b/game/tests/unit/core/treasure_effects_test.gd @@ -162,13 +162,13 @@ func _init() -> void: "线香:装配起战(battle 级充能 1)") var inv8 = load("res://core/treasure_items.gd").new(db) var rec8: Dictionary = inv8.add_item("incense_stick", "attunement", Vector2i(-1, -1), 0) - ob8.treasure_inventory = inv8 + ob8.reward_grant().treasure_inventory = inv8 var h8: int = int(rec8.get("handle", -1)) # handle 取 add_item 返回(snapshot 投影无 handle 键) var tgt8: Vector2i = _first_closed_safe(ob8) Kit.check_quiet(tgt8 != Vector2i(-9, -9), "线香:夹具存在未开安全格") var v8: Dictionary = ob8.reward_grant().use_treasure(h8, tgt8) Kit.check_quiet(bool(v8.get("ok", false)), "线香正常:单格真视受理(cell 目标解析)") - Kit.check_quiet(bool(ob8.treasure_inventory.query_overlays().get("mine_reveal", []).has(tgt8)) + Kit.check_quiet(bool(ob8.reward_grant().treasure_inventory.query_overlays().get("mine_reveal", []).has(tgt8)) and ob8.minesweeper.is_revealed(tgt8) == false, "线香断言:目标格入真视叠加层且未被开格(真视≠开格——信息红线)") Kit.check_quiet(inv8.snapshot_inventory().is_empty() or bool(inv8.snapshot_inventory()[0].get("used", false)), @@ -240,7 +240,7 @@ func _init() -> void: "天眼:装配起战(level 级充能 1)") var inv11 = load("res://core/treasure_items.gd").new(db) var rec11: Dictionary = inv11.add_item("sky_eye", "attunement", Vector2i(-1, -1), 0) - ob11.treasure_inventory = inv11 + ob11.reward_grant().treasure_inventory = inv11 var h11: int = int(rec11.get("handle", -1)) var tgt11: Vector2i = _first_closed_safe(ob11) var v11: Dictionary = ob11.reward_grant().use_treasure(h11, tgt11) @@ -254,7 +254,7 @@ func _init() -> void: if ob11.minesweeper.is_revealed(c): opened_safe = false Kit.check_quiet(mines_shown == 0 or opened_safe, "天眼反例①:危险格仅显示不被开格(棋盘事实不可变)") - var overlay11: Array = ob11.treasure_inventory.query_overlays().get("mine_reveal", []) + var overlay11: Array = ob11.reward_grant().treasure_inventory.query_overlays().get("mine_reveal", []) var overlay_ok := true for c_v in (v11.get("resolved", []) as Array): if not overlay11.has(c_v): @@ -289,7 +289,7 @@ func _init() -> void: "计数链:装配起战(GainJournal 携带)") var inv13 = load("res://core/treasure_items.gd").new(db) var rec13: Dictionary = inv13.add_item("incense_stick", "attunement", Vector2i(-1, -1), 0) - ob13.treasure_inventory = inv13 + ob13.reward_grant().treasure_inventory = inv13 var h13: int = int(rec13.get("handle", -1)) var tgt13: Vector2i = _first_closed_safe(ob13) if tgt13 != Vector2i(-9, -9): @@ -308,6 +308,11 @@ func _init() -> void: var ob14 = adr14.runner().current_battle() Kit.check_quiet(ob14 != null and (ob14._facts.attunement as Array) == ["morning_order", "wide_scout_band"], "装配不可变:战斗运行态装配集与冒险装配集逐位同源(运行零写口——快照对比)") + # 注册面批同代编排归约 V5:库存注入改址(level_runner 注入改经 reward_grant() 出口直注)后 + # 引用同一性——战斗域经出口持有的库存与关卡域同一实例;use_treasure 受理链经新地址走通 + # 由本套件 ob8/ob11/ob13 三段既有受理链承载(注入面同批改址后全绿即证据)。 + Kit.check_quiet(ob14 != null and ob14.reward_grant().treasure_inventory == adr14.runner().treasure_inventory, + "库存注入改址:战斗域经 reward_grant 出口持有的库存与关卡域同一引用〔编排归约 V5〕") # ⑮ 扩展行收敛(迭代 38 卡④ wr2④⑤——套件首次 feats 注入,V6 形态自 round_flow_skill_pool_test 移植: # enc 带 feats 键+ledger,专长触发经 _judge_seal 真实封印判定管线) @@ -332,7 +337,7 @@ func _init() -> void: Kit.check_quiet(mines15.size() == 3, "封印匠:板上存在三枚危险格(断言前提)") if mines15.size() == 3: for mv in mines15: - ob15._grant_seg._judge_seal({"cell": mv, "hit": true}, 1) + ob15._rounds._grant_seg._judge_seal({"cell": mv, "hit": true}, 1) var smith_row := {} for r in rows15: if String(r.get("effect_id", "")) == "mine_neutralized": @@ -354,7 +359,7 @@ func _init() -> void: cell15b = cv break if cell15b != null: - ob15b._grant_seg._judge_seal({"cell": cell15b, "hit": true}, 1) + ob15b._rounds._grant_seg._judge_seal({"cell": cell15b, "hit": true}, 1) var beacon_row := {} for r in rows15b: if String(r.get("effect_id", "")) == "fx_preview_area":