refactor(tools): 测量面去单关直入口——难度读数改冒险条件口径(用户指令 2026-09-23)

- verifier_batch.gd: level 段(直入口单关基线 360 局/全量)整段移除——满血零上下文进关
  与真实冒险条件通关率脱节(直入口 L2 普通 5% vs 营地检查点 L2 条件 90% 同期并存),
  曾误导曲线调参;逐关读数由 adventure 段行内 levels_cleared/stopped_at 派生,零补采
- balance_verifier.py: 直入口验收带(LEVEL_BANDS)退役,新增①逐关条件通关率表
  (通关数/到达数,±95%CI,带待重锚只测不判——重锚归曲线调参批);营地检查点判定同批
  暂停(旧带值锚直入口读数不可比);--n 参数随段删除;--from-raw 兼容旧 raw(level 行忽略)
- 退役 level_monte_carlo.gd + level_debug.gd(单关扫描/单关诊断——同口径产 misleading 数据,
  替代面 = balance_verifier 冒险段 / adventure_monte_carlo)
- level_autoplay 头注:run_level 降格机制夹具/单战锚点专用——通关率/难度类测量禁用
- 保留面(非测量,零改动):smoke_all 机制夹具(治疗数学/选路状态机/L2 首遇战 12 种子锚行)、
  rhythm_batch/defense_scan/damage_probe 单战机制扫描——断言对象是具体机制非通关率数据
- 文档同步:难度曲线与Boss v0.19(带口径退役注记:调参表行+验收标准行+版本历史)/
  entities.yaml(clear_rate_target 两轮过期带值对齐退役注记+tool_carrier 换主载体)/
  CLAUDE.md(工具行两处)/ README(headless 工具节)/ 07-体验验证清单 / docs/code-conventions
  §5.2 / board_stat_probe 注释
- 验证:py_compile 过;冒烟 553 项 0 失败三闸全过;balance_verifier --quick 端到端 36 局
  2.7 分钟出报告(完美档逐关 87.5%→42.9%→0% 冒险条件口径首读数);doc 三闸失败集与
  master 逐项一致(lint 8/死链 7/term 1 均既有)
This commit is contained in:
DKChan committed 2026-09-23 05:24:13 +08:00
1 parent 7040dbc51f
commit d5783a5740
14 files changed
+119 -331

No files matched your search

