merge: 冒烟表值锚配置推导化——测试阈值随配置表走(用户指令 2026-09-23)

This commit is contained in:
DKChan committed 2026-09-23 03:11:53 +08:00
commit 3309aae2a3
1 file changed
+69 -27
@@ -284,7 +284,16 @@ func _smoke_adventure() -> void:
var a8 = same["a"]
var r8 = a8.runner()
var gp0: int = int(r8.gp)
check(gp0 >= 4, "进店余额 ≥ hp_up L2 首价 4(%d)" % gp0)
var hp_price := -1
for p in _db.shop.products:
if String(p.get("product_id", "")) == "hp_up":
hp_price = int(p.get("base_price", -1))
var l2_price_bonus := 0
for pb in _db.shop.price_level_bonus:
if int(pb.get("level_index", -1)) == 2:
l2_price_bonus = int(pb.get("bonus", 0))
check(gp0 >= hp_price + l2_price_bonus,
"进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" % [hp_price + l2_price_bonus, hp_price, l2_price_bonus])
check(r8.buy_product("hp_up"), "购买 hp_up 受理(出账 + 加算区)")
check(int(a8.bonus.get("max_hp", -1)) == int(_db.adventure.level_clear_hp_bonus) + 3,
"统一冒险加算区含商店值(max_hp = %d[L1 大奖励] + 3[商店]——同一本账前半)" % int(_db.adventure.level_clear_hp_bonus))
@@ -408,7 +417,12 @@ func _smoke_reward_identity_unit() -> void:
var cs1: Dictionary = reward_economy.grant_clear()
var cs2: Dictionary = reward_economy.grant_clear()
check(String(cs1.cell_type) == "blast" and String(cs1.reward_id) == "clear_blast", "清图批尾身份 = clear_blast(clear_default 唯一活行)")
check(int(cs1.power) >= 24 and int(cs1.power) <= 40, "清图奖励 power ∈ [24,40](区间行 roll 值 %d)" % int(cs1.power))
var cb_range := [-1, -1]
for row in _db.reward.rows:
if String(row.reward_id) == "clear_blast":
cb_range = [int(row.get("power_min", row.power)), int(row.get("power_max", row.power))]
check(int(cs1.power) >= int(cb_range[0]) and int(cs1.power) <= int(cb_range[1]),
"清图奖励 power ∈ 表区间 [%d, %d](roll 值 %d——配置推导)" % [cb_range[0], cb_range[1], int(cs1.power)])
check(JSON.stringify(cs1) == JSON.stringify(cs2), "grant_clear 同批次重调幂等")
# 退役后受理拒绝
reward_economy.retire_mapping()
@@ -459,6 +473,7 @@ func _smoke_flag_ap_exhausted() -> void:
func _smoke_level() -> void:
print("--- 关卡节点地图·线性推进(战×2→道具→战×2→恢复→Boss)---")
var autoplay = load("res://smoke/level_autoplay.gd")
var hero_base := {"atk": int(_db.hero.atk), "hp": int(_db.hero.max_hp), "ac": int(_db.hero.armor_class)} # 配置基线快照——⑤ 回基线对比用(改表自动跟随,不再硬编码)
# ① 满配通关路径(atk+200%/hp+100%——测试面板同源的属性覆盖)
var win = autoplay.run_level(_db, 42, 1, 200, 100)
check(win.result == "victory", "关卡·满配(atk+200/hp+100)通关(实测:%s @节点 %d)" % [win.result, win.defeat_node_index])
@@ -483,8 +498,10 @@ func _smoke_level() -> void:
var rep2 = autoplay.run_level(_db, 777, 1, 100, 100, true)
check(JSON.stringify(rep1.rows) == JSON.stringify(rep2.rows), "关卡回放断言:同种子两遍结算步骤流逐行相同")
check(rep1.rows.size() > 0, "关卡步骤流非空(%d 行)" % rep1.rows.size())
# ⑤ 属性覆盖恢复:模拟后 hero 表回基线(调整板不泄漏到后续会话;基线血线 20=Package B 第 2 轮拍板)
check(int(_db.hero.atk) == 2 and int(_db.hero.max_hp) == 20, "模拟后 hero 表恢复基线(atk=%d hp=%d)" % [int(_db.hero.atk), int(_db.hero.max_hp)])
# ⑤ 属性覆盖恢复:模拟后 hero 表回基线(调整板不泄漏到后续会话;基线 = 函数入口的配置快照,改表自动跟随)
check(int(_db.hero.atk) == int(hero_base.atk) and int(_db.hero.max_hp) == int(hero_base.hp) \
and int(_db.hero.armor_class) == int(hero_base.ac),
"模拟后 hero 表恢复基线(atk=%d hp=%d ac=%d——对配置快照)" % [int(_db.hero.atk), int(_db.hero.max_hp), int(_db.hero.armor_class)])
# ---- 护甲等级掷点批·换装护栏(2026-09-16 用户拍板:百分比减伤行整体退役换装 AC,无双轨)----
@@ -527,8 +544,9 @@ func _smoke_ac_swap_guard() -> void:
wall_detail += " %s(%d)" % [eff_id, floor_v]
check(wall_ok, "hero_defense 池 percent 行叠满不可触池底(墙最深%s > clamp_lo %d——单效果归零结构性不可达)" % [
wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.pool_clamp_lo)])
# ③ 单位表 armor_class
check(int(_db.hero.armor_class) == 10, "主角 armor_class = 10(暂定基准——EV 期望减免 21.25%%)")
# ③ 单位表 armor_class:域内校验(配置推导——正式投值归怪物/难度批,改表不断锚)
check(int(_db.hero.armor_class) >= 0 and int(_db.hero.armor_class) <= 30,
"主角 armor_class 在域内(%d——暂定基准随表走)" % int(_db.hero.armor_class))
var ac_all := true
for row in _db.enemy.rows:
if not row.has("armor_class") or int(row.armor_class) != 0:
@@ -1005,20 +1023,21 @@ func _auto_play_round(settlement, out: Dictionary) -> void:
func _smoke_power_interval() -> void:
print("--- 地图战斗资源·伤害量值区间(上阶/下阶随机)---")
# ① 表侧静态锚:区间行配对齐全、power = 中点(行动经济 条款①期望当量锚)——现行区间行 = heal_minor/clear_blast 两行
# (Package B 第 2/4 轮 2026-09-22:heal_minor [1,1]→[2,2] 定值、clear_blast [8,18]→[24,40]——中点锚 power 随动)
var interval_rows := {"heal_minor": [2, 2], "clear_blast": [24, 40]}
# ① 表侧静态锚(配置推导——2026-09-23 重构,改表不再断锚):凡带 power_min/power_max 的行 = 区间行,
# 校验配对齐全(lo ≤ hi)+ power = floor 中点(行动经济 条款①期望当量锚);其余行必须零区间字段。
# 行清单与区间值全部随表走——现行区间行 heal_minor/clear_blast 只是恰然,不再是锚。
var interval_rows := {} # rid → [lo, hi](读表)
var anchors_ok := true
for row in _db.reward.rows:
var rid := String(row.reward_id)
if interval_rows.has(rid):
if row.has("power_min") or row.has("power_max"):
var lo: int = int(row.get("power_min", -1))
var hi: int = int(row.get("power_max", -1))
if lo != interval_rows[rid][0] or hi != interval_rows[rid][1] or int(row.power) != (lo + hi) / 2:
interval_rows[rid] = [lo, hi]
if lo > hi or int(row.power) != (lo + hi) / 2:
anchors_ok = false
elif row.has("power_min") or row.has("power_max"):
anchors_ok = false # 区间配对只许两行(其余全定值行)
check(anchors_ok, "区间行域与中点锚在位(heal_minor/clear_blast——其余行零区间字段)")
check(anchors_ok and interval_rows.size() >= 1,
"区间行配对齐全且 power = floor 中点(配置推导 %d 行:%s)" % [interval_rows.size(), str(interval_rows.keys())])
# ② 运行侧:24 板 roll——区间行快照 power 全域内 + 上下阶实际流动 + 定值行不受扰
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260914)
@@ -1026,7 +1045,9 @@ func _smoke_power_interval() -> void:
var eligible: Array = []
for i in range(6):
eligible.append({"cell": Vector2i(i, 0)})
var seen := {"heal_minor": {}, "clear_blast": {}} # rid → {roll 值集}
var seen := {}
for rid in interval_rows.keys():
seen[rid] = {} # rid → {roll 值集}(区间行随表注册)
var in_domain := true
var fixed_ok := true
for trial in range(24):
@@ -1045,20 +1066,29 @@ func _smoke_power_interval() -> void:
fixed_ok = false # 定值行快照 power 必须仍是表值
var cs: Dictionary = reward_economy.grant_clear()
var cv := int(cs.power)
seen["clear_blast"][cv] = true
if cv < interval_rows["clear_blast"][0] or cv > interval_rows["clear_blast"][1]:
var c_rid := String(cs.reward_id)
if not seen.has(c_rid):
seen[c_rid] = {}
seen[c_rid][cv] = true
if interval_rows.has(c_rid) and (cv < interval_rows[c_rid][0] or cv > interval_rows[c_rid][1]):
in_domain = false
check(in_domain, "24 板全区间行快照 power ∈ [power_min, power_max](含清图槽)")
check(fixed_ok, "定值行快照 power 恒等表值——区间批零外溢")
# 数值压缩批(2026-09-17):heal_minor 区间 [1,1] 退化为定值行——变异流动断言只适用 clear_blast;
# Package B 第 2 轮(2026-09-22):heal_minor 定值 1→2——改查 roll 恒等于定值 2。
var variance_ok: bool = int(seen["clear_blast"].size()) >= 2
var heal_fixed := true
for v in seen["heal_minor"].keys():
if int(v) != 2:
heal_fixed = false
check(variance_ok, "上阶/下阶实际流动(clear_blast ≥2 个不同 roll 值)")
check(heal_fixed, "heal_minor 定值行 roll 恒 2([2,2] 零宽区间)")
# 定值行(区间 lo==hi 的行,如 heal_minor)roll 恒等表值;区间行(lo<hi,如 clear_blast)须实际流动。
# 全部按 interval_rows 配置推导(2026-09-23 重构)——新增/改区间行自动纳入,不再逐行硬编码。
var variance_ok := true
var fixed_rows_ok := true
for rid in interval_rows.keys():
var lo: int = interval_rows[rid][0]
var hi: int = interval_rows[rid][1]
var samples: int = seen.get(rid, {}).size()
if lo == hi:
if samples > 0 and not (samples == 1 and seen[rid].has(lo)):
fixed_rows_ok = false
elif samples >= 2 and seen[rid].size() < 2:
variance_ok = false
check(variance_ok, "区间行上下阶实际流动(≥2 样本的区间行须 ≥2 个不同 roll 值)")
check(fixed_rows_ok, "定值行(lo=hi)roll 恒等表值")
func _row_power(rid: String) -> int:
@@ -3077,7 +3107,19 @@ func _smoke_nine_types_gp_stream() -> void:
if String(row.get("reward_id", "")) == "gp_lose":
gp_neg = int(row.weight)
check(cell_rows == 18, "奖励字典 18 格(九型 × 正负——清图槽 clear_default 独立批尾不计)")
check(pos_weight == 78, "正向权重合计 78(heal_minor 2→4 后 76→78——Package B 第 2 轮)")
# 正向权重结构校验(配置推导,2026-09-23 重构):正向 cell 行权重 ≥1(必在池)、负向行权重恒 0(不入池)、
# 合计即配置自身的和(改表自动跟随,不再硬编码合计值)。
var pos_all_in_pool := true
var neg_all_zero := true
for row in _db.reward.rows:
if String(row.get("pool_tag", "")) != "cell":
continue
if String(row.get("polarity", "")) == "positive" and int(row.get("weight", 0)) < 1:
pos_all_in_pool = false
if String(row.get("polarity", "")) == "negative" and int(row.get("weight", 0)) != 0:
neg_all_zero = false
check(pos_all_in_pool and neg_all_zero,
"正向 cell 行权重 ≥1 全在池 / 负向行权重恒 0 不入池(正向合计 %d——配置推导)" % pos_weight)
check(gp_pos == 2 and gp_neg == 0, "gp 型两行(金币袋 gp_gain 正向 weight 2 / 破财 gp_loss 负向 weight 0 不入池)")
var rng = _fresh_rng(42)
check(rng.derive_at("gp_drop", 0) == 6226476397888000011, "R3 向量 gp_drop m=42 scope=0(GP+商店房实施批——registry salt_gp_drop=0xD6E8FEB8665FD7F1 权威值)")