feat(combat): 伤害下限全来源批——任何伤害落地取值域 [1,+∞)(2026-09-20 用户拍板,台账 100)

用户对 06「零伤害/无威胁组合的跨字段护栏」直接给词表:所有伤害(不分敌我)
最终落点不能是 0 或负数,最小取值 1,无上限。

- DamageMath.counter_component 增 landed_floor 参数(空桶恒 0 边界不变)
- battle_orchestrator 四落点保底:棋盘克制直伤/爆裂正向/负向反噬/地形变异
  (攻击双方原有闸不动;apply_damage n≤0 拒绝保留作真 bug 兜底)
- 06 条目整条解决移除:上界护栏随『不设上限』裁决、退化 0 基线敌经下限消解
- 战斗结算 v0.19 四处措辞+取值域成文(与未合并伏击分支撞号注记避让)
- registry ac_roll / settlement_table.gd 注释同步
- 冒烟 352→355 项 0 失败三闸全过(贴底/穿透/空桶三断言)
- 现行表值零行为变更:纯结构保证+调参防误配
This commit is contained in:
DKChan committed 2026-09-20 03:25:09 +08:00
1 parent a20ea38086
commit 6a9f5901c1
8 files changed
+40 -28

No files matched your search

@@ -297,7 +297,7 @@ func _settle() -> void:
var vrow: Dictionary = db.variation.row_by_id(String(vid))
if String(vrow.get("effect", "")) != "flat_damage":
continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面)
var dmg: int = int(vrow.get("effect_value", 0))
var dmg: int = maxi(int(db.settlement.damage_floor), int(vrow.get("effect_value", 0))) # 伤害下限全来源(2026-09-20)
_enemy_system.apply_damage(_enemy_system.hero_unit(), dmg)
_emit_row("damage", "地形变异", evt.cell, ri, "%s → 主角" % String(vrow.get("display_name", vid)), dmg, {"target": "hero", "variant_id": String(vid)}, "")
for evt in _facts_flags:
@@ -372,6 +372,8 @@ func _apply_percent_row(row: Dictionary, source_class: String, cell: Vector2i, r
# blast 正向 = 全体存活敌各 power 点(无敌 → 奖励未生效行);blast 负向 = 敌方伏击 +stacks 层(cap/溢出行协议不变);
# heal 正向 = 主角回复 clamp(满血 → 奖励未生效行);heal 负向 = 主角直伤 power 点(不经增益状态;归零 → 战斗段短路/判定段判负)。
# pierce/magic(伤害道具批 2026-09-18)正向 = 单目标克制直伤(必中全额);负向 = 主角直伤。
# 伤害落地全局下限(2026-09-20 用户拍板「任何伤害取值域 [1,+∞)」):本函数全部伤害兑付点经 damage_floor 保底——
# 克制修正后触底、表值误配 ≤0 都不可能打出 0/负伤害(v=0 伤害行语义早已退役,本批把棋盘侧从"表值碰巧 ≥1"升级为结构保证)。
# 返回 "redeemed" = 实际有效兑付 / "zero" = 零兑付(行已自记)——清图高光只记实际有效兑付。
func _apply_flat_row(row: Dictionary, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> String:
var ctype := String(row.cell_type)
@@ -384,16 +386,18 @@ func _apply_flat_row(row: Dictionary, source_class: String, cell: Vector2i, ri:
if alive.is_empty():
_emit_row("reward_ineffective", source_class, cell, ri, "%s 未生效(无存活敌人)" % display, 0, {"batch_key": String(row.reward_id)}, highlight)
return "zero"
var blast_dmg: int = maxi(int(db.settlement.damage_floor), power) # 伤害下限全来源(2026-09-20)
for u in alive:
_enemy_system.apply_damage(u, power) # 恒定表值,逐个;受击即死——死亡即不动手
_emit_row("damage", source_class, cell, ri, "%s → 全体敌人 ×%d" % [display, alive.size()], power, {"target": "all_enemies", "count": alive.size()}, highlight)
_enemy_system.apply_damage(u, blast_dmg) # 恒定表值(下限保底),逐个;受击即死——死亡即不动手
_emit_row("damage", source_class, cell, ri, "%s → 全体敌人 ×%d" % [display, alive.size()], blast_dmg, {"target": "all_enemies", "count": alive.size()}, highlight)
return "redeemed"
if ctype == "pierce" or ctype == "magic":
# 伤害道具批(2026-09-18):穿刺/魔法两型——正向 = 单目标克制直伤(attack_type 字段查克制矩阵,
# 必中全额不掷点——棋盘来源伤害既有口径;目标 = 固定顺位最前存活敌);负向 = 主角直伤(复用反噬通道)。
if String(row.get("polarity", "positive")) == "negative":
_enemy_system.apply_damage(_enemy_system.hero_unit(), power)
_emit_row("damage", source_class, cell, ri, "%s → 主角" % display, power, {"target": "hero"}, highlight)
var backfire: int = maxi(int(db.settlement.damage_floor), power) # 伤害下限全来源(2026-09-20)
_enemy_system.apply_damage(_enemy_system.hero_unit(), backfire)
_emit_row("damage", source_class, cell, ri, "%s → 主角" % display, backfire, {"target": "hero"}, highlight)
return "redeemed"
var target = _enemy_system.default_target()
if target == null:
@@ -401,14 +405,15 @@ func _apply_flat_row(row: Dictionary, source_class: String, cell: Vector2i, ri:
return "zero"
var atk_type: String = String(row.get("attack_type", ctype))
var delta: int = _counter_delta(atk_type, String(target.get("armor_type", "light")))
var dealt: int = DamageMath.counter_component(power, delta)
var dealt: int = DamageMath.counter_component(power, delta, int(db.settlement.damage_floor))
_enemy_system.apply_damage(target, dealt)
_emit_row("damage", source_class, cell, ri, "%s → %s" % [display, String(target.display_name)], dealt, {"target": String(target.display_name), "attack_type": atk_type, "effectiveness": delta}, highlight)
return "redeemed"
if ctype == "heal":
if String(row.get("polarity", "positive")) == "negative":
_enemy_system.apply_damage(_enemy_system.hero_unit(), power)
_emit_row("damage", source_class, cell, ri, "%s → 主角" % display, power, {"target": "hero"}, highlight)
var backlash: int = maxi(int(db.settlement.damage_floor), power) # 伤害下限全来源(2026-09-20)
_enemy_system.apply_damage(_enemy_system.hero_unit(), backlash)
_emit_row("damage", source_class, cell, ri, "%s → 主角" % display, backlash, {"target": "hero"}, highlight)
return "redeemed"
if int(_enemy_system.hero_unit().get("hp", 0)) <= 0:
# 主角已倒下(同轮更早的负向直伤致死)——治疗不再合法作用,按"奖励未生效"留痕
@@ -532,7 +537,8 @@ func _combat_segment(ri: int) -> void:
# 原"基础 + 穿刺/魔法充能三分量"模型随充能系统整体退役——克制策略归冒险前配置(营地换型 / 未来冒险前参数面),
# 局内不再有挖掘攒弹药的第二伤害供给。attack_up 增益经 hero_offense 池乘攻击合计(见池乘区注记)。
# 护甲等级掷点批(2026-09-16):基础分量吃掷点判定(含暴击);棋盘来源伤害不掷不折;
# 任何攻击落地伤害 ≥ damage_floor(全局 max(1,·))——"被减伤归零 v=0"零值伤害行分支随批退役。
# 任何伤害落地 ≥ damage_floor(全局 max(1,·)——2026-09-20 用户拍板扩全来源:攻击双方/棋盘直伤/地形变异/反噬,
# 取值域 [1,+∞) 无上限)——"被减伤归零 v=0"零值伤害行分支随批退役。
var hero_alive: bool = int(_enemy_system.hero_unit().get("hp", 0)) > 0
var hero_target = _enemy_system.default_target()
if hero_alive and hero_target != null:
@@ -15,12 +15,15 @@ static func pool_damage(d_raw: int, p_hero: int, p_enemy: int) -> int:
# 充能分量:充能点数(调用方已按奖励定义换算)+ 克制修正点数(加法核——2026-09-18 换装,原 percent 乘区退役);
# 空桶(点数 ≤ 0)不吃修正恒 0——零充能分量无伤害可修正,不产生负贡献(等价旧量纲 0×任何 percent = 0)
# 空桶(点数 ≤ 0)不吃修正恒 0——零充能分量无伤害可修正,不产生负贡献(等价旧量纲 0×任何 percent = 0;
# 空桶不受 landed_floor 影响——没有伤害落地就没有下限可言)
# 类型化点数分量:点数 + 克制修正(加法核)——2026-09-19 充能退役后唯一消费方 = 奖励直伤道具(棋盘火力)
static func counter_component(points: int, counter_delta: int) -> int:
# landed_floor:落地伤害全局下限(读 settlement.damage_floor,调用方传入)——2026-09-20 用户拍板
# 「任何伤害落地取值域 [1,+∞)」:克制修正后触底也保底 1,调参降档不可能打出 0/负伤害
static func counter_component(points: int, counter_delta: int, landed_floor: int = 0) -> int:
if points <= 0:
return 0
return points + counter_delta
return maxi(landed_floor, points + counter_delta)
# ---- 护甲等级掷点批(2026-09-16 用户拍板——判定式与档位参数全在 settlement.tres,本类只做算术)----
@@ -22,7 +22,8 @@ extends Resource
@export var armor_effectiveness: Dictionary = {} # 克制修正矩阵(加法点数 delta):{攻击类型 {normal|pierce|magic}: {护甲 {light|ward|heavy}: 点数 [-3,+3]}}
# ---- 护甲等级掷点批(2026-09-16 用户拍板,数字即规范——registry settlement_table.ac_roll/ac_discount_tiers 同批定稿)----
# 判定式:掷 1d20,掷点 ≥ 防守方 AC + 攻击加成 → 全额;低掷按缺口(AC−掷点)查档打折,无 miss、无零收益档;
# nat 最大面(crit_roll)= 暴击:该次基础攻击分量 ×2 且恒全额;任何攻击落地伤害全局下限 damage_floor(≥1)。
# nat 最大面(crit_roll)= 暴击:该次基础攻击分量 ×2 且恒全额;任何伤害落地全局下限 damage_floor(≥1)——
# 2026-09-20 用户拍板扩全来源(攻击双方/棋盘直伤/地形变异/反噬,取值域 [1,+∞) 无上限)。
# 掷点只吃基础攻击分量(主角普通分量 / 敌方攻击拍与重击拍基线),穿刺/魔法充能与棋盘来源伤害不掷不折。
# 微调旋钮 = 档位边界(roll_tier_bounds)与档位值(roll_tier_discounts),不动 AC 值(armor_class 归单位表)。
@export var roll_faces: int = 20 # [2,1000] knob——掷点骰面(d20)
@@ -31,7 +32,7 @@ extends Resource
@export var roll_tier_discounts: Array = [] # 档位值 percent 降序(与 bounds 同长;[75,50,25] = ×0.75/×0.5/×0.25)
@export var crit_roll: int = 20 # [1,roll_faces] knob——nat 最大面 = 暴击触发掷点(恒全额)
@export var crit_multiplier: int = 200 # [100,999] percent knob——暴击倍率(200 = ×2,只乘该次基础攻击分量)
@export var damage_floor: int = 1 # [1,999] knob——任何攻击落地伤害全局下限("被减伤归零 v=0"行语义随批退役)
@export var damage_floor: int = 1 # [1,999] knob——任何伤害落地全局下限·全来源("被减伤归零 v=0"行语义随批退役)
@export var armor_roll_stream: String = "armor_roll" # 掷点封闭流名(rng_service STREAM_NAMES 成员;逐次掷逐次取号,可逐位回放)
const ARMOR_KEYS := ["light", "ward", "heavy"]
@@ -356,6 +356,12 @@ func _smoke_armor_roll() -> void:
check(all_positive, "全战斗流零 v=0 伤害行(伤害下限全局生效)")
check(attack_ok, "hero_attack/enemy_action 伤害行掷点读数+档位随行留档(P2 归因)")
check(board_ok, "棋盘来源伤害行零掷点字段(伏击附加/爆裂/直伤/清障炸药必中全额)")
# ⑥ 全来源伤害下限(2026-09-20 用户拍板:任何伤害落地取值域 [1,+∞) 无上限——棋盘克制直伤此前
# 靠表值 2+(−1)=1 贴底无结构保证,反噬/地形变异/爆裂同为表值直落,本批全部收进 damage_floor 闸内)
var df: int = int(_db.settlement.damage_floor)
check(DamageMath.counter_component(2, -1, df) == 1, "棋盘克制直伤贴底 → 下限 1(点数 2 vs 不利修正 −1)")
check(DamageMath.counter_component(1, -3, df) == 1, "克制修正触底穿透 → 下限 1(点数 1 vs 修正 −3)")
check(DamageMath.counter_component(0, 5, df) == 0, "空桶不吃修正恒 0(零点数分量无伤害可落地——边界不变不受下限影响)")
# 定位种子下首场战斗第一行主角攻击伤害行(停手轮——零授予零充能,普通分量 = 攻击基线裸值)