卡①T4c(拆件批wr5/wr9/V12): settlement 拆两子 Resource 段——新建 settlement_core_table.gd(7列:source_label_map/effect_pool_map/pool_clamp_lo/pool_clamp_hi/round_direction/p_perfect/seal_hit_premium)+damage_roll_table.gd(10列:equivalence_ref_damage/armor_effectiveness/roll_faces/attack_bonus/roll_tier_bounds/roll_tier_discounts/crit_roll/crit_multiplier/damage_floor/armor_roll_stream)+.uid边车;主表三列形(segment_order留主表+core+damage_roll,类变量18→3,const四枚随主表),validate拆五私有(_validate_scalars/_validate_roll_scalars(9分支超阈续拆)/_validate_frozen/_validate_tier_table/_validate_armor_matrix)错误载荷逐字同构+段缺失fail-fast;settlement.tres单文件嵌套重建(两sub_resource,18列值与尾注批注史逐字—脚本比对18/18无diff+tail_equal);消费方改址97处(.settlement.<列>→.settlement.core./damage_roll. 链式95处+cross_validator局部2处,逐规则计数断言全中;segment_order直读1处零改址);测试改址9文件含G15清单(sim/probe/config_db交叉校验)。验收:gdstyle基线外0 NEW+16套件924 PASS+零SCRIPT ERROR〔拆件批 V12〕
This commit is contained in:
1 parent
bc7a171e3d
commit
e2a2fbf3ee
23 files changed
+221
-160
No files matched your search
@@ -43,13 +43,13 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
# 唯一来路=三选一共享池〔节点/关卡完成〕;共享池为「抽到道具位再动态掷定具体道具」,维度覆盖不可
|
||||
# 静态校验。
|
||||
# ② SettlementTable.effect_pool_map 悬空
|
||||
for eff in st_tab.effect_pool_map.keys():
|
||||
for eff in st_tab.core.effect_pool_map.keys():
|
||||
if not effect_ids.has(eff):
|
||||
push_diag.call("error", "poolmap_dangling", "SettlementTable.effect_pool_map 悬空 effect_id: %s" % eff)
|
||||
# ③ source_label_map 键集封闭性(键 = 增益状态 source_class ∪ {enemy_action, hero_attack, seal_miss})
|
||||
var required_sources := ["safe_cell", "seal_hit", "mine_hit", "clear_reward", "hero_attack", "enemy_action", "seal_miss"]
|
||||
for s in required_sources:
|
||||
if not st_tab.source_label_map.has(s):
|
||||
if not st_tab.core.source_label_map.has(s):
|
||||
push_diag.call("error", "segorder_violation", "source_label_map 缺必需键: %s" % s)
|
||||
# ⑤ 技能池引用完整性(敌人技能池批——敌人表池 × 技能表;独立入口 validate_skill_pool_links 供冒烟直验)
|
||||
var en: EnemyTable = loaded.get("enemy")
|
||||
|
||||
@@ -274,7 +274,7 @@ func _hero_attack_ctx(t: Dictionary) -> Dictionary:
|
||||
var ac_hits: Array[Variant] = (t.context.get("fx_attack_hits", []) as Array)
|
||||
var ac_payload: Dictionary[Variant, Variant] = (t.context.get("fx_attack_payload", {}) as Dictionary)
|
||||
var roll_read: Dictionary[Variant, Variant] = _armor_roll()
|
||||
var threshold: int = defender_ac(target) + int(db.settlement.attack_bonus)
|
||||
var threshold: int = defender_ac(target) + int(db.settlement.damage_roll.attack_bonus)
|
||||
# armor_roll 发射点(敌护甲掷骰判定;免掷恒折扣订阅面)
|
||||
var ar_hits := _fx_hits("armor_roll", {"target_ac": threshold, "roll": int(roll_read.roll)})
|
||||
# 按序改写族第一拍:矿工重锤命中交换(正负双修原生形态:命中判定减成 trade_hit 点〔更易命中〕
|
||||
@@ -367,7 +367,7 @@ func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> D
|
||||
var ac_hits: Array[Variant] = ctx["ac_hits"]
|
||||
var roll_read: Dictionary[Variant, Variant] = ctx["roll_read"]
|
||||
var atk_type: String = ctx["atk_type"]
|
||||
var rolled: int = DamageMath.rolled_component(base_value, int(ctx["discount"]), bool(ctx["crit"]), int(db.settlement.crit_multiplier), int(ctx["cdelta"]))
|
||||
var rolled: int = DamageMath.rolled_component(base_value, int(ctx["discount"]), bool(ctx["crit"]), int(db.settlement.damage_roll.crit_multiplier), int(ctx["cdelta"]))
|
||||
var payload: Dictionary[Variant, Variant] = {"target": String(target.display_name), "target_id": String(target.get("enemy_id", ""))}
|
||||
payload[atk_type] = rolled # 分量键 = 实际攻击类型(归因字段,单分量模型)
|
||||
payload["roll"] = int(roll_read.roll)
|
||||
@@ -376,7 +376,7 @@ func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> D
|
||||
if bool(ctx["crit"]):
|
||||
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)
|
||||
var landed: int = maxi(int(db.settlement.damage_roll.damage_floor), pooled)
|
||||
# 加法族:英雄侧 rider(enemy_skill rider 对称位补齐口径)——后置必中附加,独立于掷点与乘区
|
||||
# (奥术余烬「普攻命中后恒定结算」语义);猎巢人巢控光环(per nest_count)/矿工的执拗开格加成
|
||||
# (per dig_streak、cap 封顶)同通道合计;档一质变 rider+1 / 档二(crit 时)rider 乘暴击倍率。
|
||||
@@ -390,7 +390,7 @@ func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> D
|
||||
hero_rider += contrib
|
||||
if hero_rider > 0:
|
||||
if bool(ctx["crit"]) and rider_tier_crit:
|
||||
hero_rider = int(round(float(hero_rider) * float(db.settlement.crit_multiplier)))
|
||||
hero_rider = int(round(float(hero_rider) * float(db.settlement.damage_roll.crit_multiplier)))
|
||||
landed += hero_rider
|
||||
payload["hero_rider"] = hero_rider
|
||||
units.apply_damage(target, landed)
|
||||
@@ -408,7 +408,7 @@ func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> D
|
||||
if next_target != null:
|
||||
# source_class 用中性名(fx_overflow 泛用通道,不挂件名)
|
||||
var of_t := open_ticket(DamageController.KIND_BOARD_TYPED, "fx_overflow",
|
||||
"贯穿弹射", "board", maxi(int(db.settlement.damage_floor), int(landed * int(h.get("value", 50)) / 100)),
|
||||
"贯穿弹射", "board", maxi(int(db.settlement.damage_roll.damage_floor), int(landed * int(h.get("value", 50)) / 100)),
|
||||
DamageController.TARGET_FRONT_ALIVE, int(t.round_index), t.origin_cell, "pierce")
|
||||
if policy == POLICY_SEGMENT:
|
||||
_queue.append(of_t)
|
||||
@@ -440,13 +440,13 @@ func _hero_attack_deliver(t: Dictionary, ctx: Dictionary, redemption: Dictionary
|
||||
func _resolve_enemy_skill(t: Dictionary) -> void:
|
||||
var hero: Dictionary[Variant, Variant] = units.hero_unit()
|
||||
var roll_read: Dictionary[Variant, Variant] = _armor_roll()
|
||||
var threshold: int = defender_ac(hero) + int(db.settlement.attack_bonus)
|
||||
var threshold: int = defender_ac(hero) + int(db.settlement.damage_roll.attack_bonus)
|
||||
var crit: bool = bool(roll_read.crit)
|
||||
var discount: int = _roll_discount(int(roll_read.roll), threshold)
|
||||
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 rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.damage_roll.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 后置于伤害下限——独立必中加项,不进乘区
|
||||
var total: int = maxi(int(db.settlement.damage_roll.damage_floor), pooled) + int(t.rider) # rider 后置于伤害下限——独立必中加项,不进乘区
|
||||
# damage_taken 发射+施加(kind=enemy_skill;铁壁整备减伤订阅面:
|
||||
# shield 加法族合计 value×per(charge_layers),稳健拓荒树规则同面(rebuild 合成订阅);
|
||||
# 减后过下限保底。damage_reduce〔背水 hp_ratio when〕/counter_add〔保险丝/
|
||||
@@ -466,7 +466,7 @@ func _resolve_enemy_skill(t: Dictionary) -> void:
|
||||
var set_v = h.get("set", null)
|
||||
EffectEngine.counter_add(_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null)
|
||||
if shield > 0:
|
||||
total = maxi(int(db.settlement.damage_floor), total - shield)
|
||||
total = maxi(int(db.settlement.damage_roll.damage_floor), total - shield)
|
||||
units.apply_damage(hero, total)
|
||||
var payload: Dictionary[Variant, Variant] = {"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, "attack_type": String(t.attack_type)}
|
||||
@@ -499,7 +499,7 @@ func _resolve_board_typed(t: Dictionary) -> void:
|
||||
# 无模式 delta 才入棋盘窗(mode 路由:clamp_bonus=主角攻击面专享、discount_shift=折扣档面)
|
||||
if String(h.get("kind", "")) == "modify_ticket" and h.has("delta") and String(h.get("mode", "")) == "":
|
||||
cdelta += int(h.delta)
|
||||
var dealt: int = DamageMath.counter_component(int(t.base_value), cdelta, int(db.settlement.damage_floor))
|
||||
var dealt: int = DamageMath.counter_component(int(t.base_value), cdelta, int(db.settlement.damage_roll.damage_floor))
|
||||
units.apply_damage(target, dealt)
|
||||
emit_row_cb.call({"type": "damage", "source_class": String(t.source_class), "origin_cell": t.origin_cell, "highlight": String(t.highlight)}, int(t.round_index),
|
||||
"%s → %s" % [String(t.display), String(target.display_name)], dealt,
|
||||
@@ -514,7 +514,7 @@ func _resolve_board_blast(t: Dictionary) -> void:
|
||||
_ineffective_row(t, "无存活敌人")
|
||||
_outcomes[int(t.ticket_id)] = "zero"
|
||||
return
|
||||
var blast_dmg: int = maxi(int(db.settlement.damage_floor), int(t.base_value)) # 伤害下限全来源
|
||||
var blast_dmg: int = maxi(int(db.settlement.damage_roll.damage_floor), int(t.base_value)) # 伤害下限全来源
|
||||
for u in alive:
|
||||
units.apply_damage(u, blast_dmg) # 恒定表值(下限保底),逐个;受击即死——死亡即不动手
|
||||
emit_row_cb.call({"type": "damage", "source_class": String(t.source_class), "origin_cell": t.origin_cell, "highlight": String(t.highlight)}, int(t.round_index),
|
||||
@@ -525,7 +525,7 @@ func _resolve_board_blast(t: Dictionary) -> void:
|
||||
|
||||
# 棋盘主角直伤(反噬/治疗包负向/地形变异):无类型必中,下限保底;context 键并入行载荷
|
||||
func _resolve_board_flat_hero(t: Dictionary) -> void:
|
||||
var dealt: int = maxi(int(db.settlement.damage_floor), int(t.base_value)) # 伤害下限全来源
|
||||
var dealt: int = maxi(int(db.settlement.damage_roll.damage_floor), int(t.base_value)) # 伤害下限全来源
|
||||
units.apply_damage(units.hero_unit(), dealt)
|
||||
var extra: Dictionary[Variant, Variant] = {"target": "hero"}
|
||||
for k in t.context.keys():
|
||||
@@ -538,16 +538,16 @@ func _resolve_board_flat_hero(t: Dictionary) -> void:
|
||||
|
||||
# 护甲等级掷点(armor_roll 封闭流——逐次掷逐次取号、逐位可回放):返回 {roll, crit}
|
||||
func _armor_roll() -> Dictionary:
|
||||
var stream: RandomNumberGenerator = rng.open_stream(String(db.settlement.armor_roll_stream))
|
||||
var roll: int = stream.randi_range(1, int(db.settlement.roll_faces))
|
||||
return {"roll": roll, "crit": roll == int(db.settlement.crit_roll)}
|
||||
var stream: RandomNumberGenerator = rng.open_stream(String(db.settlement.damage_roll.armor_roll_stream))
|
||||
var roll: int = stream.randi_range(1, int(db.settlement.damage_roll.roll_faces))
|
||||
return {"roll": roll, "crit": roll == int(db.settlement.damage_roll.crit_roll)}
|
||||
|
||||
|
||||
# 掷点折扣读数:nat 最大面恒全额(暴击分支前置),其余查档(缺口 = 阈值 − 掷点)
|
||||
func _roll_discount(roll: int, threshold: int) -> int:
|
||||
if roll == int(db.settlement.crit_roll):
|
||||
if roll == int(db.settlement.damage_roll.crit_roll):
|
||||
return 100
|
||||
return DamageMath.roll_discount_percent(roll, threshold, db.settlement.roll_tier_bounds, db.settlement.roll_tier_discounts)
|
||||
return DamageMath.roll_discount_percent(roll, threshold, db.settlement.damage_roll.roll_tier_bounds, db.settlement.damage_roll.roll_tier_discounts)
|
||||
|
||||
|
||||
# 防守方护甲等级读数(数值轴——与 armor_type 克制属性轴严格分离):敌人 = spawn 期行值快照;
|
||||
@@ -576,14 +576,14 @@ func defender_ac(unit: Dictionary) -> int:
|
||||
func _counter_delta(attack_type: String, armor: String) -> int:
|
||||
if attack_type == "":
|
||||
return 0
|
||||
var tbl: Dictionary[Variant, Variant] = db.settlement.armor_effectiveness
|
||||
var tbl: Dictionary[Variant, Variant] = db.settlement.damage_roll.armor_effectiveness
|
||||
var row: Dictionary[Variant, Variant] = tbl.get(attack_type, {})
|
||||
return int(row.get(armor, 0))
|
||||
|
||||
|
||||
# 折扣档改善(猎弱本能——折扣表向全额方向移 value 档;查不到当前档 = 不动防御口径)
|
||||
func _discount_shift_up(discount: int, shift: int) -> int:
|
||||
var tbl: Array[Variant] = db.settlement.roll_tier_discounts
|
||||
var tbl: Array[Variant] = db.settlement.damage_roll.roll_tier_discounts
|
||||
var idx := tbl.find(discount)
|
||||
if idx < 0:
|
||||
return discount
|
||||
@@ -597,9 +597,9 @@ func clamped_pool(pool: String) -> int:
|
||||
var row: Dictionary[Variant, Variant] = db.status_effect_row(eff.effect_id)
|
||||
if row.is_empty():
|
||||
continue
|
||||
if String(db.settlement.effect_pool_map.get(eff.effect_id, "")) == pool and String(row.unit) == "percent":
|
||||
if String(db.settlement.core.effect_pool_map.get(eff.effect_id, "")) == pool and String(row.unit) == "percent":
|
||||
total += eff.effective_value # 池内加算(L×m 原样——越域不 clamp 由本处收口)
|
||||
return clampi(total, int(db.settlement.pool_clamp_lo), int(db.settlement.pool_clamp_hi))
|
||||
return clampi(total, int(db.settlement.core.pool_clamp_lo), int(db.settlement.core.pool_clamp_hi))
|
||||
|
||||
|
||||
# 攻击行显示名(掷点档位随行文本可读;重档折扣 = "被压制")
|
||||
|
||||
@@ -329,7 +329,7 @@ func _counter_delta_of(matchup: String) -> int:
|
||||
var parts := matchup.split("_vs_")
|
||||
if parts.size() != 2:
|
||||
return 0
|
||||
var tbl: Dictionary[Variant, Variant] = db.settlement.armor_effectiveness
|
||||
var tbl: Dictionary[Variant, Variant] = db.settlement.damage_roll.armor_effectiveness
|
||||
var row: Dictionary[Variant, Variant] = tbl.get(String(parts[0]), {})
|
||||
return int(row.get(String(parts[1]), 0))
|
||||
|
||||
|
||||
@@ -196,8 +196,8 @@ func grant_routed(snap: Dictionary, polarity: String, att: Dictionary) -> bool:
|
||||
# premium_pct 贯穿 _apply_percent_row 应用与呈现同乘)——标空/挖格/清图不乘,雷安同池冻结不动。
|
||||
# 取整=四舍五入(与伤害管线 round_direction=floor 分域——registry clause3_inequality 登记;
|
||||
# 1 点量纲行 ac_up/action 取整后不变=整数粒度地板)。
|
||||
if source_class == "seal_hit" and int(db.settlement.seal_hit_premium) != 100:
|
||||
var prem_pct: int = int(db.settlement.seal_hit_premium)
|
||||
if source_class == "seal_hit" and int(db.settlement.core.seal_hit_premium) != 100:
|
||||
var prem_pct: int = int(db.settlement.core.seal_hit_premium)
|
||||
row = row.duplicate()
|
||||
row["power"] = int(round(int(row.power) * prem_pct / 100.0))
|
||||
row["premium_pct"] = prem_pct
|
||||
|
||||
@@ -94,7 +94,7 @@ func _enemy_actions(ri: int) -> void:
|
||||
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(oc.db.settlement.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult))))
|
||||
rider = maxi(int(oc.db.settlement.damage_roll.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult))))
|
||||
var et := oc._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), String(u.get("attack_type", "normal")), rider)
|
||||
et["source_unit"] = u
|
||||
|
||||
@@ -146,7 +146,7 @@ func _reveal_mine_apply(mh_hits: Array, snap: Dictionary, cell: Vector2i, ri: in
|
||||
if fx_host._fx_bonus_delta.has(axis):
|
||||
fx_host._fx_bonus_delta[axis] += int(h.get("delta", 0))
|
||||
"blast_scaled":
|
||||
var blast_v := maxi(int(oc.db.settlement.damage_floor), int(float(int(snap.get("power", 0))) * float(h.get("mult", 1))))
|
||||
var blast_v := maxi(int(oc.db.settlement.damage_roll.damage_floor), int(float(int(snap.get("power", 0))) * float(h.get("mult", 1))))
|
||||
var bs_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_blast_scaled", "陷阱爆发", "board",
|
||||
blast_v, DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "", {})
|
||||
reward_grant.submit(bs_t, false)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 战斗结算表「伤害掷点与护甲」段 schema:当量参考敞口、攻击类型×护甲克制矩阵、护甲等级掷点
|
||||
## 与伤害下限共 10 列纯数据载体(由 SettlementTable.damage_roll 持有;零函数零随机零 await)。
|
||||
## 域校验由主表 validate 分面执行(fail-fast)。
|
||||
class_name DamageRollTable
|
||||
extends Resource
|
||||
|
||||
@export var equivalence_ref_damage: int = 12 # [1,999] knob——当量参考敞口 D_ref
|
||||
# ---- 攻击类型 × 护甲克制 + 充能奖励定义(数值 provisional)----
|
||||
# 主角攻击 = 普通(atk×(1+attack_up),不吃克制)+ 穿刺 + 魔法(后两者由充能供给),三分量各自 floor 后相加。
|
||||
# 【两条抽象分离,勿焊死】攻击类型 = 伤害的属性轴,只决定查 armor_effectiveness 哪一行,定义里不含任何棋盘来源;
|
||||
# 充能 = 按攻击类型分桶的附加伤害储备,"谁往桶里放东西"属**奖励的定义**(开格/标雷只是奖励触发源,奖励内容可改)。
|
||||
# 无 damage_a_per_cell / damage_a_cap / damage_b_per_seal 与 charge_grants 字段(攻击模型=单分量)。
|
||||
@export var armor_effectiveness: Dictionary[String, Variant] = {} # 克制修正矩阵(加法点数 delta):{攻击类型 {normal|pierce|magic}: {护甲 {light|ward|heavy}: 点数 [-3,+3]}}
|
||||
# ---- 护甲等级掷点(数字即规范——registry settlement_table.ac_roll/ac_discount_tiers 定稿)----
|
||||
# 判定式:掷 1d20,掷点 ≥ 防守方 AC + 攻击加成 → 全额;低掷按缺口(AC−掷点)查档打折,无 miss、无零收益档;
|
||||
# nat 最大面(crit_roll)= 暴击:该次基础攻击分量 ×2 且恒全额;任何伤害落地全局下限 damage_floor(≥1)——
|
||||
# 全来源(攻击双方/棋盘直伤/地形变异/反噬,取值域 [1,+∞) 无上限)。
|
||||
# 掷点只吃基础攻击分量(主角普通分量 / 敌方攻击拍与重击拍基线),穿刺/魔法充能与棋盘来源伤害不掷不折。
|
||||
# 微调旋钮 = 档位边界(roll_tier_bounds)与档位值(roll_tier_discounts),不动 AC 值(armor_class 归单位表)。
|
||||
@export var roll_faces: int = 20 # [2,1000] knob——掷点骰面(d20)
|
||||
@export var attack_bonus: int = 0 # [0,999] 结构位——攻击者掷点加成,切片恒 0(元进度/武器附魔未来投值)
|
||||
@export var roll_tier_bounds: Array[int] = [] # 缺口档边界升序:缺口 ≥ bounds[i] 落入第 i 档([3,7] = 缺口1-3/4-7/8+ 三档)
|
||||
@export var roll_tier_discounts: Array[int] = [] # 档位值 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 armor_roll_stream: String = "armor_roll" # 掷点用途标签(rng_service PURPOSE_TAGS 成员——不参与派生,仅作事件日志用途标记)
|
||||
@@ -0,0 +1 @@
|
||||
uid://ddd6kga4tcivx
|
||||
@@ -0,0 +1,14 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 战斗结算表「结算结构与封印溢价」段 schema:结构冻结位(段序外的取值域/池钳位/取整/效果池映射)
|
||||
## 与封印溢价旋钮共 7 列纯数据载体(由 SettlementTable.core 持有;零函数零随机零 await)。
|
||||
## 结构冻结位(pool clamp/效果池映射/取整)违例加载期致命(fail-fast)。
|
||||
class_name SettlementCoreTable
|
||||
extends Resource
|
||||
|
||||
@export var source_label_map: Dictionary[String, Variant] = {} # 结构冻结——source_class→文案键
|
||||
@export var effect_pool_map: Dictionary[String, Variant] = {} # 结构冻结——effect_id→pool(悬空加载致命)
|
||||
@export var pool_clamp_lo: int = -100 # 结构冻结
|
||||
@export var pool_clamp_hi: int = 100 # 结构冻结
|
||||
@export var round_direction: String = "floor" # 结构冻结——单一取整规则
|
||||
@export var p_perfect: int = 200 # {100,200} percent——完美封印第二份兑付触发(载体域)
|
||||
@export var seal_hit_premium: int = 100 # [100,999] percent knob——封印命中兑付溢价 ×(1+(n−100)/100):125=×1.25(条款3 条件二封印溢价形态 a,provisional);100=无溢价直通零扰动。只乘封印命中正向兑付(flat power/percent 幅度/gp 缓冲三形态),标空/挖格/清图不乘;雷安同池冻结不动
|
||||
@@ -0,0 +1 @@
|
||||
uid://swk6u2opk6ix
|
||||
@@ -1,38 +1,14 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 战斗结算表 schema:结算全局旋钮(段序/克制矩阵/掷点档位)单行全局类型化读取(垂直切片验证:完整战斗逐行归因)。
|
||||
## 战斗结算表 schema:主表(段序骨架+两段子 Resource 嵌套)——结算全局旋钮(段序/克制矩阵/掷点档位)
|
||||
## 单行全局类型化读取(垂直切片验证:完整战斗逐行归因)。主表只持段序骨架(3 列形):
|
||||
## core=SettlementCoreTable(结算结构与封印溢价 7 列)/damage_roll=DamageRollTable(伤害掷点与护甲 10 列)。
|
||||
## 结构冻结位(段序/取整/pool clamp/效果池映射)违例加载期致命(fail-fast)。
|
||||
class_name SettlementTable
|
||||
extends Resource
|
||||
|
||||
@export var equivalence_ref_damage: int = 12 # [1,999] knob——当量参考敞口 D_ref
|
||||
@export var p_perfect: int = 200 # {100,200} percent——完美封印第二份兑付触发(载体域)
|
||||
@export var seal_hit_premium: int = 100 # [100,999] percent knob——封印命中兑付溢价 ×(1+(n−100)/100):125=×1.25(条款3 条件二封印溢价形态 a,provisional);100=无溢价直通零扰动。只乘封印命中正向兑付(flat power/percent 幅度/gp 缓冲三形态),标空/挖格/清图不乘;雷安同池冻结不动
|
||||
@export var segment_order: PackedStringArray = [] # 结构冻结——段序恒等校验违例致命
|
||||
@export var source_label_map: Dictionary[String, Variant] = {} # 结构冻结——source_class→文案键
|
||||
@export var effect_pool_map: Dictionary[String, Variant] = {} # 结构冻结——effect_id→pool(悬空加载致命)
|
||||
@export var pool_clamp_lo: int = -100 # 结构冻结
|
||||
@export var pool_clamp_hi: int = 100 # 结构冻结
|
||||
@export var round_direction: String = "floor" # 结构冻结——单一取整规则
|
||||
# ---- 攻击类型 × 护甲克制 + 充能奖励定义(数值 provisional)----
|
||||
# 主角攻击 = 普通(atk×(1+attack_up),不吃克制)+ 穿刺 + 魔法(后两者由充能供给),三分量各自 floor 后相加。
|
||||
# 【两条抽象分离,勿焊死】攻击类型 = 伤害的属性轴,只决定查 armor_effectiveness 哪一行,定义里不含任何棋盘来源;
|
||||
# 充能 = 按攻击类型分桶的附加伤害储备,"谁往桶里放东西"属**奖励的定义**(开格/标雷只是奖励触发源,奖励内容可改)。
|
||||
# 无 damage_a_per_cell / damage_a_cap / damage_b_per_seal 与 charge_grants 字段(攻击模型=单分量)。
|
||||
@export var armor_effectiveness: Dictionary[String, Variant] = {} # 克制修正矩阵(加法点数 delta):{攻击类型 {normal|pierce|magic}: {护甲 {light|ward|heavy}: 点数 [-3,+3]}}
|
||||
# ---- 护甲等级掷点(数字即规范——registry settlement_table.ac_roll/ac_discount_tiers 定稿)----
|
||||
# 判定式:掷 1d20,掷点 ≥ 防守方 AC + 攻击加成 → 全额;低掷按缺口(AC−掷点)查档打折,无 miss、无零收益档;
|
||||
# nat 最大面(crit_roll)= 暴击:该次基础攻击分量 ×2 且恒全额;任何伤害落地全局下限 damage_floor(≥1)——
|
||||
# 全来源(攻击双方/棋盘直伤/地形变异/反噬,取值域 [1,+∞) 无上限)。
|
||||
# 掷点只吃基础攻击分量(主角普通分量 / 敌方攻击拍与重击拍基线),穿刺/魔法充能与棋盘来源伤害不掷不折。
|
||||
# 微调旋钮 = 档位边界(roll_tier_bounds)与档位值(roll_tier_discounts),不动 AC 值(armor_class 归单位表)。
|
||||
@export var roll_faces: int = 20 # [2,1000] knob——掷点骰面(d20)
|
||||
@export var attack_bonus: int = 0 # [0,999] 结构位——攻击者掷点加成,切片恒 0(元进度/武器附魔未来投值)
|
||||
@export var roll_tier_bounds: Array[int] = [] # 缺口档边界升序:缺口 ≥ bounds[i] 落入第 i 档([3,7] = 缺口1-3/4-7/8+ 三档)
|
||||
@export var roll_tier_discounts: Array[int] = [] # 档位值 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 armor_roll_stream: String = "armor_roll" # 掷点用途标签(rng_service PURPOSE_TAGS 成员——不参与派生,仅作事件日志用途标记)
|
||||
@export var segment_order: PackedStringArray = [] # 结构冻结——段序恒等校验违例致命(段序骨架留主表)
|
||||
@export var core: SettlementCoreTable # 「结算结构与封印溢价」段(game/data/schemas/settlement_core_table.gd)
|
||||
@export var damage_roll: DamageRollTable # 「伤害掷点与护甲」段(game/data/schemas/damage_roll_table.gd)
|
||||
|
||||
const ARMOR_KEYS := ["light", "ward", "heavy"]
|
||||
const ATTACK_TYPE_KEYS := ["normal", "pierce", "magic"]
|
||||
@@ -43,47 +19,77 @@ const P_PERFECT_DOMAIN := [100, 200]
|
||||
|
||||
func validate() -> Array:
|
||||
var errs: Array[Dictionary] = []
|
||||
if equivalence_ref_damage < 1 or equivalence_ref_damage > 999:
|
||||
errs.append({"category": "domain", "message": "equivalence_ref_damage 越域 [1,999]: %d" % equivalence_ref_damage})
|
||||
if not P_PERFECT_DOMAIN.has(p_perfect):
|
||||
errs.append({"category": "pperfect_oob", "message": "p_perfect 载体域 {100,200}: %d" % p_perfect})
|
||||
if seal_hit_premium < 100 or seal_hit_premium > 999:
|
||||
errs.append({"category": "domain", "message": "seal_hit_premium 越域 [100,999](100=无溢价): %d" % seal_hit_premium})
|
||||
if core == null or damage_roll == null:
|
||||
errs.append({"category": "missing_segment", "message": "core/damage_roll 段缺失——settlement 表须两段嵌套(core=%s, damage_roll=%s)" % [core != null, damage_roll != null]})
|
||||
return errs
|
||||
_validate_scalars(errs)
|
||||
_validate_roll_scalars(errs)
|
||||
_validate_frozen(errs)
|
||||
_validate_tier_table(errs)
|
||||
_validate_armor_matrix(errs)
|
||||
return errs
|
||||
|
||||
|
||||
# 标量域(当量参考敞口/完美封印载体/封印溢价/掷点流标签)
|
||||
func _validate_scalars(errs: Array[Dictionary]) -> void:
|
||||
if damage_roll.equivalence_ref_damage < 1 or damage_roll.equivalence_ref_damage > 999:
|
||||
errs.append({"category": "domain", "message": "equivalence_ref_damage 越域 [1,999]: %d" % damage_roll.equivalence_ref_damage})
|
||||
if not P_PERFECT_DOMAIN.has(core.p_perfect):
|
||||
errs.append({"category": "pperfect_oob", "message": "p_perfect 载体域 {100,200}: %d" % core.p_perfect})
|
||||
if core.seal_hit_premium < 100 or core.seal_hit_premium > 999:
|
||||
errs.append({"category": "domain", "message": "seal_hit_premium 越域 [100,999](100=无溢价): %d" % core.seal_hit_premium})
|
||||
if String(damage_roll.armor_roll_stream) == "":
|
||||
errs.append({"category": "domain", "message": "armor_roll_stream 空"})
|
||||
|
||||
|
||||
# 掷点标量域(骰面/暴击触发与倍率/伤害下限/攻击加成)
|
||||
func _validate_roll_scalars(errs: Array[Dictionary]) -> void:
|
||||
var dr := damage_roll
|
||||
if dr.roll_faces < 2 or dr.roll_faces > 1000:
|
||||
errs.append({"category": "domain", "message": "roll_faces 越域 [2,1000]: %d" % dr.roll_faces})
|
||||
if dr.crit_roll < 1 or dr.crit_roll > dr.roll_faces:
|
||||
errs.append({"category": "domain", "message": "crit_roll 越域 [1,roll_faces(%d)]: %d" % [dr.roll_faces, dr.crit_roll]})
|
||||
if dr.crit_multiplier < 100 or dr.crit_multiplier > 999:
|
||||
errs.append({"category": "domain", "message": "crit_multiplier 越域 [100,999]: %d" % dr.crit_multiplier})
|
||||
if dr.damage_floor < 1 or dr.damage_floor > 999:
|
||||
errs.append({"category": "domain", "message": "damage_floor 越域 [1,999]: %d" % dr.damage_floor})
|
||||
if dr.attack_bonus < 0 or dr.attack_bonus > 999:
|
||||
errs.append({"category": "domain", "message": "attack_bonus 越域 [0,999]: %d" % dr.attack_bonus})
|
||||
|
||||
|
||||
# 结构冻结(段序恒等〔主表〕/取整规则与池钳位〔core 段〕)
|
||||
func _validate_frozen(errs: Array[Dictionary]) -> void:
|
||||
var seg := Array(segment_order)
|
||||
if seg != FROZEN_SEGMENT_ORDER:
|
||||
errs.append({"category": "segorder_violation", "message": "segment_order 违例——恒等拍板段序校验失败: %s" % str(seg)})
|
||||
if round_direction != "floor":
|
||||
errs.append({"category": "frozen", "message": "round_direction 结构冻结 floor: %s" % round_direction})
|
||||
if pool_clamp_lo != -100 or pool_clamp_hi != 100:
|
||||
if core.round_direction != "floor":
|
||||
errs.append({"category": "frozen", "message": "round_direction 结构冻结 floor: %s" % core.round_direction})
|
||||
if core.pool_clamp_lo != -100 or core.pool_clamp_hi != 100:
|
||||
errs.append({"category": "frozen", "message": "pool_clamp 域结构冻结 [-100,+100]"})
|
||||
if roll_faces < 2 or roll_faces > 1000:
|
||||
errs.append({"category": "domain", "message": "roll_faces 越域 [2,1000]: %d" % roll_faces})
|
||||
if crit_roll < 1 or crit_roll > roll_faces:
|
||||
errs.append({"category": "domain", "message": "crit_roll 越域 [1,roll_faces(%d)]: %d" % [roll_faces, crit_roll]})
|
||||
if crit_multiplier < 100 or crit_multiplier > 999:
|
||||
errs.append({"category": "domain", "message": "crit_multiplier 越域 [100,999]: %d" % crit_multiplier})
|
||||
if damage_floor < 1 or damage_floor > 999:
|
||||
errs.append({"category": "domain", "message": "damage_floor 越域 [1,999]: %d" % damage_floor})
|
||||
if attack_bonus < 0 or attack_bonus > 999:
|
||||
errs.append({"category": "domain", "message": "attack_bonus 越域 [0,999]: %d" % attack_bonus})
|
||||
if String(armor_roll_stream) == "":
|
||||
errs.append({"category": "domain", "message": "armor_roll_stream 空"})
|
||||
# 档位表形状:bounds 升序且 ∈ [1, faces-1];discounts 降序且 ∈ [1,99](无零档——非归零);
|
||||
# 档数 = 边界数 + 1(缺口 [1,b0] → discounts[0],(b_i,b_{i+1}] → discounts[i+1],> b 末值 → 末位档)
|
||||
if roll_tier_bounds.is_empty() or roll_tier_discounts.size() != roll_tier_bounds.size() + 1:
|
||||
errs.append({"category": "cross_field", "message": "roll_tier_bounds/discounts 形状违例(档数须 = 边界数 + 1): %d 边界 vs %d 档" % [roll_tier_bounds.size(), roll_tier_discounts.size()]})
|
||||
else:
|
||||
for i in roll_tier_bounds.size():
|
||||
var b: int = int(roll_tier_bounds[i])
|
||||
if b < 1 or b >= roll_faces or (i > 0 and b <= int(roll_tier_bounds[i - 1])):
|
||||
errs.append({"category": "cross_field", "message": "roll_tier_bounds[%d] 越域/非升序: %d" % [i, b]})
|
||||
var d: int = int(roll_tier_discounts[i])
|
||||
if d < 1 or d > 99 or (i > 0 and d >= int(roll_tier_discounts[i - 1])):
|
||||
errs.append({"category": "cross_field", "message": "roll_tier_discounts[%d] 越域/非降序(档位不设零档): %d" % [i, d]})
|
||||
|
||||
|
||||
# 档位表形状:bounds 升序且 ∈ [1, faces-1];discounts 降序且 ∈ [1,99](无零档——非归零);
|
||||
# 档数 = 边界数 + 1(缺口 [1,b0] → discounts[0],(b_i,b_{i+1}] → discounts[i+1],> b 末值 → 末位档)
|
||||
func _validate_tier_table(errs: Array[Dictionary]) -> void:
|
||||
var bounds := damage_roll.roll_tier_bounds
|
||||
var discounts := damage_roll.roll_tier_discounts
|
||||
if bounds.is_empty() or discounts.size() != bounds.size() + 1:
|
||||
errs.append({"category": "cross_field", "message": "roll_tier_bounds/discounts 形状违例(档数须 = 边界数 + 1): %d 边界 vs %d 档" % [bounds.size(), discounts.size()]})
|
||||
return
|
||||
for i in bounds.size():
|
||||
var b: int = int(bounds[i])
|
||||
if b < 1 or b >= damage_roll.roll_faces or (i > 0 and b <= int(bounds[i - 1])):
|
||||
errs.append({"category": "cross_field", "message": "roll_tier_bounds[%d] 越域/非升序: %d" % [i, b]})
|
||||
var d: int = int(discounts[i])
|
||||
if d < 1 or d > 99 or (i > 0 and d >= int(discounts[i - 1])):
|
||||
errs.append({"category": "cross_field", "message": "roll_tier_discounts[%d] 越域/非降序(档位不设零档): %d" % [i, d]})
|
||||
|
||||
|
||||
# 克制矩阵 3×3 满格域 [-3,+3](缺项回退 -99 即越域报错——键集满格结构冻结)
|
||||
func _validate_armor_matrix(errs: Array[Dictionary]) -> void:
|
||||
for atk_type in ATTACK_TYPE_KEYS:
|
||||
var row: Dictionary[Variant, Variant] = armor_effectiveness.get(atk_type, {})
|
||||
var row: Dictionary[Variant, Variant] = damage_roll.armor_effectiveness.get(atk_type, {})
|
||||
for armor in ARMOR_KEYS:
|
||||
var pct: int = int(row.get(armor, -99))
|
||||
if pct < -3 or pct > 3:
|
||||
errs.append({"category": "domain", "message": "armor_effectiveness.%s.%s 越域 [-3,+3]: %d" % [atk_type, armor, pct]})
|
||||
return errs
|
||||
@@ -1,13 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="SettlementTable" load_steps=2 format=3]
|
||||
[gd_resource type="Resource" script_class="SettlementTable" load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://data/schemas/settlement_table.gd" id="1"]
|
||||
[ext_resource type="Script" path="res://data/schemas/settlement_core_table.gd" id="2"]
|
||||
[ext_resource type="Script" path="res://data/schemas/damage_roll_table.gd" id="3"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
equivalence_ref_damage = 4
|
||||
p_perfect = 200
|
||||
seal_hit_premium = 125
|
||||
segment_order = PackedStringArray("grant", "buff_debuff", "combat", "decay", "verdict")
|
||||
[sub_resource type="Resource" id="SettlementCoreTable_core"]
|
||||
script = ExtResource("2")
|
||||
source_label_map = {
|
||||
"safe_cell": "安全格",
|
||||
"seal_hit": "封印命中",
|
||||
@@ -28,6 +26,12 @@ effect_pool_map = {
|
||||
pool_clamp_lo = -100
|
||||
pool_clamp_hi = 100
|
||||
round_direction = "floor"
|
||||
p_perfect = 200
|
||||
seal_hit_premium = 125
|
||||
|
||||
[sub_resource type="Resource" id="DamageRollTable_roll"]
|
||||
script = ExtResource("3")
|
||||
equivalence_ref_damage = 4
|
||||
armor_effectiveness = {
|
||||
"normal": {
|
||||
"light": 0,
|
||||
@@ -54,6 +58,12 @@ crit_multiplier = 200
|
||||
damage_floor = 1
|
||||
armor_roll_stream = "armor_roll"
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
segment_order = PackedStringArray("grant", "buff_debuff", "combat", "decay", "verdict")
|
||||
core = SubResource("SettlementCoreTable_core")
|
||||
damage_roll = SubResource("DamageRollTable_roll")
|
||||
|
||||
; 攻击类型 × 护甲克制批(2026-09-12 用户拍板结构;2026-09-15 命名统一批 + 抽象分离批,数值 provisional 待用户/C3 调参):
|
||||
; 主角攻击 = 单一基础分量(2026-09-19 充能退役批·用户拍板——原三分量模型退役,克制策略归冒险前配置:
|
||||
; 类型 = hero 表 attack_type,营地换型 = 改字段;局内无挖掘攒弹药的第二供给)。
|
||||
|
||||
@@ -133,7 +133,7 @@ func _flush_pending_agg() -> void:
|
||||
func _source_label(src_key: String) -> String:
|
||||
if src_key == "":
|
||||
return ""
|
||||
return String(_db.settlement.source_label_map.get(src_key, src_key))
|
||||
return String(_db.settlement.core.source_label_map.get(src_key, src_key))
|
||||
|
||||
|
||||
func _gold_frame(text: String) -> String:
|
||||
@@ -171,7 +171,7 @@ func _format_row(row: Dictionary, agg_count := -1) -> String:
|
||||
if row.get("roll", null) != null:
|
||||
roll_txt = "〔掷 %d vs 护甲 %d" % [int(row.roll), int(row.ac)]
|
||||
if bool(row.get("crit", false)):
|
||||
roll_txt += " · 暴击×%d" % int(_db.settlement.crit_multiplier)
|
||||
roll_txt += " · 暴击×%d" % int(_db.settlement.damage_roll.crit_multiplier)
|
||||
elif int(row.tier) < 100:
|
||||
roll_txt += " · ×%d%%" % int(row.tier)
|
||||
roll_txt += "〕"
|
||||
|
||||
+10
-10
@@ -53,17 +53,17 @@ func _initialize() -> void:
|
||||
|
||||
func _probe_boss(db, seed_val: int) -> void:
|
||||
var math = load("res://core/damage_math.gd")
|
||||
var clamp_lo: int = int(db.settlement.pool_clamp_lo)
|
||||
var clamp_hi: int = int(db.settlement.pool_clamp_hi)
|
||||
var clamp_lo: int = int(db.settlement.core.pool_clamp_lo)
|
||||
var clamp_hi: int = int(db.settlement.core.pool_clamp_hi)
|
||||
var hero_def_negative := 0
|
||||
for eff_id in db.settlement.effect_pool_map.keys():
|
||||
if String(db.settlement.effect_pool_map[eff_id]) != "hero_defense":
|
||||
for eff_id in db.settlement.core.effect_pool_map.keys():
|
||||
if String(db.settlement.core.effect_pool_map[eff_id]) != "hero_defense":
|
||||
continue
|
||||
var row: Dictionary = db.status_effect_row(eff_id)
|
||||
if not row.is_empty() and String(row.get("unit", "")) == "percent":
|
||||
hero_def_negative = mini(hero_def_negative, int(row.get("magnitude", 0)))
|
||||
print("表值面:hero_defense 池最负 percent 行 %d · pool_clamp [%d, %d] · damage_floor %d · AC 基准 %d(+ac_up×%d 层)" % [
|
||||
hero_def_negative, clamp_lo, clamp_hi, int(db.settlement.damage_floor), int(db.hero.armor_class), int(db.status_effect_row("ac_up").get("max_stack", 0))])
|
||||
hero_def_negative, clamp_lo, clamp_hi, int(db.settlement.damage_roll.damage_floor), int(db.hero.armor_class), int(db.status_effect_row("ac_up").get("max_stack", 0))])
|
||||
|
||||
print("--- Run A:fresh 池(零挖掘停手轮——无授予,攻击链裸验)---")
|
||||
var run_a := _run_boss(db, seed_val, false)
|
||||
@@ -175,11 +175,11 @@ func _analyze_round(db, math, rows: Array, beats: Dictionary) -> Dictionary:
|
||||
# 池归属表驱动(settlement.effect_pool_map),percent 池加算后 clamp(战斗结算 F2 同口径)
|
||||
var pools := {"hero_defense": 0, "enemy_output": 0, "hero_offense": 0, "flat_attach": 0}
|
||||
for eid in last_pool:
|
||||
var pool_name: String = String(db.settlement.effect_pool_map.get(eid, ""))
|
||||
var pool_name: String = String(db.settlement.core.effect_pool_map.get(eid, ""))
|
||||
if pools.has(pool_name):
|
||||
pools[pool_name] += int(last_pool[eid])
|
||||
var clamp_lo: int = int(db.settlement.pool_clamp_lo)
|
||||
var clamp_hi: int = int(db.settlement.pool_clamp_hi)
|
||||
var clamp_lo: int = int(db.settlement.core.pool_clamp_lo)
|
||||
var clamp_hi: int = int(db.settlement.core.pool_clamp_hi)
|
||||
var pd: int = clampi(int(pools["hero_defense"]), clamp_lo, clamp_hi)
|
||||
var pe: int = clampi(int(pools["enemy_output"]), clamp_lo, clamp_hi)
|
||||
var lines: Array = []
|
||||
@@ -194,11 +194,11 @@ func _analyze_round(db, math, rows: Array, beats: Dictionary) -> Dictionary:
|
||||
# 掷点口径重建:基线按行留档档位打折(暴击 ×倍率),附加必中
|
||||
var tier: int = int(row.get("tier", 100))
|
||||
var crit: bool = bool(row.get("crit", false))
|
||||
var base_factor: float = float(tier) * (float(db.settlement.crit_multiplier) / 100.0 if crit else 1.0)
|
||||
var base_factor: float = float(tier) * (float(db.settlement.damage_roll.crit_multiplier) / 100.0 if crit else 1.0)
|
||||
var rolled_base := int(floor(float(baseline) * base_factor / 100.0))
|
||||
var raw: int = rolled_base + attach
|
||||
var tick: int = int(row.get("value", 0))
|
||||
var expected: int = maxi(int(db.settlement.damage_floor), math.pool_damage(raw, pd, pe))
|
||||
var expected: int = maxi(int(db.settlement.damage_roll.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" % [
|
||||
|
||||
@@ -79,12 +79,12 @@ func _run_recipe_sequence(db) -> Array:
|
||||
for e in units.all_enemies():
|
||||
enemy_hp_before.append(int(e.hp))
|
||||
var front = units.default_target()
|
||||
var threshold_expected: int = ctrl.defender_ac(front) + int(h.db.settlement.attack_bonus)
|
||||
var threshold_expected: int = ctrl.defender_ac(front) + int(h.db.settlement.damage_roll.attack_bonus)
|
||||
var atk_type := ""
|
||||
var eff_keys: Array = h.db.settlement.armor_effectiveness.keys()
|
||||
var eff_keys: Array = h.db.settlement.damage_roll.armor_effectiveness.keys()
|
||||
if eff_keys.size() > 0:
|
||||
atk_type = String(eff_keys[0])
|
||||
var floor_v: int = int(h.db.settlement.damage_floor)
|
||||
var floor_v: int = int(h.db.settlement.damage_roll.damage_floor)
|
||||
|
||||
var t1: int = ctrl.submit(ctrl.open_ticket(DamageControllerScript.KIND_HERO_ATTACK, "hero_attack",
|
||||
"T1主角攻击", "战斗段", int(h.db.hero.atk), DamageControllerScript.TARGET_FRONT_ALIVE, 1))
|
||||
@@ -123,9 +123,9 @@ func _run_recipe_sequence(db) -> Array:
|
||||
var r1: Dictionary = _row_by_display_prefix("T1主角攻击")
|
||||
var ex1: Dictionary = r1.get("extra", {})
|
||||
Kit.check("主角攻击行 AC = 防守方 AC + 攻击加成(开单前快照一致)", int(ex1.get("ac", -1)) == threshold_expected)
|
||||
Kit.check("掷点 ∈ [1, 骰面]", int(ex1.get("roll", -1)) >= 1 and int(ex1.get("roll", -1)) <= int(h.db.settlement.roll_faces))
|
||||
Kit.check("掷点 ∈ [1, 骰面]", int(ex1.get("roll", -1)) >= 1 and int(ex1.get("roll", -1)) <= int(h.db.settlement.damage_roll.roll_faces))
|
||||
var tier_ok := false
|
||||
for dv in h.db.settlement.roll_tier_discounts:
|
||||
for dv in h.db.settlement.damage_roll.roll_tier_discounts:
|
||||
if int(dv) == int(ex1.get("tier", -1)):
|
||||
tier_ok = true
|
||||
if int(ex1.get("tier", -1)) == 100:
|
||||
@@ -158,7 +158,7 @@ func _run_recipe_sequence(db) -> Array:
|
||||
continue
|
||||
if String(r.source_class).is_empty() or String(r.display).is_empty():
|
||||
protocol_ok = false
|
||||
if int(r.value) < int(h.db.settlement.damage_floor):
|
||||
if int(r.value) < int(h.db.settlement.damage_roll.damage_floor):
|
||||
protocol_ok = false
|
||||
if not (r.extra is Dictionary) or not r.extra.has("target"):
|
||||
protocol_ok = false
|
||||
@@ -186,7 +186,7 @@ func _test_policy_immediate_mode(db) -> void:
|
||||
if not h.spawn_ok:
|
||||
return
|
||||
h.ctrl.policy = DamageControllerScript.POLICY_IMMEDIATE
|
||||
var floor_v: int = int(h.db.settlement.damage_floor)
|
||||
var floor_v: int = int(h.db.settlement.damage_roll.damage_floor)
|
||||
var t: int = h.ctrl.submit(h.ctrl.open_ticket(DamageControllerScript.KIND_BOARD_FLAT_HERO, "mine_hit",
|
||||
"A模式反噬", "棋盘兑付", floor_v, DamageControllerScript.TARGET_HERO, 1))
|
||||
Kit.check("A 模式开单即结(无 flush 即 redeemed)", h.ctrl.outcome(t) == "redeemed")
|
||||
@@ -282,7 +282,7 @@ func _test_requires_gate_and_zero(db) -> void:
|
||||
var h = _harness(db)
|
||||
if not h.spawn_ok:
|
||||
return
|
||||
var floor_v: int = int(h.db.settlement.damage_floor)
|
||||
var floor_v: int = int(h.db.settlement.damage_roll.damage_floor)
|
||||
# 先清场(flush_submit 同步开单——清图批专用通道的契约面同时被覆盖)
|
||||
var front_hp: int = int(h.units.query_alive("enemy")[0].max_hp)
|
||||
var big: int = front_hp * 10
|
||||
@@ -326,7 +326,7 @@ func _test_ac_merge(db) -> void:
|
||||
return
|
||||
var base_ac: int = int(h.db.hero.armor_class)
|
||||
Kit.check("缺省兜底:defender_ac(hero) = hero 表值", h.ctrl.defender_ac(h.units.hero_unit()) == base_ac)
|
||||
var override_v: int = base_ac + int(h.db.settlement.damage_floor)
|
||||
var override_v: int = base_ac + int(h.db.settlement.damage_roll.damage_floor)
|
||||
h.ctrl.set_hero_ac_override(override_v)
|
||||
Kit.check("override 合流入参生效", h.ctrl.defender_ac(h.units.hero_unit()) == override_v)
|
||||
# ac 型状态效果叠其上(找 hero 侧 unit=ac 行)
|
||||
@@ -351,15 +351,15 @@ func _test_pool_clamp(db) -> void:
|
||||
var h = _harness(db)
|
||||
if not h.spawn_ok:
|
||||
return
|
||||
var lo: int = int(h.db.settlement.pool_clamp_lo)
|
||||
var hi: int = int(h.db.settlement.pool_clamp_hi)
|
||||
var lo: int = int(h.db.settlement.core.pool_clamp_lo)
|
||||
var hi: int = int(h.db.settlement.core.pool_clamp_hi)
|
||||
Kit.check("空效果池读 0(表 clamp 域含 0 前提)", h.ctrl.clamped_pool("hero_offense") == 0 and lo <= 0 and hi >= 0)
|
||||
# 找 hero_offense 池的 percent 型 hero 侧效果行
|
||||
var pool_eff := {}
|
||||
for row in h.db.status_effects.rows:
|
||||
var eid := String(row.get("effect_id", ""))
|
||||
if String(row.get("unit", "")) == "percent" and String(row.get("target_side", "")) == "hero" \
|
||||
and String(h.db.settlement.effect_pool_map.get(eid, "")) == "hero_offense":
|
||||
and String(h.db.settlement.core.effect_pool_map.get(eid, "")) == "hero_offense":
|
||||
pool_eff = row
|
||||
break
|
||||
if pool_eff.is_empty():
|
||||
|
||||
@@ -24,7 +24,7 @@ func _init() -> void:
|
||||
|
||||
|
||||
func _floor_val() -> int:
|
||||
return int(db.settlement.damage_floor)
|
||||
return int(db.settlement.damage_roll.damage_floor)
|
||||
|
||||
|
||||
# ---- F2 乘区:池内加算 → 池间乘算 → floor(单一取整规则)----
|
||||
@@ -32,11 +32,11 @@ func _floor_val() -> int:
|
||||
func _test_pool_damage() -> void:
|
||||
var d := _floor_val() + 9 # 表值推导的任意正探针量(非玩法行值)
|
||||
Kit.check("中性双池 (0,0) 原样透传", DamageMathScript.pool_damage(d, 0, 0) == d)
|
||||
Kit.check("负敌方池压低不增高", DamageMathScript.pool_damage(d, 0, int(db.settlement.pool_clamp_lo)) <= d)
|
||||
Kit.check("正·主角池抬升不低于原值", DamageMathScript.pool_damage(d, int(db.settlement.pool_clamp_hi), 0) >= d)
|
||||
Kit.check("负敌方池压低不增高", DamageMathScript.pool_damage(d, 0, int(db.settlement.core.pool_clamp_lo)) <= d)
|
||||
Kit.check("正·主角池抬升不低于原值", DamageMathScript.pool_damage(d, int(db.settlement.core.pool_clamp_hi), 0) >= d)
|
||||
Kit.check("主角池单调不减(+50% vs +49% 探针档)", DamageMathScript.pool_damage(d, 50, 0) >= DamageMathScript.pool_damage(d, 49, 0))
|
||||
Kit.check("对称性:池值互换结果不变(乘算交换)", DamageMathScript.pool_damage(d, 30, -20) == DamageMathScript.pool_damage(d, -20, 30))
|
||||
Kit.check("输出非负(maxi(0,·) 封底)", DamageMathScript.pool_damage(d, int(db.settlement.pool_clamp_lo), int(db.settlement.pool_clamp_lo)) >= 0)
|
||||
Kit.check("输出非负(maxi(0,·) 封底)", DamageMathScript.pool_damage(d, int(db.settlement.core.pool_clamp_lo), int(db.settlement.core.pool_clamp_lo)) >= 0)
|
||||
|
||||
|
||||
# ---- 克制分量:空桶不吃修正恒 0;落地下限保底([1,+∞) 全来源)----
|
||||
@@ -54,8 +54,8 @@ func _test_counter_component() -> void:
|
||||
|
||||
func _test_roll_discount_percent() -> void:
|
||||
var threshold := 10 # 查档探针阈值(夹具值——档位参数本身全部来自表)
|
||||
var bounds: Array = db.settlement.roll_tier_bounds
|
||||
var discounts: Array = db.settlement.roll_tier_discounts
|
||||
var bounds: Array = db.settlement.damage_roll.roll_tier_bounds
|
||||
var discounts: Array = db.settlement.damage_roll.roll_tier_discounts
|
||||
Kit.check("前置:档位数 = 边界数 + 1(查档契约——档数 = 边界数 + 1)", discounts.size() == bounds.size() + 1 and discounts.size() > 0)
|
||||
Kit.check("掷点 ≥ 阈值恒全额 100", DamageMathScript.roll_discount_percent(threshold, threshold, bounds, discounts) == 100
|
||||
and DamageMathScript.roll_discount_percent(threshold + 5, threshold, bounds, discounts) == 100)
|
||||
@@ -78,7 +78,7 @@ func _test_roll_discount_percent() -> void:
|
||||
|
||||
func _test_rolled_component() -> void:
|
||||
var base := float(_floor_val() + 7) # 表值推导探针基线
|
||||
var crit_mult: int = int(db.settlement.crit_multiplier)
|
||||
var crit_mult: int = int(db.settlement.damage_roll.crit_multiplier)
|
||||
Kit.check("满额折扣无暴击 = floor(基线) + 修正 0", DamageMathScript.rolled_component(base, 100, false, crit_mult, 0) == int(floor(base)))
|
||||
Kit.check("满额折扣暴击按表倍率放大", DamageMathScript.rolled_component(base, 100, true, crit_mult, 0) == int(floor(base * float(crit_mult) / 100.0)))
|
||||
Kit.check("折扣降低不高于满额", DamageMathScript.rolled_component(base, 50, false, crit_mult, 0) <= DamageMathScript.rolled_component(base, 100, false, crit_mult, 0))
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://ehnthaqk7h12
|
||||
@@ -109,7 +109,7 @@ func _smoke_armor_roll() -> void:
|
||||
var dist := {100: 0, 75: 0, 50: 0, 25: 0}
|
||||
var tier_ok := true
|
||||
for roll in range(1, 21):
|
||||
var d: int = DamageMath.roll_discount_percent(roll, cfg.ac, _db.settlement.roll_tier_bounds, _db.settlement.roll_tier_discounts)
|
||||
var d: int = DamageMath.roll_discount_percent(roll, cfg.ac, _db.settlement.damage_roll.roll_tier_bounds, _db.settlement.damage_roll.roll_tier_discounts)
|
||||
if not dist.has(d):
|
||||
tier_ok = false
|
||||
continue
|
||||
@@ -117,7 +117,7 @@ func _smoke_armor_roll() -> void:
|
||||
Kit.check("查档 AC%d 全掷点域分布(100/75/50/25 = %s)" % [cfg.ac, str([dist[100], dist[75], dist[50], dist[25]])], tier_ok and dist == cfg.expect)
|
||||
var ev_sum := 0
|
||||
for roll in range(1, 21):
|
||||
ev_sum += DamageMath.roll_discount_percent(roll, 10, _db.settlement.roll_tier_bounds, _db.settlement.roll_tier_discounts)
|
||||
ev_sum += DamageMath.roll_discount_percent(roll, 10, _db.settlement.damage_roll.roll_tier_bounds, _db.settlement.damage_roll.roll_tier_discounts)
|
||||
Kit.check("AC10 EV 锚:期望系数合计 %d/2000 = 期望减免 21.25%%(nat20 恒全额含内)" % ev_sum, ev_sum == 1575)
|
||||
# ② nat20 暴击(行为面):探针按行为面扫描
|
||||
# 「主角先手首掷 = 20」的种子,不假设 armor_roll scope0 位
|
||||
@@ -125,12 +125,12 @@ func _smoke_armor_roll() -> void:
|
||||
var crit_rows := {}
|
||||
for s in range(1, 800):
|
||||
crit_rows = _first_hero_attack_rows(s)
|
||||
if int(crit_rows.get("roll", -1)) == int(_db.settlement.crit_roll):
|
||||
if int(crit_rows.get("roll", -1)) == int(_db.settlement.damage_roll.crit_roll):
|
||||
crit_seed = s
|
||||
break
|
||||
Kit.check("nat20 种子可达(seed=%d——主角先手首掷命中 %d)" % [crit_seed, int(_db.settlement.crit_roll)], crit_seed > 0)
|
||||
Kit.check("nat20 种子可达(seed=%d——主角先手首掷命中 %d)" % [crit_seed, int(_db.settlement.damage_roll.crit_roll)], crit_seed > 0)
|
||||
Kit.check("nat20 → 行留档 roll=20 + crit 标记(主角先手普通分量)",
|
||||
int(crit_rows.get("roll", -1)) == int(_db.settlement.crit_roll) and bool(crit_rows.get("crit", false)))
|
||||
int(crit_rows.get("roll", -1)) == int(_db.settlement.damage_roll.crit_roll) and bool(crit_rows.get("crit", false)))
|
||||
Kit.check("nat20 → 普通分量 ×2(%d = 2×%d,暴击倍率只乘该分量,穿刺/魔法不吃)" % [int(crit_rows.get("normal", -1)), int(_db.hero.atk)],
|
||||
int(crit_rows.get("normal", -1)) == 2 * int(_db.hero.atk) and int(crit_rows.get("value", -1)) == 2 * int(_db.hero.atk))
|
||||
# ③ 削弱续时(行为面):授予→衰减一轮→再授予 → 层数保持 1、剩余结算数重置
|
||||
@@ -172,7 +172,7 @@ func _smoke_armor_roll() -> void:
|
||||
Kit.check("棋盘来源伤害行零掷点字段(爆裂/直伤/清障炸药/奖励即时必中全额)", board_ok)
|
||||
# ⑥ 全来源伤害下限(任何伤害落地取值域 [1,+∞) 无上限——棋盘克制直伤/反噬/地形变异/爆裂
|
||||
# 全部收在 damage_floor 闸内)
|
||||
var df: int = int(_db.settlement.damage_floor)
|
||||
var df: int = int(_db.settlement.damage_roll.damage_floor)
|
||||
Kit.check("棋盘克制直伤贴底 → 下限 1(点数 2 vs 不利修正 −1)", DamageMath.counter_component(2, -1, df) == 1)
|
||||
Kit.check("克制修正触底穿透 → 下限 1(点数 1 vs 修正 −3)", DamageMath.counter_component(1, -3, df) == 1)
|
||||
Kit.check("空桶不吃修正恒 0(零点数分量无伤害可落地——边界不变不受下限影响)", DamageMath.counter_component(0, 5, df) == 0)
|
||||
@@ -351,7 +351,7 @@ func _smoke_damage_types() -> void:
|
||||
Kit.check("ogre_chief 护甲 = light(Boss 行集——护甲类型重配批 2026-09-18:关卡 1 全轻甲)",
|
||||
enemy_system_c.spawn(1, 4, int(_db.hero.max_hp), [], -1, -1, [], "", "boss") and String(enemy_system_c.all_enemies()[0].armor_type) == "light")
|
||||
# ② 克制矩阵表值在位(三档点数 克制 +1 / 中性 0 / 被克 −1,加法核非 percent)
|
||||
var am: Dictionary = _db.settlement.armor_effectiveness
|
||||
var am: Dictionary = _db.settlement.damage_roll.armor_effectiveness
|
||||
Kit.check("普通行:轻甲 0 / 魔法护甲 +1 / 重甲 −1(加法核)",
|
||||
int(am.normal.light) == 0 and int(am.normal.ward) == 1 and int(am.normal.heavy) == -1)
|
||||
Kit.check("穿刺行:轻甲 −1 / 魔法护甲 +1 / 重甲 0 中性(用户拍板)",
|
||||
@@ -452,9 +452,9 @@ func _smoke_attack_chain_fresh_pool() -> void:
|
||||
Kit.check("非施放攻击拍采样充足(%d 拍——增益施放行零兑付为设计语义不计)" % ticks.size(), ticks.size() >= 2)
|
||||
var all_full := true
|
||||
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 base_factor: float = float(t.tier) * (float(_db.settlement.damage_roll.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.get("attach", 0)))
|
||||
var expected: int = maxi(int(_db.settlement.damage_roll.damage_floor), rolled_base + int(t.get("attach", 0)))
|
||||
if int(t.tick) != expected or int(t.tick) <= 0:
|
||||
all_full = false
|
||||
Kit.check("fresh 池非蓄力拍 d_tick = max(1, 掷点档位基线)(Boss 攻击链健康——Bug#2 回归断言)", all_full)
|
||||
@@ -667,8 +667,8 @@ func _smoke_skill_pool_data() -> void:
|
||||
Kit.check("蓄力状态行 empower(enemy +100 percent——敌方输出 ×2 走既有 enemy_output 池)", not empower.is_empty() and String(empower.target_side) == "enemy" and int(empower.magnitude) == 100)
|
||||
Kit.check("硬化状态行 fortify(enemy −50 percent——敌方防御池负值 = 主角攻击折半;+100 符号 bug 2026-09-18 纠正,provisional 归 状态层重设计批)", not fortify.is_empty() and String(fortify.target_side) == "enemy" and int(fortify.magnitude) == -50)
|
||||
Kit.check("effect_pool_map 收编 empower/fortify(敌方防御池 enemy_defense 入表)",
|
||||
String(_db.settlement.effect_pool_map.get("empower", "")) == "enemy_output" \
|
||||
and String(_db.settlement.effect_pool_map.get("fortify", "")) == "enemy_defense")
|
||||
String(_db.settlement.core.effect_pool_map.get("empower", "")) == "enemy_output" \
|
||||
and String(_db.settlement.core.effect_pool_map.get("fortify", "")) == "enemy_defense")
|
||||
|
||||
|
||||
# ② 同种子回放逐位复现(D3 大地精,多实例同种)
|
||||
@@ -900,7 +900,7 @@ func _smoke_debuff_rider() -> void:
|
||||
var snare_seed := -1
|
||||
var base_ac: int = int(_db.hero.armor_class)
|
||||
var off_m: int = int(_db.status_effect_row("off_guard").magnitude)
|
||||
var floor_v: int = int(_db.settlement.damage_floor)
|
||||
var floor_v: int = int(_db.settlement.damage_roll.damage_floor)
|
||||
for s in range(1, 401):
|
||||
if venom_seed > 0 and snare_seed > 0:
|
||||
break
|
||||
@@ -932,7 +932,7 @@ func _smoke_debuff_rider() -> void:
|
||||
if rider_v != maxi(floor_v, int(floor(float(boss_atk) * 0.75))):
|
||||
venom_check_ok = false
|
||||
# 终值精确复算(停手轮池恒空):主分量链结果 + 必中附加后置
|
||||
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 base_factor: float = float(int(row.tier)) * (float(_db.settlement.damage_roll.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.get("attach", 0))) + rider_v
|
||||
if int(row.value) != expected:
|
||||
|
||||
@@ -494,7 +494,7 @@ func _smoke_polarity_routing() -> void:
|
||||
# δ 读 settlement.seal_hit_premium(125 provisional);九型正向行值全固定(唯一区间行 clear_blast
|
||||
# 系清图槽不入格池)⇒ 逐型期望全确定。整数量级四舍五入域注记:ac_up/action 1 点量纲取整后不变
|
||||
# =整数粒度地板——等号压线判读在当量聚合面(audit 工具/registry clause3_inequality),非断言面。
|
||||
var prem_pct: int = int(_db.settlement.seal_hit_premium)
|
||||
var prem_pct: int = int(_db.settlement.core.seal_hit_premium)
|
||||
Kit.check("封印溢价配置在位(seal_hit_premium=125 provisional——形态 a 兑付溢价)", prem_pct == 125)
|
||||
var prem_rows := 0
|
||||
var prem_ok := true
|
||||
@@ -676,7 +676,7 @@ func _smoke_negative_dictionary() -> void:
|
||||
not sapped.is_empty() and String(sapped.get("target_side", "")) == "hero" \
|
||||
and int(sapped.get("magnitude", 0)) == -25 and int(sapped.get("duration_settlements", 0)) == 1 \
|
||||
and int(sapped.get("max_stack", 0)) == 16 and String(sapped.get("stack_policy", "")) == "stack")
|
||||
Kit.check("effect_pool_map 收编 sapped → hero_offense(attack_up 镜像池)", String(_db.settlement.effect_pool_map.get("sapped", "")) == "hero_offense")
|
||||
Kit.check("effect_pool_map 收编 sapped → hero_offense(attack_up 镜像池)", String(_db.settlement.core.effect_pool_map.get("sapped", "")) == "hero_offense")
|
||||
# 行为面:逐型负向兑付形态(真实板 + 真实路由路径)
|
||||
# heal 权重低(无 heal 板概率 ≈1/3)——单板五型齐备不再必然:
|
||||
# 本断言多板扫描,取第一块五型齐备的板做行为断言。
|
||||
|
||||
@@ -60,7 +60,7 @@ func _test_damage_controller_vocabularies() -> void:
|
||||
# ---- D2↔B1:掷点用途标签 ∈ RngService 封闭词表(EC12——跨系统字面量契约的成员性断言) ----
|
||||
|
||||
func _test_rng_tag_closure(db) -> void:
|
||||
var stream_label: String = String(db.settlement.armor_roll_stream)
|
||||
var stream_label: String = String(db.settlement.damage_roll.armor_roll_stream)
|
||||
Kit.check("settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)",
|
||||
RngServiceScript.PURPOSE_TAGS.has(stream_label))
|
||||
Kit.check("RngService 用途标签词表非空且互异(封闭语义由流改标签)",
|
||||
|
||||
@@ -282,25 +282,25 @@ func _smoke_ac_swap_guard() -> void:
|
||||
Kit.check("奖励表 defense 正向行 effect_id = ac_up(显示名盾牌加护,权重/tier 不变)",
|
||||
String(ac_reward.get("effect_id", "")) == "ac_up" and String(ac_reward.get("display_name", "")) == "盾牌加护")
|
||||
Kit.check("effect_pool_map 无 defense_up/ac_up 键(减伤不入池——AC 不经乘区)",
|
||||
not _db.settlement.effect_pool_map.has("defense_up") and not _db.settlement.effect_pool_map.has("ac_up"))
|
||||
not _db.settlement.core.effect_pool_map.has("defense_up") and not _db.settlement.core.effect_pool_map.has("ac_up"))
|
||||
# ② 免伤墙归零护栏(泛化——BUG-0003 免疫)
|
||||
var wall_ok := true
|
||||
var wall_detail := ""
|
||||
for eff_id in _db.settlement.effect_pool_map.keys():
|
||||
if String(_db.settlement.effect_pool_map[eff_id]) != "hero_defense":
|
||||
for eff_id in _db.settlement.core.effect_pool_map.keys():
|
||||
if String(_db.settlement.core.effect_pool_map[eff_id]) != "hero_defense":
|
||||
continue
|
||||
var row: Dictionary = _db.status_effect_row(eff_id)
|
||||
if row.is_empty() or String(row.get("unit", "")) != "percent":
|
||||
continue
|
||||
var floor_v: int = int(row.get("magnitude", 0)) * int(row.get("max_stack", 1))
|
||||
if floor_v <= int(_db.settlement.pool_clamp_lo):
|
||||
if floor_v <= int(_db.settlement.core.pool_clamp_lo):
|
||||
wall_ok = false
|
||||
wall_detail += " %s(%d)" % [eff_id, floor_v]
|
||||
Kit.check("hero_defense 池 percent 行叠满不可触池底(墙最深%s > clamp_lo %d——单效果归零结构性不可达)" % [
|
||||
wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.pool_clamp_lo)], wall_ok)
|
||||
wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.core.pool_clamp_lo)], wall_ok)
|
||||
var hd_maps := 0
|
||||
for eff_id in _db.settlement.effect_pool_map.keys():
|
||||
if String(_db.settlement.effect_pool_map[eff_id]) == "hero_defense":
|
||||
for eff_id in _db.settlement.core.effect_pool_map.keys():
|
||||
if String(_db.settlement.core.effect_pool_map[eff_id]) == "hero_defense":
|
||||
hd_maps += 1
|
||||
Kit.check("hero_defense 池映射数==0(绊线钉——红=已加映射,去核上方护栏是否已同步激活)", hd_maps == 0)
|
||||
# ③ 单位表 armor_class:域内校验(配置推导——正式投值归怪物/难度批,改表不断锚)
|
||||
@@ -370,12 +370,12 @@ func _smoke_ac_swap_guard() -> void:
|
||||
Kit.check("第一关相邻深度带总量比 ≤ 2.5(12/10/22/35/72——迭代 24 N1 曲线护栏,带键升序;越带%s)" % [
|
||||
curve_detail if curve_detail != "" else "无"], curve_ok)
|
||||
# ④ 掷点参数域(加载校验已过——此处钉形状语义)
|
||||
var bounds: Array = _db.settlement.roll_tier_bounds
|
||||
var discounts: Array = _db.settlement.roll_tier_discounts
|
||||
var bounds: Array = _db.settlement.damage_roll.roll_tier_bounds
|
||||
var discounts: Array = _db.settlement.damage_roll.roll_tier_discounts
|
||||
Kit.check("掷点参数拍板值在位(d20 / nat20 暴击 ×200%% / 下限 1 / attack_bonus 0 / armor_roll 流)",
|
||||
int(_db.settlement.roll_faces) == 20 and int(_db.settlement.crit_roll) == 20 \
|
||||
and int(_db.settlement.crit_multiplier) == 200 and int(_db.settlement.damage_floor) == 1 \
|
||||
and int(_db.settlement.attack_bonus) == 0 and String(_db.settlement.armor_roll_stream) == "armor_roll")
|
||||
int(_db.settlement.damage_roll.roll_faces) == 20 and int(_db.settlement.damage_roll.crit_roll) == 20 \
|
||||
and int(_db.settlement.damage_roll.crit_multiplier) == 200 and int(_db.settlement.damage_roll.damage_floor) == 1 \
|
||||
and int(_db.settlement.damage_roll.attack_bonus) == 0 and String(_db.settlement.damage_roll.armor_roll_stream) == "armor_roll")
|
||||
Kit.check("档位表拍板值在位(缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25——无零档)",
|
||||
bounds == [3, 7] and discounts == [75, 50, 25])
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ func _sweep_source_names() -> void:
|
||||
row_view._tip_label.text.contains("来源:危险格"))
|
||||
row_view._hide_tip()
|
||||
Kit.check("结算表 source_label_map[mine_hit]==「危险格」(u6 换代——ConfigDB 真装载)〔wa7〕",
|
||||
String(_db.settlement.source_label_map.get("mine_hit", "")) == "危险格")
|
||||
String(_db.settlement.core.source_label_map.get("mine_hit", "")) == "危险格")
|
||||
var log_view = load("res://presentation/views/battle_log_view.gd").new(_db)
|
||||
var row_txt := String(log_view._format_row({"type": "effect", "source_class": "mine_hit",
|
||||
"display_name": "挖掘反噬", "value": -5, "pool_value": -5, "round_index": 1}))
|
||||
|
||||
Reference in new issue
Block a user