merge: 伏击上限删除批+格面资源八型批落籍——分支 ambush-cap-review-20260919/20 两笔(656f000 上限删除/9类行协议+7876642 八型批:伏击退役换装警觉、action 第8型行动点±1、护甲型±1、六 GDD+registry+06+ux 同步、工具口径注记);冒烟 330 项 0 失败;台账 101;警觉/行动点 provisional 归曲线调参批(06 已登记),读数位移待重投,真人批建议终版规则上执行

# Conflicts:
#	design/gdd/06-待办清单.md
#	design/gdd/systems/战斗循环/战斗结算.md
#	design/registry/entities.yaml
#	design/ux/battle.md
#	prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd
This commit is contained in:
DKChan committed 2026-09-20 04:05:17 +08:00
commit 3468066fcb
33 files changed
+275 -267

No files matched your search

@@ -30,6 +30,7 @@ var last_round_end: Dictionary:
get: return _last_round_end
var _settlement_consumed := false # AC-14 门:notify_settlement_done 消费后才受理 round_start
var _next_round_mode := "" # 调试观测量(同板续轮 / 换新图)
var _pending_ap_delta := 0 # 下轮行动点修正排队(格面资源八型批 2026-09-20——action 型 ±1 排队,轮开始兑现后清零)
func _init(db_ref) -> void:
@@ -118,6 +119,16 @@ func notify_settlement_done(next_round_mode: String) -> bool:
return true
# 下轮行动点修正排队(格面资源八型批 2026-09-20):action 型正负行结算期调用,同轮多次授予累加;
# 相位无关(授予发生在结算段,此时本类可能处于 ACTIVE/WAITING 任一相位)
func queue_ap_delta(delta: int) -> void:
_pending_ap_delta += delta
func pending_ap_delta() -> int:
return _pending_ap_delta
func notify_round_start() -> bool:
if phase == PHASE_IDLE:
pass # 战斗首轮唯一出口
@@ -126,7 +137,8 @@ func notify_round_start() -> bool:
else:
_reject("phase")
return false
_ap = db.action_economy.ap_per_round # 每轮硬重置(RAP 不动——唯一跨轮携带量)
_ap = maxi(0, db.action_economy.ap_per_round + _pending_ap_delta) # 每轮基线 + 排队修正(下限 0)
_pending_ap_delta = 0
_phase = PHASE_ACTIVE
_settlement_consumed = false
return true
@@ -20,9 +20,9 @@ signal round_started(round_index: int)
signal board_changed()
signal settlement_snapshot(snap: Dictionary) # 每结算末尾的全量状态快照(playtest_logger 消费——JSONL 数值分析)
# 明细流水行型协议(战斗结算 Core Rule;10 类——2026-09-15 格面资源批由 15 类收拢,
# 清单改动须同步 battle_log_view 的 _format_row 与本文档头注):
# 效果行 effect · 伤害行 damage · 治疗行 heal · 溢出未生效行 overflow ·
# 明细流水行型协议(战斗结算 Core Rule;9 类——2026-09-20 伏击单轮上限退役,溢出未生效行 overflow 随之下线;
# 2026-09-15 格面资源批由 15 类收拢,清单改动须同步 battle_log_view 的 _format_row 与本文档头注):
# 效果行 effect · 伤害行 damage · 治疗行 heal ·
# 奖励未生效行 reward_ineffective · 标旗未中行 flag_miss · 施放行 cast(敌人技能池批由蓄力行 charge 改型)· 衰减行 decay ·
# 判定行 verdict · 高光行 highlight
# 行来源断言豁免集合(行型层)——判定段 / 衰减段本就无棋盘来源,勿一刀切误炸
@@ -58,8 +58,6 @@ var _flag_consumed := {} # 兑付资格以雷格实例为键
var _flag_miss_seen := {} # 标旗未中行去重(每安全格实例一次)
var _perfect_hit_true := 0 # 板级完美排雷计数(install 重置)
var _perfect_hit_false := 0
var _enemy_cap := 0 # EnemyTable 行值快照(伏击 cap)
var _m_amb := 0 # StatusEffectTable.ambush.magnitude 快照
var _hero_atk := 0 # 主角攻击基值快照(encounter.hero_atk;E4 收口——表值运行期不可变)
@@ -113,8 +111,6 @@ func begin_battle(enc: Dictionary) -> bool:
_fatal("敌人系统 spawn 被拒")
return false
var erow: Dictionary = db.resolve_enemy_rowset(int(enc.level_index), int(enc.node_depth))
_enemy_cap = int(erow.get("advantage_per_round_cap", 0))
_m_amb = int(db.status_effect_row("ambush").get("magnitude", 0))
if not _install_new_board():
return false
_reset_round_facts() # 首板 install 的初始展开事件不入轮 facts(resource 恒 false 的双保险)
@@ -369,7 +365,7 @@ func _apply_percent_row(row: Dictionary, source_class: String, cell: Vector2i, r
# flat 行兑付(负向恒即时,无库存语义):
# blast 正向 = 全体存活敌各 power 点(无敌 → 奖励未生效行);blast 负向 = 敌方伏击 +stacks 层(cap/溢出行协议不变);
# blast 正向 = 全体存活敌各 power 点(无敌 → 奖励未生效行);blast 负向 = 警觉 percent 行(effect_id=alert,走 _apply_percent_row 不入本函数);
# heal 正向 = 主角回复 clamp(满血 → 奖励未生效行);heal 负向 = 主角直伤 power 点(不经增益状态;归零 → 战斗段短路/判定段判负)。
# pierce/magic(伤害道具批 2026-09-18)正向 = 单目标克制直伤(必中全额);负向 = 主角直伤。
# 伤害落地全局下限(2026-09-20 用户拍板「任何伤害取值域 [1,+∞)」):本函数全部伤害兑付点经 damage_floor 保底——
@@ -379,9 +375,12 @@ func _apply_flat_row(row: Dictionary, source_class: String, cell: Vector2i, ri:
var ctype := String(row.cell_type)
var power := int(row.power)
var display := String(row.display_name) + label_suffix
if ctype == "action":
var delta: int = power if String(row.get("polarity", "positive")) == "positive" else -power
action_economy.queue_ap_delta(delta)
_emit_row("effect", source_class, cell, ri, "%s(下轮行动点 %+d)" % [display, delta], delta, {"effect_id": "ap_delta", "pool_value": action_economy.pending_ap_delta()}, highlight)
return "redeemed"
if ctype == "blast":
if String(row.get("polarity", "positive")) == "negative":
return _apply_ambush_grant(row, source_class, cell, ri, highlight, label_suffix)
var alive: Array = _enemy_system.query_alive("enemy")
if alive.is_empty():
_emit_row("reward_ineffective", source_class, cell, ri, "%s 未生效(无存活敌人)" % display, 0, {"batch_key": String(row.reward_id)}, highlight)
@@ -429,21 +428,6 @@ func _apply_flat_row(row: Dictionary, source_class: String, cell: Vector2i, ri:
return "zero"
# blast 负向:敌方伏击 +stacks 层(伏击池 cap 与"溢出未生效"行协议不变——敌人系统 F3 逐层守恒,
# 多源共享同一池值;单结算内先到先占 cap,超出部分逐层记溢出行)
func _apply_ambush_grant(row: Dictionary, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> String:
var requested: int = maxi(1, int(row.get("stacks", 1)))
var remaining_cap: int = maxi(0, _enemy_cap - _status_effects.pool_layers("ambush"))
var plan: Dictionary = _enemy_system.ambush_grant_plan(requested, remaining_cap)
for i in range(int(plan.granted)):
_status_effects.apply_effect("ambush", {"source_class": source_class, "origin_cell": cell, "round_index": ri})
if int(plan.granted) > 0:
_emit_row("effect", source_class, cell, ri, "敌方伏击 +%d 层%s" % [int(plan.granted), label_suffix], int(plan.granted), {"effect_id": "ambush", "pool_value": _status_effects.pool_layers("ambush") * _m_amb}, highlight)
for i in range(int(plan.overflow)):
_emit_row("overflow", source_class, cell, ri, "伏击溢出未生效", 0, {"batch_key": "ambush_overflow"})
return "redeemed" if int(plan.granted) > 0 else "zero"
# 旗判定批:标中 = 该雷格资源的正向兑付(每雷实例一次;高光 = 强制检查点);
# 标空 = "标旗未中"归因行 + 该安全格资源的负向兑付(同键去重——每格实例一次)。
func _judge_flag(evt: Dictionary, ri: int) -> void:
@@ -530,7 +514,6 @@ func _roll_round_actions() -> void:
# ---- 战斗段 ----
func _combat_segment(ri: int) -> void:
var L := _status_effects.pool_layers("ambush") # 授予段后的当轮值;池共享不消耗
# ⓪ 主角先手攻击(基础攻击恒存在——2026-09-11 用户裁决;先于敌方行动;授予段负向直伤致死 → 致电短路不先手)。
# 单分量伤害构成(2026-09-19 充能退役批·用户拍板):主角攻击 = 唯一基础分量(atk,吃掷点含暴击;
# 类型 = 主角 attack_type 表值——2026-09-18 营地扩展前置批:量与类型抽象分离;查甲效矩阵对应行取克制修正)。
@@ -577,8 +560,8 @@ func _combat_segment(ri: int) -> void:
_fatal("敌实例待发动作悬空: %s" % skill_id)
continue
if String(srow.type) == "buff" or String(srow.type) == "debuff":
# 增益/妨害技(新效应词汇批 2026-09-19)= 0 伤害施放行:不兑付伏击(旧蓄力拍"零兑付"语义由施放行
# 继承——本轮喂的伏击逢施放拍不兑现的读怪奖励保留);状态授予即数值兑现,技能冷却与增益持续两计时器分账。
# 增益/妨害技(新效应词汇批 2026-09-19)= 0 伤害施放行:不掷不折;状态授予即数值兑现,技能冷却与增益持续两计时器分账。
# (原"本轮喂的伏击逢施放拍不兑现"读怪奖励随伏击退役一并作废——2026-09-20 八型批)
# 落点由状态行 target_side 路由(buff → 敌方侧 / debuff → 主角侧——容器零改动,proposal-status-vocabulary §1)。
var dur: int = int(srow.get("buff_duration", 1))
_status_effects.apply_effect(String(srow.buff_ref), {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": ri}, dur)
@@ -587,7 +570,7 @@ func _combat_segment(ri: int) -> void:
continue
# 伤害技(含普攻——倍率恒 1):三乘法链 ① A = 攻击基线 × 技能倍率(判定对象 = 技能基础伤害分量)
# → ② d20 vs 主角 AC(nat20 暴击 ≠ 技能重击,两层严格区分)→ ③ +克制修正点数(敌打主角无甲轴恒中性 delta 0——加法核 2026-09-18 换装)
# → 伏击附加(后置加法、必中全额、不吃 AC)→ 增益池乘区 → 伤害下限 max(1,·)
# → 增益池乘区 → 伤害下限 max(1,·)
# → 必中附加分量 rider(新效应词汇批 2026-09-19:后置加法、必中全额、不进乘区不出暴击——
# proposal-status-vocabulary §2;价位锚 rider_mult=0.75 = AC 折扣档最轻档"不掉档的稳定伤害")。
var baseline: int = int(round(float(u.atk) * float(srow.get("damage_mult", 1.0))))
@@ -599,10 +582,10 @@ func _combat_segment(ri: int) -> void:
var crit_e: bool = bool(roll_read_e.crit)
var discount_e: int = _roll_discount(int(roll_read_e.roll), threshold_e)
var rolled_base: int = DamageMath.rolled_component(float(baseline), discount_e, crit_e, int(db.settlement.crit_multiplier), 0) # 敌打主角无甲轴恒中性——加法核中性 delta = 0(原 percent 100 退役)
var d_tick := _resolve_damage(rolled_base + L * _m_amb)
var d_tick := _resolve_damage(rolled_base)
d_tick = maxi(int(db.settlement.damage_floor), d_tick) + rider # rider 后置于伤害下限——独立必中加项,不进乘区
_enemy_system.apply_damage(_enemy_system.hero_unit(), d_tick)
var payload_e: Dictionary = {"target": "hero", "skill_id": skill_id, "baseline": baseline, "attach": L * _m_amb,
var payload_e: Dictionary = {"target": "hero", "skill_id": skill_id, "baseline": baseline,
"roll": int(roll_read_e.roll), "ac": threshold_e, "tier": discount_e}
if crit_e:
payload_e["crit"] = true
@@ -648,7 +631,12 @@ func _defender_ac(unit: Dictionary) -> int:
if not row.is_empty() and String(row.unit) == "ac":
total += int(eff.effective_value)
return total
return int(unit.get("armor_class", 0))
var total: int = int(unit.get("armor_class", 0))
for eff_e in _status_effects.query_active("enemy"):
var erow_ac: Dictionary = db.status_effect_row(eff_e.effect_id)
if not erow_ac.is_empty() and String(erow_ac.unit) == "ac":
total += int(eff_e.effective_value)
return total
# F2 乘区:池内加算 → clamp [-100,+100] → 池间乘算 → floor(单一取整规则)——算术归 DamageMath(O3)
@@ -731,7 +719,6 @@ func _emit_settlement_snapshot(verdict: String) -> void:
"board": board_stats,
"enemies": enemies,
"buffs": buffs,
"ambush_layers": _status_effects.pool_layers("ambush"),
"round_facts": {"path": _facts_path, "safe": safe_n, "mines": mines_hit, "flag_events": _facts_flags.size(), },
})
@@ -796,18 +783,17 @@ func _battle_over(result: String) -> void:
# 协作对象(_enemy_system/_status_effects/_reward_economy)私有化后,跨层读取一律经本快照或下列命名面(diagnostic_snapshot 同款模式,
# 禁再链式穿透 settlement.enemy_system.hero 一类路径)。载荷 schema 成文(code-conventions 2.1):
# round / battle_result / phase / ap / rap / can_refresh / hero{hp,max_hp,atk,alive,armor_class} /
# enemies[{order,display_name,hp,max_hp,enemy_id,armor_type,armor_class,pending_skill,pending_type,pending_name,effects[],ambush_layers,alive}] /
# (敌人技能池批批 2 定稿:beat/ring_size/ring_index 拆除 → pending_* 三字段 + 敌方增益 effects 投影;池与权重不公开)
# ambush_layers / hero_effects[](增益状态 query_active 投影)
# enemies[{order,display_name,hp,max_hp,enemy_id,armor_type,armor_class,pending_skill,pending_type,pending_name,effects[],alive}] /
# (敌人技能池批批 2 定稿:beat/ring_size/ring_index 拆除 → pending_* 三字段 + 敌方增益 effects 投影;池与权重不公开;
# 八型批 2026-09-20:ambush_layers 拆除——伏击退役,警觉并入 effects 投影)hero_effects[](增益状态 query_active 投影)
func presentation_snapshot() -> Dictionary:
var enemies: Array = []
var all: Array = _enemy_system.all_enemies()
var ambush: int = _status_effects.pool_layers("ambush")
# 敌方增益投影(side-wide 状态——蓄力/硬化/削弱/暴走等 percent 行;伏击单独走 ambush_layers)
# 敌方增益投影(side-wide 状态——蓄力/硬化/削弱/暴走 percent 行 + 警觉 ac 行,八型批 2026-09-20)
var enemy_effects: Array = []
for eff in _status_effects.query_active("enemy"):
var erow: Dictionary = db.status_effect_row(eff.effect_id)
if not erow.is_empty() and String(erow.unit) == "percent" and String(eff.effect_id) != "ambush":
if not erow.is_empty() and String(erow.unit) in ["percent", "ac"]:
enemy_effects.append({"effect_id": eff.effect_id, "display_name": String(eff.display_name), "layers": int(eff.layers)})
for i in all.size():
var u: Dictionary = all[i]
@@ -827,7 +813,6 @@ func presentation_snapshot() -> Dictionary:
"pending_type": String(pending_row.get("type", "")) if not pending_row.is_empty() else "",
"pending_name": String(pending_row.get("display_name", "")) if not pending_row.is_empty() else "",
"effects": enemy_effects,
"ambush_layers": ambush,
"alive": alive,
})
var hero := {}
@@ -842,7 +827,6 @@ func presentation_snapshot() -> Dictionary:
"can_refresh": can_refresh(),
"hero": hero,
"enemies": enemies,
"ambush_layers": ambush,
"hero_effects": _status_effects.query_active("hero"),
}
@@ -168,7 +168,7 @@ func polarity_row(cell_type: String, polarity: String) -> Dictionary:
and String(row.get("cell_type", "")) == cell_type \
and String(row.get("polarity", "")) == polarity:
return row.duplicate() # E6:表行值拷贝出口——消费方禁改配置源
_fatal("极性字典缺行(五型×正负齐备为加载前提): %s %s" % [cell_type, polarity])
_fatal("极性字典缺行(八型×正负齐备为加载前提): %s %s" % [cell_type, polarity])
return {}
@@ -59,7 +59,7 @@ func apply_effect(effect_id: String, source: Dictionary, duration_override: int
for inst in _instances:
if inst.effect_id == effect_id:
if inst.layers >= max_stack:
return false # 满层拒绝原子——容器零写入(溢出行归 战斗结算)
return false # 满层拒绝原子——容器零写入(拒收由战斗结算记"奖励未生效"行)
inst.layers += 1 # stack 授予不触碰 R(spec 决议)
return true
_instances.append(_new_instance(effect_id, 1, dur, source, snap_m, snap_unit, snap_display))
@@ -82,7 +82,6 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_override:
"pending_skill": "", # 本拍摇定动作(开轮摇号写入;兑现前死亡 = 作废不 commit)
"roll_seq": 0, # 单敌摇号序(behavior_roll 子流 scope 派生向量——registry 登记)
"basic_streak": 0, # 保底计数:连续执行普攻的拍数(执行上报驱动)
"advantage_per_round_cap": int(row.advantage_per_round_cap),
"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 护甲等级掷点批)
"alive": true,
@@ -238,12 +237,6 @@ func all_enemies() -> Array:
return _enemies.duplicate()
# F3 机制算术纯函数(战斗结算 预截流消费面):cap 判定在授予前;守恒 granted + overflow = mines_r
func ambush_grant_plan(mines_r: int, cap: int) -> Dictionary:
var granted: int = mini(mines_r, cap)
return {"granted": granted, "overflow": mines_r - granted}
func despawn_all() -> bool:
if _despawned or not _spawned:
_diag("重复/无效 despawn_all")
@@ -86,9 +86,6 @@ func validate() -> Array:
var w: int = int(entry.get("weight", 0))
if w <= 0 or w > 9999:
errs.append({"category": "domain", "message": "%s pool 权重越域 (0,9999]: %d" % [eid, w]})
var cap: int = int(row.get("advantage_per_round_cap", -1))
if cap < 0 or cap > 16:
errs.append({"category": "domain", "message": "%s advantage_per_round_cap 越域 [0,16]" % eid})
# armor_type(可省略——缺省 light 向后兼容)
var at: String = String(row.get("armor_type", "light"))
if not ARMOR_TYPES.has(at):
@@ -3,17 +3,17 @@
# Date: 2026-09-10(2026-09-15 格面资源批结构性重写:category/use_timing/库存/供给轴下线,五型 × 极性字典取代)
# 数据配置 RewardTable schema(地图战斗资源 回填值)。
# 行 = Dictionary,键 reward_id(Rule 4 豁免:定义表);跨字段校验族 + 池护栏。
# 五型封闭枚举(attack/defense/heal/blast/weaken)× 极性(positive/negative):
# 八型封闭枚举(attack/defense/heal/blast/weaken/pierce/magic/action)× 极性(positive/negative):
# 正向行 = 摇号池(权重 = 类型频次);负向行 = 纯兑付字典(不消费权重)。
class_name RewardTable
extends Resource
@export var rows: Array = [] # Array[Dictionary]
const CELL_TYPES := ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic"] # 七型封闭(伤害道具批 2026-09-18 五→七)——清单外加载拒绝
const CELL_TYPES := ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic", "action"] # 八型封闭(格面资源八型批 2026-09-20 七→八)——清单外加载拒绝
const POLARITIES := ["positive", "negative"]
const POOL_TAGS := ["cell", "clear_default"] # cell = 格面资源单一池;clear_<biome> 命名空间预留拒绝
const CELL_TYPE_KEYS := ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic"] # 字典完整性校验轴(类型 × 极性 = 十四格)
const CELL_TYPE_KEYS := ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic", "action"] # 字典完整性校验轴(类型 × 极性 = 十六格)
func validate() -> Array:
@@ -61,15 +61,12 @@ func validate() -> Array:
if stacks < 1 or stacks > 16:
errs.append({"category": "domain", "message": "%s stacks 越域 [1,16]: %d" % [rid, stacks]})
# 跨字段:effect_id 非空 = percent 行(引用悬空校验归 ConfigDB 跨表通道);空 = flat 行。
# flat 行 power ≥ 1(直付行:治疗/群伤/直伤)——例外 = blast 负向(伏击加层行,消费 stacks 不消费 power)。
# flat 行 power ≥ 1(直付/直兑行:治疗/群伤/直伤/行动点)——action 型 = 排队点数行(power = ±点数量级)。
var eff: String = row.get("effect_id", "")
var is_layer_row: bool = eff == "" and ctype == "blast" and pol == "negative"
if power < 0 or power > 999:
errs.append({"category": "domain", "message": "%s power 越域 [0,999]: %d" % [rid, power]})
if eff == "" and power < 1 and not is_layer_row:
if eff == "" and power < 1:
errs.append({"category": "cross_field", "message": "%s flat 行 power ≥ 1" % rid})
if is_layer_row and int(row.get("stacks", 1)) < 1:
errs.append({"category": "cross_field", "message": "%s 加层行 stacks ≥ 1" % rid})
# 区间配对(缺省 = 定值行向后兼容):必成对 · percent 行禁止 · power = 中点锚
var has_min: bool = row.has("power_min")
var has_max: bool = row.has("power_max")
@@ -22,7 +22,6 @@ rows = [{
"skill_id": "lunge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -43,7 +42,6 @@ rows = [{
"skill_id": "lunge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -66,7 +64,6 @@ rows = [{
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -90,7 +87,6 @@ rows = [{
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -113,7 +109,6 @@ rows = [{
"skill_id": "harden",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -136,7 +131,6 @@ rows = [{
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -160,7 +154,6 @@ rows = [{
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -183,7 +176,6 @@ rows = [{
"skill_id": "charge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -206,7 +198,6 @@ rows = [{
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -230,7 +221,6 @@ rows = [{
"skill_id": "charge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -254,7 +244,6 @@ rows = [{
"skill_id": "charge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -280,7 +269,6 @@ rows = [{
"skill_id": "web_snare",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "ward",
"armor_class": 0
}]
@@ -87,13 +87,12 @@ rows = [{
"pool_tag": "cell",
"tier": 1
}, {
"reward_id": "ambush_double",
"reward_id": "enemy_alert",
"cell_type": "blast",
"polarity": "negative",
"effect_id": "",
"display_name": "敌方伏击",
"effect_id": "alert",
"display_name": "警觉",
"power": 0,
"stacks": 2,
"weight": 0,
"pool_tag": "cell",
"tier": 1
@@ -161,6 +160,26 @@ rows = [{
"weight": 0,
"pool_tag": "cell",
"tier": 1
}, {
"reward_id": "ap_up",
"cell_type": "action",
"polarity": "positive",
"effect_id": "",
"display_name": "行动点",
"power": 1,
"weight": 6,
"pool_tag": "cell",
"tier": 1
}, {
"reward_id": "ap_drain",
"cell_type": "action",
"polarity": "negative",
"effect_id": "",
"display_name": "迟滞",
"power": 1,
"weight": 0,
"pool_tag": "cell",
"tier": 1
}]
; 格面资源批(2026-09-15 用户拍板——registry reward_table 条目为现行 schema 事实源):
@@ -168,8 +187,11 @@ rows = [{
; 装板期一次摇定;摇号只消费正向行权重(类型频次 = attack 20 / defense 16 / blast 16 / heal 3 / weaken 12,合计 67;
; 头段"heal 15 / weaken 12,合计 79"为历史注记——数值压缩批后 heal 3、合计 67,见本文件尾部 2026-09-17 批注);
; 负向行 weight=0 不参与摇号,纯(类型, 极性)兑付字典——heal 负向 = 主角直伤 4(不经增益状态),
; blast 负向 = ambush +2 层(stacks=2,伏击池 cap 与"溢出未生效"行协议不变)。
; blast 负向 = 警觉(敌方 AC +1,2026-09-20 伏击退役换装)。
; 可选类三行(knife_t1/knife_adv/heal_pack)与注入池 active_inject 随可选类道具整体删除(供给控制器/保底/窗口同步下线)。
; 格面资源八型批(2026-09-20 用户拍板):① blast 负向 ambush_double 退役 → enemy_alert(警觉,effect_id=alert
; 敌方 AC +1 percent 行);② 新增第 8 型 action 两行——ap_up(正向,下轮行动点 +1)/ ap_drain(负向,下轮
; 行动点 −1),排队兑现于下一轮开始(action_economy.queue_ap_delta);③ off_guard 数值 −2→−1 归 status_effects.tres。
; 区间行(伤害量值区间批 2026-09-14):heal_minor [2,6] · clear_blast [25,55]——中点锚 power 恒等期望当量,
; roll 时机 = 身份预分配批内追加取号(既有取号序零扰动);spark_aoe 定值行无区间字段。
; 减伤墙调参批(2026-09-13):defense_up 权重 24→16——攻击成为池内最高行(历史注记)。
@@ -19,7 +19,6 @@ source_label_map = {
effect_pool_map = {
"attack_up": "hero_offense",
"weaken": "enemy_output",
"ambush": "flat_attach",
"enrage": "enemy_output",
"sapped": "hero_offense",
"empower": "enemy_output",
@@ -63,7 +62,7 @@ armor_roll_stream = "armor_roll"
; source_label_map 删 item 键;effect_pool_map 增 sapped→hero_offense(weaken 型负向——乏力)。
; enrage(敌方暴走 +25%,enemy_output 池)/ off_guard(我方失衡 +25%,hero_defense 池)/
; sapped(我方乏力 -25%,hero_offense 池)= attack/defense/weaken 三型负向字典行
; (极性路由兑付:挖雷格 / 标错安全格按该格 cell_type 取负向行;heal 负向 = 主角直伤不经状态、blast 负向 = ambush+2 层)。
; (极性路由兑付:挖雷格 / 标错安全格按该格 cell_type 取负向行;heal 负向 = 主角直伤不经状态、blast 负向 = 警觉(敌方 AC +1,2026-09-20 八型批换装))。
; 减伤墙调参批(2026-09-13):pool_clamp 域 [-100,+100] 结构冻结不动;减伤杠杆走 status_effects.tres(批注在该表)。
; 护甲等级掷点批(2026-09-16 用户拍板,数字即规范——战斗结算 GDD 护甲等级掷点节定稿同批):
; roll_faces=20 / roll_tier_bounds=[3,7] / roll_tier_discounts=[75,50,25] / crit_roll=20 / crit_multiplier=200 /
@@ -23,15 +23,6 @@ rows = [{
"stack_policy": "stack",
"max_stack": 2
}, {
"effect_id": "ambush",
"display_name": "伏击",
"target_side": "enemy",
"magnitude": 1,
"unit": "flat",
"duration_settlements": 1,
"stack_policy": "stack",
"max_stack": 16
}, {
"effect_id": "weaken",
"display_name": "削弱",
"target_side": "enemy",
@@ -53,7 +44,7 @@ rows = [{
"effect_id": "off_guard",
"display_name": "失衡",
"target_side": "hero",
"magnitude": -2,
"magnitude": -1,
"unit": "ac",
"duration_settlements": 1,
"stack_policy": "stack",
@@ -85,6 +76,15 @@ rows = [{
"duration_settlements": 2,
"stack_policy": "refresh",
"max_stack": 1
}, {
"effect_id": "alert",
"display_name": "警觉",
"target_side": "enemy",
"magnitude": 1,
"unit": "ac",
"duration_settlements": 1,
"stack_policy": "stack",
"max_stack": 16
}]
; 减伤墙调参批(2026-09-13,playtest-2026-09-12 报告 Q1 触发——defense_scan 实测墙轮 39%、
@@ -120,3 +120,8 @@ rows = [{
; weaken(削弱)叠层改续时:stack_policy stack→refresh、duration_settlements 1→2(同名重复授予
; 只刷新剩余结算数、层数恒 1——结构性消饱和,×2 叠满池触底 ×0 免伤轮从根杜绝;refresh 实现与
; empower/fortify 同款零代码)。duration 终值、off_guard N 值、当量锚复算全部归 状态层重设计批。
;
; 格面资源八型批(2026-09-20 用户拍板):① off_guard magnitude −2→−1(护甲型奖励/惩罚数值对齐 = ±1);
; ② ambush(伏击)行整体退役——群伤负向不再放大敌方伤害;③ 新增 alert(警觉:敌方护甲等级 +1,
; target_side enemy、unit ac、1 结算、stack 上限 16)——blast 型负向新载体,消费 = _defender_ac 敌方侧
; ac 型读法(本批新接线)。状态表九行→九行(伏击出、警觉进)。数值全部 provisional 归 难度曲线与Boss。
@@ -15,7 +15,7 @@ signal review_requested
const BAND_H_DIG := 24
const BAND_H_SETTLE := 84
const AGG_TYPES := ["reward_ineffective", "overflow"]
const AGG_TYPES := ["reward_ineffective"]
const COL_GOLD := "#f0a828"
const COL_INK := "#33261b"
@@ -194,8 +194,8 @@ func _format_row(row: Dictionary, agg_count := -1) -> String:
return prefix + "[color=%s]%s[/color]" % [body_col, body]
"heal":
return prefix + "[color=#7cc47c]%s +%d%s%s[/color]" % [name_, v, tail, suffix]
"reward_ineffective", "overflow":
# 未生效两类行:⚠ icon + 划线形状冗余(非色相独载)+ 聚合计数
"reward_ineffective":
# 未生效类行:⚠ icon + 划线形状冗余(非色相独载)+ 聚合计数
var n := (" ×%d" % agg_count) if agg_count > 1 else ""
return prefix + "[s][color=#b08060]⚠ %s%s[/color][/s]%s" % [name_, n, suffix]
"flag_miss":
@@ -78,7 +78,7 @@ func _init() -> void:
# data = 战斗结算 presentation_snapshot 投影:{order, display_name, hp, max_hp, enemy_id,
# pending_skill/pending_type/pending_name(摇定动作三字段), effects[](敌方增益), ambush_layers}
# pending_skill/pending_type/pending_name(摇定动作三字段), effects[](敌方增益,含警觉 ac 行)}
func set_data(d: Dictionary) -> void:
_name_label.text = "%s" % String(d.get("display_name", "敌人"))
_hp_bar.max_value = maxi(1, int(d.get("max_hp", 1)))
@@ -87,11 +87,8 @@ func set_data(d: Dictionary) -> void:
_armor = String(d.get("armor_type", "light"))
_armor_class = int(d.get("armor_class", 0))
set_pending(String(d.get("pending_type", "")), String(d.get("pending_skill", "")))
# 状态行:伏击池(flat 共享池,单独通道)+ 敌方增益 percent 状态(蓄力/硬化/削弱/暴走——side-wide)
# 状态行:敌方增益状态(蓄力/硬化/削弱/暴走 percent + 警觉 ac——side-wide,八型批 2026-09-20)
var parts: Array = []
var ambush: int = int(d.get("ambush_layers", 0))
if ambush > 0:
parts.append("伏击池 ×%d(全体共享)" % ambush)
for eff in d.get("effects", []):
parts.append("%s ×%d" % [String(eff.get("display_name", "")), int(eff.get("layers", 1))])
_status_label.text = " · ".join(parts)
@@ -11,7 +11,7 @@ extends HBoxContainer
const MAX_SLOTS := 4
# 极性/语义映射(数据源 = SettlementTable.effect_pool_map 效果语义 + StatusEffectTable display_name):
# 益 = ▲/◆/▼/◇(金/蓝族);害 = ⚡/⚔/✕/☾/⇧/▣(旗朱族;⇧/▣ = 敌方增益威胁纹样)——形状+色相双通道,
# 文字名常驻第三通道。状态表九行全映射(attack_up/ac_up/weaken/ambush/enrage/off_guard/sapped/empower/fortify)
# 文字名常驻第三通道。状态表九行全映射(attack_up/ac_up/weaken/alert/enrage/off_guard/sapped/empower/fortify)
# ——HUD框架 图标键契约"一键一枚专属纹样,查不到的键立即断言 + ? 占位 + 诊断"
# (2026-09-15 格面资源批补 sapped;2026-09-16 护甲等级掷点批 defense_up → ac_up 换装;
# 同日 敌人技能池批补 empower/fortify 敌方增益两键——敌方增益显示 = 敌卡状态行文本 + 本图标键词汇)。
@@ -19,7 +19,7 @@ const EFFECT_ICON := {
"attack_up": ["▲", Color8(176, 122, 20), "益"],
"ac_up": ["◇", Color8(46, 110, 142), "益"],
"weaken": ["▼", Color8(46, 110, 142), "益"],
"ambush": ["⚡", Color8(198, 61, 34), "害"],
"alert": ["◎", Color8(198, 61, 34), "害"],
"enrage": ["⚔", Color8(198, 61, 34), "害"],
"off_guard": ["✕", Color8(198, 61, 34), "害"],
"sapped": ["☾", Color8(198, 61, 34), "害"],
@@ -30,12 +30,12 @@ const EFFECT_SEMANTIC := {
"attack_up": "主角攻击加成(hero_offense 池加算)",
"ac_up": "护甲等级 +1/层(掷点折扣判定阈值——AC 数值轴,受击低掷按档位打折)",
"weaken": "敌方输出削弱(enemy_output 池乘算)",
"ambush": "敌方全体攻击附加(flat_attach——blast 型负向,战斗段共享不消耗)",
"alert": "敌方护甲等级 +1/层(blast 型负向——我方普通攻击掷点更易落入折扣档)",
"enrage": "敌方输出加成(enemy_output 池加算——attack 型负向)",
"off_guard": "主角承伤加深(hero_defense 池加算——defense 型负向)",
"sapped": "主角输出降低(hero_offense 池加算——weaken 型负向)",
"empower": "敌方输出 ×2(enemy_output 池加算——蓄力技施放;持续 1 个有效结算)",
"fortify": "敌方防御 +100(enemy_defense 池——硬化技施放,主角攻击折半;持续 1 个有效结算)",
"fortify": "敌方防御 −50(enemy_defense 池——硬化技施放,主角攻击 ×0.5;持续 1 个有效结算)",
}
const SOURCE_NAMES := {"safe_cell": "安全格", "flag_hit": "标中旗", "mine_hit": "踩雷", "clear_reward": "清图奖励"}
# 未知图标键的占位纹样(HUD框架:占位也要可见,禁静默空图标)
@@ -2,7 +2,7 @@
# Date: 2026-09-13(playtest-2026-09-12 报告 Top1——零承伤根因分割探针)
# 2026-09-16 改掷点口径(护甲等级掷点批):defense_up 墙已随 AC 换装退役——
# H1 旧 defense_up 叠满墙失义(hero_defense 池现仅 off_guard 正向行,×0 墙结构面消失)
# H2 Boss/敌攻击链健康:fresh 池下非蓄力拍 d_tick = max(下限, 池乘区(掷点档位基线 + 伏击附加))
# H2 Boss/敌攻击链健康:fresh 池下非蓄力拍 d_tick = max(下限, 池乘区(掷点档位基线))(伏击附加随八型批 2026-09-20 退役)
# 快照盲区说明:settlement_snapshot 取在衰减段之后,时效 1 的 ac_up 当轮授予当轮衰减——
# 真人局 JSONL buffs[] 恒空 ≠ 状态为空,本探针改从结算行(授予/续存 effect 行的 pool_value)重建战斗段时点池值。
# 观测面:全部读战斗结算步骤行 / 表值 / 呈现快照(零核心穿透);池归属/档位/下限全读表(数据配置 Rule 1——零数值字面量)。
@@ -214,7 +214,7 @@ func _analyze_round(db, math, rows: Array, beats: Dictionary) -> Dictionary:
var expected: int = maxi(int(db.settlement.damage_floor), math.pool_damage(raw, pd, pe))
var roll_txt := "掷%s" % str(int(row.get("roll", -1)))
var zeroed := "✓" if tick == expected else "✗ 算术不符!"
lines.append("拍=%s %s/档%d%s 基线%d→掷后%d +伏击%d hero_def=%d enemy_out=%d d_tick=%d 期望=%d %s" % [
lines.append("拍=%s %s/档%d%s 基线%d→掷后%d 附加%d hero_def=%d enemy_out=%d d_tick=%d 期望=%d %s" % [
String(beats.get(int(row.get("round_index", 0)), "?")), roll_txt, tier, "暴击" if crit else "",
baseline, rolled_base, attach, pd, pe, tick, expected, zeroed])
ticks.append({"raw": raw, "tick": tick, "expected": expected, "ok": tick == expected})
@@ -355,7 +355,7 @@ func _smoke_armor_roll() -> void:
board_ok = false # 棋盘来源(爆裂/直伤/清障炸药)必中全额——不掷不留档
check(all_positive, "全战斗流零 v=0 伤害行(伤害下限全局生效)")
check(attack_ok, "hero_attack/enemy_action 伤害行掷点读数+档位随行留档(P2 归因)")
check(board_ok, "棋盘来源伤害行零掷点字段(伏击附加/爆裂/直伤/清障炸药必中全额)")
check(board_ok, "棋盘来源伤害行零掷点字段(爆裂/直伤/清障炸药/奖励即时必中全额)")
# ⑥ 全来源伤害下限(2026-09-20 用户拍板:任何伤害落地取值域 [1,+∞) 无上限——棋盘克制直伤此前
# 靠表值 2+(−1)=1 贴底无结构保证,反噬/地形变异/爆裂同为表值直落,本批全部收进 damage_floor 闸内)
var df: int = int(_db.settlement.damage_floor)
@@ -1142,7 +1142,7 @@ func _smoke_playtest_logger() -> void:
minesweeper.settle_batch([{"action": "stop"}])
check(snaps.size() == 2, "每结算一快照(%d 次结算 → %d 快照)" % [2, snaps.size()])
var s1: Dictionary = snaps[0]
var required := ["kind", "level", "depth", "round", "verdict", "hero", "economy", "board", "enemies", "buffs", "ambush_layers", "round_facts", "spawn_count"]
var required := ["kind", "level", "depth", "round", "verdict", "hero", "economy", "board", "enemies", "buffs", "round_facts", "spawn_count"]
var missing := []
for k in required:
if not s1.has(k):
@@ -1257,7 +1257,7 @@ func _smoke_attack_chain_fresh_pool() -> void:
var ticks: Array = [] # {baseline, attach, roll, tier, crit, tick}——敌行动伤害行重建
settlement.step_row.connect(func(row):
if row.type == "damage" and row.source_class == "enemy_action":
ticks.append({"baseline": int(row.baseline), "attach": int(row.attach), "roll": int(row.roll),
ticks.append({"baseline": int(row.baseline), "attach": int(row.get("attach", 0)), "roll": int(row.roll),
"tier": int(row.tier), "crit": bool(row.get("crit", false)), "tick": int(row.value)}))
check(settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp)}), "fresh 池回归战斗启动(Boss 行)")
# 停手轮:零挖掘零旗 → 零授予 → 池恒空;Boss 拍 = 开轮摇号结果(伤害技/普攻兑付伤害行、硬化施放行不计)
@@ -1270,10 +1270,10 @@ func _smoke_attack_chain_fresh_pool() -> void:
for t in ticks:
var base_factor: float = float(t.tier) * (float(_db.settlement.crit_multiplier) / 100.0 if t.crit else 1.0)
var rolled_base := int(floor(float(t.baseline) * base_factor / 100.0))
var expected: int = maxi(int(_db.settlement.damage_floor), rolled_base + int(t.attach))
var expected: int = maxi(int(_db.settlement.damage_floor), rolled_base + int(t.get("attach", 0)))
if int(t.tick) != expected or int(t.tick) <= 0:
all_full = false
check(all_full, "fresh 池非蓄力拍 d_tick = max(1, 掷点档位基线 + 伏击附加)(Boss 攻击链健康——Bug#2 回归断言)")
check(all_full, "fresh 池非蓄力拍 d_tick = max(1, 掷点档位基线)(Boss 攻击链健康——Bug#2 回归断言)")
func _encounter_replay(seed_val: int) -> Array:
var out: Array = []
@@ -1399,9 +1399,9 @@ func _smoke_refresh_provenance_guard() -> void:
# 极性路由断言(战斗结算 验收):挖安全格/标中旗只出正向、挖雷格/标错旗只出负向、兑付行指回来源格。
# 正向字典效果集 = {attack_up, ac_up, weaken}(ac_up = 2026-09-16 护甲等级掷点批换装,原 defense_up)
# + spark_aoe/heal_minor(flat);
# 负向字典效果集 = {enrage, off_guard, sapped}(percent)+ 直伤(heal flat)/ ambush(blast flat)。
# 负向字典效果集 = {enrage, off_guard, sapped}(percent)+ 直伤(heal flat)/ alert 警觉(blast percent——2026-09-20 换装)。
const POSITIVE_EFFECTS := ["attack_up", "ac_up", "weaken"]
const NEGATIVE_EFFECTS := ["enrage", "off_guard", "sapped", "ambush"]
const NEGATIVE_EFFECTS := ["enrage", "off_guard", "sapped", "alert"]
func _smoke_polarity_routing() -> void:
@@ -1422,7 +1422,7 @@ func _smoke_polarity_routing() -> void:
if eff != "" and not POSITIVE_EFFECTS.has(eff):
safe_all_pos = false
check(safe_all_pos, "挖安全格 → 只出正向兑付(效果行 ∈ 正向字典 + 每格 ≥1 行——%d 格)" % probe["safe_cells"].size())
# 挖到的雷格:只出负向——来源行必存在且 effect 行 ∈ 负向集(ambush = blast 负向;heal 负向 = damage 行)
# 挖到的雷格:只出负向——来源行必存在且 effect 行 ∈ 负向集(alert = blast 负向;heal 负向 = damage 行)
var mine_all_neg := true
for e in probe["mine_cells"].keys():
var cell_arr: Array = [e.x, e.y]
@@ -1517,9 +1517,9 @@ func _rows_at(rows: Array, source_class: String, cell) -> Array:
# 负向字典逐型直验(类型化兑付行协议):找真实板上各型非封锁格,直调 _grant_routed 负向路由,
# 断言每型的兑付形态(enrage/off_guard/sapped 效果行、heal 直伤行、blast ambush+cap 溢出协议)。
# 断言每型的兑付形态(enrage/off_guard/sapped 效果行、heal 直伤行、blast 警觉行、action 行动点排队)。
func _smoke_negative_dictionary() -> void:
print("--- 负向字典逐型直验(sapped 在位 / heal 直伤 / blast 伏击+2 与 cap 溢出)---")
print("--- 负向字典逐型直验(sapped 在位 / heal 直伤 / blast 警觉 / action 行动点)---")
# 数据面:sapped 行在位(状态表七行 + effect_pool_map 收编)
var sapped: Dictionary = _db.status_effect_row("sapped")
check(not sapped.is_empty() and String(sapped.get("target_side", "")) == "hero" \
@@ -1582,20 +1582,23 @@ func _smoke_negative_dictionary() -> void:
if String(row.get("reward_id", "")) == "heal_backlash":
backlash_power = int(row.power)
check(hp_after == hp_before - backlash_power, "heal 型负向 → 主角直伤 %d(%d → %d,不经增益状态)" % [backlash_power, hp_before, hp_after])
# blast:负向 = 伏击 +2 层;cap 内生效、超 cap 逐层溢出(cap 读敌行值——零字面量)
# blast:负向 = 警觉(敌方护甲等级 +1 percent 行——2026-09-20 伏击退役换装)
if by_type.has("blast"):
var cap: int = int(_db.resolve_enemy_rowset(1, 0).get("advantage_per_round_cap", 0))
rows.clear()
var snap_b: Dictionary = settlement._reward_economy.query_full_snapshot(by_type["blast"])
settlement._grant_routed(snap_b, "negative", "mine_hit", by_type["blast"], 1)
var layers_after_first: int = settlement._status_effects.pool_layers("ambush")
check(layers_after_first == mini(2, cap), "blast 型负向 → 伏击 +%d 层(cap=%d)" % [layers_after_first, cap])
while settlement._status_effects.pool_layers("ambush") < cap:
settlement._grant_routed(snap_b, "negative", "mine_hit", by_type["blast"], 1)
rows.clear()
settlement._grant_routed(snap_b, "negative", "mine_hit", by_type["blast"], 1)
check(_rows_of_type(rows, "overflow").size() >= 1 and settlement._status_effects.pool_layers("ambush") == cap,
"blast 型负向超 cap → 溢出未生效行(伏击池守恒在 cap=%d)" % cap)
var alert_ok: bool = false
for eff_a in settlement._status_effects.query_active("enemy"):
if String(eff_a.effect_id) == "alert":
alert_ok = true
check(alert_ok, "blast 型负向 → 警觉(敌方护甲等级 +1 状态在位)")
# action:负向 = 下轮行动点 −1 / 正向 = +1(排队,轮开始兑现——八型批 2026-09-20;确定性直验不依赖板面摇号)
rows.clear()
var d0: int = settlement.action_economy.pending_ap_delta()
settlement._grant_routed({"reward_id": "ap_drain", "cell_type": "action", "polarity": "negative", "display_name": "迟滞", "power": 1}, "negative", "mine_hit", Vector2i(0, 0), 1)
check(settlement.action_economy.pending_ap_delta() == d0 - 1, "action 型负向 → 下轮行动点 −1(排队)")
settlement._grant_routed({"reward_id": "ap_up", "cell_type": "action", "polarity": "positive", "display_name": "行动点", "power": 1}, "positive", "safe_open", Vector2i(0, 0), 1)
check(settlement.action_economy.pending_ap_delta() == d0, "action 型正负抵消 → 排队净值归零")
# 资源面断言:chain/dig 事件恒携带 resource=true(雷格同权);initial 恒 false(封锁格);
@@ -2137,7 +2140,7 @@ func _has_err_category(errs: Array, category: String) -> bool:
# ⑦ 新效应词汇批实装探针(2026-09-19——妨害技蛛网束缚 + 毒爆必中附加分量,实装面 = L2 Boss 卓尔):
# 停手轮驱动 (2,8)——零挖掘零旗 → 零池零伏击,伤害行可逐拍精确复算;种子 hunt 至少各命一粒毒爆伤害行
# 停手轮驱动 (2,8)——零挖掘零旗 → 零池,伤害行可逐拍精确复算;种子 hunt 至少各命一粒毒爆伤害行
# 与蛛网束缚施放行;毒分量随开战掷定的攻击基线缩放(atk 区间 2–4 → rider 1–3)。
func _smoke_debuff_rider() -> void:
var venom_seed := -1
@@ -2180,7 +2183,7 @@ func _smoke_debuff_rider() -> void:
# 终值精确复算(停手轮池恒空):主分量链结果 + 必中附加后置
var base_factor: float = float(int(row.tier)) * (float(_db.settlement.crit_multiplier) / 100.0 if bool(row.get("crit", false)) else 1.0)
var rolled_main: int = int(floor(float(int(row.baseline)) * base_factor / 100.0))
var expected: int = maxi(floor_v, rolled_main + int(row.attach)) + rider_v
var expected: int = maxi(floor_v, rolled_main + int(row.get("attach", 0))) + rider_v
if int(row.value) != expected:
venom_check_ok = false
elif row.has("rider"):
@@ -2200,7 +2203,7 @@ func _smoke_debuff_rider() -> void:
if has_venom and venom_seed < 0:
venom_seed = s
check(venom_seed > 0 and venom_rows_seen >= 1, "毒爆必中附加分量种子可达(seed=%d,毒爆伤害行 ×%d)" % [venom_seed, venom_rows_seen])
check(venom_check_ok, "毒爆伤害行逐拍精确复算:终值 = max(1, 掷点档位主分量 + 伏击附加) + 必中附加(rider 只随 rider_mult 行出现)")
check(venom_check_ok, "毒爆伤害行逐拍精确复算:终值 = max(1, 掷点档位主分量) + 必中附加(rider 只随 rider_mult 行出现)")
check(snare_seed > 0, "蛛网束缚落主角:施放后掷点阈值 = 基准 AC + 失衡幅度(off_guard ac 轴端到端,seed=%d)" % snare_seed)