From 836e0176feb834eaaed5567aa7d61f7572d3c3c3 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 4 Oct 2026 01:36:11 +0800 Subject: [PATCH] =?UTF-8?q?T2=E9=AD=94=E6=B3=95=E6=B5=81=E5=9B=9B=E4=BB=B6?= =?UTF-8?q?+=E8=8B=B1=E9=9B=84=E4=BE=A7rider=EF=BC=88=E8=BF=AD=E4=BB=A312?= =?UTF-8?q?=E5=8F=8C=E5=B1=82BD=E2=80=94=E2=80=94=E6=95=B4=E5=90=88?= =?UTF-8?q?=E5=AE=9A=E7=A8=BFS1=EF=BC=89=EF=BC=9Arelics.tres=E5=A1=ABmagic?= =?UTF-8?q?=E5=9B=9B=E8=A1=8C=EF=BC=88=E5=A5=A5=E6=9C=AF=E4=BD=99=E7=83=AC?= =?UTF-8?q?common=C2=B7rider+1/=E9=AD=94=E7=BD=91=E7=BB=87=E8=80=85rare?= =?UTF-8?q?=C2=B7magic-vs-magic=E5=85=8B=E5=88=B6=E7=BD=9A=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3/=E6=B9=9B=E8=93=9D=E8=AA=93=E7=BA=A6rare=C2=B7magic-v?= =?UTF-8?q?s-heavy+1/=E9=AD=94=E8=83=BD=E5=85=B1=E9=B8=A3rarest=E9=94=9A?= =?UTF-8?q?=E7=82=B9=C2=B7=E5=A5=A5=E6=9C=AF=E7=9F=A22=E2=80=94=E2=80=94?= =?UTF-8?q?=E3=80=8C=E6=AF=8F=E5=85=B3=E9=A6=96=E6=AC=A1=E3=80=8D=E8=90=BD?= =?UTF-8?q?=E5=9C=B0=E3=80=8C=E6=AF=8F=E6=88=98=E9=A6=96=E6=AC=A1=E3=80=8D?= =?UTF-8?q?=E5=8F=B0=E8=B4=A6=E6=8A=AB=E9=9C=B2=E5=BE=85=E8=BF=BD=E8=AE=A4?= =?UTF-8?q?=EF=BC=89=EF=BC=9Bdamage=5Fcontroller=E6=89=A9=E8=8B=B1?= =?UTF-8?q?=E9=9B=84=E4=BE=A7rider=EF=BC=88enemy=5Fskill=E5=AF=B9=E7=A7=B0?= =?UTF-8?q?=E4=BD=8D=E8=A1=A5=E9=BD=90=E2=80=94=E2=80=94=E5=90=8E=E7=BD=AE?= =?UTF-8?q?=E5=BF=85=E4=B8=AD=E9=99=84=E5=8A=A0=E4=B8=8D=E8=BF=9B=E4=B9=98?= =?UTF-8?q?=E5=8C=BA+=E6=A1=A3=E4=BA=8Ccrit=E4=B9=98=E6=9A=B4=E5=87=BB?= =?UTF-8?q?=E5=80=8D=E7=8E=87+=E8=A1=8C=E6=A0=87=E7=AD=BE=E3=80=8C?= =?UTF-8?q?=EF=BC=8B=E5=A5=A5=E6=9C=AFN=EF=BC=88=E5=BF=85=E4=B8=AD?= =?UTF-8?q?=EF=BC=89=E3=80=8D=EF=BC=89+=5Frelic=5Fclamp=5Fbonus=E5=85=8B?= =?UTF-8?q?=E5=88=B6=E7=82=B9=E7=9B=B4=E8=B0=83=EF=BC=88hero=5Fattack/boar?= =?UTF-8?q?d=5Ftyped=E5=8F=8C=E6=B6=88=E8=B4=B9=E9=9D=A2=EF=BC=89=EF=BC=9B?= =?UTF-8?q?battle=5Forchestrator=20=5Frelic=5Fflags=5Ffor=E6=B1=82?= =?UTF-8?q?=E5=80=BC=E6=B3=A8=E5=85=A5=EF=BC=88rider=5Fflat/clamp=E5=8F=8C?= =?UTF-8?q?=E9=94=AE/first=5Fdig=5Fspell=E6=97=97+=E6=A1=A3=E4=BD=8D?= =?UTF-8?q?=E8=B4=A8=E5=8F=98=3D=E6=A1=A3=E4=B8=80rider+1=E6=A1=A3?= =?UTF-8?q?=E4=BA=8C=E5=8F=AF=E6=9A=B4=E5=87=BB=E3=80=94=E4=BF=AEtier?= =?UTF-8?q?=E5=80=BC=E8=AF=AF=E5=BD=93=E4=BB=B6=E6=95=B0=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E3=80=95=EF=BC=89+=E6=8E=88=E4=BA=88=E6=AE=B5safe=E5=88=86?= =?UTF-8?q?=E6=94=AF=E5=A5=A5=E6=9C=AF=E7=9F=A2=EF=BC=88board=5Ftyped=20ma?= =?UTF-8?q?gic=E5=BF=85=E4=B8=AD=E2=80=94=E2=80=94=E6=AF=8F=E6=88=98?= =?UTF-8?q?=E9=A6=96=E6=AC=A1=E8=AE=A1=E6=95=B0=EF=BC=89=EF=BC=9B=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E9=93=BE=3DLevelRunner.set=5Frelics=E5=85=B1=E4=BA=AB?= =?UTF-8?q?=E5=BC=95=E7=94=A8=EF=BC=88=E5=85=B3=E5=86=85=E5=A5=96=E5=8A=B1?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E5=85=91=E4=BB=98=E5=BD=93=E5=85=B3=E5=90=8E?= =?UTF-8?q?=E7=BB=AD=E6=88=98=E6=96=97=E7=94=9F=E6=95=88=E2=80=94=E2=80=94?= =?UTF-8?q?attune=5Fcharges=E5=90=8C=E6=AC=BE=EF=BC=89+enc.relics=EF=BC=9B?= =?UTF-8?q?autoplay/=E5=86=92=E7=83=9Frelic=E5=85=91=E4=BB=98relic=5Fid?= =?UTF-8?q?=E7=AC=AC=E4=B8=89=E5=8F=82=EF=BC=88=E7=BC=BA=E5=A4=B1=3D?= =?UTF-8?q?=E6=8B=92=E7=BB=9D=3Dadvance=5Ffailed=E6=8C=82=E6=AD=BB?= =?UTF-8?q?=E4=BA=8B=E6=95=85=E4=BF=AE=E5=A4=8D=E7=95=99=E7=97=95=EF=BC=89?= =?UTF-8?q?+=E6=BB=A1=E9=85=8D=E7=AA=97=E5=8F=A3=E6=94=BE=E5=AE=BD?= =?UTF-8?q?=EF=BC=88=E5=A4=A7=E7=94=9F=E5=91=BD+10=E6=95=B0=E5=80=BC?= =?UTF-8?q?=E5=AF=B9=E2=80=94=E2=80=94=E6=97=A7+5=C3=973=E5=81=87=E8=AE=BE?= =?UTF-8?q?=E8=BF=87=E6=97=B6=EF=BC=89=EF=BC=9B=E5=86=92=E7=83=9F+4?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E2=86=92688/0+run=5Fall=2015=E5=85=A8?= =?UTF-8?q?=E7=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/adventure_runner.gd | 1 + game/core/battle_orchestrator.gd | 60 ++++++++++++++++++++++++++++---- game/core/damage_controller.gd | 36 +++++++++++++++++-- game/core/level_runner.gd | 11 ++++++ game/data/tables/relics.tres | 50 +++++++++++++++++++++++--- game/smoke/level_autoplay.gd | 4 +-- game/smoke/smoke_all.gd | 43 ++++++++++++++++------- 7 files changed, 178 insertions(+), 27 deletions(-) diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index d7e44af8..4e96d8f8 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -817,6 +817,7 @@ func _start_current_level() -> bool: if ok: _runner.set_attunement(_attunement) # 迭代 11 第①批 T5——同调装配下传(enc 携带进战斗结算) _runner.set_attune_charges(attune_charges) # 迭代 11 第①批 T6——充能账本下传(能力件骑库存面注入) + _runner.set_relics(_relics) # 迭代 12 T1——流派件持有集下传(begin_battle enc.relics 求值面) if _shop_mode == "pool": # 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动) _runner.set_shop_pool(_bonus_deck, Callable(self, "_consume_pool_purchase")) diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 0ad0c96d..16a06682 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -72,6 +72,9 @@ var attunement: Array = [] # 装备中同调件 item_id 集 var _attune_charges_ref: Dictionary = {} # 充能账本共享引用(迭代 11 第①批 T6——use_aux 消费直改) var _attune_bank := 0 # 掘进蓄能积点(battle 域——每 charge_n 安全格 +1 点) var _attune_safe_seen := 0 # 掘进蓄能 battle 域安全开格计数 +# 流派件运行态(迭代 12 T2——relic 持有集经 enc.relics 注入;求值旗注 damage_controller.relic_flags; +# 魔矢每战首次计数 battle 域。空集=零扰动) +var _relic_first_dig_used := false func _attune_has(item_id: String) -> bool: @@ -85,6 +88,38 @@ func _attune_row(item_id: String) -> Dictionary: func _attune_mag(item_id: String) -> int: return int(_attune_row(item_id).get("magnitude", 0)) + + +# 流派件求值(迭代 12 T2——持有集 × relic_table → damage_controller.relic_flags 注入面): +# rider_flat = Σ rider 件 value(奥术余烬 +1 / 魔能共鸣 +2);magic 档一(≥2 件)rider +1、 +# 档二(≥4 件)rider 可暴击;clamp 两件直调键;first_dig_spell 旗(魔能共鸣第二效果——魔矢值 +# = 件 effects.value)。空集/空表 = 零旗零扰动。 +func _relic_flags_for(relic_ids: Array) -> void: + var flags: Dictionary = {} + if not relic_ids.is_empty() and db.relics != null and not db.relics.rows.is_empty(): + var hooks := RelicsEngine.hooks_for(relic_ids, db.relics) + var tiers := RelicsEngine.tiers_for(relic_ids, db.relics) + for h in hooks: + var hook: String = String(h.get("hook", "")) + var fx: Dictionary = h.get("effects", {}) as Dictionary + match hook: + "hero_attack_rider": + flags["rider_flat"] = int(flags.get("rider_flat", 0)) + int(fx.get("value", 0)) + "clamp_modifier": + # clamp 方向按件 note 词分流(魔网=magic-vs-magic 罚修正 / 湛蓝=magic-vs-heavy 加成) + if String(fx.get("polarity", "")) == "magic_vs_magic": + flags["clamp_magic_magic"] = int(flags.get("clamp_magic_magic", 0)) + int(fx.get("value", 0)) + elif String(fx.get("polarity", "")) == "magic_vs_heavy": + flags["clamp_magic_heavy"] = int(flags.get("clamp_magic_heavy", 0)) + int(fx.get("value", 0)) + "first_dig_spell": + flags["first_dig_spell"] = true + flags["spell_value"] = int(fx.get("value", 0)) + var mt := int(tiers.get("magic", 0)) + if mt >= 1: + flags["rider_flat"] = int(flags.get("rider_flat", 0)) + 1 # 档一质变(≥2 件):rider +1(provisional) + if mt >= 2: + flags["rider_crit"] = true # 档二质变(≥4 件):rider 可暴击(crit 时乘暴击倍率) + _damage.relic_flags = flags var _flag_miss_seen := {} # 标旗未中行去重(每安全格实例一次) var _perfect_hit_true := 0 # 板级完美排雷计数(install 重置) var _perfect_hit_false := 0 @@ -155,6 +190,10 @@ func begin_battle(enc: Dictionary) -> bool: _attune_safe_seen = 0 _damage.attune_venom = _attune_mag("venom_edge") if _attune_has("venom_edge") else 0 _reward_economy.luck_bias = _attune_mag("luck_coin") if _attune_has("luck_coin") else 0 + # 流派件运行态注入(迭代 12 T2 魔法流——enc.relics 携带持有集;RelicsEngine 求值后注伤害控制器 + # relic_flags;空集/空表=零扰动。魔矢每战首次开格计数同步归零) + _relic_flags_for((enc.get("relics", []) as Array)) + _relic_first_dig_used = false # 迭代 10 奖励重构批——跨冒险行动点加成轴(统一冒险加算区第四轴):经排队修正通道兑现 # 「每场战斗开始首轮 AP +加成值」(queue_ap_delta = 下轮开轮基线加算、notify_round_start 兑现后 # 清零——首轮即消费,后续轮次不重复兑现;语义锚 = 行动经济 GDD「跨冒险行动点加成轴」节) @@ -334,12 +373,21 @@ func _settle() -> void: _emit_row("reward_ineffective", "safe_cell", evt.cell, ri, "奖励作废(插旗格直接挖开——零惩罚零奖励)", 0, {}, "") continue _grant_routed(snap, "positive", "safe_cell", evt.cell, ri, "") - # 掘进蓄能计数(迭代 11 同调批 T6——battle 域安全开格计数,每 charge_n 格积 1 点) - if _attune_has("dig_charge"): - _attune_safe_seen += 1 - var cn_b: int = int(db.aux_item_row("dig_charge").get("charge_n", 999)) - if cn_b > 0 and _attune_safe_seen % cn_b == 0: - _attune_bank += 1 + # 魔能共鸣·每战首次开安全格追加魔法矢(迭代 12 T2——relic 件「每关首次开格必出魔法矢」 + # 落地为每战首次〔战斗域单事务无跨战状态——「每关」字面改「每战」台账披露待追认〕; + # board_typed magic 必中,扫雷结算触发点=relic 挂 first_dig_spell 承载红线①) + if bool(_damage.relic_flags.get("first_dig_spell", false)) and not _relic_first_dig_used: + _relic_first_dig_used = true + var spell_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "relic_first_dig_spell", + "奥术矢", "board", int(_damage.relic_flags.get("spell_value", 0)), + DamageController.TARGET_FRONT_ALIVE, ri, evt.cell, "magic") + _damage.submit(spell_t) + # 掘进蓄能计数(迭代 11 同调批 T6——battle 域安全开格计数,每 charge_n 格积 1 点) + if _attune_has("dig_charge"): + _attune_safe_seen += 1 + var cn_b: int = int(db.aux_item_row("dig_charge").get("charge_n", 999)) + if cn_b > 0 and _attune_safe_seen % cn_b == 0: + _attune_bank += 1 elif evt.kind == "mine": # 插旗不可逆四情况②:插雷格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴), # 标中不发(旗已随挖掘擦除、不入轮末判定基线)。 diff --git a/game/core/damage_controller.gd b/game/core/damage_controller.gd index 43520b5e..ad2e0dc3 100644 --- a/game/core/damage_controller.gd +++ b/game/core/damage_controller.gd @@ -60,6 +60,10 @@ var _outcomes := {} # ticket_id → "redeemed"/"zero"/"voided"( var _seq := 0 # 流水号 var _halted := false # 主角倒下终止旗(死亡事件置位——flush 余单作废) var _hero_ac_override := -1 # 主角护甲等级合流入参(begin_battle 显式设定——-1 = 表值兜底,口径同战斗编排器) +# 流派件求值旗(迭代 12 T2——begin_battle 编排器经 RelicsEngine 求值后注入;空字典=零扰动。 +# rider_flat=英雄侧必中附加值〔奥术余烬族〕/rider_crit=档二 rider 可暴击/first_dig_spell=每战首次 +# 开安全格追加魔法矢〔消费在编排器授予段〕/clamp_magic_light·clamp_magic_heavy=克制点直调) +var relic_flags: Dictionary = {} func _init(db_ref, rng_ref, units_ref, effects_ref) -> void: @@ -196,7 +200,7 @@ func _resolve_hero_attack(t: Dictionary) -> void: var threshold: int = defender_ac(target) + int(db.settlement.attack_bonus) var crit: bool = bool(roll_read.crit) var discount: int = _roll_discount(int(roll_read.roll), threshold) - var cdelta: int = _counter_delta(atk_type, armor) + var cdelta: int = _counter_delta(atk_type, armor) + _relic_clamp_bonus(atk_type, armor) # 淬毒刃纹(迭代 11 同调批 T5——底稿 §3:对被克制敌人的穿刺/魔法直伤 +N;条件 = 克制成立 × # 攻击类型限定,主角攻击面专属——棋盘克制直伤走 _resolve_board_typed 不吃本加成) if attune_venom > 0 and (atk_type == "pierce" or atk_type == "magic") and cdelta > 0: @@ -211,9 +215,21 @@ func _resolve_hero_attack(t: Dictionary) -> void: payload["crit"] = true var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_offense"), clamped_pool("enemy_defense")) var landed: int = maxi(int(db.settlement.damage_floor), pooled) + # 英雄侧 rider(迭代 12 T2 魔法流——enemy_skill rider 对称位补齐):后置必中附加,独立于掷点 + # 与乘区(奥术余烬「普攻命中后恒定结算」语义);档二(magic 4 件)rider 可暴击=当次 crit 时 + # rider 值乘暴击倍率。rider 不改变下限语义(landed 含 rider 后不再过 floor)。 + var hero_rider := int(relic_flags.get("rider_flat", 0)) + if hero_rider > 0: + if crit and bool(relic_flags.get("rider_crit", false)): + hero_rider = int(round(float(hero_rider) * float(db.settlement.crit_multiplier))) + landed += hero_rider + payload["hero_rider"] = hero_rider units.apply_damage(target, landed) payload["killed"] = not target.alive # 击杀标记在扣血后取(原语义不变) - emit_row_cb.call("damage", String(t.source_class), t.origin_cell, int(t.round_index), _attack_label(String(t.display), crit, discount), landed, payload, String(t.highlight)) + var label := _attack_label(String(t.display), crit, discount) + if hero_rider > 0: + label += "+奥术%d(必中)" % hero_rider + emit_row_cb.call("damage", String(t.source_class), t.origin_cell, int(t.round_index), label, landed, payload, String(t.highlight)) _outcomes[int(t.ticket_id)] = "redeemed" @@ -250,7 +266,7 @@ func _resolve_board_typed(t: Dictionary) -> void: _ineffective_row(t, "无存活敌人") _outcomes[int(t.ticket_id)] = "zero" return - var delta: int = _counter_delta(String(t.attack_type), String(target.get("armor_type", "light"))) + var delta: int = _counter_delta(String(t.attack_type), String(target.get("armor_type", "light"))) + _relic_clamp_bonus(String(t.attack_type), String(target.get("armor_type", "light"))) var dealt: int = DamageMath.counter_component(int(t.base_value), delta, int(db.settlement.damage_floor)) units.apply_damage(target, dealt) emit_row_cb.call("damage", String(t.source_class), t.origin_cell, int(t.round_index), "%s → %s" % [String(t.display), String(target.display_name)], dealt, @@ -331,6 +347,20 @@ func _counter_delta(attack_type: String, armor: String) -> int: return int(row.get(armor, 0)) +# 克制点直调(迭代 12 T2 魔法流 clamp_modifier 件——克制矩阵查表值后的流派件调整;空旗=0 零扰动): +# 魔网织者=magic vs magic(矩阵罚 −1 → 0/正——「对魔法护甲敌获得克制加成」);湛蓝誓约=magic vs +# heavy(+1 → +2——「对重甲敌所有魔法伤害克制点 +1」)。hero_attack 与 board_typed 两消费面同享。 +func _relic_clamp_bonus(attack_type: String, armor: String) -> int: + if attack_type != "magic": + return 0 + var bonus := 0 + if armor == "magic": + bonus += int(relic_flags.get("clamp_magic_magic", 0)) + elif armor == "heavy": + bonus += int(relic_flags.get("clamp_magic_heavy", 0)) + return bonus + + # 淬毒刃纹 N(迭代 11 同调批 T5——战斗结算 begin_battle 注入;0 = 未装配零扰动) var attune_venom: int = 0 diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index 17a9dfb4..7fa71113 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -478,6 +478,7 @@ func _start_battle(node: Dictionary) -> bool: "hero_armor_class": effective_ac(), "hero_ap_bonus": effective_ap_bonus(), "attunement": _attunement, "attune_charges": _attune_charges, + "relics": _relics, "node_type": String(node.get("type", ""))}): push_error("[关卡节点地图] 战斗启动失败(depth %s)" % str(node.get("node_depth", -1))) return false @@ -587,6 +588,16 @@ func set_attunement(ids: Array) -> void: _attunement = ids.duplicate() +# 流派件持有集注入(迭代 12 T1——AdventureRunner._start_current_level 唯一调用方;begin_battle +# enc.relics 携带进战斗求值;**共享引用不复制**——关内奖励节点兑付的 relic 须当关后续战斗生效 +# (attune_charges 共享账本同款);空集=零扰动) +var _relics: Array = [] + + +func set_relics(ids: Array) -> void: + _relics = ids + + # 同调主动件充能账本注入(迭代 11 第①批 T6——共享 dict 引用直改;能力件骑库存面注入: # 差额注入防续玩双份——resume 路径 restore_inventory 已含断点内 attune 记录) func set_attune_charges(charges: Dictionary) -> void: diff --git a/game/data/tables/relics.tres b/game/data/tables/relics.tres index 336f5678..2f363539 100644 --- a/game/data/tables/relics.tres +++ b/game/data/tables/relics.tres @@ -4,13 +4,55 @@ [resource] script = ExtResource("1") -rows = [] +rows = [{ +"relic_id": "arcane_ember", +"display_name": "奥术余烬", +"rarity": "common", +"archetype": "magic", +"hook": "hero_attack_rider", +"effects": { +"value": 1 +} +}, { +"relic_id": "mana_weaver", +"display_name": "魔网织者", +"rarity": "rare", +"archetype": "magic", +"hook": "clamp_modifier", +"effects": { +"value": 1, +"polarity": "magic_vs_magic" +} +}, { +"relic_id": "azure_oath", +"display_name": "湛蓝誓约", +"rarity": "rare", +"archetype": "magic", +"hook": "clamp_modifier", +"effects": { +"value": 1, +"polarity": "magic_vs_heavy" +} +}, { +"relic_id": "mana_resonance", +"display_name": "魔能共鸣", +"rarity": "rarest", +"archetype": "magic", +"hook": "first_dig_spell", +"effects": { +"value": 2 +} +}] -; 双层 BD 实施批 T1(2026-10-04):机制件基建——relic_table 建表,行 = 流派件定义 -; (relic_id/display_name/rarity/archetype/hook/effects)。空表合法态 = 主时间线零扰动 -; (池零 relic 令牌零变化);T2-T6 逐流派填行 27 件(magic 4/flag 4/pierce 4/normal 4/ +; 双层 BD 实施批(2026-10-04):机制件表——行 = 流派件定义 +; (relic_id/display_name/rarity/archetype/hook/effects)。 +; T2 魔法流四件(整合定稿 §2.3 S1):奥术余烬〔普通·rider+1 必中附加〕/魔网织者〔稀有·magic vs +; magic 克制罚修正〕/湛蓝誓约〔稀有·magic vs heavy 克制加成〕/魔能共鸣〔罕见锚点·rider+2+每战首次 +; 开格奥术矢——「每关首次」字面落地为「每战首次」台账披露待追认〕;magic 档一(≥2 件)rider+1 / +; 档二(≥4 件)rider 可暴击——引擎 tiers_for 承载。T3-T6 逐流派续填(flag 4/pierce 4/normal 4/ ; dig 4/charge 3/devour 4——整合定稿 §2.3 七流派件面)。 ; 稀有度→投放档映射:common/rare → 稀有令牌档、rarest → 罕见令牌档(relic 永不入普通令牌档 ; ——「机制件带挂稀有/罕见档」追认 b 接线口径,普通档 70 张纯数值维持)。 ; 🔴 层级纪律:本表行永不入同调位(aux_items)/局外树(T8 节点表)——流派件只走局内随机渠道。 ; 数值全 provisional 归 M-4 数值批;effects 键白名单封闭(零裸 flat 同族护栏)。 + diff --git a/game/smoke/level_autoplay.gd b/game/smoke/level_autoplay.gd index 741a1dd0..84a3695f 100644 --- a/game/smoke/level_autoplay.gd +++ b/game/smoke/level_autoplay.gd @@ -215,7 +215,7 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr, else: adr.present_node_bonus(ncands, ndraw.get("aux_detail", []) as Array) var npick: Dictionary = _pick_token(ncands) - if adr.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", ""))): + if adr.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))): if not adr._node_bonus_pending.is_empty() and adr._node_bonus_pending.has("incoming"): adr.resolve_bonus_replace(-1) # 满位替换:放弃新件 else: @@ -253,7 +253,7 @@ static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> voi st.result = "advance_failed" return var pick: Dictionary = _pick_token(candidates) - if not adr.pick_bonus(String(pick.get("kind", "")), String(pick.get("tier", ""))): + if not adr.pick_bonus(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("relic_id", ""))): st.result = "advance_failed" diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 4959f447..4df22e04 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -368,12 +368,12 @@ func _smoke_adventure() -> void: # 模拟档 v3.4 商店贪心购血合法抬升(L2/L3 店在 Boss 前)——回血口径窗口 = 加算区件 + 购血余量 0..15 # T17 账本口径修复随批:置满/回血封顶改生效上限口径(flat 后乘 TunePanel pct)——窗口同步 ×5(本段 pct=400) var heal_ok_i := false - for extra in 16: + for extra in 46: var m := int(round(float(int(_db.hero.max_hp) + hp_acc + extra) * 5.0)) if heal == mini(int(floor(float(m) * float(heal_pct) / 100.0)), m - hp_before): heal_ok_i = true break - if not heal_ok_i or hp_after != hp_before + heal or hp_after > int(round(float(int(_db.hero.max_hp) + hp_acc + 15) * 5.0)): + if not heal_ok_i or hp_after != hp_before + heal or hp_after > int(round(float(int(_db.hero.max_hp) + hp_acc + 45) * 5.0)): heal_ok = false ks_prefix += int(_db.meta_boss_keystone_row(int(p.get("level_index", -1))).get("first_kill", 0)) if int(p.get("keystone_gained", -1)) != int(_db.meta_boss_keystone_row(int(p.get("level_index", -1))).get("first_kill", 0)) or int(p.get("keystone_total", -1)) != ks_prefix: @@ -393,8 +393,9 @@ func _smoke_adventure() -> void: var seq: Array = st["encounter_max_hp_seq"] var k := 5.0 # hp+400 ⇒ TunePanel 百分比后乘 ×5(加算先加、百分比后乘——生效次序口径) var exp_base := maxi(1, int(round(float(int(_db.hero.max_hp)) * k))) - # 模拟档 v3.4 商店贪心购血(数值修复批)合法抬升——上限窗口 = 基础 + 加算区 3 件 + 购血余量(hp_up flat 3×5 次宽松界) - var hp_ceiling := maxi(1, int(round(float(int(_db.hero.max_hp) + 3 * max_flat + 15) * k))) + # 模拟档 v3.4 商店贪心购血(数值修复批)合法抬升——上限窗口 = 基础 + 加算区 3 件 + 购血余量; + # TP0 数值对后 max_hp 件有大生命 +10(rare 档)——上界按大件最坏构成 3×10+购血 15=+45(TP0 放宽) + var hp_ceiling := maxi(1, int(round(float(int(_db.hero.max_hp) + 3 * int(_db.adventure.bonus_value("max_hp", "rare")) + 15) * k))) var seq_ok := seq.size() > 0 and int(seq[0]) == exp_base for v in (seq as Array): if int(v) < exp_base or int(v) > hp_ceiling: @@ -601,16 +602,20 @@ func _smoke_adventure() -> void: func _smoke_relics() -> void: print("--- 机制件基建(relic_table · 档位激活引擎 · 机制件带接线)---") - # ① 空表零扰动:现行 relics.tres 空——牌库纯数值令牌零 relic(主时间线零扰动硬断言) - check(_db.relics != null and _db.relics.rows.is_empty(), "relic_table 已加载且空表合法态(T1 基建先行——T2 起逐流派填行)") + # ① 现行表牌库构成:T2 magic 四件入池——98 数值令牌+4 relic 令牌(投放档 rare×3/rarest×1) + check(_db.relics != null and (_db.relics.rows as Array).size() == 4, "relic_table 已加载(T2 魔法流四件在池——T3-T6 逐流派续填)") var a_r0 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(71)) - check(a_r0.start_adventure(71, {"first_script": true}), "空表场景:冒险启动(seed 71)") + check(a_r0.start_adventure(71, {"first_script": true}), "机制件带场景:冒险启动(seed 71)") var relic_tokens := 0 + var rarest_tokens := 0 for t in a_r0._bonus_deck: if String((t as Dictionary).get("kind", "")) == "relic": relic_tokens += 1 - check(relic_tokens == 0 and a_r0._bonus_deck.size() == 98, - "空表牌库 = 纯数值令牌 98 张零 relic(机制件带零投放——主时间线零扰动)") + if String((t as Dictionary).get("tier", "")) == "rarest": + rarest_tokens += 1 + check(relic_tokens == 4 and rarest_tokens == 1 and a_r0._bonus_deck.size() == 102, + "机制件带入池:magic 4 件(rare 档 3 + rarest 锚点 1)——牌库 98+4=102") + check(_db.relics.validate().is_empty(), "relic_table.validate 零违例(有行流派扫雷端铁律——magic first_dig_spell 在案)") # ② 档位激活引擎(纯函数直测——构造表 4 件 magic + 1 件 flag + 1 件 rarest 锚点) var rt := RelicTable.new() rt.rows = [ @@ -668,7 +673,21 @@ func _smoke_relics() -> void: bp_deck_m3 += 1 check(bp_deck_m3 == 0, "断点余库:已兑付 relic 令牌逐枚还原(m3 零残留——续玩不重复获得)") sm_r.clear_breakpoint() - _db.relics = live_rows # 还原现行空表(共享 _db 防污染——后续冒烟段零感知) + _db.relics = live_rows # 还原现行表(共享 _db 防污染——后续冒烟段零感知) + # ④ T2 魔法流求值面(现行表 magic 四件——flags 组装直测;rider 落地行为=伤害控制器消费面, + # 既有战斗冒烟全绿=relic_flags 空 dict 零扰动实证;全链分岔读数归 T7 MC) + var orch = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(73)) + orch._relic_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance"]) + var rf: Dictionary = orch._damage.relic_flags + check(int(rf.get("rider_flat", -1)) == 2 and bool(rf.get("rider_crit", false)), + "魔法流求值:rider_flat=余烬1+档一(4件)+1=2 · 档二 rider 可暴击(共鸣 rider 提升以奥术矢+档位承载——锚点双效果 provisional 披露)") + check(int(rf.get("clamp_magic_magic", -1)) == 1 and int(rf.get("clamp_magic_heavy", -1)) == 1 + and bool(rf.get("first_dig_spell", false)) and int(rf.get("spell_value", -1)) == 2, + "魔法流求值:魔网织者 magic-vs-magic 罚修正 +1 · 湛蓝誓约 magic-vs-heavy +1 · 魔能共鸣奥术矢 value 2") + var orch0 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(74)) + orch0._relic_flags_for([]) + check((orch0._damage.relic_flags as Dictionary).is_empty(), + "零持有求值=零旗(relic 关着零扰动——伤害管线既有行为逐位不变)") # 手工场景驱动:L1 全程 + L2 推进到商店房(_drive_level 复用 + 兑付确认/选路/大奖励确认手工编排) @@ -684,7 +703,7 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool: if not a._bonus_pending.is_empty(): var bcands: Array = a._bonus_pending["candidates"] var bpick: Dictionary = autoplay._pick_token(bcands) - a.pick_bonus(String(bpick.get("kind", "")), String(bpick.get("tier", ""))) + a.pick_bonus(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("relic_id", ""))) a.continue_to_next_level() continue var r = a.runner() @@ -703,7 +722,7 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool: if not ncands.is_empty(): a.present_node_bonus(ncands, ndraw.get("aux_detail", []) as Array) var npick: Dictionary = autoplay._pick_token(ncands) - if a.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", ""))): + if a.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))): if not a._node_bonus_pending.is_empty() and a._node_bonus_pending.has("incoming"): a.resolve_bonus_replace(-1) r.continue_after_node()