Files
gplan-2/game/smoke/adventure_monte_carlo.gd
T
DKChan 5cf7f4b6ae 立《特性》概念:局内加强物品统一命名为「特性」,两种形态收敛为枚举值 + 文档/称谓/数据层标识符统一
- 新增 design/gdd/systems/冒险循环/特性.md(概念唯一定义文档)
  定义=一次冒险内角色自身得到的提升(不是持有物);作用范围、生命周期、
  获取途径(战场奖励/商店·训练)、关联与边界五要素齐全;数值一律指路表不入正文
- 02-系统清单:冒险循环补行 + 命名对照表补 ADVENTURE_FEATURE
- registry entities.yaml:新增 feature 概念条目(owner/referenced_by)+ adventure_table 字段名同步
- 数据层标识符 112 处换名(值零变更):BONUS_IDS→FEATURE_KINDS、bonus_token_count→feature_quota、
  bonus_value→feature_value、token_*/bonus_*_flat→feature_quota_*/feature_value_*
- 称谓统一 94 处:旧称「流派件/数值件/本关强化」→ 特性 + 形态修饰语(规则改写型/属性加成型)
- 两张 schema 顶部加概念指路注释,代码可直接查到定义文档
- 06-待办清单:BD 分层条目现状改写 + 新增「命名统一后续」条目(relic 词族与类名对齐待实施)

未动:类名与文件名(Godot .uid/.tscn 引用需跑测试自证)、断点存档键(兼容)
2026-10-05 01:46:35 +08:00

144 lines
7.3 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.aux_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"]),
"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)