feat(balance): Package B 七轮数值优化+关卡成长×永久成长对齿批——worktree adventure-mc-playtest 落籍

- Package B 迭代七轮:enemy HP 阶梯重锚+阶段拔高(L2/L3 14 行+伤害轴 atk 3)、AP 6→8、
  clear_blast 26/18/35→32/24/40、heal_minor 加强、hero max_hp 20、d3 缩板 10×10(板随战斗时长配)
- 数值设计批(game-balancer 工作流):关卡怪物成长×永久锻造对齿,forge base_price 10→20
- 三阶段快照验证(report_M0/M1/M2):墙真实存在、锻造单调开门,拍板 B 接受现曲线
- 详见 qa-data/iteration_log.md(九段台账)与 数值设计_关卡成长×永久成长_v1.md
- smoke 工具入库:adventure_monte_carlo.gd / calibrate_boards.gd;qa-data 原始 fulllog 不入库
This commit is contained in:
DKChan committed 2026-09-23 02:32:25 +08:00
1 parent aeaa5d7b6b
commit 8acd107533
82 files changed
+113787 -135

No files matched your search

@@ -4,10 +4,10 @@
[resource]
script = ExtResource("1")
ap_per_round = 6
ap_per_round = 8
dig_ap_cost = 1
flag_place_ap_cost = 1
rap_refresh_cost = 6
rap_refresh_cost = 8
refresh_cap_per_round = 2
move_ap_cost = 0
chain_ap_cost = 0
@@ -20,3 +20,9 @@ chain_ap_cost = 0
; 须单独立批。减伤幅度下调(-30 → 下调)为同根因配套项,待用户拍板数值后另行落表;
; 终局方案 = 减伤换装护甲等级 AC+1(随 AC 掷点定稿批 EV 中性换装)。
; 观察口径:AP 6 不够再降 4(墙覆盖率 ~19%,代价 = 12×12 板挖掘节奏拖长)。
;
; Package B 数值优化批·首版(2026-09-22——qa-data/实现计划书_PackageB.md §4.2):ap_per_round 6→8、
; rap_refresh_cost 6→8 随动(维持 registry 登记"刷新成本 = 每轮 AP,满停一轮恰一刷"不变式;schema
; 非硬约束,系设计不变式)。上批 8→6 所杀"首轮全标旗→减伤削弱堆满→零承伤通关"退化,在 B1 敌人 HP
; 大幅抬升后单轮堆满已无法零承伤通关多轮整场,风险缓解;复测须盯 analyze.py 零承伤轮指标,明显回升
; 则按计划书 §6.5 AP 8→7 回调。dig/flag/move/chain cost 与 refresh_cap 未动(move/chain 结构冻结 0)。
@@ -40,13 +40,13 @@ rows = [{
}, {
"level_index": 1,
"node_depth": 3,
"board_width": 12,
"board_height": 12,
"board_width": 10,
"board_height": 10,
"mine_density": 0.25,
"inferability_target": 0.88,
"solver_rules": "single_subset",
"min_safe_count": 60,
"max_gen_attempts": 24,
"min_safe_count": 50,
"max_gen_attempts": 16,
"board_type": "dungeon"
}, {
"level_index": 1,
@@ -243,3 +243,10 @@ rows = [{
; 陈旧行清理(GP+商店房实施批收尾 2026-09-21):并行表代理曾把 Boss 板行照抄到 (2,8)/(3,8)——节点 8 现为
; 商店房、无板行需求,原 (2,8)/(3,8) 行成陈旧冗余已删(删前核 (·,9) 行在位且内容与被删行逐字段一致);
; 本行集迁移 = Boss 重编号 8→9 的商店房批连带。
;
; Package B 迭代·第 6 轮·d3 改板试点(2026-09-22——用户提出并授权,破 §4.4 板面冻结的单行试点):
; (1,3) 第一关·节点3·熊地精双雄 板 12×12→10×10(min_safe_count 60→50、max_gen_attempts 24→16,
; 对齐 (1,0)/(1,1) 的 10×10 行)。依据:第 5 轮读数 d3 完成度 84% 中 83.2pp 系 R₀ 免费展开、实挖仅
; ~18 格/3.7 轮——总血 28-42 撑不起 121 格精英板;缩板后同战斗量读数 (68.6+18)/87≈100% 回带,
; 清图率升(打完近全翻开,清图尖峰 24-40 伤可兑现),轮数/承伤/胜率预计不动(难度刀≠设计意图修复)。
; 连带义务:board_calibration.csv 全量重跑 calibrate_boards(§4.4 预告)。provisional 未提交。
@@ -47,13 +47,13 @@ rows = [{
}, {
"level_index": 1,
"node_depth": 2,
"enemy_id": "bugbear",
"display_name": "熊地精",
"unit_count": 1,
"hp": 13,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"enemy_id": "bugbear",
"display_name": "熊地精",
"unit_count": 1,
"hp": 50,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
"pool": [{
"skill_id": "attack",
"weight": 6
@@ -70,10 +70,10 @@ rows = [{
"level_index": 1,
"node_depth": 3,
"enemy_id": "bugbear",
"display_name": "熊地精双雄",
"unit_count": 2,
"unit_count_max": 3,
"hp": 6,
"display_name": "熊地精双雄",
"unit_count": 2,
"unit_count_max": 3,
"hp": 14,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
@@ -93,12 +93,12 @@ rows = [{
"level_index": 1,
"node_depth": 4,
"enemy_id": "ogre_chief",
"display_name": "食人魔酋长",
"unit_count": 1,
"hp": 50,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"display_name": "食人魔酋长",
"unit_count": 1,
"hp": 72,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
"pool": [{
"skill_id": "attack",
"weight": 6
@@ -115,32 +115,32 @@ rows = [{
"level_index": 2,
"node_depth": 0,
"enemy_id": "bugbear",
"display_name": "熊地精",
"unit_count": 1,
"hp": 10,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
"pool": [{
"skill_id": "attack",
"weight": 6
}, {
"skill_id": "lunge",
"weight": 1
}, {
"skill_id": "heavy",
"weight": 2
}],
"armor_type": "light",
"armor_class": 0
}, {
"level_index": 2,
"node_depth": 1,
"enemy_id": "bugbear",
"display_name": "熊地精猎群",
"unit_count": 2,
"unit_count_max": 3,
"hp": 5,
"display_name": "熊地精",
"unit_count": 1,
"hp": 50,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
"pool": [{
"skill_id": "attack",
"weight": 6
}, {
"skill_id": "lunge",
"weight": 1
}, {
"skill_id": "heavy",
"weight": 2
}],
"armor_type": "light",
"armor_class": 0
}, {
"level_index": 2,
"node_depth": 1,
"enemy_id": "bugbear",
"display_name": "熊地精猎群",
"unit_count": 2,
"unit_count_max": 3,
"hp": 22,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
@@ -160,9 +160,9 @@ rows = [{
"level_index": 2,
"node_depth": 4,
"enemy_id": "giant_spider",
"display_name": "巨蜘蛛",
"unit_count": 2,
"hp": 5,
"display_name": "巨蜘蛛",
"unit_count": 2,
"hp": 24,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
@@ -182,12 +182,12 @@ rows = [{
"level_index": 2,
"node_depth": 5,
"enemy_id": "bugbear",
"display_name": "熊地精重击手",
"unit_count": 1,
"hp": 13,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"display_name": "熊地精重击手",
"unit_count": 1,
"hp": 88,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"pool": [{
"skill_id": "attack",
"weight": 5
@@ -203,12 +203,12 @@ rows = [{
}, {
"level_index": 2,
"node_depth": 6,
"enemy_id": "giant_spider",
"display_name": "巨蜘蛛巢群",
"unit_count": 2,
"unit_count_max": 3,
"hp": 4,
"atk": 1,
"enemy_id": "giant_spider",
"display_name": "巨蜘蛛巢群",
"unit_count": 2,
"unit_count_max": 3,
"hp": 22,
"atk": 1,
"atk_min": 1,
"atk_max": 2,
"pool": [{
@@ -226,12 +226,12 @@ rows = [{
}, {
"level_index": 2,
"node_depth": 7,
"enemy_id": "giant_spider",
"display_name": "巨蜘蛛巢群",
"unit_count": 2,
"unit_count_max": 3,
"hp": 4,
"atk": 2,
"enemy_id": "giant_spider",
"display_name": "巨蜘蛛巢群",
"unit_count": 2,
"unit_count_max": 3,
"hp": 22,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
"pool": [{
@@ -249,13 +249,13 @@ rows = [{
}, {
"level_index": 2,
"node_depth": 9,
"enemy_id": "drider",
"display_name": "蛛化卓尔精灵",
"unit_count": 1,
"hp": 58,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"enemy_id": "drider",
"display_name": "蛛化卓尔精灵",
"unit_count": 1,
"hp": 112,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"pool": [{
"skill_id": "attack",
"weight": 6
@@ -274,13 +274,13 @@ rows = [{
}, {
"level_index": 3,
"node_depth": 0,
"enemy_id": "duergar",
"display_name": "灰矮人",
"unit_count": 1,
"hp": 15,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"enemy_id": "duergar",
"display_name": "灰矮人",
"unit_count": 1,
"hp": 62,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"pool": [{
"skill_id": "attack",
"weight": 6
@@ -297,12 +297,12 @@ rows = [{
"level_index": 3,
"node_depth": 1,
"enemy_id": "hook_horror",
"display_name": "钩爪恐魔",
"unit_count": 2,
"hp": 5,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
"display_name": "钩爪恐魔",
"unit_count": 2,
"hp": 30,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"pool": [{
"skill_id": "attack",
"weight": 6
@@ -319,13 +319,13 @@ rows = [{
"level_index": 3,
"node_depth": 4,
"enemy_id": "hook_horror",
"display_name": "钩爪恐魔",
"unit_count": 2,
"unit_count_max": 3,
"hp": 5,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
"display_name": "钩爪恐魔",
"unit_count": 2,
"unit_count_max": 3,
"hp": 26,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"pool": [{
"skill_id": "attack",
"weight": 6
@@ -342,12 +342,12 @@ rows = [{
"level_index": 3,
"node_depth": 5,
"enemy_id": "duergar",
"display_name": "灰矮人重装手",
"unit_count": 1,
"hp": 16,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"display_name": "灰矮人重装手",
"unit_count": 1,
"hp": 98,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"pool": [{
"skill_id": "attack",
"weight": 5
@@ -364,13 +364,13 @@ rows = [{
"level_index": 3,
"node_depth": 6,
"enemy_id": "hook_horror",
"display_name": "钩爪恐魔巢群",
"unit_count": 2,
"unit_count_max": 3,
"hp": 6,
"atk": 2,
"atk_min": 1,
"atk_max": 3,
"display_name": "钩爪恐魔巢群",
"unit_count": 2,
"unit_count_max": 3,
"hp": 26,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"pool": [{
"skill_id": "attack",
"weight": 6
@@ -387,13 +387,13 @@ rows = [{
"level_index": 3,
"node_depth": 7,
"enemy_id": "duergar",
"display_name": "灰矮人小队",
"unit_count": 2,
"unit_count_max": 3,
"hp": 8,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"display_name": "灰矮人小队",
"unit_count": 2,
"unit_count_max": 3,
"hp": 26,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"pool": [{
"skill_id": "attack",
"weight": 6
@@ -409,13 +409,13 @@ rows = [{
}, {
"level_index": 3,
"node_depth": 9,
"enemy_id": "duergar_king",
"display_name": "灰矮人石君王",
"unit_count": 1,
"hp": 75,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"enemy_id": "duergar_king",
"display_name": "灰矮人石君王",
"unit_count": 1,
"hp": 140,
"atk": 3,
"atk_min": 2,
"atk_max": 4,
"pool": [{
"skill_id": "attack",
"weight": 6
@@ -514,3 +514,41 @@ rows = [{
; 陈旧行清理(GP+商店房实施批收尾 2026-09-21):并行表代理曾把 Boss 行照抄到 (2,8)/(3,8)——节点 8 现为
; 商店房、无战斗行需求,原 (2,8)/(3,8) 行成陈旧冗余已删(删前核 (·,9) 行在位且内容与被删行逐字段一致);
; 本行集迁移 = Boss 重编号 8→9 的商店房批连带。
;
; Package B 数值优化批·首版(2026-09-22——qa-data/实现计划书_PackageB.md §4.1,worktree
; adventure-mc-playtest,provisional 未提交):B1 敌人 HP 阶梯整体重锚(完成度主旋钮——零加成基线
; naive 完成度普遍过低,见 qa-data/分析报告_零加成全流程.md)。只改 hp 字段;atk/atk_min/atk_max/
; 池/armor_type/armor_class/unit_count 全部不动(atk 系 §6 承伤迭代旋钮,改时须三值同步保中点恒等)。
; 改动清单:L1 d2 熊地精 13→50 · d3 双雄 6→20 · d4 酋长 50→72(d0/d1 弱档不动);
; L2 d0 10→45 · d1 5→18 · d4 巨蜘蛛 5→20 · d5 13→68 · d6 巢群 4→18 · d7 巢群 4→18 · d9 卓尔 58→85;
; L3 d0 灰矮人 15→50 · d1 钩爪 5→25 · d4 钩爪 5→20 · d5 16→76 · d6 巢群 6→20 · d7 小队 8→20 ·
; d9 石君王 75→105。以 MC 复测读数为准(runbook 计划书 §5;迭代记录 qa-data/iteration_log.md)。
;
; Package B 迭代·第 3 轮(2026-09-22——计划书 §6.2 承伤带守卫,用户拍板):8 行 atk-3 精英/boss
; 三值同步 3(2/4)→2(1/3)。第 2 轮 max_hp/heal 组合拳后单场承伤% 仍 49-62%(战斗变长吃掉血池收益,
; heal 加强主导经济修复)——§6.2「AP 已到顶 → 长战斗档 atk −1」条件成立。行清单:L1 d2 熊地精 /
; d4 酋长 · L2 d5 重击手 / d9 卓尔 · L3 d0 灰矮人 / d5 重装手 / d7 小队 / d9 石君王。
; atk-1/2 行(弱怪/群怪/L1 d3 双雄)不动。预期单场承伤 ~35%(20% 带在 Package B 框架内结构性
; 不可达,带位全达标归 Package A)。provisional 未提交。
;
; Package B 迭代·第 4 轮(2026-09-22——用户拍板 A):L1 d3 熊地精双雄 hp 20→16/单位
; (总量 [32,48])。第 3 轮后唯一未回调的 L1 高压行——双单位每轮双判定,单场承伤 51%、
; 胜率 70%,拖住 L1 通过率(40.7% vs 靶 70-78%)。其余行不动。provisional 未提交。
;
; Package B 迭代·第 5 轮(2026-09-22——用户拍板「继续」,修理刀三处,胜率带优先、完成度让路):
; ① 第一关·节点3·熊地精双雄 hp 16→14/单位(冲 ≥90 胜率靶;该行胜率/完成度两靶互斥已登记);
; ② 第二关·节点9·蛛化卓尔精灵 85→80(L2 boss 胜率 59% 回 60-75 带);
; ③ 第三关·节点9·灰矮人石君王 105→92(L3 boss 胜率 38% 向 50-70 带靠;该行完成度靶未设带,让路合法)。
; 否决项登记:食人魔酋长 +10% 方案经测算否决——boss 完成度 >130 需 HP +~50-75%(98→104 买不回靶),
; 会把刚入带的 66% 胜率打回带外,纯亏。provisional 未提交。
;
; 数值设计批·关卡成长×永久成长对齿(2026-09-22——设计权威 qa-data/数值设计_关卡成长×永久成长_v1.md,
; 用户三拍板 1A/2A/3A,第 7 轮):第二/三关阶段拔高 14 行(L1 一行不动)。血轴=每关上一档;
; 伤害轴=atk 2→3 三值同步 3(2/4)【L2 d5 重击手/d9 卓尔;L3 d0 灰矮人/d1 钩爪/d4 钩爪/d5 重装手/
; d6 巢群/d7 小队/d9 石君王】。清单:L2 d0 45→50 · d1 18→22 · d4 20→24 · d5 68→88+atk3 · d6 18→22 ·
; d7 18→22 · d9 80→112+atk3;L3 d0 50→62+atk3 · d1 25→30+atk3 · d4 20→26+atk3 · d5 76→98+atk3 ·
; d6 20→26+atk3 · d7 20→26+atk3 · d9 92→140+atk3。
; 目标对齿(熟练玩家):S0 零成长 L2 达 boss ~20%/通 ~0%、L3 ~0;S1(≈5 次 L1 通关锻造后,hero 快照
; 攻4/血26/甲11)L2 达 ~70%/通 ~50%;S2(攻6/血32/甲12)L3 通 ~55%。验证 = hero.tres 阶段快照 MC
; (设计文档 §6,快照后恢复基线)。验证出带微调旋钮序:L2 d1/d6/d7 血 22→20 → d4 24→22 → d9 112→108。
; provisional 未提交。
@@ -4,7 +4,7 @@
[resource]
script = ExtResource("1")
max_hp = 17
max_hp = 20
atk = 2
armor_class = 10
armor_type = "light"
@@ -25,3 +25,8 @@ attack_type = "normal"
; attack_type = 主角基础攻击类型(normal/pierce/magic,缺省 normal):基础攻击量(atk)与攻击类型抽象分离——
; 营地永久强化"换成魔法/穿刺攻击"= 改此字段(atk 量轴零改动),战斗段基础分量即按此查甲效矩阵。
; 两轴全部 provisional,营地建设批(元进度侧)拍板正式投值。
;
; Package B 迭代·第 2 轮(2026-09-22——计划书 §6.2,用户拍板一B):max_hp 17→20。第 1 轮读数
; 触发 §6.2(L1 精英承伤 43-55% vs 靶 ≤20%、L1 净消耗 17.7/17=104% 破产、normal L1 通过率
; 8.3% vs 靶 70-78%)的生存侧修复。单变量归因原则:同轮第二变量(heal_minor 加强)在 reward.tres。
; 血线 17/27/37→20/30/40(关间大奖励 +10 不变)。provisional 未提交。
@@ -8,19 +8,19 @@ forge_products = [{
"attr": "max_hp",
"display_name": "生命锻造",
"flat_value": 3,
"base_price": 10,
"base_price": 20,
"price_ratio_pct": 150
}, {
"attr": "atk",
"display_name": "攻击锻造",
"flat_value": 1,
"base_price": 10,
"base_price": 20,
"price_ratio_pct": 150
}, {
"attr": "armor_class",
"display_name": "护甲锻造",
"flat_value": 1,
"base_price": 10,
"base_price": 20,
"price_ratio_pct": 150
}]
boss_keystone = [{
@@ -84,3 +84,9 @@ conversion = [{
; (首杀全量、重复杀固定小额恒定不递减);工坊六件阈值 1/1/2/2/3/3 按总通关次数(首版只挂通关次数渠道,
; 钥石直购价随定价批后补);conversion 全 0 = 未标定占位(标定归设计批,护甲行强条目:验收读数必须按
; 解锁后参数重测)。
;
; 数值设计批·关卡成长×永久成长对齿(2026-09-22——设计权威 qa-data/数值设计_关卡成长×永久成长_v1.md,
; game-balancer 工作流,用户三拍板 1A/2A/3A):forge base_price 10→20(三行同,ratio 150% 不变,
; 效果档 血+3/攻+1/甲+1 不变)——S1 一档(攻+2/血+6/甲+1 ≈ 120 GP)对齐「≈5 次 L1 通关开门」节奏
; (实测 L1 通关局收入 ≈29-35 GP)。换算率表维持全 0(墙/钥匙闭环不依赖该轴,留扩展)。
; 配套怪表改动见 enemy.tres 同日批注。provisional 未提交。
@@ -40,10 +40,10 @@ rows = [{
"polarity": "positive",
"effect_id": "",
"display_name": "急救敷料",
"power": 1,
"power_min": 1,
"power_max": 1,
"weight": 2,
"power": 2,
"power_min": 2,
"power_max": 2,
"weight": 4,
"pool_tag": "cell",
"tier": 1
}, {
@@ -112,9 +112,9 @@ rows = [{
"polarity": "positive",
"effect_id": "",
"display_name": "清障炸药",
"power": 13,
"power_min": 8,
"power_max": 18,
"power": 32,
"power_min": 24,
"power_max": 40,
"weight": 1,
"pool_tag": "clear_default",
"tier": 2
@@ -241,3 +241,19 @@ rows = [{
; 挖雷格兑付=缓冲 -2 钱包侧 clamp ≥0〕;正向权重合计 74→76);行值 provisional 归 难度曲线与Boss。
; gp 两行 tier=1(provisional 占位);effect_id gp_gain/gp_loss = 战斗内缓冲(escrow)通道路由键
;(战斗结算 _apply_percent_row 分流,不入增益容器——ConfigDB ① status_effects 悬空豁免归 core 面)。
;
; Package B 数值优化批·首版(2026-09-22——qa-data/实现计划书_PackageB.md §4.3):clear_blast
; 区间 [8,18]→[18,35](中点 power 13→26)——清第一图回归真正伤害尖峰,撑起 boss"必清图才打得动"
; 节奏(L1 boss 清图≥1 靶 ≥80%);其余行全部不动。provisional,以 MC 复测读数为准
;(runbook 计划书 §5;迭代记录 qa-data/iteration_log.md)。
; ⚠️ 计划书笔误代拍板:原文"power 25"违反 schema 中点锚硬校验(config_fatal)——power ≡ floor((min+max)/2),
; (18+35)/2=26 故落 26(期望当量 +1 vs 计划书 25,掷点区间 18–35 与计划一致,玩法值零偏离)。
;
; Package B 迭代·第 2 轮(2026-09-22——计划书 §6.2,用户拍板一B):heal_minor 恢复量 1→2
;(区间三值同步 power/min/max = 2/2/2 过中点锚校验)+ 出现权重 2→4(正向池合计 76→78)。
; 与 hero.tres max_hp 17→20 同轮双变量(用户拍板组合拳,归因混在一起由下轮分摊)。provisional 未提交。
;
; Package B 迭代·第 4 轮(2026-09-22——计划书 §6.4,用户拍板 A):clear_blast 26→32
;(区间 [18,35]→[24,40],(24+40)/2=32 恰为整中点锚)。boss 面三缺同解(normal 胜率 62% 贴下沿 /
; 清图率 49% vs ≥80 / 完成度 99 vs >130)——清图尖峰加大让"先清一图"成为 boss 战最优路径。
; 副作用已知:perfect L1 boss 胜率 97% 越 75% 上界加剧。provisional 未提交。
@@ -0,0 +1,80 @@
# 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]
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("表加载失败")
quit(1)
return
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)
var sum: Dictionary = st["summary"]
var rec := {
"policy": policy, "seed": seed_val, "atk_pct": 0, "hp_pct": 0, "route": route,
"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"]),
"refreshes": int(st["refreshes"]), "choices": int(st["choices"]),
"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"],
"wallet_count": (st["wallet_events"] as Array).size(),
}
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)
@@ -0,0 +1 @@
uid://ckva7jc2mpuyb
@@ -0,0 +1,75 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-09-22
# 棋盘口径校准(分析会话取数工具——冒险零加成批完成度分母实证):
# 逐 board_gen 行真实生成 N 板,统计 实际雷数/安全格数/R₀ 初始展开占比——
# 生成期「先布后删+可推理率重试」使实际雷数远低于表口径 round(d×(W×H−9)),
# 日志侧只有清图板可反推 safe_total(未清图深度无样本),本工具给全网格精确分母。
# 运行:Godot_console.exe --headless --path . --script res://smoke/calibrate_boards.gd -- [N]
# 输出:stdout CSV 行 level,depth,w,h,n,mines_mean,mines_med,safe_mean,safe_med,r0_mean,inf_mean
extends SceneTree
class FakeDB:
var row: Dictionary
func resolve_board_gen(_L: int, _D: int) -> Dictionary:
return row
func status_effect_row(_id: String) -> Dictionary:
return {}
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(10, int(args[0]))
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("表加载失败")
quit(1)
return
var gen = load("res://core/board_generator.gd").new()
print("level,depth,w,h,n,mines_mean,mines_med,safe_mean,safe_med,r0_mean,inf_mean")
for row in db.board_gen.rows:
var rd := {
"level_index": int(row.level_index), "node_depth": int(row.node_depth),
"board_width": int(row.board_width), "board_height": int(row.board_height),
"mine_density": float(row.mine_density), "inferability_target": float(row.inferability_target),
"solver_rules": String(row.solver_rules), "min_safe_count": int(row.min_safe_count),
"max_gen_attempts": int(row.max_gen_attempts), "board_type": String(row.board_type),
}
var fdb := FakeDB.new()
fdb.row = rd
var mines: Array = []
var safes: Array = []
var r0s: Array = []
var infs: Array = []
for i in range(n):
var rg := RandomNumberGenerator.new()
rg.seed = 900000 + i
var b = gen.generate(int(row.level_index), int(row.node_depth), rg, fdb)
if b == null:
continue
mines.append(b.mines.size())
safes.append(b.safe_cells().size())
r0s.append(int(b.metrics.r0_count))
infs.append(float(b.metrics.inferability))
if mines.is_empty():
printerr("L%d d%d 生成全部失败" % [int(row.level_index), int(row.node_depth)])
continue
print("%d,%d,%d,%d,%d,%.1f,%d,%.1f,%d,%.1f,%.3f" % [
int(row.level_index), int(row.node_depth), int(row.board_width), int(row.board_height),
mines.size(), _mean(mines), _med(mines), _mean(safes), _med(safes), _mean(r0s), _mean(infs)])
quit(0)
func _mean(a: Array) -> float:
var t := 0.0
for v in a:
t += float(v)
return t / float(a.size())
func _med(a: Array) -> int:
var b := a.duplicate()
b.sort()
return int(b[b.size() / 2])
@@ -0,0 +1 @@
uid://ctkxq74fp3667