150 lines
7.8 KiB
GDScript
150 lines
7.8 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# Date: 2026-09-21
|
||
# 冒险全流程期望扫描(三关连探零加成基线批量试玩——run_adventure 的蒙特卡洛批量工具,
|
||
# 补 level_monte_carlo 只测单关的空白面):固定 atk+0%/hp+0%(零天赋格加成,关间大奖励
|
||
# 按冒险结构现行设计自动兑付),逐局独立种子,输出:
|
||
# ① 每局一行 RUNSUM JSON(stdout,局级摘要:策略/种子/result/summary/战斗轮数/承伤/踩雷/
|
||
# 刷新/行进序列/逐战上限序列——后续统计分析的取数面);
|
||
# ② 全量操作日志 = playtest_logger 共享会话 JSONL(与真人试玩同合规口径,每策略一批一文件,
|
||
# run 行携带种子/策略切分局次,落 user://logs/)。
|
||
# 本工具只采集与落盘数据,不做统计判读(通关率/败因分布归分析会话)。
|
||
# 运行:Godot_console.exe --headless --path . --script res://smoke/adventure_monte_carlo.gd -- [N] [seed0] [normal|perfect|both] [route min|max]
|
||
extends SceneTree
|
||
|
||
|
||
var _run_cursor := 0 # 全局局序号(seed = seed0 + cursor,逐局独立消费一次)
|
||
|
||
|
||
func _initialize() -> void:
|
||
var args := OS.get_cmdline_user_args()
|
||
var n := 200
|
||
if args.size() >= 1 and args[0].is_valid_int():
|
||
n = maxi(5, int(args[0]))
|
||
var seed0 := 71000
|
||
if args.size() >= 2 and args[1].is_valid_int():
|
||
seed0 = int(args[1])
|
||
var policy_arg := "both"
|
||
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"]
|
||
var route := "min"
|
||
if args.size() >= 4 and args[3] in ["min", "max"]:
|
||
route = args[3]
|
||
# 同调分层开关(迭代 11 第①批 T8——off=现行基线零装配零扰动;on=贪心装配画像,确定性同种子可复放)
|
||
var attune_mode := "off"
|
||
if args.size() >= 5 and args[4] in ["off", "on"]:
|
||
attune_mode = args[4]
|
||
# 商店分层开关(迭代 11 第②批 T5——off=legacy 对照缺省零扰动;on=共享池货架面,终局池读数随行)
|
||
var shop_mode := "off"
|
||
if args.size() >= 6 and args[5] in ["off", "on"]:
|
||
shop_mode = args[5]
|
||
# 特性/树分层开关(迭代 12 T7/T10——off=基线;formed=成型画像开局注入七流派各 2 件档一成型集
|
||
# 〔测量面专用 params.relics_seed——真实局规则改写型特性只走局内随机渠道〕;ab_a/ab_b=Fork posture 两侧
|
||
# rule 键集——A/B 面两侧差 <~3pp 验收载体)
|
||
var relic_mode := "off"
|
||
if args.size() >= 7 and args[6] in ["off", "formed", "ab_a", "ab_b"]:
|
||
relic_mode = args[6]
|
||
var relic_profile := {}
|
||
if relic_mode == "formed":
|
||
relic_profile = {"mode": "on", "seed_ids": [
|
||
"arcane_ember", "mana_weaver", "pierce_shot", "chain_arrows",
|
||
"miner_stubborn", "miner_hammer", "greedy_lantern", "vein_intuition",
|
||
"mine_atlas", "nest_hunter", "armory_prep", "iron_wall",
|
||
"ley_harvest", "thorn_weaver"]}
|
||
elif relic_mode == "ab_a":
|
||
relic_profile = {"mode": "off", "tree_rules": ["posture_first_strike"]}
|
||
elif relic_mode == "ab_b":
|
||
relic_profile = {"mode": "off", "tree_rules": ["posture_bulwark"]}
|
||
var db = load("res://core/config_db.gd").new()
|
||
if not db.load_tables("res://data/tables", false):
|
||
print("表加载失败")
|
||
quit(1)
|
||
return
|
||
var attune_profile := {}
|
||
if attune_mode == "on":
|
||
# 贪心画像:中间进度计数(通关 3/Boss 击杀 6)经谓词表求值 Slots;装配序 = 表序 passive 优先至
|
||
# Slots-1 + 首个 active(slots=3 → [淬毒刃纹, 标中殉爆, 掘进蓄能],provisional 画像归调参批);
|
||
# Y = 营地升级 Lv1 表值 1。autoplay 默认不刷——Y 仅注入容量,刷新消费归玩家策略面(钓鱼归玩家)。
|
||
var slots: int = db.attunement_growth.slots({"clear_count": 3, "boss_kill_count": 6})
|
||
var passives: Array = []
|
||
var actives: Array = []
|
||
for r in db.treasure_attunement_rows():
|
||
if String(r.get("form", "")) == "passive":
|
||
passives.append(String(r.get("item_id", "")))
|
||
elif String(r.get("form", "")) == "active_charged":
|
||
actives.append(String(r.get("item_id", "")))
|
||
var equipped: Array = []
|
||
for p in passives:
|
||
if equipped.size() >= maxi(0, slots - 1):
|
||
break
|
||
equipped.append(p)
|
||
if slots > equipped.size() and not actives.is_empty():
|
||
equipped.append(actives[0])
|
||
for p in passives:
|
||
if equipped.size() >= slots:
|
||
break
|
||
if not equipped.has(p):
|
||
equipped.append(p)
|
||
attune_profile = {"equipped": equipped, "refresh_y": 1}
|
||
var autoplay = load("res://smoke/level_autoplay.gd")
|
||
var t0 := Time.get_ticks_msec()
|
||
print("=== 冒险全流程扫描:三关连探 · N=%d/策略 · 种子 %d 起逐局独立 · 策略=%s · atk+0%%/hp+0%% 零加成 · route=%s ===" % [n, seed0, policy_arg, route])
|
||
for policy in policies:
|
||
# 每策略一个共享日志会话(全量 JSONL 体量随局数增长,分文件便于定位)
|
||
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)
|
||
var tally := {}
|
||
for i in range(n):
|
||
var seed_val := seed0 + _run_cursor
|
||
_run_cursor += 1
|
||
var st = autoplay.run_adventure(db, seed_val, 0, 0, false, logger, policy, route, {}, attune_profile,
|
||
{"mode": "pool"} if shop_mode == "on" else {}, relic_profile)
|
||
var sum: Dictionary = st["summary"]
|
||
var rec := {
|
||
"policy": policy, "seed": seed_val, "atk_pct": 0, "hp_pct": 0, "route": route,
|
||
"attune": attune_mode, "equipped": attune_profile.get("equipped", []),
|
||
"relic_mode": relic_mode,
|
||
"result": st["result"],
|
||
"cleared": bool(sum.get("cleared", false)),
|
||
"levels_cleared": int(sum.get("levels_cleared", -1)),
|
||
"stopped_at": int(sum.get("stopped_at", -1)),
|
||
"keystone_total": int(sum.get("keystone_total", -1)),
|
||
"battles": int(st["battles"]), "rounds": int(st["rounds"]),
|
||
"hero_dmg": int(st["hero_dmg"]), "mine_hits": int(st["mine_hits"]),
|
||
"mine_dmg": int(st.get("mine_dmg", 0)),
|
||
"taken": int(st["hero_dmg"]) + int(st.get("mine_dmg", 0)),
|
||
"refreshes": int(st["refreshes"]), "choices": int(st["choices"]),
|
||
# 旗经济五字段(迭代14②判式换代批 T1——η=flag_hit÷flag_placed、标空率=flag_miss÷flag_placed
|
||
# 派生归分析侧;gain/penalty=结算流水行值合计(原始行值口径,等价换算归判读侧——登记))
|
||
"flag_placed": int(st.get("flag_placed", 0)), "flag_hit": int(st.get("flag_hit", 0)),
|
||
"flag_miss": int(st.get("flag_miss", 0)),
|
||
"flag_hit_gain": int(st.get("flag_hit_gain", 0)),
|
||
"flag_miss_penalty": int(st.get("flag_miss_penalty", 0)),
|
||
"relics": st.get("relics", []),
|
||
"visited_count": (st["visited_ids"] as Array).size(),
|
||
"encounter_max_hp_seq": st["encounter_max_hp_seq"],
|
||
"reward_events": st["reward_events"],
|
||
"shop_gp_seq": st["shop_gp_seq"],
|
||
"level_templates": st.get("level_templates", []),
|
||
"wallet_count": (st["wallet_events"] as Array).size(),
|
||
}
|
||
if shop_mode == "on":
|
||
# pool 面增量字段(迭代 11 第②批 T5——off 行零新增保对拍逐位干净)
|
||
rec["shop_mode"] = "pool"
|
||
rec["shop_buys"] = int(st.get("shop_buys", 0))
|
||
rec["pool_stock"] = st.get("pool_stock", {})
|
||
var rs := String(sum.get("result", ""))
|
||
tally[rs] = int(tally.get(rs, 0)) + 1
|
||
print("RUNSUM " + JSON.stringify(rec))
|
||
if (i + 1) % 20 == 0:
|
||
print("... %s 进度 %d/%d(耗时 %.1f 秒)" % [policy, i + 1, n, float(Time.get_ticks_msec() - t0) / 1000.0])
|
||
logger.close_session()
|
||
print("BATCH %s N=%d 终局计数=%s 日志=%s" % [policy, n, JSON.stringify(tally), log_path])
|
||
print("(口径注:零加成 = atk/hp 天赋格百分比均 0;关间大奖励(HP 上限提升+回满)按冒险结构设计自动兑付,未额外剥离 · 全程日志见 user://logs/ · 总耗时 %.1f 秒)" % [float(Time.get_ticks_msec() - t0) / 1000.0])
|
||
quit(0)
|