fix(slice): 战斗数值结构修复批——乘区接线四件套+削弱续时(2026-09-18 盘问会话用户拍板,直接执行)——①主角侧乘区接线:主角攻击 Σ三分量后 ×(100+hero_offense)/100×(100+enemy_defense)/100 两池串乘(pool_damage 首参 0→_clamped_pool),sapped 乏力死效果随接线变活(原 hero_offense 池无消费方);②attack_up 普通分量直乘旁路退役(base_val=表值攻击裸值,增伤覆盖面=普通+穿刺+魔法合计——挪不是加防算两遍);③失衡换装 AC 轴:off_guard unit percent→ac·magnitude +25→−2(provisional),settlement effect_pool_map 删 off_guard 键——hero_defense 池清空结构保留,走 _defender_ac 既有读法零代码;④硬化符号纠正:fortify +100→−50(负值入敌方防御池=主角攻击折半;旧正值实际×2翻倍与"硬化=减伤"语义相反,用户确认系符号 bug);⑤削弱叠层改续时:weaken stack→refresh·duration 1→2(同名重复授予只刷新剩余结算数、层数恒 1——结构性消饱和,×2 叠满池触底 ×0 免伤轮根治;refresh 实现 empower/fortify 同款零代码);⑥数值终值全不在本批(provisional 归状态层重设计批);同步面:冒烟 296→300 项 0 失败(fortify 断言改形+旧「削弱×2 池触底」采样通道退役〔触发路径随续时结构性消失〕+新增 _smoke_pool_wiring 四断言〔同种子恒同掷点法:充电基线 103→增伤 154/乏力 77/硬化 51 精确关系〕+_weaken_refresh_probe 两断言〔层数 1/剩余次数重置 2/单层池读数=行值〕+免伤墙护栏注释同步)+ui/click/layout/tune 四探针 PASS;GDD 战斗结算 v0.14(直乘句×2+池乘区公式行+现状注记销项)/增益状态 v0.9(三行语义+例子表削弱叠两层→暴走叠两层+联动纪律句失效改写)/地图战斗资源 v0.11(极性矩阵失衡行失同步修正)/06-待办(削弱档位条目补续时定案注记)+07-体验验证清单新文档(Boss 战节奏/削弱承伤/增伤全段/乏力感知/失衡归因五观测项+验收重测批义务)+02 清单索引行;registry status_effect 三行+value_status+effect_pool_map 批注+修订记录+顺手收口 fields 量纲枚举漏登 ac(2026-09-16 换装批遗留);active.md 快照节/台账 69;残留措辞(承伤加深/直乘)grep 清零;并行会话 design/ 三未跟踪文件未碰

This commit is contained in:
DKChan committed 2026-09-18 18:45:03 +08:00
1 parent b7d1b56ae4
commit b51af088e1
12 files changed
+210 -83

No files matched your search

