# VERTICAL SLICE - NOT FOR PRODUCTION # Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2) # Date: 2026-09-10 # 数据配置 SettlementTable schema(战斗结算 回填值;单行全局,Rule 4 豁免)——八字段口径 + 克制/充能批 + 护甲等级掷点批。 # 三行致命 manifest:segorder_violation / poolmap_dangling / pperfect_oob。 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 segment_order: PackedStringArray = [] # 结构冻结——恒等拍板段序校验违例致命 @export var source_label_map: Dictionary = {} # 结构冻结——source_class→文案键 @export var effect_pool_map: Dictionary = {} # 结构冻结——effect_id→pool(悬空加载致命) @export var pool_clamp_lo: int = -100 # 结构冻结 @export var pool_clamp_hi: int = 100 # 结构冻结 @export var round_direction: String = "floor" # 结构冻结——单一取整规则 # ---- 攻击类型 × 护甲克制批 + 充能奖励定义(2026-09-12 用户拍板结构;2026-09-15 命名统一批 / 抽象分离批;数值 provisional)---- # 主角攻击 = 普通(atk×(1+attack_up),不吃克制)+ 穿刺 + 魔法(后两者由充能供给),三分量各自 floor 后相加。 # 【两条抽象分离,勿焊死】攻击类型 = 伤害的属性轴,只决定查 armor_effectiveness 哪一行,定义里不含任何棋盘来源; # 充能 = 按攻击类型分桶的附加伤害储备,"谁往桶里放东西"属**奖励的定义**(开格/标雷只是奖励触发源,奖励内容可改)。 # 原 damage_a_per_cell / damage_a_cap / damage_b_per_flag 与 charge_grants 充能奖励定义均退役(2026-09-19 充能退役批——攻击模型回归单分量)。 @export var armor_effectiveness: Dictionary = {} # 克制修正矩阵(加法点数 delta):{攻击类型 {normal|pierce|magic}: {护甲 {light|ward|heavy}: 点数 [-3,+3]}} # ---- 护甲等级掷点批(2026-09-16 用户拍板,数字即规范——registry settlement_table.ac_roll/ac_discount_tiers 同批定稿)---- # 判定式:掷 1d20,掷点 ≥ 防守方 AC + 攻击加成 → 全额;低掷按缺口(AC−掷点)查档打折,无 miss、无零收益档; # nat 最大面(crit_roll)= 暴击:该次基础攻击分量 ×2 且恒全额;任何伤害落地全局下限 damage_floor(≥1)—— # 2026-09-20 用户拍板扩全来源(攻击双方/棋盘直伤/地形变异/反噬,取值域 [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 = [] # 缺口档边界升序:缺口 ≥ bounds[i] 落入第 i 档([3,7] = 缺口1-3/4-7/8+ 三档) @export var roll_tier_discounts: Array = [] # 档位值 percent 降序(与 bounds 同长;[75,50,25] = ×0.75/×0.5/×0.25) @export var crit_roll: int = 20 # [1,roll_faces] knob——nat 最大面 = 暴击触发掷点(恒全额) @export var crit_multiplier: int = 200 # [100,999] percent knob——暴击倍率(200 = ×2,只乘该次基础攻击分量) @export var damage_floor: int = 1 # [1,999] knob——任何伤害落地全局下限·全来源("被减伤归零 v=0"行语义随批退役) @export var armor_roll_stream: String = "armor_roll" # 掷点封闭流名(rng_service STREAM_NAMES 成员;逐次掷逐次取号,可逐位回放) const ARMOR_KEYS := ["light", "ward", "heavy"] const ATTACK_TYPE_KEYS := ["normal", "pierce", "magic"] const FROZEN_SEGMENT_ORDER := ["grant", "buff_debuff", "combat", "decay", "verdict"] const P_PERFECT_DOMAIN := [100, 200] func validate() -> Array: var errs: Array = [] 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}) 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: 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]}) for atk_type in ATTACK_TYPE_KEYS: var row: Dictionary = 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