From ab3ba4f4a05ef0418f233493220d7174f1c2ca25 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 13 Oct 2026 07:14:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A324):=20N3=20=E6=95=8C?= =?UTF-8?q?=E6=96=B9attack=5Ftype=E8=BD=B4=E5=BC=80=E5=88=97=E2=80=94?= =?UTF-8?q?=E2=80=9420=E8=A1=8C=E5=85=A8=E5=A1=ABnormal+schema=E6=9E=9A?= =?UTF-8?q?=E4=B8=BE=E6=A0=A1=E9=AA=8C+=E5=8D=95=E4=BD=8D=E5=BF=AB?= =?UTF-8?q?=E7=85=A7+=E5=BC=80=E5=8D=95=E4=BC=A0=E5=80=BC+=E6=95=8C?= =?UTF-8?q?=E6=96=B9=E7=BB=93=E7=AE=97=E5=85=8B=E5=88=B6=E6=B6=88=E8=B4=B9?= =?UTF-8?q?=E7=82=B9(=E5=8D=A1=E7=89=87=C2=A72=E6=BC=8F=E9=A1=B9,V5?= =?UTF-8?q?=E7=9F=A9=E9=98=B5=E8=AF=81=E8=AF=8D=E5=BC=BA=E5=88=B6)+V5?= =?UTF-8?q?=E5=9B=9B=E6=96=AD=E8=A8=80(=E5=86=92=E7=83=9F949/0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/battle_orchestrator.gd | 7 ++-- game/core/damage_controller.gd | 13 ++++--- game/core/unit_container.gd | 1 + game/data/schemas/enemy_table.gd | 9 +++++ game/data/tables/enemy.tres | 25 ++++++++++++- game/smoke/smoke_all.gd | 63 ++++++++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 9 deletions(-) diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 3419c36e..e3e6ea5e 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -1397,14 +1397,15 @@ func _combat_segment(ri: int) -> void: _enemy_system.commit_action(u) continue # 伤害技(含普攻——倍率恒 1)= 敌方伤害技单据:基线 = round(atk×倍率)、rider 预计算归开单侧 - # (表值算术零随机),掷点/克制恒中性/hero_defense×enemy_output 池乘区/下限/rider 后置/commit - # 全归控制器管线(兑现序 = 开单序;来源敌已死在兑现时作废——死亡即不动手)。 + # (表值算术零随机),掷点/克制查表(第 9 参 = 敌方 attack_type——迭代 24 N3 轴开列前恒空串不查表)/ + # hero_defense×enemy_output 池乘区/下限/rider 后置/commit 全归控制器管线(兑现序 = 开单序; + # 来源敌已死在兑现时作废——死亡即不动手)。 var baseline: int = int(round(float(u.atk) * float(srow.get("damage_mult", 1.0)))) var rider := 0 if srow.has("rider_mult"): rider = maxi(int(db.settlement.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult)))) var et := _damage.open_ticket(DamageController.KIND_ENEMY_SKILL, "enemy_action", "%s %s" % [u.display_name, String(srow.display_name)], "combat", \ - baseline, DamageController.TARGET_HERO, ri, Vector2i(-1, -1), "", rider) + baseline, DamageController.TARGET_HERO, ri, Vector2i(-1, -1), String(u.get("attack_type", "normal")), rider) et["source_unit"] = u et["context"] = {"skill_id": skill_id, "baseline": baseline} _damage.submit(et) diff --git a/game/core/damage_controller.gd b/game/core/damage_controller.gd index 12b49692..c175a501 100644 --- a/game/core/damage_controller.gd +++ b/game/core/damage_controller.gd @@ -33,7 +33,8 @@ const POLICY_SEGMENT := "segment" # B:攒批到段界 flush 统一结( # hero_attack 战斗段·主角先手:掷点折扣暴击 → 克制点数(attack_type vs 敌护甲类型)→ # hero_offense×enemy_defense 池乘区 → 下限 → 交付;行载荷带 roll/ac/tier/killed。 # enemy_skill 战斗段·敌方伤害技(含普攻):基线 = round(atk×倍率)(开单侧算好入 base_value)→ -# 掷点折扣暴击 → 克制恒中性(敌打主角无甲轴 delta 0)→ hero_defense×enemy_output +# 掷点折扣暴击 → 克制点数(attack_type vs 主角护甲类型——迭代 24 N3 轴开列, +# 前态恒中性 delta 0)→ hero_defense×enemy_output # 池乘区 → 下限 → +rider(必中附加)→ 交付 → commit_action;来源敌已死 = 作废。 # board_typed 棋盘·单目标克制直伤(穿刺弹/魔法矢正向):必中全额,克制点数进加法核,下限保底。 # board_blast 棋盘·群伤(爆裂粉尘/清障炸药):无类型恒定表值,全体存活敌逐个交付。 @@ -413,7 +414,8 @@ func _resolve_hero_attack(t: Dictionary) -> void: _outcomes[int(t.ticket_id)] = "redeemed" -# 敌方伤害技:掷点 → 折扣/暴击 → 克制恒中性 delta 0(敌打主角无甲轴)→ hero_defense×enemy_output +# 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型——迭代 24 N3 轴开列; +# 前态恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output # 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手) func _resolve_enemy_skill(t: Dictionary) -> void: var hero: Dictionary = units.hero_unit() @@ -421,7 +423,8 @@ func _resolve_enemy_skill(t: Dictionary) -> void: var threshold: int = defender_ac(hero) + int(db.settlement.attack_bonus) var crit: bool = bool(roll_read.crit) var discount: int = _roll_discount(int(roll_read.roll), threshold) - var rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.crit_multiplier), 0) + var cdelta: int = _counter_delta(String(t.attack_type), String(hero.get("armor_type", "light"))) + var rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.crit_multiplier), cdelta) var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_defense"), clamped_pool("enemy_output")) var total: int = maxi(int(db.settlement.damage_floor), pooled) + int(t.rider) # rider 后置于伤害下限——独立必中加项,不进乘区 # damage_taken 发射+施加(内容数据驱动扩展批 T3——kind=enemy_skill;铁壁整备减伤订阅面: @@ -446,7 +449,7 @@ func _resolve_enemy_skill(t: Dictionary) -> void: total = maxi(int(db.settlement.damage_floor), total - shield) units.apply_damage(hero, total) var payload: Dictionary = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)), - "roll": int(roll_read.roll), "ac": threshold, "tier": discount} + "roll": int(roll_read.roll), "ac": threshold, "tier": discount, "attack_type": String(t.attack_type)} if crit: payload["crit"] = true if int(t.rider) > 0: @@ -547,7 +550,7 @@ func defender_ac(unit: Dictionary) -> int: # 克制修正点数读值(SettlementTable.armor_effectiveness:{攻击类型: {护甲: 点数}}——缺项回退 0 中性; -# attack_type "" = 无类型恒不查(群伤/反噬/地形/敌方技)) +# attack_type "" = 无类型恒不查(群伤/反噬/地形——敌方技自迭代 24 N3 起随单轴查表)) func _counter_delta(attack_type: String, armor: String) -> int: if attack_type == "": return 0 diff --git a/game/core/unit_container.gd b/game/core/unit_container.gd index f438f036..b4258fa9 100644 --- a/game/core/unit_container.gd +++ b/game/core/unit_container.gd @@ -118,6 +118,7 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_overrides "basic_streak": 0, # 保底计数:连续执行普攻的拍数(执行上报驱动) "armor_type": String(row.get("armor_type", "light")), # 护甲类型快照(克制矩阵消费面——战斗结算·战斗段;2026-09-15 命名统一批:light/ward/heavy) "armor_class": int(row.get("armor_class", 0)), # 护甲等级快照(掷点判定阈值基数——战斗结算·战斗段;2026-09-16 护甲等级掷点批) + "attack_type": String(row.get("attack_type", "normal")), # 攻击类型快照(克制矩阵消费面——敌方伤害单据第 9 参;迭代 24 N3 轴开列,缺省 normal 向后兼容) "alive": true, "is_hero": false, }) diff --git a/game/data/schemas/enemy_table.gd b/game/data/schemas/enemy_table.gd index 353532f1..899cf046 100644 --- a/game/data/schemas/enemy_table.gd +++ b/game/data/schemas/enemy_table.gd @@ -17,6 +17,11 @@ extends Resource # 2026-09-15 命名统一批:位置键 {none, a, b} → 语义键 {light, ward, heavy} = 轻甲 / 魔法护甲 / 重甲) const ARMOR_TYPES := ["light", "ward", "heavy"] +# 攻击类型封闭枚举(迭代 24 N3 2026-10-13——敌方攻击类型轴开列,承接锻造工坊迁移面⑥; +# 克制矩阵点数制键前缀 = 本枚举:normal → light 0 / ward +1 / heavy −1;pierce/magic 为差异化 +# 预留档,差异化填值属新设计另立项。可省略 + 缺省 normal 向后兼容) +const ATTACK_TYPES := ["normal", "pierce", "magic"] + # 实例化数量域(2026-10-02 修复批 T2 魔法数结构化——数量域 [1,3] 双落点收口单点: # 本表 validate 与 单位容器 spawn 守卫共用;遭遇骰结果与行值 unit_count/unit_count_max 同域。 # 2026-10-07 行集多行批:MAX 语义 = **单种数**(单行实例数上限),行集实例总数 ≤4 另由 @@ -111,6 +116,10 @@ func validate() -> Array: var at: String = String(row.get("armor_type", "light")) if not ARMOR_TYPES.has(at): errs.append({"category": "enum", "message": "%s armor_type 未知枚举: %s(域 {light, ward, heavy})" % [eid, at]}) + # attack_type(迭代 24 N3——可省略,缺省 normal 向后兼容;写法沿用 armor_type 同款) + var att: String = String(row.get("attack_type", "normal")) + if not ATTACK_TYPES.has(att): + errs.append({"category": "enum", "message": "%s attack_type 未知枚举: %s(域 {normal, pierce, magic})" % [eid, att]}) # armor_class(可省略——缺省 0 向后兼容;2026-09-16 护甲等级掷点批:防守方掷点判定阈值基数) var ac: int = int(row.get("armor_class", 0)) if ac < 0 or ac > 999: diff --git a/game/data/tables/enemy.tres b/game/data/tables/enemy.tres index 71f39f1a..df5bd973 100644 --- a/game/data/tables/enemy.tres +++ b/game/data/tables/enemy.tres @@ -22,6 +22,7 @@ rows = [{ "skill_id": "lunge", "weight": 1 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 0 }, { @@ -42,6 +43,7 @@ rows = [{ "skill_id": "lunge", "weight": 1 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 1 }, { @@ -64,6 +66,7 @@ rows = [{ "skill_id": "heavy", "weight": 2 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 2 }, { @@ -87,6 +90,7 @@ rows = [{ "skill_id": "heavy", "weight": 2 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 2 }, { @@ -113,6 +117,7 @@ rows = [{ "weight": 2, "unlock_hp_pct": 50 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 8 }, { @@ -135,7 +140,8 @@ rows = [{ "skill_id": "heavy", "weight": 2 }], - "armor_type": "light", + "attack_type": "normal", +"armor_type": "light", "armor_class": 1 }, { "level_index": 2, @@ -158,6 +164,7 @@ rows = [{ "skill_id": "heavy", "weight": 2 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 2 }, { @@ -180,6 +187,7 @@ rows = [{ "skill_id": "charge", "weight": 1 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 2 }, { @@ -202,6 +210,7 @@ rows = [{ "skill_id": "heavy", "weight": 2 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 8 }, { @@ -228,6 +237,7 @@ rows = [{ "skill_id": "venom_spit", "weight": 2 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 2 }, { @@ -254,6 +264,7 @@ rows = [{ "skill_id": "venom_spit", "weight": 2 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 2 }, { @@ -280,6 +291,7 @@ rows = [{ "skill_id": "venom_spit", "weight": 2 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 3 }, { @@ -309,6 +321,7 @@ rows = [{ "weight": 2, "unlock_hp_pct": 50 }], +"attack_type": "normal", "armor_type": "ward", "armor_class": 10 }, { @@ -334,6 +347,7 @@ rows = [{ "skill_id": "shield_bash", "weight": 2 }], +"attack_type": "normal", "armor_type": "heavy", "armor_class": 2 }, { @@ -356,6 +370,7 @@ rows = [{ "skill_id": "charge", "weight": 1 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 3 }, { @@ -379,6 +394,7 @@ rows = [{ "skill_id": "charge", "weight": 1 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 3 }, { @@ -404,6 +420,7 @@ rows = [{ "skill_id": "shield_bash", "weight": 2 }], +"attack_type": "normal", "armor_type": "heavy", "armor_class": 9 }, { @@ -427,6 +444,7 @@ rows = [{ "skill_id": "charge", "weight": 1 }], +"attack_type": "normal", "armor_type": "light", "armor_class": 3 }, { @@ -453,6 +471,7 @@ rows = [{ "skill_id": "shield_bash", "weight": 2 }], +"attack_type": "normal", "armor_type": "heavy", "armor_class": 4 }, { @@ -479,6 +498,7 @@ rows = [{ "weight": 2, "unlock_hp_pct": 50 }], +"attack_type": "normal", "armor_type": "heavy", "armor_class": 12 }] @@ -646,3 +666,6 @@ rows = [{ ; 2026-09-24 阶名化解方案((1,3) 改名大地精以阶名归位化解同族血量倒挂观感)实机验证未达预期,按体验反馈重裁: ; 第一关总量曲线 12/10/50/35/72 的 5 倍墙消除(22 后曲线 12/10/22/35/72 相邻比最大 2.2);敌种 bugbear/池/数量/atk 区间零改动。 ; 曲线标定其余行归 M4 数值批。 +; N3 敌方攻击类型轴开列(2026-10-13 迭代 24 卡①,承接锻造工坊实装批迁移面⑥):20 行全填 normal 挂敌种行—— +; 不做差异化(pierce/magic 属新设计另立项)。全局平移效应=主角穿重甲受伤 −1 / 魔法护甲 +1 / 轻甲 0, +; 锻造甲路线首次具备战斗意义;克制矩阵数值零触碰(settlement.tres armor_effectiveness 不改)。 diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index c4cd935e..1dc37529 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -73,6 +73,7 @@ func _run_core_smoke(db, rng) -> void: _db = db _rng = rng _smoke_ac_swap_guard() + _smoke_enemy_attack_type() _smoke_armor_roll() _smoke_pool_wiring() _smoke_board_generator() @@ -1235,6 +1236,68 @@ func _smoke_level() -> void: "模拟后 hero 表恢复基线(atk=%d hp=%d ac=%d——对配置快照)" % [int(_db.hero.atk), int(_db.hero.max_hp), int(_db.hero.armor_class)]) +# ---- 迭代 24 N3:敌方攻击类型轴(2026-10-13——表列/单位快照/单据载荷/矩阵命中证词)---- +# 克制矩阵命中证词 = 同种子三甲型配对实测(不是查表存在性证词):同种子下 armor_roll 流消费逐位一致、 +# 阈值(hero armor_class)不变,三甲型唯一差异 = 克制点数 cdelta(normal_vs_light 0 / heavy −1 / ward +1) +# → heavy 伤害 == light −1、ward == light +1(要求基线伤害 ≥ 2——地板 1 会吞 −1 位移)。 +# 承伤位移红线(卡片 §8 L3):全局位移只允许 ±1 量级——非此量级 = 矩阵消费面有未盘点的第二落点(停机)。 +func _smoke_enemy_attack_type() -> void: + print("--- 敌方攻击类型轴(迭代 24 N3)---") + # ① 表列:20 行全填 normal(N3 全填不做差异化——Q9) + var at_ok := true + var at_detail := "" + for row in _db.enemy.rows: + var at := String(row.get("attack_type", "")) + if at != "normal": + at_ok = false + at_detail += " %s:%s" % [String(row.get("enemy_id", "?")), at] + check(at_ok, "敌方 %d 行 attack_type 全填 normal(迭代 24 N3 轴开列,越轨行%s)" % [ + _db.enemy.rows.size(), at_detail if at_detail != "" else "无"]) + # ②③④ 矩阵命中证词扫描:(3,0) 灰矮人 atk 区间 [2,4] 基线足够((1,2) 熊地精 atk 1 会塌地板); + # 同 seed 三甲型各开一场、各取首个敌方伤害行,对拍 ±1。 + var matrix_found := false + var snap_ok := true + var payload_at := "" + var t_light := -1 + var t_heavy := -1 + var t_ward := -1 + for seed_offset in range(40): + var totals := {} + for armor in ["light", "heavy", "ward"]: + var rng = load("res://core/rng_service.gd").new() + rng.set_master_seed(20261013 + seed_offset) + var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng) + var rows: Array = [] + settlement.step_row.connect(func(row): rows.append(row)) + if not settlement.begin_battle({"level_index": 3, "node_depth": 0, "hero_hp": int(_db.hero.max_hp), "armor_type": armor}): + totals[armor] = -1 + continue + if armor == "light": + for u in settlement._enemy_system.all_enemies(): + if String(u.get("attack_type", "")) == "": + snap_ok = false + settlement.minesweeper.settle_batch([{"action": "stop"}]) + for row in rows: + if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action": + totals[armor] = int(row.get("value", 0)) + if armor == "light": + payload_at = String(row.get("attack_type", "")) # 载荷摊平在行顶层(_emit_row 摊平 extra) + break + if not totals.has(armor): + totals[armor] = -1 + if int(totals.get("light", -1)) >= 2 and int(totals.get("heavy", -1)) >= 1 and int(totals.get("ward", -1)) >= 1: + t_light = int(totals["light"]) + t_heavy = int(totals["heavy"]) + t_ward = int(totals["ward"]) + matrix_found = (t_heavy == t_light - 1 and t_ward == t_light + 1) + break + check(snap_ok, "敌方单位快照 attack_type 非空(spawn 注入面——迭代 24 N3)") + check(matrix_found and t_light >= 2, + "克制矩阵命中证词:同种子三甲型实测 heavy %d == light %d − 1 · ward %d == light %d + 1(迭代 24 N3 全局平移 −1/0/+1)" % [ + t_heavy, t_light, t_ward, t_light]) + check(payload_at == "normal", "敌方伤害技单据载荷 attack_type 非空(= %s——第 9 参传值面,迭代 24 N3)" % payload_at) + + # ---- 护甲等级掷点批·换装护栏(2026-09-16 用户拍板:百分比减伤行整体退役换装 AC,无双轨)---- # ① 换装在位:defense_up 状态行/池映射/奖励行三处全无残留,ac_up 三处就位(unit=ac,2026-09-20 改只续时不叠层); # ② 免伤墙归零护栏泛化保留(BUG-0003 事故类永久免疫):hero_defense 池任何 percent 行