# VERTICAL SLICE - NOT FOR PRODUCTION # 冒险全流程期望扫描(三关连探零加成基线批量试玩——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://sim/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] # 同调分层开关(off=现行基线零装配零扰动;on=贪心装配画像,确定性同种子可复放) var attune_mode := "off" if args.size() >= 5 and args[4] in ["off", "on"]: attune_mode = args[4] # 商店分层开关(off=legacy 对照缺省零扰动;on=共享池货架面,终局池读数随行) var shop_mode := "off" if args.size() >= 6 and args[5] in ["off", "on"]: shop_mode = args[5] # 特性/树分层开关(off=基线;formed=成型画像开局注入五事件族 14 件成型集 # 〔测量面专用 params.relics_seed——真实局规则改写型特性只走局内随机渠道〕;ab_a/ab_b=Fork posture 两侧 # rule 键集——A/B 面两侧差 <~3pp 验收载体;rules=任意树规则键集注入〔逗号分隔 args[7]——V19 三组配对载体〕) var feat_mode := "off" if args.size() >= 7 and args[6] in ["off", "formed", "ab_a", "ab_b", "rules"]: feat_mode = args[6] var feat_profile := {} if feat_mode == "formed": # 成型画像=五事件族覆盖 14 件 # (开格 4/封印 3/踩雷 4/节奏 2/战斗 1——每族含跨事件账本件,账本件=攒层/计数面 MC 读数承载) feat_profile = {"mode": "on", "seed_ids": [ "twin_pick", "geologist", "hard_hat", "frontier_miner", "seal_echo", "seal_smith", "seal_stake", "pain_memory", "trap_instinct", "chain_traps", "blood_honey", "last_drop", "market_close", "execution_scholar"]} elif feat_mode == "ab_a": feat_profile = {"mode": "off", "tree_rules": ["posture_first_strike"]} elif feat_mode == "ab_b": feat_profile = {"mode": "off", "tree_rules": ["posture_bulwark"]} elif feat_mode == "rules": feat_profile = {"mode": "off", "tree_rules": Array(String(args[7] if args.size() >= 8 else "").split(",", false))} # V19——任意键集配对注入(Array 包裹防 PackedStringArray 被 adventure_runner is Array 守卫整组丢弃) 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": # 贪心画像:Slots=3(槽位判定源=树同调位节点点亮数,模拟画像=三节点 # 全点亮常量);装配序 = # 表序 passive 优先至 Slots-1 + 首个 active(slots=3 → [淬毒刃纹, 标中殉爆, 掘进蓄能], # provisional 画像归调参);Y = 营地升级 Lv1 表值 1。autoplay 默认不刷——Y 仅注入容量, # 刷新消费归玩家策略面(钓鱼归玩家)。 var slots: int = 3 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://sim/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 {}, feat_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", []), "feat_mode": feat_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"]), # 封印经济五字段(η=seal_hit÷seal_placed、标空率=seal_miss÷seal_placed # 派生归分析侧;gain/penalty=结算流水行值合计(原始行值口径,等价换算归判读侧——登记)) "seal_placed": int(st.get("seal_placed", 0)), "seal_hit": int(st.get("seal_hit", 0)), "seal_miss": int(st.get("seal_miss", 0)), "seal_hit_gain": int(st.get("seal_hit_gain", 0)), "seal_miss_penalty": int(st.get("seal_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 面增量字段(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)