@@ -441,8 +441,9 @@ func _roll_round_actions() -> void:
func _combat_segment(ri: int) -> void:
var L := _status_effects.pool_layers("ambush") # 授予段后的当轮值;池共享不消耗
# ⓪ 主角先手攻击(基础攻击恒存在——2026-09-11 用户裁决;先于敌方行动;授予段负向直伤致死 → 致电短路不先手)。
# 三段伤害构成(2026-09-12 用户拍板;2026-09-15 命名统一批 + 攻击类型/奖励抽象分离批):基础分量(atk×(1+attack_up),吃掷点,
# 类型 = 主角 attack_type 表值——2026-09-18 营地扩展前置批:量与类型抽象分离,原硬编码 normal 退役)
# 三段伤害构成(2026-09-12 用户拍板;2026-09-15 命名统一批 + 攻击类型/奖励抽象分离批):基础分量(atk,吃掷点,
# 类型 = 主角 attack_type 表值——2026-09-18 营地扩展前置批:量与类型抽象分离,原硬编码 normal 退役;
# attack_up 增益不再直乘本分量——2026-09-18 结构修复批改经 hero_offense 池乘三段合计,见池乘区注记)
# + 穿刺/魔法(由充能供给——充能按攻击类型分桶,谁能充/充哪个桶归奖励定义 charge_grants)。攻击类型只管查甲效矩阵哪一行,与来源无关。
# 护甲等级掷点批(2026-09-16):普通分量吃掷点判定(含暴击);穿刺/魔法充能分量与棋盘来源伤害不掷不折;
# 任何攻击落地伤害 ≥ damage_floor(全局 max(1,·))——"被减伤归零 v=0"零值伤害行分支随批退役。
@@ -451,13 +452,12 @@ func _combat_segment(ri: int) -> void:
if hero_alive and hero_target != null:
var armor: String = String(hero_target.get("armor_type", "light"))
var base_atk_type: String = String(_enemy_system.hero_unit().get("attack_type", "normal")) # 基础分量类型 = 主角表值(营地扩展前置批 2026-09-18)
var up := clampi(_pool_effective("attack_up"), int(db.settlement.pool_clamp_lo), int(db.settlement.pool_clamp_hi))
var points := _charge_points() # {攻击类型: 充能点数}——奖励定义投递的结果(本段不认识"开格/标雷")
var roll_read: Dictionary = _armor_roll() # 基础分量掷点(每吃掷点分量逐次掷——掷点只吃基础分量口径不变)
var threshold: int = _defender_ac(hero_target) + int(db.settlement.attack_bonus)
var crit: bool = bool(roll_read.crit)
var discount: int = _roll_discount(int(roll_read.roll), threshold)
var base_val: float = float(_hero_atk) * (100.0 + float(up)) / 100.0
var base_val: float = float(_hero_atk) # 基础分量 = 表值攻击裸值(增益直乘旁路已退役——乘区统一走池)
var comp: Dictionary = {}
var d_hero := 0
for atk_type in SettlementTable.ATTACK_TYPE_KEYS:
@@ -476,10 +476,11 @@ func _combat_segment(ri: int) -> void:
payload["tier"] = discount
if crit:
payload["crit"] = true
# 增益池乘区·敌方防御池(敌人技能池批——硬化技载体):Σ三分量后 ×(100+clamp(enemy_defense))/100。
# 四池对称:敌打主角 = hero_defense×enemy_output(_resolve_damage);主角打敌 = hero_offense×enemy_defense
# (hero_offense 现行走普通分量 attack_up 直乘的切片现状——登记遗留,本批不动)。
d_hero = DamageMath.pool_damage(d_hero, 0, _clamped_pool("enemy_defense"))
# 增益池乘区·主角侧 + 敌方防御池(2026-09-18 结构修复批——hero_offense 乘区接线落地,
# attack_up 普通分量直乘旁路退役:增伤/乏力改乘 Σ三分量合计,覆盖面 = 普通+穿刺+魔法;
# 与既有 enemy_defense 池乘区同段串乘)。四池对称:敌打主角 = hero_defense×enemy_output
# (_resolve_damage);主角打敌 = hero_offense×enemy_defense。
d_hero = DamageMath.pool_damage(d_hero, _clamped_pool("hero_offense"), _clamped_pool("enemy_defense"))
var d_landed: int = maxi(int(db.settlement.damage_floor), d_hero)
_enemy_system.apply_damage(hero_target, d_landed)
payload["killed"] = not hero_target.alive # 击杀标记在扣血后取(原语义不变)
@@ -21,7 +21,6 @@ effect_pool_map = {
"weaken": "enemy_output",
"ambush": "flat_attach",
"enrage": "enemy_output",
"off_guard": "hero_defense",
"sapped": "hero_offense",
"empower": "enemy_output",
"fortify": "enemy_defense"
@@ -102,3 +101,11 @@ armor_roll_stream = "armor_roll"
; 穿刺 cap 8→3(per_unit 1 不变,EV ≈ ÷2.7);魔法 2/雷→1/雷(per_unit ÷3 进 1,EV ≈ ÷2)。
; 充能期望压缩比 ≈1/2~1/3 之间,粒度限制下的近似,登记为已知偏差。
; roll_faces/折扣档/暴击/damage_floor/克制矩阵/pool_clamp 全部不动(相对轴)。clamp 域 [-100,+100] 共用。
;
; 战斗数值结构修复批(2026-09-18 盘问会话用户拍板):
; effect_pool_map 删 off_guard 键——off_guard(失衡)换装 AC 轴(unit percent→ac、+25→−2,provisional
; 归 状态层重设计批;消费走 _defender_ac 既有 ac 型读法零代码),退离 hero_defense 池(池清空、结构保留;
; 上方 2026-09-15 attack/defense/weaken 三型负向字典行的极性路由注记不变——defense 型兑付行仍是 off_guard,
; 变的只是池归属)。hero_offense 乘区接线同步落地(battle_orchestrator 主角攻击段):Σ三分量后
; ×(100+clamp(hero_offense))/100×(100+clamp(enemy_defense))/100——attack_up 普通分量直乘旁路退役,
; 增伤/乏力覆盖面 = 普通+穿刺+魔法 合计;上方"hero_offense 现行走直乘——登记遗留"注记随之销项。
@@ -37,8 +37,8 @@ rows = [{
"target_side": "enemy",
"magnitude": -65,
"unit": "percent",
"duration_settlements": 1,
"stack_policy": "stack",
"duration_settlements": 2,
"stack_policy": "refresh",
"max_stack": 16
}, {
"effect_id": "enrage",
@@ -53,8 +53,8 @@ rows = [{
"effect_id": "off_guard",
"display_name": "失衡",
"target_side": "hero",
"magnitude": 25,
"unit": "percent",
"magnitude": -2,
"unit": "ac",
"duration_settlements": 1,
"stack_policy": "stack",
"max_stack": 16
@@ -80,7 +80,7 @@ rows = [{
"effect_id": "fortify",
"display_name": "硬化",
"target_side": "enemy",
"magnitude": 100,
"magnitude": -50,
"unit": "percent",
"duration_settlements": 2,
"stack_policy": "refresh",
@@ -111,3 +111,12 @@ rows = [{
; 数值压缩批(2026-09-17 用户拍板暂定):ambush magnitude 3→1(伏击附加 = 层数×幅度,3 整除 ÷3)。
; percent 全族(attack_up 50 / weaken −65 / enrage 25 / off_guard 25 / sapped −25 / empower·fortify 100)
; 与 duration/max_stack 全部不动(相对轴与结构轴不压缩)。
;
; 战斗数值结构修复批(2026-09-18 盘问会话用户拍板——本批三行全部临时值,provisional 归 状态层重设计批):
; off_guard(失衡)换装 AC 轴:unit percent→ac、magnitude +25→−2(失衡 = 主角 AC −2,更易被命中;
; 消费走 _defender_ac 既有 ac 型读法零代码;settlement effect_pool_map 同批删 off_guard 键——
; hero_defense 池清空、池结构保留);fortify(硬化)符号纠正:+100→−50(乘区 = enemy_defense 池 −50
; → 主角攻击 ×0.5 折半——旧 +100 实际 ×2 翻倍,与"硬化=减伤"语义相反,用户确认系符号 bug);
; weaken(削弱)叠层改续时:stack_policy stack→refresh、duration_settlements 1→2(同名重复授予
; 只刷新剩余结算数、层数恒 1——结构性消饱和,×2 叠满池触底 ×0 免伤轮从根杜绝;refresh 实现与
; empower/fortify 同款零代码)。duration 终值、off_guard N 值、当量锚复算全部归 状态层重设计批。
@@ -64,6 +64,7 @@ func _run_core_smoke(db, rng) -> void:
_rng = rng
_smoke_ac_swap_guard()
_smoke_armor_roll()
_smoke_pool_wiring()
_smoke_board_generator()
_smoke_variation_framework()
_smoke_full_battle(1, 0, "L1 · 地精")
@@ -204,8 +205,8 @@ func _smoke_level() -> void:
# ---- 护甲等级掷点批·换装护栏(2026-09-16 用户拍板:百分比减伤行整体退役换装 AC,无双轨)----
# ① 换装在位:defense_up 状态行/池映射/奖励行三处全无残留,ac_up 三处就位(unit=ac ×2 层);
# ② 免伤墙归零护栏泛化保留(BUG-0003 事故类永久免疫):hero_defense 池任何 percent 行
# magnitude × max_stack > pool_clamp_lo——单效果叠满不可触池底(现行池内仅 off_guard 正向行,
# 墙结构面已随 AC 换装消失,本断言防未来负向行回归时静默造墙);
# magnitude × max_stack > pool_clamp_lo——单效果叠满不可触池底(off_guard 已于 2026-09-18
# 结构修复批退离 hero_defense 池换装 AC 轴,池内现无 percent 行——本断言防未来负向行回归时静默造墙);
# ③ 单位表 armor_class 字段:主角 10(暂定)/ 敌方行 0(暂定——正式值归怪物设定批);
# ④ 掷点参数域:档位边界升序/档位值降序无零档/骰面与暴击参数在域(加载校验双保险)。
@@ -293,15 +294,13 @@ func _smoke_armor_roll() -> void:
"nat20 → 行留档 roll=20 + crit 标记(主角先手普通分量)")
check(int(crit_rows.get("normal", -1)) == 2 * int(_db.hero.atk) and int(crit_rows.get("value", -1)) == 2 * int(_db.hero.atk),
"nat20 → 普通分量 ×2(%d = 2×%d,暴击倍率只乘该分量,穿刺/魔法不吃)" % [int(crit_rows.get("normal", -1)), int(_db.hero.atk)])
# ③ 伤害下限(行为面):削弱 ×2 = 池触底 ×0 → 落地伤害 = damage_floor(零值行已退役)
var floor_rows := _weaken_zero_round_rows()
check(not floor_rows.is_empty() and int(floor_rows[0].get("value", -1)) == int(_db.settlement.damage_floor),
"池触底 ×0 → 落地伤害 = %d(全局 max(1,·)——×%d 拍采样)" % [int(_db.settlement.damage_floor), floor_rows.size()])
var no_zero_label := true
for r in floor_rows:
if String(r.get("display_name", "")).contains("归零") or int(r.get("value", 0)) <= 0:
no_zero_label = false
check(no_zero_label, "零值伤害行语义退役:无\"被减伤归零\"行、无 v=0 伤害行(被压制/保底下限可见)")
# ③ 削弱续时(行为面):授予→衰减一轮→再授予 → 层数保持 1、剩余结算数重置(2026-09-18 结构
# 修复批叠层改续时——削弱 ×2 叠满池触底 ×0 的免伤轮结构性根治,旧 ×2 叠层采样通道随批退役)
var weaken_probe := _weaken_refresh_probe()
check(int(weaken_probe.get("layers", -1)) == 1 and int(weaken_probe.get("remaining", -1)) == 2,
"削弱重复授予 → 层数保持 1、剩余结算数重置 2(续时语义——结构性消饱和)")
check(int(weaken_probe.get("pool", -999)) == int(_db.status_effect_row("weaken").magnitude),
"削弱单层池读数 = 行值(enemy_output 池单层贡献——×2 叠满触底 ×0 不可达,免伤轮根治)")
# ④ AC12 折扣档分布(行为面):ac_up ×2 逐轮授给(时效 1)→ Boss 行攻击拍逐拍判定
var tiers := _ac12_tier_samples()
var tier_distinct := {}
@@ -349,29 +348,23 @@ func _first_hero_attack_rows(seed_val: int) -> Dictionary:
return {}
# 削弱 ×2(池 enemy_output 触底 ×0)下的敌方攻击伤害行采样(D2 熊地精)。
# 敌人技能池批:开轮摇号下首拍可能是施放行(零伤害)——逐轮重授削弱并停手,直到采到伤害行为止。
func _weaken_zero_round_rows() -> Array:
# 削弱续时探测(2026-09-18 结构修复批——旧 _weaken_zero_round_rows ×2 叠层采样随批退役):
# 授予 → 衰减一轮 → 再授予,读 weaken 实例投影 {layers, remaining, pool}。
# 直授通道(来源格真实坐标惯例——沿前例);depth 2 板(D2 熊地精)仅作宿主,行为与敌种无关。
func _weaken_refresh_probe() -> Dictionary:
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260916)
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": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp)}):
return rows
# 测试通道:直授削弱 ×2(来源格取真实坐标——行来源断言要求;时效 1 当轮结算内有效)
var out: Array = []
var guard := 8
while out.is_empty() and guard > 0 and settlement.battle_result == "":
guard -= 1
rows.clear()
for i in range(2):
settlement._status_effects.apply_effect("weaken", {"source_class": "mine_hit", "origin_cell": Vector2i(0, 0), "round_index": settlement.round_index})
settlement.minesweeper.settle_batch([{"action": "stop"}])
for row in rows:
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action":
out.append(row)
return out
return {}
var src: Dictionary = {"source_class": "mine_hit", "origin_cell": Vector2i(0, 0), "round_index": settlement.round_index}
settlement._status_effects.apply_effect("weaken", src)
settlement._status_effects.decay_round() # remaining 2 → 1(先兑付后衰减——只借衰减器,不结算)
settlement._status_effects.apply_effect("weaken", src) # 续时:remaining ← 2,层数不动
for eff in settlement._status_effects.query_active("enemy"):
if String(eff.effect_id) == "weaken":
return {"layers": int(eff.layers), "remaining": int(eff.remaining_settlements), "pool": int(settlement._clamped_pool("enemy_output"))}
return {}
# AC12 折扣档采样:每停手轮前直授 ac_up ×2(时效 1 逐轮衰减)——敌拍逐拍判定留档。
@@ -403,6 +396,48 @@ func _ac12_tier_samples() -> Array:
return []
# ---- 主角乘区接线 + 硬化折半方向(2026-09-18 结构修复批护栏)----
# 同种子停手轮恒同掷点(效果只改乘区、不动掷点流)→ 落地伤害精确关系:
# 增伤 = 基线 ×1.5、乏力 = ×0.75(hero_offense 池乘 Σ三分量合计——普通+穿刺同乘,直乘旁路退役);
# 硬化 = 基线 ×0.5 且 < 基线(enemy_defense 池负值折半——+100 翻倍符号 bug 随批纠正)。
func _smoke_pool_wiring() -> void:
print("--- 主角乘区接线 + 硬化折半方向 ---")
var base_row := _hero_attack_row_with({"pierce_charge": 4}, 100, 20260918)
var up_row := _hero_attack_row_with({"pierce_charge": 4, "effects": ["attack_up"]}, 100, 20260918)
var sapped_row := _hero_attack_row_with({"pierce_charge": 4, "effects": ["sapped"]}, 100, 20260918)
var fortify_row := _hero_attack_row_with({"pierce_charge": 4, "effects": ["fortify"]}, 100, 20260918)
var base_v := int(base_row.get("value", -1))
check(int(base_row.get("pierce", -1)) == 3 and base_v > 0, "充电注入基线在位(穿刺分量 = cap 3、基线 %d——合计含充电分量前提)" % base_v)
check(int(up_row.get("value", -1)) == int(floor(base_v * 1.5)),
"增伤经乘区加成合计(%d → %d = ×1.5——attack_up 直乘旁路退役,覆盖面=普通+穿刺+魔法)" % [base_v, int(up_row.get("value", -1))])
check(int(sapped_row.get("value", -1)) == int(floor(base_v * 0.75)),
"乏力经乘区削减合计(%d → %d = ×0.75——充电分量同削)" % [base_v, int(sapped_row.get("value", -1))])
check(int(fortify_row.get("value", -1)) == int(floor(base_v * 0.5)) and int(fortify_row.get("value", -1)) < base_v,
"硬化折半方向(%d → %d = ×0.5 且 < 基线——敌方防御池负值减伤,翻倍 bug 退役)" % [base_v, int(fortify_row.get("value", -1))])
# 构造停手轮首行主角攻击行:opts = {pierce_charge: int, effects: Array};hero_atk 覆盖 100 拉开量级
# (floor 关系可精确断言);效果直授沿 _weaken_refresh_probe 前例;充电计数直注 _charge_counts
# (重置只在轮末出口分发/战斗终——停手轮前注入可存活到战斗段)。
func _hero_attack_row_with(opts: Dictionary, hero_atk_val: int, seed_val: int) -> Dictionary:
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(seed_val)
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": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp), "hero_atk": hero_atk_val}):
return {}
for eff in opts.get("effects", []):
settlement._status_effects.apply_effect(String(eff), {"source_class": "mine_hit", "origin_cell": Vector2i(0, 0), "round_index": settlement.round_index})
if opts.has("pierce_charge"):
settlement._charge_counts["safe_cell"] = int(opts.pierce_charge)
settlement.minesweeper.settle_batch([{"action": "stop"}])
for row in rows:
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "hero_attack":
return row
return {}
func _smoke_board_generator() -> void:
print("--- 棋盘生成 ---")
var board_generator = load("res://core/board_generator.gd").new()
@@ -1288,7 +1323,7 @@ func _smoke_negative_dictionary() -> void:
if by_type.size() == 5:
break
check(by_type.size() == 5, "扫描 6 板内出现五型齐备板(%s)" % str(by_type.keys()))
# attack/defense/weaken:percent 负向行走 apply_effect
# attack/defense/weaken:负向字典行走 apply_effect(off_guard 2026-09-18 换装 ac 轴——兑付形态不变)
var percent_map := {"attack": "enrage", "defense": "off_guard", "weaken": "sapped"}
for ctype in percent_map.keys():
if not by_type.has(ctype):
@@ -1611,8 +1646,8 @@ func _smoke_skill_pool_data() -> void:
var fortify: Dictionary = _db.status_effect_row("fortify")
check(not empower.is_empty() and String(empower.target_side) == "enemy" and int(empower.magnitude) == 100,
"蓄力状态行 empower(enemy +100 percent——敌方输出 ×2 走既有 enemy_output 池)")
check(not fortify.is_empty() and String(fortify.target_side) == "enemy" and int(fortify.magnitude) == 100,
"硬化状态行 fortify(enemy +100 percent——敌方防御池新词汇载体)")
check(not fortify.is_empty() and String(fortify.target_side) == "enemy" and int(fortify.magnitude) == -50,
"硬化状态行 fortify(enemy −50 percent——敌方防御池负值 = 主角攻击折半;+100 符号 bug 2026-09-18 纠正,provisional 归 状态层重设计批)")
check(String(_db.settlement.effect_pool_map.get("empower", "")) == "enemy_output" \
and String(_db.settlement.effect_pool_map.get("fortify", "")) == "enemy_defense",
"effect_pool_map 收编 empower/fortify(敌方防御池 enemy_defense 入表)")