Files
gplan-2/game/smoke/adventure_monte_carlo.gd
T
DKChan c571af226f merge: 奇物概念收口与词族对齐(迭代15②号卡·文档与数据层)
概念层由用户逐轮裁定定稿(《奇物》v1.0→v2.7),本批只做落地对齐。

主要内容:
- 新建 design/gdd/systems/冒险循环/奇物.md v2.7(本概念唯一定义文档)
- 新建 design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md
  (loop engineering 八件成文 + 12 条批末验收断言 + 11 条设计契约含 6 条停机级)
- 代码词族对齐:feature_* → FEATURE_KINDS/feature_quota_*/feature_value_*
  (adventure_table.gd 24 字段 + 方法 + 常量,adventure.tres 21 键,值零变更)
- 三个 schema 补概念指路注释;relic_table.gd / adventure_table.gd 概念注释指向奇物.md
- ux/battle.md、level-map.md、HUD框架.md、06-待办清单.md、02-系统清单.md、
  registry/entities.yaml 同步称谓与元信息

六处冲突解法:
- 02-系统清单.md:版本历史整段重编(master v0.1-v1.3 原号复原,奇物系列从 v1.4 起)
- ux/battle.md:取分支侧新称谓,但「魔法道具槽」须写「秘宝槽」(master v1.3 已更名)
- adventure_table.gd:合成并集(取本批改名 + 保留 master SR-1 商店刷新三字段)
- level_map_screen.gd:块1 取 master M5 底栏结构,块2 取本批配色与称谓
- registry/entities.yaml:7 处三方混战,块1-5/7 取 master,块6 取本批
- roadmap.md:迭代 15 两卡并存(①调试 已落籍 / ②奇物 待开工)

合并后自查修正:
- 批量替换误伤 70 处(清图奖励/安全格奖励/关卡大奖励/奖励身份预读 等
  属地图战斗资源域;「奖励道具经济」旧文件名与「迭代 10 奖励重构」属历史留痕)→ 全部回退
- battle.md 历史修订行误改 → 恢复 master 原文
- 奇物.md 5 处死链(../ux/ 少一层、魔法道具.md 已更名)→ 修
- registry 条目 markdown 链接 → 改纯路径(同文件惯例)
- 奇物.md R1 散文段 656 字 → 拆段,现在零结构性告警

验证读数(合并后主区全量复跑):
- 冒烟 755 项 / 0 失败,三闸全过
- ui_input_probe 十六断言全绿
- ui_layout_probe 四区尺寸逐位吻合 battle.md 规格
- doc_link 死链 0;doc_term 与基线同;gdd_lint 零新增
- 旧标识符残留全 0;_status_box 未成死代码,四个投喂键齐全
2026-10-05 07:24:15 +08:00

150 lines
7.8 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"]),
# 旗经济五字段(迭代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)