feat(slice): 技能池新效应词汇批——妨害技+必中附加分量(L2 Boss 卓尔实装;多段命中退役;2026-09-19 用户逐项拍板,设计权威 docs/proposal-status-vocabulary.md v1.0 新建)——①三词收敛两词:妨害技=第四技能类型 debuff(0 伤害施放行,字段复用 buff_ref/buff_duration,落点由状态行 target_side 路由——容器零改动;池纪律+每池妨害≤1;施放类 side 守卫 buff→enemy/debuff→hero);固定值伤害改形必中附加分量 rider_mult(域 (0,1],分量值 max(floor, floor(atk×rider)) 随基线缩放——固定值不通用/跨关稀释析出此改形;价位锚 0.75=AC 折扣档最轻档"不掉档的稳定伤害",可掷分量期望 AC10≈0.80/AC12≈0.76 对表,×0.5 永久第三档案经用户质询否决);多段命中退役(无防御磨损体系无对照机制,P1 字段位保留);②实装面=仅 L2 Boss 蛛化卓尔精灵(用户拍板,其余敌种不动):毒爆+必中毒分量(atk3→毒2,atk 区间 2-4→1-3 随开战掷定缩放);新行蛛网束缚 web_snare(debuff→off_guard 失衡〔首发轴用户拍板〕,duration 2=1 有效拍,CD4,池 6/2/2→6/2/2/2 全 provisional);③结算面:三乘法链尾部+rider 后置加法(max(floor,主分量链)+rider,payload rider 键+行文本「+附加N(必中)」);意图徽标三档扩四档(+妨害系 ☠ 紫);④加载校验扩两族(pool_composition 妨害>1 / cast_side 施放类 side 守卫——validate_skill_pool_links 增 status_table 显式入参修装载序 Nil 崩);⑤验证:冒烟 306→317 项 0 失败(新探针 _smoke_debuff_rider 四断言:毒爆行逐拍精确复算/rider 键封闭/蛛网束缚落主角端到端/种子可达+校验拒绝组六断言)+四探针 PASS;顺手修并行 board_type 批漏网(smoke variant_pool Array→String 字面量,原断言被 SCRIPT ERROR 静默吞);800 局模拟试玩(两关×20 种子×双画像 4200-4219):L2 基线通关 90%→80%、剩余死因 100% 集中 Boss(设计方向生效)、台阶倒挂收窄 65vs90→65vs80,L1 65%/85% 与批前一致零行为变更回归;analyze_playtest.py 反事实重算+rider 识别(敌方招式成分,无奖励世界只剥伏击保留 rider);⑥文档:敌人系统 v0.10(枚举四类/词汇表 rider 行/池纪律/意图三族/P1 表核销)+战斗结算 v0.17(三乘法链尾 rider/公式行/必中家族/验收两条)+增益状态 v0.10(off_guard 第二来源=敌方技能施放)+registry skill 条目改写+off_guard 行+修订块+07 头注与速览行(重测变量+1)+README 批节+06 条目解决移除;数值全 provisional 归 难度曲线与Boss;本提交一并入库并行会话已获用户批准的完整批次:06 残留清理批(75→55 条,台账 81)+旗行为基线前置采集批(冒烟 11 断言,台账 83)+两处悬空指认连带(关卡节点地图/acceptance-ap6-ac)——e66d156 先例
This commit is contained in:
1 parent
e66d1561bc
commit
d236d9f8e1
20 files changed
+449
-250
No files matched your search
@@ -513,3 +513,12 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/s
|
||||
**文档/registry 同批(批 3)**:敌人系统 v0.5(技能池章节重写)/ 战斗结算 v0.11(随机口径三处/三乘法链/施放行/敌方防御池/AC 措辞四处)/ 增益状态 v0.5(九行 + 敌方防御池;顺手清偿 v0.4 挂起的 sapped 文档同步)/ 难度曲线与Boss v0.6(曲线纪律/Boss 专属池/技能池投值表 + EV 实测登记)/ 战斗表现 v0.3 / HUD框架 v0.4 / 数据配置(敌人表措辞 + 技能表行)/ 03-内容规模 / 02-系统清单 / 关卡节点地图 / ux battle.md(改型注记)/ architecture.md(单位容器 API 面)/ registry(rng/skill 新条目/enemy/status_effect/settlement/术语七词条/修订记录)/ 06-待办清单(改型"单拍行为环体验验证"、收编关闭"行为环废除与蓄力拍退役"、新增 P1 五项 + P2 技能图鉴)。
|
||||
|
||||
**实现决策登记(提案未定处的自行拍板,按封装纪律报告)**:① 保底 M 载体 = 技能表全局单字段 pity_threshold(不分敌种,难度曲线单旋钮);② 冷却"施放后连续 N 拍不可摇中"的实现 = 摇号按当前冷却值排除、递减在其后(与提案"递减→排除"的可摇集合逐拍恒等);③ 增益持续与技能冷却两计时器 = apply_effect duration_override 通道;④ 敌方防御池乘区落点 = 主角攻击 Σ三分量后(hero_offense 现行走普通分量直乘的切片现状不动——登记遗留);⑤ 摇号器类名 SkillLottery(core/skill_lottery.gd)。
|
||||
|
||||
## 2026-09-19 技能池新效应词汇批(用户拍板——设计权威 docs/proposal-status-vocabulary.md v1.0)
|
||||
|
||||
**三词收敛两词**:妨害技(debuff 新类型)+ 必中附加分量(rider_mult,"固定值伤害"改形);多段命中经用户拍板退役(无防御磨损体系,无对照机制)。**实装面 = 仅 L2 Boss 蛛化卓尔精灵,其余敌种不动**:
|
||||
- 毒爆 venom_burst + `rider_mult = 0.75`——必中毒分量 = max(1, floor(atk × 0.75))(atk 开战掷定 2–4 → 毒 1–3 点),后置加法、必中全额、不进乘区不出暴击;伤害行 payload 增 `rider` 键 + 行文本「+附加N(必中)」。
|
||||
- 新技能行**蛛网束缚 web_snare**(debuff → off_guard 失衡,duration 2 = 1 有效结算,冷却 4,池权重 2 → 卓尔池 6/2/2/2 全 provisional)——0 伤害施放行,状态按 target_side 落主角(掷点阈值 10 → 8)。
|
||||
- 结构面:SkillTable type 枚举 +debuff;池纪律 +每池妨害 ≤1;施放类 side 守卫(buff→enemy / debuff→hero,`validate_skill_pool_links` 增 status_table 显式入参——装载序修正);意图徽标 +妨害系 ☠(紫)。
|
||||
- 验证:冒烟 306 项 0 失败(+新探针 `_smoke_debuff_rider`:毒爆行逐拍精确复算 / rider 键封闭 / 蛛网束缚落主角端到端 / 六项加载校验拒绝)+ 四探针 PASS;800 局模拟试玩(两关 × 20 种子 × 双画像)——L2 基线通关 90%→80%、死因全部集中 Boss(设计方向生效),L1 零行为变更回归一致(65%/85%)。
|
||||
- 顺手收口:smoke_all `variant_pool(1, ["dungeon"])` → `"dungeon"`(并行 board_type 批签名 Array→String 的漏网同步——此前该断言被 SCRIPT ERROR 静默吞掉);analyze_playtest.py 反事实重算 +rider 识别。
|
||||
@@ -481,9 +481,10 @@ func _combat_segment(ri: int) -> void:
|
||||
if srow.is_empty():
|
||||
_fatal("敌实例待发动作悬空: %s" % skill_id)
|
||||
continue
|
||||
if String(srow.type) == "buff":
|
||||
# 增益技 = 0 伤害施放行:不兑付伏击(旧蓄力拍"零兑付"语义由施放行继承——本轮喂的伏击
|
||||
# 逢施放拍不兑现的读怪奖励保留);状态授予即数值兑现,技能冷却与增益持续两计时器分账。
|
||||
if String(srow.type) == "buff" or String(srow.type) == "debuff":
|
||||
# 增益/妨害技(新效应词汇批 2026-09-19)= 0 伤害施放行:不兑付伏击(旧蓄力拍"零兑付"语义由施放行
|
||||
# 继承——本轮喂的伏击逢施放拍不兑现的读怪奖励保留);状态授予即数值兑现,技能冷却与增益持续两计时器分账。
|
||||
# 落点由状态行 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)
|
||||
_emit_row("cast", "enemy_action", Vector2i(-1, -1), ri, "%s 施放 %s" % [u.display_name, String(srow.display_name)], 0, {"skill_id": skill_id, "effect_id": String(srow.buff_ref), "duration": dur})
|
||||
@@ -491,21 +492,31 @@ func _combat_segment(ri: int) -> void:
|
||||
continue
|
||||
# 伤害技(含普攻——倍率恒 1):三乘法链 ① A = 攻击基线 × 技能倍率(判定对象 = 技能基础伤害分量)
|
||||
# → ② d20 vs 主角 AC(nat20 暴击 ≠ 技能重击,两层严格区分)→ ③ +克制修正点数(敌打主角无甲轴恒中性 delta 0——加法核 2026-09-18 换装)
|
||||
# → 伏击附加(后置加法、必中全额、不吃 AC)→ 增益池乘区 → 伤害下限 max(1,·)。
|
||||
# → 伏击附加(后置加法、必中全额、不吃 AC)→ 增益池乘区 → 伤害下限 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))))
|
||||
var rider := 0
|
||||
if srow.has("rider_mult"):
|
||||
rider = maxi(int(db.settlement.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult))))
|
||||
var roll_read_e: Dictionary = _armor_roll()
|
||||
var threshold_e: int = _defender_ac(_enemy_system.hero_unit()) + int(db.settlement.attack_bonus)
|
||||
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)
|
||||
d_tick = maxi(int(db.settlement.damage_floor), d_tick)
|
||||
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,
|
||||
"roll": int(roll_read_e.roll), "ac": threshold_e, "tier": discount_e}
|
||||
if crit_e:
|
||||
payload_e["crit"] = true
|
||||
_emit_row("damage", "enemy_action", Vector2i(-1, -1), ri, _attack_label("%s %s" % [u.display_name, String(srow.display_name)], crit_e, discount_e), d_tick, payload_e)
|
||||
if rider > 0:
|
||||
payload_e["rider"] = rider
|
||||
var label_e := _attack_label("%s %s" % [u.display_name, String(srow.display_name)], crit_e, discount_e)
|
||||
if rider > 0:
|
||||
label_e += "+附加%d(必中)" % rider
|
||||
_emit_row("damage", "enemy_action", Vector2i(-1, -1), ri, label_e, d_tick, payload_e)
|
||||
_enemy_system.commit_action(u) # 调用序③执行上报——技能进冷却 + 保底计数(每结算每存活敌恰一次)
|
||||
|
||||
|
||||
|
||||
@@ -167,21 +167,35 @@ func _cross_validate(loaded: Dictionary) -> void:
|
||||
# ⑤ 技能池引用完整性(敌人技能池批——敌人表池 × 技能表;独立入口 validate_skill_pool_links 供冒烟直验)
|
||||
var skt = loaded.get("skill")
|
||||
if skt != null and en != null:
|
||||
for e in validate_skill_pool_links(skt, en):
|
||||
for e in validate_skill_pool_links(skt, en, loaded.get("status_effects")):
|
||||
push_diag("error", e.category, e.message)
|
||||
|
||||
|
||||
# 池模板 × 技能行跨表校验(加载期致命;返回错误列表——ConfigDB 诊断通道与冒烟直验共用):
|
||||
# skill_id 悬空致命 + 池构成纪律(每池恰一行普攻、增益技 ≤1——proposal §2.1 结构保证)。
|
||||
func validate_skill_pool_links(skill_table, enemy_table) -> Array:
|
||||
# skill_id 悬空致命 + 池构成纪律(每池恰一行普攻、增益技 ≤1、妨害技 ≤1——proposal §2.1 结构保证 +
|
||||
# proposal-status-vocabulary §1.2)+ 施放类 side 守卫(增益引用 enemy 侧 / 妨害引用 hero 侧状态行)。
|
||||
# status_table 显式入参:装载序上本校验先于成员赋值执行(self.status_effects 尚为 Nil)——装载通道传 loaded 表,
|
||||
# 冒烟直验传 _db.status_effects;传 null 时 side 守卫跳过(其他校验不受影响)。
|
||||
func validate_skill_pool_links(skill_table, enemy_table, status_table = null) -> Array:
|
||||
var errs: Array = []
|
||||
var skill_types := {}
|
||||
for row in skill_table.rows:
|
||||
skill_types[String(row.get("skill_id", ""))] = String(row.get("type", ""))
|
||||
# 施放类 side 守卫(新效应词汇批 2026-09-19):buff → 敌方侧状态 / debuff → 主角侧状态,防反向挂接
|
||||
for row in skill_table.rows:
|
||||
var st: String = String(row.get("type", ""))
|
||||
if st != "buff" and st != "debuff":
|
||||
continue
|
||||
var want_side: String = "enemy" if st == "buff" else "hero"
|
||||
var eff: String = String(row.get("buff_ref", ""))
|
||||
var srow: Dictionary = status_table.row_by_id(eff) if status_table != null else {}
|
||||
if not srow.is_empty() and String(srow.get("target_side", "")) != want_side:
|
||||
errs.append({"category": "cast_side", "message": "SkillTable %s(%s)引用 %s 的 target_side=%s ≠ %s(施放类 side 守卫)" % [String(row.get("skill_id", "")), st, eff, String(srow.get("target_side", "")), want_side]})
|
||||
for row in enemy_table.rows:
|
||||
var eid: String = String(row.get("enemy_id", ""))
|
||||
var basic_n := 0
|
||||
var buff_n := 0
|
||||
var debuff_n := 0
|
||||
for entry in row.get("pool", []):
|
||||
var sid: String = String(entry.get("skill_id", ""))
|
||||
if not skill_types.has(sid):
|
||||
@@ -192,10 +206,14 @@ func validate_skill_pool_links(skill_table, enemy_table) -> Array:
|
||||
basic_n += 1
|
||||
"buff":
|
||||
buff_n += 1
|
||||
"debuff":
|
||||
debuff_n += 1
|
||||
if basic_n != 1:
|
||||
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池普攻行数 ≠ 1 (%d)——普攻冷却恒 0 = 空池不可能的结构前提" % [eid, basic_n]})
|
||||
if buff_n > 1:
|
||||
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池增益技 > 1 (%d)——每池增益 ≤1 纪律(纠偏三)" % [eid, buff_n]})
|
||||
if debuff_n > 1:
|
||||
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池妨害技 > 1 (%d)——每池妨害 ≤1 纪律(新效应词汇批 2026-09-19)" % [eid, debuff_n]})
|
||||
return errs
|
||||
|
||||
|
||||
|
||||
@@ -13,9 +13,10 @@ extends Resource
|
||||
# (特殊技内按权重、不受冷却限制——proposal §2.1 两道闸之二)。全局单旋钮(不分敌种)。
|
||||
@export var pity_threshold: int = 4
|
||||
|
||||
const SKILL_TYPES := ["basic", "damage", "buff"] # 普通攻击 / 伤害 / 增益(封闭枚举——新类型走文档修订)
|
||||
const SKILL_TYPES := ["basic", "damage", "buff", "debuff"] # 普通攻击 / 伤害 / 增益 / 妨害(封闭枚举——新效应词汇批 2026-09-19 由三扩四)
|
||||
const MULT_DOMAIN_MIN := 1.1 # damage_mult 域 [1.1, 16] provisional(proposal §2.2)
|
||||
const MULT_DOMAIN_MAX := 16.0
|
||||
const RIDER_MULT_MAX := 1.0 # rider_mult 域 (0, 1](必中附加分量——proposal-status-vocabulary §2;首发 0.75 锚 AC 折扣档最轻档)
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
@@ -52,7 +53,10 @@ func validate() -> Array:
|
||||
errs.append({"category": "cross_field", "message": "%s 普攻 cooldown_beats ≠ 0(禁配——池恒非空的结构前提): %d" % [sid, cd]})
|
||||
if row.has("buff_ref") or row.has("buff_duration"):
|
||||
errs.append({"category": "cross_field", "message": "%s 普攻不带增益字段" % sid})
|
||||
# 伤害技:存倍率不存固定值——伤害 = 施放者攻击基线 × 技能倍率(三乘法链第一乘)
|
||||
if row.has("rider_mult"):
|
||||
errs.append({"category": "cross_field", "message": "%s 普攻不带必中附加分量(rider_mult 仅伤害技)" % sid})
|
||||
# 伤害技:存倍率不存固定值——伤害 = 施放者攻击基线 × 技能倍率(三乘法链第一乘);
|
||||
# 可选 rider_mult = 必中附加分量(后置加法、不吃骰不出暴击——proposal-status-vocabulary §2)
|
||||
if type == "damage":
|
||||
if not row.has("damage_mult"):
|
||||
errs.append({"category": "cross_field", "message": "%s 伤害技缺 damage_mult(存倍率不存固定值)" % sid})
|
||||
@@ -62,15 +66,33 @@ func validate() -> Array:
|
||||
errs.append({"category": "domain", "message": "%s damage_mult 越域 [%.1f, %.1f]: %s" % [sid, MULT_DOMAIN_MIN, MULT_DOMAIN_MAX, str(m)]})
|
||||
if row.has("buff_ref") or row.has("buff_duration"):
|
||||
errs.append({"category": "cross_field", "message": "%s 伤害技不带增益字段" % sid})
|
||||
if row.has("rider_mult"):
|
||||
var rm: float = float(row.rider_mult)
|
||||
if rm <= 0.0 or rm > RIDER_MULT_MAX:
|
||||
errs.append({"category": "domain", "message": "%s rider_mult 越域 (0, %.1f]: %s" % [sid, RIDER_MULT_MAX, str(rm)]})
|
||||
# 增益技:0 伤害动作(无倍率),效果引用 + 持续回合;悬空引用由 ConfigDB 跨表校验兜底
|
||||
if type == "buff":
|
||||
if row.has("damage_mult"):
|
||||
errs.append({"category": "cross_field", "message": "%s 增益技为 0 伤害动作,不带 damage_mult" % sid})
|
||||
if row.has("rider_mult"):
|
||||
errs.append({"category": "cross_field", "message": "%s 增益技不带必中附加分量(rider_mult 仅伤害技)" % sid})
|
||||
if String(row.get("buff_ref", "")) == "":
|
||||
errs.append({"category": "cross_field", "message": "%s 增益技缺 buff_ref(效果引用)" % sid})
|
||||
var bd: int = int(row.get("buff_duration", 0))
|
||||
if bd < 1 or bd > 99:
|
||||
errs.append({"category": "domain", "message": "%s buff_duration 越域 [1,99]: %d" % [sid, bd]})
|
||||
# 妨害技(新效应词汇批 2026-09-19):0 伤害施放动作,对主角施加负面状态——
|
||||
# 字段复用 buff_ref/buff_duration(落点由状态行 target_side 路由,ConfigDB side 守卫兜底);池纪律妨害 ≤1 在跨表通道
|
||||
if type == "debuff":
|
||||
if row.has("damage_mult"):
|
||||
errs.append({"category": "cross_field", "message": "%s 妨害技为 0 伤害动作,不带 damage_mult" % sid})
|
||||
if row.has("rider_mult"):
|
||||
errs.append({"category": "cross_field", "message": "%s 妨害技不带必中附加分量(rider_mult 仅伤害技)" % sid})
|
||||
if String(row.get("buff_ref", "")) == "":
|
||||
errs.append({"category": "cross_field", "message": "%s 妨害技缺 buff_ref(效果引用)" % sid})
|
||||
var dbd: int = int(row.get("buff_duration", 0))
|
||||
if dbd < 1 or dbd > 99:
|
||||
errs.append({"category": "domain", "message": "%s buff_duration 越域 [1,99]: %d" % [sid, dbd]})
|
||||
if not has_basic:
|
||||
errs.append({"category": "structural", "message": "SkillTable 无普攻行(basic)——池构成纪律的结构前提"})
|
||||
if pity_threshold < 1 or pity_threshold > 99:
|
||||
|
||||
@@ -276,6 +276,9 @@ rows = [{
|
||||
}, {
|
||||
"skill_id": "venom_burst",
|
||||
"weight": 2
|
||||
}, {
|
||||
"skill_id": "web_snare",
|
||||
"weight": 2
|
||||
}],
|
||||
"advantage_per_round_cap": 4,
|
||||
"armor_type": "ward",
|
||||
@@ -342,6 +345,11 @@ rows = [{
|
||||
;
|
||||
; 甲效矩阵加法核换装批(2026-09-18 用户拍板——settlement.tres armor_effectiveness 量纲 percent→整数点数,
|
||||
; 三档 克制 +1 / 中性 0 / 被克 −1,五档百分比词表随批退役)。
|
||||
;
|
||||
; 新效应词汇批(2026-09-19 用户拍板——设计权威 docs/proposal-status-vocabulary.md v1.0):卓尔池
|
||||
; 6/2/2 → 6/2/2/2(+蛛网束缚 web_snare 2——妨害技,对主角施 off_guard;每池妨害 ≤1 纪律入跨表校验)。
|
||||
; 主分量权重期望 1.67 → 1.25(零伤害拍占 2/12),加毒爆必中附加分量(max(1,floor(atk×0.75)),atk 区间
|
||||
; 2–4 → 1–3 点)后净期望待抽样复核归 难度曲线与Boss;实装面 = 仅卓尔,其余敌种不动。
|
||||
; 现行读数效果:L1/L2 小怪全 light → 穿刺类(开格充能/穿刺弹)被克 −1(穿刺弹 2→1、充能 3 点→2)、普通/魔法中性;
|
||||
; L2 仅 Boss 战非中性(魔法甲)——基础攻击与穿刺弹克制 +1、魔法矢被克 −1(原威力 + delta 自行核算后落笔)。
|
||||
; 原重配批注「魔法矢克 Boss ×150、穿刺弹被克 ×50」与当批矩阵正反互倒(系笔误),随本批订正为上行读数。
|
||||
@@ -47,6 +47,14 @@ rows = [{
|
||||
"display_name": "毒爆",
|
||||
"type": "damage",
|
||||
"damage_mult": 2.5,
|
||||
"rider_mult": 0.75,
|
||||
"cooldown_beats": 4
|
||||
}, {
|
||||
"skill_id": "web_snare",
|
||||
"display_name": "蛛网束缚",
|
||||
"type": "debuff",
|
||||
"buff_ref": "off_guard",
|
||||
"buff_duration": 2,
|
||||
"cooldown_beats": 4
|
||||
}]
|
||||
|
||||
@@ -67,3 +75,14 @@ rows = [{
|
||||
; 毒爆 venom_burst = 伤害 ×2.5 冷却 4(蛛化卓尔精灵 drider 专属池独占技——"Boss=专属池独占技+权重轮廓"
|
||||
; 纪律的本批兑现;池引用唯一性 = 唯一 enemy_id: drider)。
|
||||
; 两行均为 damage 型(存倍率不存固定值——伤害 = 施放者攻击基线 × 技能倍率,三乘法链第一乘)。
|
||||
;
|
||||
; 新效应词汇批(2026-09-19 用户拍板——设计权威 docs/proposal-status-vocabulary.md v1.0,全值 provisional
|
||||
; 投值归 难度曲线与Boss):三词收敛两词——多段命中退役(无防御磨损体系,无对照机制);固定值伤害改形
|
||||
; 必中附加分量(rider_mult,域 (0,1]):后置加法、必中全额、不进乘区不出暴击,分量值 =
|
||||
; max(damage_floor, floor(攻击基线 × rider_mult))——随基线缩放保持共享技能表前提;
|
||||
; ×0.75 价位锚 = AC 折扣档最轻档"不掉档的稳定伤害"(可掷分量期望 AC10≈0.80 / AC12≈0.76 对表)。
|
||||
; ① 毒爆 + rider_mult 0.75(必中毒分量:atk3 → 毒 2 点,atk 区间 2–4 → 1–3 点随开战掷定值缩放)。
|
||||
; ② 蛛网束缚 web_snare(新行,妨害型 debuff——对主角施加 off_guard 失衡):0 伤害施放行(不掷不折不兑付
|
||||
; 伏击),字段复用 buff_ref/buff_duration;duration 2 = 1 个有效结算(empower/fortify 先兑付后衰减含
|
||||
; 施放当轮口径);冷却 4;首发载荷 = 失衡(用户拍板),乏力 sapped 为第二词汇暂不实装;
|
||||
; 池纪律 +:每池妨害 ≤1(ConfigDB 跨表校验);实装面 = 仅卓尔池,其余敌种不动。
|
||||
@@ -11,13 +11,15 @@ class_name EnemyCardView
|
||||
extends PanelContainer
|
||||
|
||||
const COL_INK := Color8(51, 38, 27)
|
||||
# 摇定动作呈现映射(敌人技能池批 §2.4 三档可见性):键 = pending_type(快照投影)。
|
||||
# 摇定动作呈现映射(敌人技能池批 §2.4 三档可见性;新效应词汇批 2026-09-19 扩四档):键 = pending_type(快照投影)。
|
||||
# 普通攻击零表示("—" 中性色——完全不可预测);伤害特殊技 = 攻击系 ⚔(朱红);
|
||||
# 增益特殊技 = 防御/增益系 ✦(蓝)。nat20 暴击无前置表示(掷点层,与技能重击严格区分)。
|
||||
# 增益特殊技 = 防御/增益系 ✦(蓝);妨害特殊技 = 妨害系 ☠(紫——对主角施减益,proposal-status-vocabulary §1.4)。
|
||||
# nat20 暴击无前置表示(掷点层,与技能重击严格区分)。
|
||||
const PENDING_STYLE := {
|
||||
"basic": ["—", Color8(120, 106, 88)],
|
||||
"damage": ["⚔", Color8(198, 61, 34)],
|
||||
"buff": ["✦", Color8(46, 110, 142)],
|
||||
"debuff": ["☠", Color8(122, 66, 148)],
|
||||
}
|
||||
# 蓄力档技能集(2026-09-18 用户拍板:重击类独立蓄力提示——伤害技 ⚔ / 增益技 ✦ / 重击类 蓄力 三档占位)
|
||||
const CHARGE_SKILL_IDS := ["heavy"]
|
||||
@@ -105,7 +107,7 @@ func _set_order(i: int) -> void:
|
||||
|
||||
|
||||
# 摇定动作渲染(敌人技能池批——presenter 不翻译,渲染映射唯一定义处归本类〔code-review 2026-09-12 先例〕):
|
||||
# pending_type = 战斗结算 快照投影(basic/damage/buff;"" = 未摇定/死亡残位)。
|
||||
# pending_type = 战斗结算 快照投影(basic/damage/buff/debuff;"" = 未摇定/死亡残位)。
|
||||
# 信号/入参契约:set_data 每结算行刷新、set_pending 每轮开轮刷新(开轮摇号时点)。
|
||||
func set_pending(ptype: String, pid: String) -> void:
|
||||
if _dead:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://cg2dgdnbkd7op
|
||||
@@ -82,6 +82,7 @@ func _run_core_smoke(db, rng) -> void:
|
||||
_smoke_clear_reward_chain()
|
||||
_smoke_refresh_provenance_guard()
|
||||
_smoke_polarity_routing()
|
||||
_smoke_flag_misfire_dig_after_pull()
|
||||
_smoke_negative_dictionary()
|
||||
_smoke_resource_surface()
|
||||
_smoke_settlement_segments()
|
||||
@@ -489,7 +490,7 @@ func _mk_stream(stream_name: String, scope: int) -> RandomNumberGenerator:
|
||||
func _smoke_variation_framework() -> void:
|
||||
print("--- 地形变异 框架面(空清单合法 + 候选池门控通道)---")
|
||||
check(_db.variation.rows.is_empty(), "变异清单为空(P0 合法态——零变体格)")
|
||||
check(_db.variant_pool(1, ["dungeon"]).is_empty(), "候选池为空——棋盘生成静默产出无变异图(不报错、不警告)")
|
||||
check(_db.variant_pool(1, "dungeon").is_empty(), "候选池为空——棋盘生成静默产出无变异图(不报错、不警告)")
|
||||
check(_db.variation.row_by_id("nonexistent").is_empty(), "清单外 variant_id 返回空(读取通道在位)")
|
||||
|
||||
|
||||
@@ -863,6 +864,77 @@ func _find_flag_target(minesweeper, want_mine: bool) -> Dictionary:
|
||||
return {}
|
||||
|
||||
|
||||
# ---- 旗奖励语义实证(2026-09-19 观测项⑧语义锚——用户质询「误旗判定后拔旗再挖是否仍有奖励」)----
|
||||
# 场景 A(跨轮):标安全格 → 收轮判定(flag_miss 负向兑付)→ 拔旗 → 挖开 → 收轮。
|
||||
# 语义锚 = 惩罚与奖励并行:判定走消费型 grant、挖开走查询型 query_full_snapshot,两通道互不吞并。
|
||||
# 场景 B(同轮插撤):标安全格 → 同轮拔旗 → 挖开 → 收轮。被拔不判(抵消过滤)——零惩罚零影响。
|
||||
func _smoke_flag_misfire_dig_after_pull() -> void:
|
||||
print("--- 旗奖励语义(误旗判定后拔旗再挖)---")
|
||||
# 场景 A:跨轮(判定已走)
|
||||
var rng_a = load("res://core/rng_service.gd").new()
|
||||
rng_a.set_master_seed(20260919)
|
||||
var settle_a = load("res://core/battle_orchestrator.gd").new(_db, rng_a)
|
||||
var rows_a: Array = []
|
||||
settle_a.step_row.connect(func(row): rows_a.append(row))
|
||||
check(settle_a.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp)}), "旗语义 A:开局受理")
|
||||
var ms_a = settle_a.minesweeper
|
||||
var t_a := _find_flag_target(ms_a, false)
|
||||
check(not t_a.is_empty(), "旗语义 A:找到安全格旗目标")
|
||||
if not t_a.is_empty():
|
||||
if t_a.hero_cell != ms_a.hero_pos():
|
||||
ms_a.settle_batch([{"action": "move", "cell": t_a.hero_cell}])
|
||||
ms_a.settle_batch([{"action": "flag_place", "cell": t_a.flag_cell}])
|
||||
ms_a.settle_batch([{"action": "stop"}]) # 收轮 → 判定批:flag_miss + 负向兑付
|
||||
ms_a.settle_batch([{"action": "flag_pull", "cell": t_a.flag_cell}])
|
||||
var dig_a: Array = ms_a.settle_batch([{"action": "dig", "cell": t_a.flag_cell}])
|
||||
check(not dig_a.is_empty() and bool(dig_a[0].get("accepted", false)), "旗语义 A:拔旗后挖开被受理")
|
||||
ms_a.settle_batch([{"action": "stop"}])
|
||||
var target_a: Array = [t_a.flag_cell.x, t_a.flag_cell.y]
|
||||
var miss_attr := 0
|
||||
var miss_neg := 0
|
||||
var dig_reward := 0
|
||||
for row in rows_a:
|
||||
if row.get("origin_cell", [0, 0]) != target_a:
|
||||
continue
|
||||
if row.get("type", "") == "flag_miss":
|
||||
miss_attr += 1
|
||||
if String(row.get("source_class", "")) == "flag_miss":
|
||||
miss_neg += 1
|
||||
if String(row.get("source_class", "")) == "safe_cell":
|
||||
dig_reward += 1
|
||||
check(miss_attr == 1, "旗语义 A:标空归因行恰一次(×%d——每格实例一次去重)" % miss_attr)
|
||||
check(miss_neg >= 1, "旗语义 A:标空负向兑付行在案(×%d)" % miss_neg)
|
||||
check(dig_reward >= 1, "旗语义 A:判定后拔旗再挖仍获正向奖励(×%d——惩罚与奖励并行)" % dig_reward)
|
||||
# 场景 B:同轮插撤(被拔不判)
|
||||
var rng_b = load("res://core/rng_service.gd").new()
|
||||
rng_b.set_master_seed(20260919)
|
||||
var settle_b = load("res://core/battle_orchestrator.gd").new(_db, rng_b)
|
||||
var rows_b: Array = []
|
||||
settle_b.step_row.connect(func(row): rows_b.append(row))
|
||||
check(settle_b.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp)}), "旗语义 B:开局受理")
|
||||
var ms_b = settle_b.minesweeper
|
||||
var t_b := _find_flag_target(ms_b, false)
|
||||
check(not t_b.is_empty(), "旗语义 B:找到安全格旗目标")
|
||||
if not t_b.is_empty():
|
||||
if t_b.hero_cell != ms_b.hero_pos():
|
||||
ms_b.settle_batch([{"action": "move", "cell": t_b.hero_cell}])
|
||||
ms_b.settle_batch([{"action": "flag_place", "cell": t_b.flag_cell}])
|
||||
ms_b.settle_batch([{"action": "flag_pull", "cell": t_b.flag_cell}]) # 同轮拔——发射面已过滤
|
||||
var dig_b: Array = ms_b.settle_batch([{"action": "dig", "cell": t_b.flag_cell}])
|
||||
check(not dig_b.is_empty() and bool(dig_b[0].get("accepted", false)), "旗语义 B:同轮插撤后挖开被受理")
|
||||
ms_b.settle_batch([{"action": "stop"}])
|
||||
var target_b: Array = [t_b.flag_cell.x, t_b.flag_cell.y]
|
||||
var miss_b := 0
|
||||
var reward_b := 0
|
||||
for row in rows_b:
|
||||
if row.get("type", "") == "flag_miss" or String(row.get("source_class", "")) == "flag_miss":
|
||||
miss_b += 1
|
||||
if String(row.get("source_class", "")) == "safe_cell" and row.get("origin_cell", [0, 0]) == target_b:
|
||||
reward_b += 1
|
||||
check(miss_b == 0, "旗语义 B:同轮插撤零判定零惩罚(×%d——被拔不判)" % miss_b)
|
||||
check(reward_b >= 1, "旗语义 B:同轮插撤后挖开奖励不受影响(×%d)" % reward_b)
|
||||
|
||||
|
||||
# ---- 试玩日志器冒烟(结算快照 JSONL + 诊断转储)----
|
||||
func _smoke_playtest_logger() -> void:
|
||||
print("--- PlaytestLogger(结算快照 JSONL + 诊断转储)---")
|
||||
@@ -1538,11 +1610,12 @@ func _smoke_skill_pool() -> void:
|
||||
_smoke_skill_pool_ev_anchor()
|
||||
_smoke_skill_pool_death_void()
|
||||
_smoke_skill_pool_validation_reject()
|
||||
_smoke_debuff_rider()
|
||||
|
||||
|
||||
# ① 数据面:技能表行集 + 普攻锁死 + 建议池行值 + 空池不可能(结构断言)
|
||||
func _smoke_skill_pool_data() -> void:
|
||||
check(_db.skill.rows.size() == 7, "技能表七行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆——分支节点图批 L2 新行)")
|
||||
check(_db.skill.rows.size() == 8, "技能表八行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚——分支节点图批 + 新效应词汇批)")
|
||||
var basic: Dictionary = _db.skill.row_by_id("attack")
|
||||
check(not basic.is_empty() and String(basic.type) == "basic" and int(basic.cooldown_beats) == 0
|
||||
and (not basic.has("damage_mult") or is_equal_approx(float(basic.damage_mult), 1.0)),
|
||||
@@ -1557,14 +1630,29 @@ func _smoke_skill_pool_data() -> void:
|
||||
check(not bite.is_empty() and is_equal_approx(float(bite.damage_mult), 1.5) and int(bite.cooldown_beats) == 1,
|
||||
"撕咬行(伤害 ×1.5 · 冷却 1——巨蜘蛛池载体,与突刺同倍率错开冷却轴)")
|
||||
var venom: Dictionary = _db.skill.row_by_id("venom_burst")
|
||||
check(not venom.is_empty() and is_equal_approx(float(venom.damage_mult), 2.5) and int(venom.cooldown_beats) == 4,
|
||||
"毒爆行(伤害 ×2.5 · 冷却 4——蛛化卓尔精灵专属池独占技)")
|
||||
check(not venom.is_empty() and is_equal_approx(float(venom.damage_mult), 2.5) and int(venom.cooldown_beats) == 4
|
||||
and is_equal_approx(float(venom.rider_mult), 0.75),
|
||||
"毒爆行(伤害 ×2.5 · 冷却 4 · 必中附加 ×0.75——蛛化卓尔精灵专属池独占技,新效应词汇批)")
|
||||
var venom_sole_ok := true
|
||||
for row in _db.enemy.rows:
|
||||
for entry in row.pool:
|
||||
if String(entry.skill_id) == "venom_burst" and String(row.enemy_id) != "drider":
|
||||
venom_sole_ok = false
|
||||
check(venom_sole_ok, "毒爆独占性(仅蛛化卓尔精灵池引用——Boss 专属池独占技纪律)")
|
||||
# 新效应词汇批(2026-09-19):妨害技首发行 + off_guard 落点 + 实装面收窄
|
||||
var snare: Dictionary = _db.skill.row_by_id("web_snare")
|
||||
check(not snare.is_empty() and String(snare.type) == "debuff" and String(snare.buff_ref) == "off_guard"
|
||||
and int(snare.buff_duration) == 2 and int(snare.cooldown_beats) == 4,
|
||||
"蛛网束缚行(妨害型 · buff_ref → off_guard 失衡 · duration 2 = 1 有效拍 · 冷却 4——新效应词汇批首发)")
|
||||
var snare_sole_ok := true
|
||||
for row in _db.enemy.rows:
|
||||
for entry in row.pool:
|
||||
if String(entry.skill_id) == "web_snare" and String(row.enemy_id) != "drider":
|
||||
snare_sole_ok = false
|
||||
check(snare_sole_ok, "蛛网束缚独占性(仅蛛化卓尔精灵池引用——实装面收窄,其余敌种不动)")
|
||||
var off_guard_row: Dictionary = _db.status_effect_row("off_guard")
|
||||
check(not off_guard_row.is_empty() and String(off_guard_row.target_side) == "hero" and String(off_guard_row.unit) == "ac",
|
||||
"失衡状态行(hero · ac 轴——妨害技落点,ConfigDB side 守卫正例)")
|
||||
# 行集键 = (level_index, node_depth)——分支节点图批起 bugbear 等敌种跨关异池,enemy_id 不再唯一
|
||||
var expect_pools := {
|
||||
"1_0": {"attack": 9, "lunge": 1},
|
||||
@@ -1578,7 +1666,7 @@ func _smoke_skill_pool_data() -> void:
|
||||
"2_5": {"attack": 5, "lunge": 2, "heavy": 2},
|
||||
"2_6": {"attack": 6, "bite": 2, "charge": 1},
|
||||
"2_7": {"attack": 6, "bite": 2, "charge": 1},
|
||||
"2_8": {"attack": 6, "heavy": 2, "venom_burst": 2},
|
||||
"2_8": {"attack": 6, "heavy": 2, "venom_burst": 2, "web_snare": 2},
|
||||
}
|
||||
var pools_ok := true
|
||||
for row in _db.enemy.rows:
|
||||
@@ -1591,7 +1679,7 @@ func _smoke_skill_pool_data() -> void:
|
||||
got[String(entry.skill_id)] = int(entry.weight)
|
||||
if got != expect_pools[rk]:
|
||||
pools_ok = false
|
||||
check(pools_ok, "十二行池模板 = 建议池行值(L1 地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2;L2 兄弟精 6/1/2 · 蜘蛛 6/2/1 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2——全部 provisional)")
|
||||
check(pools_ok, "十二行池模板 = 建议池行值(L1 地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2;L2 兄弟精 6/1/2 · 蜘蛛 6/2/1 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2——全部 provisional)")
|
||||
var basic_count_ok := true
|
||||
for row in _db.enemy.rows:
|
||||
var n := 0
|
||||
@@ -1830,6 +1918,26 @@ func _smoke_skill_pool_validation_reject() -> void:
|
||||
check(not et2.validate().is_empty(), "加载校验拒绝:池权重 ≤ 0")
|
||||
et2.rows = [{"enemy_id": "x", "display_name": "x", "pool": []}]
|
||||
check(not et2.validate().is_empty(), "加载校验拒绝:空池(禁)")
|
||||
# 新效应词汇批(2026-09-19):妨害 / 必中附加分量校验组
|
||||
skill_table.rows = [
|
||||
{"skill_id": "w1", "display_name": "w1", "type": "debuff", "buff_ref": "off_guard", "buff_duration": 2, "cooldown_beats": 4},
|
||||
{"skill_id": "w2", "display_name": "w2", "type": "debuff", "buff_ref": "off_guard", "buff_duration": 2, "cooldown_beats": 4}]
|
||||
enemy_table.rows = [{"enemy_id": "x", "display_name": "x", "pool": [{"skill_id": "w1", "weight": 1}, {"skill_id": "w2", "weight": 1}]}]
|
||||
check(_has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition"),
|
||||
"加载校验拒绝:池妨害技 > 1(每池妨害 ≤1 纪律)")
|
||||
skill_table.rows = [{"skill_id": "w1", "display_name": "w1", "type": "debuff", "cooldown_beats": 4}]
|
||||
check(not skill_table.validate().is_empty(), "加载校验拒绝:妨害技缺 buff_ref(效果引用)")
|
||||
skill_table.rows = [{"skill_id": "d", "display_name": "d", "type": "damage", "damage_mult": 1.5, "rider_mult": 1.5, "cooldown_beats": 1}]
|
||||
check(not skill_table.validate().is_empty(), "加载校验拒绝:rider_mult 越域 (0,1]")
|
||||
skill_table.rows = [{"skill_id": "b", "display_name": "b", "type": "buff", "buff_ref": "empower", "buff_duration": 2, "rider_mult": 0.5, "cooldown_beats": 1}]
|
||||
check(not skill_table.validate().is_empty(), "加载校验拒绝:非伤害技带必中附加分量")
|
||||
# 施放类 side 守卫:妨害技引用敌方侧状态 → 拒绝(增益技引用主角侧同罪,本组以妨害反例覆盖)
|
||||
skill_table.rows = [
|
||||
{"skill_id": "atk", "display_name": "a", "type": "basic", "cooldown_beats": 0},
|
||||
{"skill_id": "w1", "display_name": "w1", "type": "debuff", "buff_ref": "empower", "buff_duration": 2, "cooldown_beats": 4}]
|
||||
enemy_table.rows = [{"enemy_id": "x", "display_name": "x", "pool": [{"skill_id": "atk", "weight": 1}, {"skill_id": "w1", "weight": 1}]}]
|
||||
check(_has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table, _db.status_effects), "cast_side"),
|
||||
"加载校验拒绝:妨害技引用敌方侧状态(施放类 side 守卫)")
|
||||
|
||||
|
||||
func _has_err_category(errs: Array, category: String) -> bool:
|
||||
@@ -1839,6 +1947,74 @@ func _has_err_category(errs: Array, category: String) -> bool:
|
||||
return false
|
||||
|
||||
|
||||
# ⑦ 新效应词汇批实装探针(2026-09-19——妨害技蛛网束缚 + 毒爆必中附加分量,实装面 = L2 Boss 卓尔):
|
||||
# 停手轮驱动 (2,8)——零挖掘零旗 → 零池零伏击,伤害行可逐拍精确复算;种子 hunt 至少各命一粒毒爆伤害行
|
||||
# 与蛛网束缚施放行;毒分量随开战掷定的攻击基线缩放(atk 区间 2–4 → rider 1–3)。
|
||||
func _smoke_debuff_rider() -> void:
|
||||
var venom_seed := -1
|
||||
var venom_check_ok := true
|
||||
var venom_rows_seen := 0
|
||||
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)
|
||||
for s in range(1, 401):
|
||||
if venom_seed > 0 and snare_seed > 0:
|
||||
break
|
||||
var rg = load("res://core/rng_service.gd").new()
|
||||
rg.set_master_seed(s)
|
||||
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rg)
|
||||
var rows: Array = []
|
||||
settlement.step_row.connect(func(row): rows.append(row))
|
||||
if not settlement.begin_battle({"level_index": 2, "node_depth": 8, "hero_hp": int(_db.hero.max_hp)}):
|
||||
continue
|
||||
var boss_atk: int = int(settlement._enemy_system.all_enemies()[0].atk) # 开战掷定攻击基线(毒分量随其缩放)
|
||||
for i in range(6):
|
||||
if settlement.battle_result != "":
|
||||
break
|
||||
settlement.minesweeper.settle_batch([{"action": "stop"}])
|
||||
var has_venom := false
|
||||
var has_snare := false
|
||||
var snare_round := -1
|
||||
for row in rows:
|
||||
if String(row.get("type", "")) != "damage" or String(row.get("source_class", "")) != "enemy_action":
|
||||
if String(row.get("type", "")) == "cast" and String(row.get("skill_id", "")) == "web_snare":
|
||||
has_snare = true
|
||||
snare_round = int(row.get("round_index", -1))
|
||||
continue
|
||||
if String(row.get("skill_id", "")) == "venom_burst":
|
||||
has_venom = true
|
||||
venom_rows_seen += 1
|
||||
var rider_v: int = int(row.get("rider", -1))
|
||||
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 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
|
||||
if int(row.value) != expected:
|
||||
venom_check_ok = false
|
||||
elif row.has("rider"):
|
||||
venom_check_ok = false # rider 键只允许出现在带 rider_mult 的技能行
|
||||
if has_snare:
|
||||
# 施放后主角掷点阈值下调(off_guard ac 轴):同轮起的伤害行阈值读数 = 基准 AC + 失衡幅度。
|
||||
# 只取"可观测"种子(施放轮后仍有敌方伤害行——施放在末轮随战斗终局属合法不可观测,不作失败)。
|
||||
var seen_lowered := false
|
||||
for row in rows:
|
||||
if String(row.get("type", "")) != "damage" or String(row.get("source_class", "")) != "enemy_action":
|
||||
continue
|
||||
if int(row.get("round_index", -1)) >= snare_round and int(row.get("ac", -999)) == base_ac + off_m:
|
||||
seen_lowered = true
|
||||
break
|
||||
if seen_lowered and snare_seed < 0:
|
||||
snare_seed = s
|
||||
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(snare_seed > 0, "蛛网束缚落主角:施放后掷点阈值 = 基准 AC + 失衡幅度(off_guard ac 轴端到端,seed=%d)" % snare_seed)
|
||||
|
||||
|
||||
# ---- 分支节点图批冒烟:图合法性校验 + L2 幽暗地域图版行进(2026-09-17——关卡节点地图 v1.0 图版落地)----
|
||||
|
||||
func _smoke_level_graph() -> void:
|
||||
|
||||
Reference in new issue
Block a user