@@ -33,23 +33,25 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . res://smoke/ui_probe.t
Godot_v4.7.2-stable_win64_console.exe --headless --path . res://smoke/ui_tune_probe.tscn
```
关卡期望扫描(C3 调参期关卡侧批量工具原型——atk%×hp% 网格,每格输出通关率/承伤期望/败北节点):
全冒险期望扫描(零加成三关连探批量——通关率/止步分布/败退节点;调参期难度读数一律走全冒险口径):
```
Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/level_monte_carlo.gd -- [N] [seed0]
Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/adventure_monte_carlo.gd -- [N] [seed0]
```
(原关卡期望扫描 level_monte_carlo.gd 走单关直入口口径——满血零上下文进关与真实冒险脱节,随口径退役删除,2026-09-23 用户拍板。)
可选类供给跨种子扫描(playtest-2026-09-12 Top1 复核——每局治疗包/小刀/精工短刀获取数、构成占比对 9:1 配比、"≥3 治疗包"局数分布;单种子坏抽与系统性偏置由此分割):
```
Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/supply_scan.gd -- [N] [seed0]
```
**日志合规(2026-09-12 用户要求)**:任何模拟运行与真人试玩均落全量 JSONL(user://logs/)——逐操作(op/cell/accepted/round)、逐结算明细行、轮次状态快照、battle_end、level_end。模拟(level_monte_carlo / 调整板批量模拟 / supply_scan)一文件多局,run 行携带种子/档位切分局次。
**日志合规(2026-09-12 用户要求)**:任何模拟运行与真人试玩均落全量 JSONL(user://logs/)——逐操作(op/cell/accepted/round)、逐结算明细行、轮次状态快照、battle_end、level_end。模拟(verifier/adventure_monte_carlo / 调整板批量模拟 / supply_scan)一文件多局,run 行携带种子/档位切分局次。
**试玩画像口径(2026-09-19 用户拍板固化——全项目试玩/扫描/面板模拟一律只用以下两档,随机策略口径退役)**:`policy="normal"` = **普通画像·基础定式**(单数字满足规则——满足即标/满足即开,无处可推盲挖边界格、不刷新;教学后正常玩家的下限近似);`policy="perfect"` = **完美画像**(single+subset 全推理零盲挖/卡死刷新——技能上界仪)。实现单一出口 = `smoke/auto_player.gd`(novice_action / perfect_action)经 `smoke/level_autoplay.gd`,调整板批量模拟与 level_monte_carlo 自动同口径;战斗层冒烟夹具与调试转储的 `choose_dig` 直调是机制测试夹具、不承载画像语义。分级依据与新旧读数(乱点 0%/5% → 基础定式 65%/90%)见 auto_player.gd 普通策略档节与台账 77。
**试玩画像口径(2026-09-19 用户拍板固化——全项目试玩/扫描/面板模拟一律只用以下两档,随机策略口径退役)**:`policy="normal"` = **普通画像·基础定式**(单数字满足规则——满足即标/满足即开,无处可推盲挖边界格、不刷新;教学后正常玩家的下限近似);`policy="perfect"` = **完美画像**(single+subset 全推理零盲挖/卡死刷新——技能上界仪)。实现单一出口 = `smoke/auto_player.gd`(novice_action / perfect_action)经 `smoke/level_autoplay.gd`,调整板批量模拟与各扫描工具自动同口径;战斗层冒烟夹具与调试转储的 `choose_dig` 直调是机制测试夹具、不承载画像语义。分级依据与新旧读数(乱点 0%/5% → 基础定式 65%/90%)见 auto_player.gd 普通策略档节与台账 77。
单关诊断:`--script res://smoke/level_debug.gd -- [seed] [atk%] [hp%]`。
单关诊断工具(level_debug.gd)随单关直入口口径退役(2026-09-23)——败退归因走 balance_verifier 冒险段(层/节点/敌人人读归因)。
战斗级工具(v1 遗产):`dump_battle.gd`(结算流水)/ `monte_carlo.gd`(战斗期望)/ `experiment_density.gd`(密度调参)。
## 范围(v1 范围 + v2 追加,用户 2026-09-10/11 确认)
@@ -3,7 +3,7 @@
# 棋盘板形统计探针(清图支配性对账重跑批取数工具——判式"实测板形"口径的数据源):
# 对 board_gen.tres 每行 (L, D) 生成 N 张板,统计幸存雷数 |M| 与安全格数 |S|
# 的均值 / 最小 / 最大("先布后删"后的落地值——名义值 density×(W×H−9) 不含删除段)。
# 只读诊断,零游戏代码改动;装载面与 level_monte_carlo 同源(config_db.load_tables)。
# 只读诊断,零游戏代码改动;装载面与冒险扫描工具同源(config_db.load_tables)。
# 运行:Godot_console.exe --headless --path . --script res://smoke/board_stat_probe.gd -- [N] [seed0]
extends SceneTree
@@ -2,9 +2,12 @@
# Date: 2026-09-11(2026-09-12 增完整遥测接线——用户要求:任何模拟运行与真人试玩同日志合规:
# 操作记录 / 轮次状态 / 战斗结束 / 通关结束 全量 JSONL。logger=null 时自建会话(每局一文件),
# 批量调用方传共享 logger(log_run 局次标记切分)。
# 关卡自动通关策略(理性玩家近似)——呈现层数值调整板的批量模拟与 headless 期望扫描(level_monte_carlo)
# 关卡自动通关策略(理性玩家近似)——呈现层数值调整板的批量模拟与 headless 扫描
# 共用同一实现,保证"面板模拟读数"与"扫描表读数"同口径。策略与 smoke_all 战斗自动玩家同族:
# 挖最近可挖格 / 无可挖停手 / 奖励恢复点立即确认。
# 2026-09-23 单关直入口口径退役(用户拍板):run_level 降格为机制夹具/单战锚点专用
# (smoke_all 机制断言、rhythm/defense/damage 单战扫描)——通关率/难度类测量禁用本入口
# (满血零上下文进关,与真实冒险条件通关率脱节),一律走 run_adventure。
# 2026-09-15 格面资源批:道具窗口选择与可选类获取计数随可选类道具删除下线。
# 2026-09-14 增完美策略档(policy="perfect",默认 "normal" 零行为变更——既有调用方签名兼容):
# 零盲挖/全标旗/卡死刷新——上界测量仪,求解器与动作语义见 auto_player.gd 完美策略档节。
@@ -14,8 +17,8 @@
# 2026-09-21 冒险结构实现批:run_adventure 三关连探驱动(AdventureRunner——主种子恰一次、
# 大奖励立即确认 continue_to_next_level、adventure_finished 置 result/summary;外层每关重取
# runner 实例经 _drive_level 复用单关驱动)。run_level 驱动段抽成 _drive_level——签名与
# 返回结构零变化,行为逐位不变(MC 同种子对照闸承载)。
# 运行前提:db 已 load_tables;每个 run_level 全新 RngService(同种子 ⇒ 全程逐步可回放)。
# 返回结构零变化,行为逐位不变。
# 运行前提:db 已 load_tables;run_level/run_adventure 均须全新 RngService(同种子 ⇒ 全程逐步可回放)。
# 实现注:信号处理体全部为静态方法 + bind 传状态——GDScript 多行 lambda 内嵌 match/控制流的解析不可靠。
extends RefCounted
@@ -1,34 +0,0 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-09-11
# 单关卡诊断:跑一遍完整关卡并打印进度与统计(定位挂起/数值问题的临时工具)。
# 运行:Godot_console.exe --headless --path . --script res://smoke/level_debug.gd -- [seed] [atk%] [hp%]
extends SceneTree
func _initialize() -> void:
var args := OS.get_cmdline_user_args()
var seed_val := 4200
var atk_pct := 0
var hp_pct := 0
if args.size() >= 1 and args[0].is_valid_int():
seed_val = int(args[0])
if args.size() >= 2 and args[1].is_valid_int():
atk_pct = int(args[1])
if args.size() >= 3 and args[2].is_valid_int():
hp_pct = int(args[2])
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("表加载失败")
for d in db.diagnostics():
if d.severity == "error":
print(" error: [%s] %s" % [d.category, d.message])
quit(1)
return
var autoplay = load("res://smoke/level_autoplay.gd")
var t0 := Time.get_ticks_msec()
print("开始:seed=%d atk+%d%% hp+%d%%" % [seed_val, atk_pct, hp_pct])
var st = autoplay.run_level(db, seed_val, 1, atk_pct, hp_pct)
print("结果: %s · 用时 %.2f 秒" % [st.result, float(Time.get_ticks_msec() - t0) / 1000.0])
print(" 战斗节点=%d 轮数=%d 整关承伤=%d 剩余HP=%d 进BossHP=%d 踩雷=%d" % [st.battles, st.rounds, st.hero_dmg, st.hp_left, st.hp_at_boss, st.mine_hits])
print(" 败北节点=%d(%s) 恢复点回复=%d(前HP %d)" % [st.defeat_node_index, st.defeat_node_type, st.rest_healed, st.hp_before_rest])
quit(0)
@@ -1 +0,0 @@
uid://bflo5h8xysqsl
@@ -1,169 +0,0 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-09-11
# 关卡期望扫描(难度曲线与Boss 调参期关卡侧批量工具的原型——对应 棋盘生成 F4 / 战斗侧 monte_carlo.gd):
# 扫描 atk% × hp% 网格(模拟天赋树数值提升档位),输出每档"通关率 / 整关承伤期望 / 败北节点分布 /
# 通关剩余 HP 期望 / 进 Boss HP 期望 / 平均轮数"——把"多少数值能通关、通关要掉多少血"变成可测量数据。
# 运行:Godot_console.exe --headless --path . --script res://smoke/level_monte_carlo.gd -- [N] [seed0] [normal|perfect|both] [level_index] [shared|unique]
# 种子模式(2026-09-19 掷点测量修复批 A 案):shared(默认,旧行为)= 同批次全部格子×策略共用种子
# seed0+i——同种子历史坐标可比,但同一 (种子, scope) 被反复消费,聚合掷点统计被头部窗口彩票放大
# (验收重测批实证);unique = 逐局独立种子(seed0 + 全局递增序号),每个掷点恰消费一次 → 聚合
# 掷点统计回归标准二项分布(±2σ gate 口径重新有效)。验收掷点面用 unique;同种子 A/B 对照用 shared。
extends SceneTree
var _run_cursor := 0 # unique 模式全局局序号(seed = seed0 + cursor)
func _initialize() -> void:
var args := OS.get_cmdline_user_args()
var n := 30
if args.size() >= 1 and args[0].is_valid_int():
n = maxi(5, int(args[0]))
var seed0 := 4200
if args.size() >= 2 and args[1].is_valid_int():
seed0 = int(args[1])
# 关卡轴(分支节点图批 2026-09-17:L2 幽暗地域图版扫描——第 4 参 level_index,默认 1 零行为变更)
var level_arg := 1
if args.size() >= 4 and args[3].is_valid_int():
level_arg = maxi(1, int(args[3]))
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("表加载失败")
quit(1)
return
# 共享日志会话(2026-09-12 用户要求:模拟运行与真人试玩同日志合规——操作/轮次/战斗/通关全量 JSONL,
# 一文件多局,run 行携带种子/档位切分局次)
var logger = load("res://presentation/playtest_logger.gd").new()
logger.ap_per_round = int(db.action_economy.ap_per_round)
var log_path: String = logger.start_session(seed0)
print("模拟日志: %s" % log_path)
var autoplay = load("res://smoke/level_autoplay.gd")
var t0 := Time.get_ticks_msec()
# 策略轴(2026-09-14 OQ7 完美策略档——技能轴上界测量):normal = 原理性玩家近似;
# perfect = 零盲挖/全标旗/卡死刷新(auto_player.gd 完美策略档节);both = 双档对照。
var policy_arg := "normal"
if args.size() >= 3 and args[2] in ["normal", "perfect", "both"]:
policy_arg = args[2]
var policies: Array = ["normal"]
if policy_arg == "perfect":
policies = ["perfect"]
elif policy_arg == "both":
policies = ["normal", "perfect"]
# 种子模式(掷点测量修复批 A 案——见文件头注记):缺省 shared = 旧行为零变更
var seed_mode := "shared"
if args.size() >= 5 and args[4] in ["shared", "unique"]:
seed_mode = args[4]
# 基线格模式(2026-09-20 重投批工具口径):第 6 参 baseline = 只跑 atk0/hp0 一格——
# 全网格 10 格在大 N 下成本不可行(~1.35s/局),基线判读只需零加成格的大样本精度
var baseline_only := false
if args.size() >= 6 and args[5] == "baseline":
baseline_only = true
var atk_axis: Array = [0, 50, 100, 150, 200]
var hp_axis: Array = [0, 100]
if baseline_only:
atk_axis = [0]
hp_axis = [0]
var seed_label := "种子 %d+%d" % [seed0, n] if seed_mode == "shared" else "种子 %d 起逐局独立" % seed0
print("=== 关卡期望扫描:level %d · N=%d/格 · %s · 策略=%s · 种子模式=%s ===" % [level_arg, n, seed_label, policy_arg, seed_mode])
print("%-6s %-10s %-8s %-14s %-14s %-14s %-10s %-12s %-12s %-10s %-10s %-8s %-8s" % ["策略", "atk%/hp%", "通关率", "整关承伤均/p90", "通关剩余HP均(胜)", "进BossHP均", "平均轮数", "踩雷/战", "普通承伤/场%", "跳战率", "Boss承伤%", "踩雷/局", "刷新/局"])
var perfect_mine_total := 0 # 完美档构造保证 = 零踩雷(只挖已证安全格)——扫描末断言
for policy in policies:
var tag := "基线" if policy == "normal" else "完美"
for atk_pct in atk_axis:
for hp_pct in hp_axis:
var wins := 0
var dmg_all: Array = []
var hp_left_win: Array = []
var hp_boss: Array = []
var rounds_all: Array = []
var mines_per_battle: Array = []
var normal_dmg_pct: Array = [] # 每普通战斗承伤占生效 max_hp 比(难度曲线与Boss F4 口径——≤20% 带)
var boss_dmg_pct: Array = [] # Boss 战承伤占生效 max_hp 比(难度曲线与Boss F4 口径——≤50% 带)
var wipes := 0 # 普通战斗 round-1 即胜数(首轮全灭跳战——拍板④观测面)
var normal_battles := 0
var death_nodes := {}
var mine_per_run: Array = []
var refresh_per_run: Array = []
for i in range(n):
var seed_val := seed0 + i if seed_mode == "shared" else seed0 + _run_cursor
_run_cursor += 1
var st = autoplay.run_level(db, seed_val, level_arg, atk_pct, hp_pct, true, logger, policy)
dmg_all.append(st.hero_dmg)
rounds_all.append(st.rounds)
mine_per_run.append(int(st.mine_hits))
refresh_per_run.append(int(st.refreshes))
mines_per_battle.append(float(st.mine_hits) / float(maxi(1, st.battles)))
var eff_max := maxi(1, int(round(float(db.hero.max_hp) * (100.0 + float(hp_pct)) / 100.0)))
# 逐战斗承伤切分(round_index 回卷 = 新战斗;末段 = Boss 战——当且仅当本局抵达 Boss 节点)
var segs := _damage_segments(st.rows)
for k in segs.size():
var pct := 100.0 * float(segs[k].dmg) / float(eff_max)
if k == segs.size() - 1 and int(st.hp_at_boss) >= 0:
boss_dmg_pct.append(pct)
else:
normal_dmg_pct.append(pct)
normal_battles += 1
if bool(segs[k].r1_win):
wipes += 1
if st.result == "victory":
wins += 1
hp_left_win.append(st.hp_left)
hp_boss.append(st.hp_at_boss)
elif st.result == "defeat":
var key := "%d.%s" % [st.defeat_node_index, st.defeat_node_type]
death_nodes[key] = int(death_nodes.get(key, 0)) + 1
var death_txt := "—"
if not death_nodes.is_empty():
var parts: Array = []
for k in death_nodes.keys():
parts.append("%s×%d" % [k, death_nodes[k]])
death_txt = " ".join(parts)
var mine_mean := _mean(mine_per_run)
if policy == "perfect":
perfect_mine_total += int(round(mine_mean * float(n)))
print("%-6s atk%+3d%% hp%+3d%% %5.0f%% %6.1f / %-6.1f %6.1f %6.1f %6.1f %5.2f %6.1f %5.0f%% %6.1f %6.2f %6.2f %s" % [
tag, atk_pct, hp_pct, 100.0 * float(wins) / float(n),
_mean(dmg_all), _p90(dmg_all), _mean(hp_left_win), _mean(hp_boss), _mean(rounds_all), _mean(mines_per_battle),
_mean(normal_dmg_pct), 100.0 * float(wipes) / float(maxi(1, normal_battles)), _mean(boss_dmg_pct), mine_mean, _mean(refresh_per_run), death_txt])
print("(F4 带:承伤 ≤20%%/普通场 · ≤50%%/Boss 场,占生效 max_hp · F5 带:基线通关率 L1 [35,55]%% / L2 [25,45]%%——2026-09-20 用户拍板分带 · 跳战率 = 普通战斗 round-1 即胜(首轮全灭)占比 · 承伤含败者至死 · 败北节点 0-3=战斗节点序 4=Boss · 踩雷/战=每战斗负向兑付触发期望(格面资源批口径) · 完美档 = 技能轴上界测量(零盲挖/全标旗/卡死刷新——口径见 auto_player.gd)· 耗时 %.1f 秒)" % [float(Time.get_ticks_msec() - t0) / 1000.0])
if "perfect" in policies and perfect_mine_total > 0:
push_error("[level_monte_carlo] 完美策略档出现踩雷 %d 次——构造保证被破坏(只挖已证安全格),求解器/驱动有缺陷" % perfect_mine_total)
quit(1)
return
logger.close_session()
quit(0)
# 逐战斗段切分:round_index 回卷 = 新战斗(defense_scan 同口径)
# 每段 = {dmg: 英雄承伤合计, r1_win: round-1 即胜(首轮全灭跳战签名——verdict 胜利行 value=1)}
func _damage_segments(rows: Array) -> Array:
var segs: Array = []
var last_round := 0
for row in rows:
var r: int = int(row.get("round_index", 0))
if segs.is_empty() or r < last_round:
segs.append({"dmg": 0, "r1_win": false})
last_round = r
var seg: Dictionary = segs[segs.size() - 1]
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action":
seg.dmg = int(seg.dmg) + int(row.get("value", 0))
if String(row.get("type", "")) == "verdict" and r == 1 and int(row.get("value", 0)) == 1:
seg.r1_win = true
return segs
func _mean(a: Array) -> float:
if a.is_empty():
return 0.0
var t := 0.0
for v in a:
t += float(v)
return t / float(a.size())
func _p90(a: Array) -> float:
if a.is_empty():
return 0.0
var b := a.duplicate()
b.sort()
return float(b[mini(b.size() - 1, int(0.9 * b.size()))])
@@ -1 +0,0 @@
uid://h3mghae7yvvx
@@ -1,16 +1,17 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-09-22(数值验证工具批——判定端 tools/balance_verifier.py 的数据源,本文件零判定逻辑)
# 三段一次采集,stdout 以「VJSON 」前缀行出机器读 JSON(其余行为人读进度/摘要):
# level 段 直入口单关基线(level_autoplay.run_level 零加成)——L1/L2/L3 × 双画像 × n,
# 供验收带判定(分带批 2026-09-20 口径:L1 普通 [35,55] / L2 普通 [25,45] / 完美 [35,65])
# adventure 段 全冒险连探基线(run_adventure 零永久强化)× 双画像 × na——止步分布与败退节点
# (摘要 defeat 明细 + 末场战斗敌方名),回答「零加成倒在第几层哪个节点」
# 2026-09-23 单关直入口口径退役(用户拍板):原 level 段(直入口单关基线——满血零上下文进关,
# 与真实冒险的条件通关率脱节,曾误导曲线调参)整段移除;逐关难度读数由 adventure 段行内
# levels_cleared/stopped_at 派生(判定端 analyze_adventure_levels),零补采。
# 两段一次采集,stdout 以「VJSON 」前缀行出机器读 JSON(其余行为人读进度/摘要):
# adventure 段 全冒险连探基线(run_adventure 零永久强化)× 双画像 × na——止步分布、败退节点
# (摘要 defeat 明细 + 末场战斗敌方名)与逐关条件通关率派生源
# campaign 段 营地循环战役 × 双画像 × k 局——SaveManager 隔离档 + MetaProgression 真实账本:
# 逐局 hero_override() 注入 → 冒险 → settle_adventure 入账 → 花费策略买锻造,
# 回答「累计几次通关冒险后下一关可过」(元进度换算率标定的模拟读数源)
# 运行:Godot_console.exe --headless --path . --script res://smoke/verifier_batch.gd -- [k=v ...]
# n=60(直入口每格局数) na=40(基线冒险局数) k=40(战役局数) seed0=8000
# only=level,adventure,campaign(缺省全段) spend=hp_first|atk_first(锻造花费优先序)
# na=40(基线冒险局数) k=40(战役局数) seed0=8000
# only=adventure,campaign(缺省全段) spend=hp_first|atk_first(锻造花费优先序)
# 种子纪律:unique 逐局独立(掷点测量修复批 A 案——seed0 + 全局游标,每掷点恰消费一次)。
# 日志合规:全段共享 playtest_logger 会话(与真人试玩同日志),分析工具可直接消费。
# 存档红线:战役档用 user://verifier_campaign_<画像>.json 隔离路径,段首清陈旧段尾清理——
@@ -38,21 +39,19 @@ func _initialize() -> void:
print("模拟日志: %s" % log_path)
var autoplay = load("res://smoke/level_autoplay.gd")
var t0 := Time.get_ticks_msec()
var counts := {"level": 0, "adventure": 0, "campaign": 0}
if "level" in opt.only:
_seg_level(db, autoplay, logger, opt, counts)
var counts := {"adventure": 0, "campaign": 0}
if "adventure" in opt.only:
_seg_adventure(db, autoplay, logger, opt, counts)
if "campaign" in opt.only:
_seg_campaign(db, autoplay, logger, opt, counts)
logger.close_session()
print("数值验证采集完成:level %d 局 · adventure %d 局 · campaign %d 局,耗时 %.1f 秒"
% [counts.level, counts.adventure, counts.campaign, float(Time.get_ticks_msec() - t0) / 1000.0])
print("数值验证采集完成:adventure %d 局 · campaign %d 局,耗时 %.1f 秒"
% [counts.adventure, counts.campaign, float(Time.get_ticks_msec() - t0) / 1000.0])
quit(0)
func _parse_args(args: PackedStringArray) -> Dictionary:
var opt := {"n": 60, "na": 40, "k": 40, "seed0": 8000, "only": "level,adventure,campaign", "spend": "hp_first"}
var opt := {"na": 40, "k": 40, "seed0": 8000, "only": "adventure,campaign", "spend": "hp_first"}
for a in args:
var kv := String(a).split("=", true, 1)
if kv.size() != 2:
@@ -67,7 +66,7 @@ func _parse_args(args: PackedStringArray) -> Dictionary:
opt[key] = int(kv[1])
var segs: Array = []
for s in String(opt.only).split(","):
if s in ["level", "adventure", "campaign"]:
if s in ["adventure", "campaign"]:
segs.append(s)
opt["only"] = segs
if not SPEND_ORDERS.has(String(opt.spend)):
@@ -85,25 +84,7 @@ func _emit(row: Dictionary) -> void:
print("VJSON " + JSON.stringify(row))
# ---- level 段:直入口单关基线(零加成——分带批验收带判定的同口径读数) ----
func _seg_level(db, autoplay, logger, opt: Dictionary, counts: Dictionary) -> void:
for policy in ["normal", "perfect"]:
for li in [1, 2, 3]:
for i in range(int(opt.n)):
var seed_val := _next_seed(int(opt.seed0))
var st: Dictionary = autoplay.run_level(db, seed_val, li, 0, 0, false, logger, policy, "min")
_emit({
"seg": "level", "level": li, "policy": policy, "seed": seed_val,
"result": String(st.result),
"defeat_node_id": int(st.defeat_node_id), "defeat_node_type": String(st.defeat_node_type),
"battles": int(st.battles), "rounds": int(st.rounds),
})
counts.level += 1
print("进度 level 第%d层 %s %d/%d" % [li, policy, int(opt.n), int(opt.n)])
# ---- adventure 段:全冒险连探基线(零永久强化——止步分布与败退节点归因) ----
# ---- adventure 段:全冒险连探基线(零永久强化——止步分布/败退节点归因/逐关条件通关率派生源) ----
func _seg_adventure(db, autoplay, logger, opt: Dictionary, counts: Dictionary) -> void:
for policy in ["normal", "perfect"]: