W4 批次:三项设计裁定落地(Boss 战 Plan B / 离线契约 v2 / 长明灯加法化)

Boss 战修复(Plan B,用户拍板):
- HP 公式 15xPxhall_factor -> HP = P x boss_army_seconds_n(S=23.0→28.0 递增,
  每场军列约 23-28 秒清空,战斗时长成为可直接书写的设计旋钮,结构性消除
  高殿 P 抵消导致不可胜的旧 bug);hall_factor 降级为纯叙事/视觉参数
- 新增 souls_par 列(0→90000):开场冲锋 intro_dmg = HP x 10% x clamp(souls/souls_par,0,1),
  上限 20%->10%;删 hp_factor/intro_dmg_souls_factor/intro_dmg_cap_ratio 三列
- 点击伤害 P x κ x combo_mult,κ = (0.010 + 0.052 x click_ratio) x (1 + 0.30 x m06_owned),
  click_ratio = 已购点击升级 / 12(κ 0.010→0.0806,满配点击占比约 20.5%±3pp)
- 防双重计入:L5-12 点击升级 +1% output/s 留全局区(抬 P 同时抬军列 DPS 与点击
  伤害,可接受),不得再计入 κ 分子(recompute 注释固化)

离线契约 v2(P-8,改契约不改表):
- OfflineModel.settle 第三参 offline_mult -> offline_add(加法);各档效率
  min(rate+add, 1.0) 硬顶 100%,离线永不超在线
- 长明灯 x1.15 乘性 -> +5%/级 加法(满级 +30%,各档封顶 100%)
- 0-2h 100% 保持;8h = 6.2 等效小时(77.5% 效率)
- Gate #9 拆 9a/9b/9c:9a FAIL->PASS(0-2h 全额 + 8h∈[5.5,6.5]),
  9b 长明灯满级 8h∈[7.5,8.0] 且 ≤100%,9c 任意配置效率 ≤100%

数据与工具:
- gen_balance_sheets.py 补登 W3 遗留 7 行 tune(防重跑静默丢失),self_check 更新
- boss_battles/upgrades schema 同步(offline_mult->offline_add enum,漏改会违反导入)

测试(runner 7->8 用例,Node 交叉验证 145 断言全过):
- 可胜性哨兵重写为「十殿全可胜」:每殿 S_n<30、军列+点击≥HP、余时∈[2,8]、
  满配余时差≥4、点击占比∈[15,25]、H10≈20.5%±3pp
- κ 四档断言(0.010/0.036/0.062/0.0806,分母 12)
- 新增 test_offline_contract.gd:契约 v2 数值面 + 9a/9b/9c 全 PASS + β 缓议
This commit is contained in:
程基岩(engineering-lead) committed 2026-10-20 17:01:25 +08:00
1 parent 4f650aeb39
commit 08dc5020c3
22 files changed
+738 -233

No files matched your search

+4 -3
View File
@@ -158,11 +158,12 @@ func _load_bosses(rows: Array) -> void:
d.id = str(r.get("id", ""))
d.hall_id = str(r.get("hall_id", ""))
d.hall_factor = float(r.get("hall_factor", 1))
d.hp_factor = float(r.get("hp_factor", 15))
# W4 方案 B(systems-spec §1.2):HP = 产出/s × boss_army_seconds;
# 旧 hp_factor / intro_dmg_souls_factor / intro_dmg_cap_ratio 列已删除。
d.boss_army_seconds = float(r.get("boss_army_seconds", 23.0))
d.souls_par = int(r.get("souls_par", 0))
d.army_dps_factor = float(r.get("army_dps_factor", 1))
d.intro_sec = float(r.get("intro_sec", 3))
d.intro_dmg_souls_factor = float(r.get("intro_dmg_souls_factor", 0.5))
d.intro_dmg_cap_ratio = float(r.get("intro_dmg_cap_ratio", 0.2))
d.active_sec = float(r.get("active_sec", 30))
d.combo_window_sec = float(r.get("combo_window_sec", 0.6))
d.combo_step = float(r.get("combo_step", 0.02))
+24 -14
View File
@@ -2,30 +2,32 @@ class_name BossDef
extends RefCounted
# ============================================================================
# BossDef —— 殿王战参数数据行(架构 §4.3;[B§5])
# BossDef —— 殿王战参数数据行(架构 §4.3;[B§5] + systems-spec §1.2 方案 B)
#
# 公式([B§5],参数全部来自本行,代码零硬编码):
# HP = hp_factor × 进入时产出/s × hall_factor (15×产出/s×殿系数 → ~30s 的仗)
# 公式(W4 方案 B,用户 2026-10-20 拍板;参数全部来自本行 + balance_tuning,代码零硬编码):
# HP = 进入时产出/s × boss_army_seconds (S_n:军列单推秒数,可直读旋钮)
# 军列 DPS = 进入时产出/s × army_dps_factor (阴兵在打,不是你一个人)
# 点击伤害 = 点击价值 × 连击(combo_step/层,combo_cap 封顶)
# INTRO = intro_sec 秒冲锋过场;开场伤害 = min(超度×intro_dmg_souls_factor, HP×intro_dmg_cap_ratio)
# 点击伤害 = 进入时产出/s × κ × combo_mult (BossBattle 计算 κ)
# INTRO = intro_sec 秒冲锋过场;开场伤害 = HP × intro_cap × clamp(超度/souls_par, 0, 1)
# ACTIVE = active_sec 秒主动阶段
# 通过奖励 = 阴德爆发 = HP × reward_hp_ratio
# 失败 = retry_cooldown_sec 冷却可再战(H7 前败即等推 → up_gate_boss_retry)
#
# 注:hp_factor=15 是 [B§11 差异#4] 对 GDD 50× 的修复值(50× 按 DPS=产出/s
# 推演 30s 打不完)。架构文档 §1.2/§4.3 中残留的 "50×" 为陈旧引用,以本表与
# Balance Bible v1.0 §5 为准。
# 方案 B 记(systems-spec §1.2,取代旧式 HP = 15×产出/s×hall_factor):
# - 旧式使产出/s 在分子分母被约掉 ⇒ 战斗时长与玩家强度脱钩,h>2.0 时数学不可胜(D-0);
# - 新式把"这场仗军列单推几秒"变成 CSV 里直写的数字,同类 bug 结构上不可能复发;
# - hall_factor **降级为纯叙事/视觉参数**(血条观感 / UI 文案),不参与伤害计算;
# - hp_factor / intro_dmg_souls_factor / intro_dmg_cap_ratio 三列随旧公式一并删除
# (留列即成误导性死旋钮;登记见 provenance P-16 / P-19)。
# ============================================================================
var id: String # "boss_hall_01"
var hall_id: String # halls.csv 行 ID
var hall_factor: float # 殿系数 1.0→2.2
var hp_factor: float # HP 系数(15)
var hall_factor: float # 殿系数 1.0→2.2(**纯叙事/视觉,不参与计算**)
var boss_army_seconds: float # 军列单推秒数 S_n(23.0→28.0)
var souls_par: int # 开场冲锋超度基准(0/2/18/…/90000;0 = 无开场加成)
var army_dps_factor: float # 军列 DPS 系数(1.0)
var intro_sec: float # INTRO 秒数(3)
var intro_dmg_souls_factor: float # 开场伤害 = 超度 × 本值(0.5)
var intro_dmg_cap_ratio: float # 开场伤害封顶 = HP × 本值(0.2)
var active_sec: float # ACTIVE 秒数(30)
var combo_window_sec: float # 连击窗口(0.6s)
var combo_step: float # 连击每层 +2%
@@ -34,6 +36,14 @@ var retry_cooldown_sec: float # 失败冷却(60s)
var reward_hp_ratio: float # 通过阴德爆发 = HP × 本值(0.2)
## 进入时 HP(架构 §4.3 start() 口径)
## 进入时 HP(方案 B:产出/s × S_n;架构 §4.3 start() 口径)
func hp_at(output_per_sec: float) -> float:
return hp_factor * output_per_sec * hall_factor
return boss_army_seconds * output_per_sec
## 开场冲锋伤害(方案 B3:HP × cap × clamp(超度/souls_par, 0, 1);cap 由 tune_boss_intro_cap 给)
## souls_par = 0(H1)→ 无开场加成(防 0 除);cap ≤ 0 或 enabled = false 时调用方直接跳过。
func intro_factor(souls_saved: int) -> float:
if souls_par <= 0:
return 0.0
return clampf(float(souls_saved) / float(souls_par), 0.0, 1.0)