merge: 并入 master 4ce9791(直入口退役+冒险条件口径+L2 收紧)——标定批转新测量面
# Conflicts: # prototypes/dungeon-sweeper-vertical-slice/smoke/level_autoplay.gd # prototypes/dungeon-sweeper-vertical-slice/smoke/verifier_batch.gd # tools/balance_verifier.py
This commit is contained in:
commit
7fd6fbd0bd
33 files changed
+1039
-600
No files matched your search
@@ -12,7 +12,7 @@
|
||||
|
||||
1. 用 Godot 4.7.2 打开本目录(含 `project.godot` 的文件夹)
|
||||
2. 按 F5 运行(主场景 `presentation/main.tscn`)
|
||||
3. **主入口:`▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)`**——一条命走完三关(冒险结构实现批 2026-09-21:生命/GP/加算区跨关同账、非末关通关发大奖励〔上限提升+血回满+钥石〕、任意战斗倒下冒险终、结算屏出冒险摘要五件事;选路停点可「放弃冒险」(同失败结算,2026-09-21 用户拍板补入口));首关节点链:战→战→赏→战→战→疗→王。`▶ 测试直入口 · 第二关 / 第三关` 保留单关试玩模式
|
||||
3. **主入口:`▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)`**——一条命走完三关(冒险结构实现批 2026-09-21:生命/GP/加算区跨关同账、非末关通关发大奖励〔上限提升+血回满+钥石〕、任意战斗倒下冒险终、结算屏出冒险摘要五件事;选路停点可「放弃冒险」(同失败结算,2026-09-21 用户拍板补入口));首关节点链:战→战→赏→战→战→疗→王。唯一试玩入口 = 完整冒险——单关测试直入口(L2/L3)已退役(2026-09-23 用户拍板)
|
||||
4. `测试用 · 角色数值调整板`:攻击/生命百分比加成(模拟天赋树解锁)+ 批量模拟(输出通关率/承伤期望/败北节点分布)
|
||||
|
||||
headless 冒烟(55 项断言,含关卡线性推进/满配通关/裸装败北/回放确定性,约 30 秒):
|
||||
@@ -33,23 +33,25 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . res://smoke/ui_probe.t
|
||||
Godot_v4.7.2-stable_win64_console.exe --headless --path . res://smoke/ui_tune_probe.tscn
|
||||
```
|
||||
|
||||
关卡期望扫描(C3 调参期关卡侧批量工具原型——atk%×hp% 网格,每格输出通关率/承伤期望/败北节点):
|
||||
全冒险期望扫描(零加成三关连探批量——通关率/止步分布/败退节点;调参期难度读数一律走全冒险口径):
|
||||
|
||||
```
|
||||
Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/level_monte_carlo.gd -- [N] [seed0]
|
||||
Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/adventure_monte_carlo.gd -- [N] [seed0]
|
||||
```
|
||||
|
||||
(原关卡期望扫描 level_monte_carlo.gd 走单关直入口口径——满血零上下文进关与真实冒险脱节,随口径退役删除,2026-09-23 用户拍板。)
|
||||
|
||||
可选类供给跨种子扫描(playtest-2026-09-12 Top1 复核——每局治疗包/小刀/精工短刀获取数、构成占比对 9:1 配比、"≥3 治疗包"局数分布;单种子坏抽与系统性偏置由此分割):
|
||||
|
||||
```
|
||||
Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/supply_scan.gd -- [N] [seed0]
|
||||
```
|
||||
|
||||
**日志合规(2026-09-12 用户要求)**:任何模拟运行与真人试玩均落全量 JSONL(user://logs/)——逐操作(op/cell/accepted/round)、逐结算明细行、轮次状态快照、battle_end、level_end。模拟(level_monte_carlo / 调整板批量模拟 / supply_scan)一文件多局,run 行携带种子/档位切分局次。
|
||||
**日志合规(2026-09-12 用户要求)**:任何模拟运行与真人试玩均落全量 JSONL(user://logs/)——逐操作(op/cell/accepted/round)、逐结算明细行、轮次状态快照、battle_end、level_end。模拟(verifier/adventure_monte_carlo / 调整板批量模拟 / supply_scan)一文件多局,run 行携带种子/档位切分局次。
|
||||
|
||||
**试玩画像口径(2026-09-19 用户拍板固化——全项目试玩/扫描/面板模拟一律只用以下两档,随机策略口径退役)**:`policy="normal"` = **普通画像·基础定式**(单数字满足规则——满足即标/满足即开,无处可推盲挖边界格、不刷新;教学后正常玩家的下限近似);`policy="perfect"` = **完美画像**(single+subset 全推理零盲挖/卡死刷新——技能上界仪)。实现单一出口 = `smoke/auto_player.gd`(novice_action / perfect_action)经 `smoke/level_autoplay.gd`,调整板批量模拟与 level_monte_carlo 自动同口径;战斗层冒烟夹具与调试转储的 `choose_dig` 直调是机制测试夹具、不承载画像语义。分级依据与新旧读数(乱点 0%/5% → 基础定式 65%/90%)见 auto_player.gd 普通策略档节与台账 77。
|
||||
**试玩画像口径(2026-09-19 用户拍板固化——全项目试玩/扫描/面板模拟一律只用以下两档,随机策略口径退役)**:`policy="normal"` = **普通画像·基础定式**(单数字满足规则——满足即标/满足即开,无处可推盲挖边界格、不刷新;教学后正常玩家的下限近似);`policy="perfect"` = **完美画像**(single+subset 全推理零盲挖/卡死刷新——技能上界仪)。实现单一出口 = `smoke/auto_player.gd`(novice_action / perfect_action)经 `smoke/level_autoplay.gd`,调整板批量模拟与各扫描工具自动同口径;战斗层冒烟夹具与调试转储的 `choose_dig` 直调是机制测试夹具、不承载画像语义。分级依据与新旧读数(乱点 0%/5% → 基础定式 65%/90%)见 auto_player.gd 普通策略档节与台账 77。
|
||||
|
||||
单关诊断:`--script res://smoke/level_debug.gd -- [seed] [atk%] [hp%]`。
|
||||
单关诊断工具(level_debug.gd)随单关直入口口径退役(2026-09-23)——败退归因走 balance_verifier 冒险段(层/节点/敌人人读归因)。
|
||||
战斗级工具(v1 遗产):`dump_battle.gd`(结算流水)/ `monte_carlo.gd`(战斗期望)/ `experiment_density.gd`(密度调参)。
|
||||
|
||||
## 范围(v1 范围 + v2 追加,用户 2026-09-10/11 确认)
|
||||
@@ -169,7 +171,7 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/s
|
||||
|
||||
## L2 试玩采集清单(分支节点图批 2026-09-17——关卡 2「幽暗地域」试玩观测项)
|
||||
|
||||
入口:首页"▶ 测试直入口 · 第二关 · 幽暗地域(分支节点图试玩——岔路口选下一跳)"(测试模式直入口,种子框同用;正式序列走「开始冒险」主入口)。
|
||||
入口:「开始冒险」主入口推进到第二关段(单关测试直入口已退役——2026-09-23 用户拍板,唯一试玩入口 = 完整三关冒险;种子框同用)。
|
||||
|
||||
- **选路行为**:战斗胜利/兑付确认后弹出选路覆盖层(整图 + 候选按钮)——玩家是否理解"零自动推进、每跳都要选";单候选节点(恢复点→编号 6)是否被读成"卡住"。
|
||||
- **岔路使用率**:四处真实岔路的走向分布(入口 0→[1,2];战斗 1→[3,4];奖励 2→[4,5];深层 4→[6,7])——奖励点路线 vs 纯战斗路线的选择比例;整图渲染是否被实际用来规划(对比只点按钮的玩家)。
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
level_clear_hp_bonus = 10
|
||||
level_clear_hp_bonus = 5
|
||||
keystone_per_level = 10
|
||||
|
||||
; 冒险结构实现批(2026-09-21 建表——registry adventure_table 条目同批注记更新):
|
||||
|
||||
@@ -140,7 +140,7 @@ rows = [{
|
||||
"display_name": "熊地精猎群",
|
||||
"unit_count": 2,
|
||||
"unit_count_max": 3,
|
||||
"hp": 22,
|
||||
"hp": 26,
|
||||
"atk": 2,
|
||||
"atk_min": 1,
|
||||
"atk_max": 3,
|
||||
@@ -162,7 +162,7 @@ rows = [{
|
||||
"enemy_id": "giant_spider",
|
||||
"display_name": "巨蜘蛛",
|
||||
"unit_count": 2,
|
||||
"hp": 24,
|
||||
"hp": 28,
|
||||
"atk": 2,
|
||||
"atk_min": 1,
|
||||
"atk_max": 3,
|
||||
@@ -205,10 +205,10 @@ rows = [{
|
||||
"node_depth": 6,
|
||||
"enemy_id": "giant_spider",
|
||||
"display_name": "巨蜘蛛巢群",
|
||||
"unit_count": 2,
|
||||
"unit_count_max": 3,
|
||||
"hp": 22,
|
||||
"atk": 1,
|
||||
"unit_count": 2,
|
||||
"unit_count_max": 3,
|
||||
"hp": 26,
|
||||
"atk": 1,
|
||||
"atk_min": 1,
|
||||
"atk_max": 2,
|
||||
"pool": [{
|
||||
@@ -228,10 +228,10 @@ rows = [{
|
||||
"node_depth": 7,
|
||||
"enemy_id": "giant_spider",
|
||||
"display_name": "巨蜘蛛巢群",
|
||||
"unit_count": 2,
|
||||
"unit_count_max": 3,
|
||||
"hp": 22,
|
||||
"atk": 2,
|
||||
"unit_count": 2,
|
||||
"unit_count_max": 3,
|
||||
"hp": 26,
|
||||
"atk": 2,
|
||||
"atk_min": 1,
|
||||
"atk_max": 3,
|
||||
"pool": [{
|
||||
@@ -252,7 +252,7 @@ rows = [{
|
||||
"enemy_id": "drider",
|
||||
"display_name": "蛛化卓尔精灵",
|
||||
"unit_count": 1,
|
||||
"hp": 112,
|
||||
"hp": 130,
|
||||
"atk": 3,
|
||||
"atk_min": 2,
|
||||
"atk_max": 4,
|
||||
|
||||
@@ -46,15 +46,6 @@ static func debug_open_line(seed_val: int, level: int, level_mode: bool) -> Stri
|
||||
seed_val, level, "(关卡模式 · HP 延续)" if level_mode else ""]
|
||||
|
||||
|
||||
# 关卡终局文案(result = victory/defeat;载荷 = runner level_finished 快照)
|
||||
static func level_end_text(result: String, payload: Dictionary, effective_max_hp: int) -> String:
|
||||
if result == "victory":
|
||||
return "关卡通关!\n剩余 HP %d/%d" % [
|
||||
int(payload.get("hp", 0)), effective_max_hp]
|
||||
return "关卡失败……探险者倒在了节点 %d(%s)。" % [
|
||||
int(payload.get("node_index", 0)) + 1, String(payload.get("node_type", ""))]
|
||||
|
||||
|
||||
# 调试战斗终局文案(level_end = 关内两层模式的关卡末)
|
||||
static func battle_end_text(result: String, level_end: bool) -> String:
|
||||
if level_end:
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
# debug_session.gd(规范 1.1 审视线 + 调试语义单一实现)。
|
||||
# 组合根(composition root):唯一的模式流转与 core↔view 接线处。
|
||||
# 职责边界:① 构建四区视图并接线信号(节点构建归 ui_assembler.gd——规范 1.1,本类只接线)
|
||||
# ② 模式管理(完整关卡 关卡节点地图 runner / 调试两层——后者编排归 debug_session.gd;单场原语仅探针使用)
|
||||
# ② 模式管理(正式冒险 AdventureRunner——唯一试玩入口;调试两层编排归 debug_session.gd;单场原语仅探针使用)
|
||||
# ③ 核心写调用只在此处发生(minesweeper.settle_batch / 刷新编排入口 / runner 推进;
|
||||
# E4 收口:加成值零表写——面板百分比经 runner.set_hero_override / debug_session 生效值 Callable
|
||||
# 以 encounter 参数入战斗,db.hero 运行期只读)
|
||||
# ④ 全屏 States 状态机(挖掘/结算/终局)与一次性提示去重。
|
||||
# 2026-09-15 格面资源批:道具窗口与库存面板随可选类道具删除下线——E 区移除,窗口接线/选择序列删除。
|
||||
# GP+商店房实施批(2026-09-21):商店覆盖层挂接与信号接线(装配器零信号连接纪律——句柄返回、
|
||||
# 本类连接,仿 lv2btn 模式)+ runner.wallet_changed 转发试玩日志(wallet 行族)+ A 区顶栏金币余额。
|
||||
# 本类连接)+ runner.wallet_changed 转发试玩日志(wallet 行族)+ A 区顶栏金币余额。
|
||||
# 渲染与文案归 views/,核心状态→文案装配归 hud_presenter.gd 与 battle_narrator.gd,节点构建归
|
||||
# ui_assembler.gd,模态层级归 modal_stack.gd,批量模拟编排归 tune_sim_controller.gd,调试模式编排归
|
||||
# debug_session.gd——本类不拼呈现字符串。
|
||||
@@ -31,7 +31,7 @@ const MetaProgressionScript := preload("res://core/meta_progression.gd") # 元
|
||||
|
||||
var db
|
||||
var settlement = null # 当前战斗编排器(关卡与调试模式统一读它)
|
||||
var runner = null # 关卡节点地图 图版推进器(完整关卡模式;冒险模式 = 每关重取 adventure.runner())
|
||||
var runner = null # 关卡推进器(冒险模式每关重取 adventure.runner();单关 UI 直入口已退役——LevelRunner 仅存 headless 测试面)
|
||||
var adventure = null # 冒险结构编排器(冒险结构实现批——三关连探;转发信号照 runner 接线原样对接)
|
||||
var save_manager = null # 存档序列化承载(存档序列化批——启动读盘/冒险收束写盘;只经访问器与 apply 原语,禁读内部结构)
|
||||
var meta = null # 元进度账本服务(元进度实现批——余额/定价/购买/工坊解锁/Boss两档入账/注入计算;零信号纯被动)
|
||||
@@ -163,9 +163,7 @@ func _build_ui() -> void:
|
||||
var start: Dictionary = UiAssembler.build_start_overlay(self, db)
|
||||
_start_overlay = start.overlay
|
||||
_seed_edit = start.seed_edit
|
||||
start.lvbtn.pressed.connect(_start_full_level.bind(1))
|
||||
start.lv2btn.pressed.connect(_start_full_level.bind(2))
|
||||
start.lv3btn.pressed.connect(_start_full_level.bind(3))
|
||||
start.lvbtn.pressed.connect(_start_adventure_from_menu)
|
||||
start.tunebtn.pressed.connect(_show_tune_panel)
|
||||
start.dbgbtn.pressed.connect(_start_debug_levels)
|
||||
start.campbtn.pressed.connect(_open_camp)
|
||||
@@ -216,36 +214,11 @@ func _input_locked() -> bool:
|
||||
or _start_overlay.visible or _inter_overlay.visible or settlement == null
|
||||
|
||||
|
||||
# ==================== 完整关卡模式(关卡节点地图 图版推进器)====================
|
||||
# ==================== 冒险模式入口(唯一试玩入口——单关测试直入口退役 2026-09-23 用户拍板)====================
|
||||
|
||||
func _start_full_level(level_index: int = 1) -> void:
|
||||
func _start_adventure_from_menu() -> void:
|
||||
_start_overlay.visible = false
|
||||
if level_index == 1:
|
||||
_start_adventure() # 冒险首关即 L1——正式序列入口(冒险结构实现批)
|
||||
return
|
||||
# E4:加成经 runner.set_hero_override → encounter 参数入战斗(零表写——无双写叠加隐患)
|
||||
runner = LevelRunner.new(db, get_node("/root/RngService"))
|
||||
runner.set_hero_override(tune_panel.atk_pct(), tune_panel.hp_pct())
|
||||
runner.step_row.connect(_on_step_row)
|
||||
runner.round_started.connect(_on_round_started)
|
||||
runner.board_changed.connect(_on_board_changed)
|
||||
runner.node_entered.connect(_on_runner_node)
|
||||
runner.choices_presented.connect(_on_choices_presented)
|
||||
runner.level_finished.connect(_on_level_finished)
|
||||
runner.wallet_changed.connect(_on_wallet_changed)
|
||||
_level_active = true
|
||||
_adventure_mode = false
|
||||
route_overlay.set_abandon_available(false) # 单关测试直入口无冒险可放弃(放弃语义对象 = 整局冒险)
|
||||
_reset_run_scope()
|
||||
log_view.clear()
|
||||
var seed_val := _seed_value()
|
||||
logger.start_session(seed_val) # 各战斗的 attach 在 _on_runner_node 内(settlement 逐战创建)
|
||||
if not runner.start_level(level_index, seed_val):
|
||||
_fatal_screen("关卡启动失败——见输出日志")
|
||||
return
|
||||
log_view.append_bb(Narrator.level_open_line(String(runner.level_row.get("display_name", "")),
|
||||
seed_val, int(db.hero.atk), int(db.hero.max_hp),
|
||||
tune_panel.atk_pct() > 0 or tune_panel.hp_pct() > 0))
|
||||
_start_adventure()
|
||||
|
||||
|
||||
# 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接;
|
||||
@@ -322,28 +295,6 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
func _on_level_finished(payload: Dictionary) -> void:
|
||||
_level_active = false
|
||||
var result: String = String(payload.get("result", ""))
|
||||
settlement = null
|
||||
_close_modal(route_overlay) # 终局兜底(放弃入口新场景:选路层在开态——走模态栈恢复其遮挡面;未开过时 close 容错)
|
||||
_sync_settlement_visuals() # → 全关
|
||||
info_panel.set_status_cleared_feedback() # 状态槽全清可见反馈(增益状态×4④)
|
||||
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
|
||||
if result == "victory":
|
||||
# 胜利终局 overlay(金蜡封落于棋盘中央)
|
||||
_end_overlay.open_victory(Narrator.level_end_text("victory", payload, runner.effective_max_hp()),
|
||||
"返回冒险", board_view.get_global_rect())
|
||||
else:
|
||||
_end_overlay.open_defeat(Narrator.level_end_text("defeat", payload, 0),
|
||||
"返回", board_view.get_global_rect())
|
||||
logger.log_level_end(payload) # 试玩时间线:关卡终局
|
||||
runner.restore_hero_base() # 关卡结束——覆盖值归零(下次从面板重新应用)
|
||||
strip_view.refresh(runner)
|
||||
_open_modal(_end_overlay)
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
# ---- 冒险模式终局面(大奖励屏 / 冒险结算屏——冒险结构实现批)----
|
||||
|
||||
# 大奖励兑付(非末关通关恰一次):复用 _inter_overlay(label + 确认按钮,零新增交互范式);
|
||||
@@ -364,7 +315,7 @@ func _on_adventure_finished(summary: Dictionary) -> void:
|
||||
info_panel.set_status_cleared_feedback() # 状态槽全清可见反馈(增益状态×4④)
|
||||
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
|
||||
# 存档入账(元进度实现批):settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变——
|
||||
# 单关测试入口与调试两层不经过本处理器,天然不写档不污染生涯数据)。
|
||||
# 调试两层不经过本处理器,天然不写档不污染生涯数据)。
|
||||
var detail: Dictionary = {}
|
||||
if meta != null:
|
||||
detail = meta.settle_adventure(summary)
|
||||
@@ -490,7 +441,7 @@ func _show_board_fx_for_node(node_type: String = "") -> void:
|
||||
|
||||
|
||||
func _on_battle_finished(payload: Dictionary) -> void:
|
||||
# 仅调试模式接线(完整关卡的结束归 关卡节点地图 runner._on_level_finished)
|
||||
# 仅调试模式接线(冒险的关卡结束归 AdventureRunner._on_level_finished——大奖励/冒险终局分派)
|
||||
var result: String = String(payload.get("result", ""))
|
||||
_sync_settlement_visuals() # → 全关(battle_over 反馈面)
|
||||
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
|
||||
|
||||
@@ -85,7 +85,7 @@ static func build_overlays(host: Control, db, base_atk: int, base_hp: int) -> Di
|
||||
var end_overlay := BattleEndOverlay.new()
|
||||
host.add_child(end_overlay)
|
||||
|
||||
# 商店覆盖层(GP+商店房实施批 2026-09-21——商店房节点兑付呈现;按钮接线归组合根 lv2btn 模式)
|
||||
# 商店覆盖层(GP+商店房实施批 2026-09-21——商店房节点兑付呈现;按钮接线归组合根,装配器零信号连接)
|
||||
var shop_overlay := ShopOverlay.new()
|
||||
host.add_child(shop_overlay)
|
||||
|
||||
@@ -129,13 +129,9 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary:
|
||||
seed_edit.custom_minimum_size = Vector2(120, 0)
|
||||
seed_row.add_child(seed_edit)
|
||||
|
||||
# 冒险结构实现批(2026-09-21):lvbtn = 正式「开始冒险」入口(三关序列);L2/L3 保留测试直入口
|
||||
# 唯一试玩入口 = 正式「开始冒险」(三关序列);单关测试直入口(L2/L3)退役(2026-09-23 用户拍板)
|
||||
var lvbtn := UiKit.button("▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)")
|
||||
box.add_child(lvbtn)
|
||||
var lv2btn := UiKit.button("▶ 测试直入口 · 第二关 · 幽暗地域(分支节点图试玩——岔路口选下一跳)")
|
||||
box.add_child(lv2btn)
|
||||
var lv3btn := UiKit.button("▶ 测试直入口 · 第三关 · 灰矮人矿城(基线红档属预期,配合调整板加成试玩)")
|
||||
box.add_child(lv3btn)
|
||||
var tunebtn := UiKit.button("测试用 · 角色数值调整板(模拟天赋树属性提升)")
|
||||
box.add_child(tunebtn)
|
||||
var dbgbtn := UiKit.button("调试 · 连续关卡两层(HP 关内延续)")
|
||||
@@ -151,7 +147,7 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary:
|
||||
box.add_child(help)
|
||||
|
||||
return {"overlay": overlay, "box": box, "seed_edit": seed_edit,
|
||||
"lvbtn": lvbtn, "lv2btn": lv2btn, "lv3btn": lv3btn, "tunebtn": tunebtn, "dbgbtn": dbgbtn,
|
||||
"lvbtn": lvbtn, "tunebtn": tunebtn, "dbgbtn": dbgbtn,
|
||||
"campbtn": campbtn}
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# 无任何写面;图形为程序化占位质量(占位纪律)。
|
||||
# 放弃入口(2026-09-21 用户拍板「放弃接口也要有」——台账 119;推翻 冒险结构.md「MVP 无界面入口」
|
||||
# 后置条款):选路停点 = 天然非战斗期(接口契约:战斗中放弃一律拒绝),按钮常驻本层底部;
|
||||
# 可见性归组合根(冒险模式开、单关测试直入口关——放弃的语义对象是"整局冒险")。
|
||||
# 可见性归组合根(只在冒险模式开——放弃的语义对象是"整局冒险";调试两层无节点图天然不可达)。
|
||||
class_name RouteOverlay
|
||||
extends Control
|
||||
|
||||
@@ -25,7 +25,7 @@ const NODE_LABELS := {"battle": "战", "reward": "赏", "rest": "疗", "boss": "
|
||||
var _runner = null
|
||||
var _btn_box: VBoxContainer
|
||||
var _title: Label
|
||||
var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——冒险模式开/单关测试直入口关)
|
||||
var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——只在冒险模式开)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
@@ -58,7 +58,7 @@ func _init() -> void:
|
||||
visible = false
|
||||
|
||||
|
||||
# 放弃入口可见性(组合根在冒险启动/单关直入口启动时切换——语义对象 = 整局冒险)
|
||||
# 放弃入口可见性(组合根在冒险启动时开、默认关——语义对象 = 整局冒险)
|
||||
func set_abandon_available(v: bool) -> void:
|
||||
abandon_btn.visible = v
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# 棋盘板形统计探针(清图支配性对账重跑批取数工具——判式"实测板形"口径的数据源):
|
||||
# 对 board_gen.tres 每行 (L, D) 生成 N 张板,统计幸存雷数 |M| 与安全格数 |S|
|
||||
# 的均值 / 最小 / 最大("先布后删"后的落地值——名义值 density×(W×H−9) 不含删除段)。
|
||||
# 只读诊断,零游戏代码改动;装载面与 level_monte_carlo 同源(config_db.load_tables)。
|
||||
# 只读诊断,零游戏代码改动;装载面与冒险扫描工具同源(config_db.load_tables)。
|
||||
# 运行:Godot_console.exe --headless --path . --script res://smoke/board_stat_probe.gd -- [N] [seed0]
|
||||
extends SceneTree
|
||||
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
# Date: 2026-09-11(2026-09-12 增完整遥测接线——用户要求:任何模拟运行与真人试玩同日志合规:
|
||||
# 操作记录 / 轮次状态 / 战斗结束 / 通关结束 全量 JSONL。logger=null 时自建会话(每局一文件),
|
||||
# 批量调用方传共享 logger(log_run 局次标记切分)。
|
||||
# 关卡自动通关策略(理性玩家近似)——呈现层数值调整板的批量模拟与 headless 期望扫描(level_monte_carlo)
|
||||
# 关卡自动通关策略(理性玩家近似)——呈现层数值调整板的批量模拟与 headless 扫描
|
||||
# 共用同一实现,保证"面板模拟读数"与"扫描表读数"同口径。策略与 smoke_all 战斗自动玩家同族:
|
||||
# 挖最近可挖格 / 无可挖停手 / 奖励恢复点立即确认。
|
||||
# 2026-09-23 单关直入口口径退役(用户拍板):run_level 降格为机制夹具/单战锚点专用
|
||||
# (smoke_all 机制断言、rhythm/defense/damage 单战扫描)——通关率/难度类测量禁用本入口
|
||||
# (满血零上下文进关,与真实冒险条件通关率脱节),一律走 run_adventure。
|
||||
# 2026-09-15 格面资源批:道具窗口选择与可选类获取计数随可选类道具删除下线。
|
||||
# 2026-09-14 增完美策略档(policy="perfect",默认 "normal" 零行为变更——既有调用方签名兼容):
|
||||
# 零盲挖/全标旗/卡死刷新——上界测量仪,求解器与动作语义见 auto_player.gd 完美策略档节。
|
||||
@@ -14,24 +17,21 @@
|
||||
# 2026-09-21 冒险结构实现批:run_adventure 三关连探驱动(AdventureRunner——主种子恰一次、
|
||||
# 大奖励立即确认 continue_to_next_level、adventure_finished 置 result/summary;外层每关重取
|
||||
# runner 实例经 _drive_level 复用单关驱动)。run_level 驱动段抽成 _drive_level——签名与
|
||||
# 返回结构零变化,行为逐位不变(MC 同种子对照闸承载)。
|
||||
# 2026-09-23 换算率标定批:run_level 增 hero_override 尾参(永久强化 flat 注入透传至
|
||||
# LevelRunner.start_level——缺省 {} 与既有调用方逐位一致零行为变更,契约同 run_adventure 同名尾参;
|
||||
# 采集端 verifier_batch.gd inject 档消费,服务换算率标定 OFAT 读数)。
|
||||
# 运行前提:db 已 load_tables;每个 run_level 全新 RngService(同种子 ⇒ 全程逐步可回放)。
|
||||
# 返回结构零变化,行为逐位不变。
|
||||
# 运行前提:db 已 load_tables;run_level/run_adventure 均须全新 RngService(同种子 ⇒ 全程逐步可回放)。
|
||||
# 实现注:信号处理体全部为静态方法 + bind 传状态——GDScript 多行 lambda 内嵌 match/控制流的解析不可靠。
|
||||
extends RefCounted
|
||||
|
||||
const AutoPlayer := preload("res://smoke/auto_player.gd") # O4 收口:自动玩家策略单一实现
|
||||
|
||||
|
||||
static func run_level(db, seed_val: int, level_index: int, atk_pct: int, hp_pct: int, collect_rows: bool = false, logger = null, policy: String = "normal", route: String = "min", hero_override: Dictionary = {}) -> Dictionary:
|
||||
static func run_level(db, seed_val: int, level_index: int, atk_pct: int, hp_pct: int, collect_rows: bool = false, logger = null, policy: String = "normal", route: String = "min") -> Dictionary:
|
||||
var own_session := logger == null
|
||||
if own_session:
|
||||
logger = load("res://presentation/playtest_logger.gd").new()
|
||||
logger.start_session(seed_val)
|
||||
logger.ap_per_round = int(db.action_economy.ap_per_round)
|
||||
logger.log_run({"seed": seed_val, "level_index": level_index, "atk_pct": atk_pct, "hp_pct": hp_pct, "policy": policy, "route": route, "hero_override": hero_override})
|
||||
logger.log_run({"seed": seed_val, "level_index": level_index, "atk_pct": atk_pct, "hp_pct": hp_pct, "policy": policy, "route": route})
|
||||
var rng = load("res://core/rng_service.gd").new()
|
||||
rng.set_master_seed(seed_val)
|
||||
var runner = load("res://core/level_runner.gd").new(db, rng)
|
||||
@@ -47,7 +47,7 @@ static func run_level(db, seed_val: int, level_index: int, atk_pct: int, hp_pct:
|
||||
runner.node_entered.connect(_on_node_entered.bind(runner, st, logger))
|
||||
runner.level_finished.connect(_on_level_finished.bind(st, logger))
|
||||
runner.set_hero_override(atk_pct, hp_pct)
|
||||
if not runner.start_level(level_index, seed_val, hero_override):
|
||||
if not runner.start_level(level_index, seed_val):
|
||||
st.result = "start_failed"
|
||||
runner.restore_hero_base()
|
||||
if own_session:
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Date: 2026-09-11
|
||||
# 单关卡诊断:跑一遍完整关卡并打印进度与统计(定位挂起/数值问题的临时工具)。
|
||||
# 运行:Godot_console.exe --headless --path . --script res://smoke/level_debug.gd -- [seed] [atk%] [hp%]
|
||||
extends SceneTree
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var seed_val := 4200
|
||||
var atk_pct := 0
|
||||
var hp_pct := 0
|
||||
if args.size() >= 1 and args[0].is_valid_int():
|
||||
seed_val = int(args[0])
|
||||
if args.size() >= 2 and args[1].is_valid_int():
|
||||
atk_pct = int(args[1])
|
||||
if args.size() >= 3 and args[2].is_valid_int():
|
||||
hp_pct = int(args[2])
|
||||
var db = load("res://core/config_db.gd").new()
|
||||
if not db.load_tables("res://data/tables", false):
|
||||
print("表加载失败")
|
||||
for d in db.diagnostics():
|
||||
if d.severity == "error":
|
||||
print(" error: [%s] %s" % [d.category, d.message])
|
||||
quit(1)
|
||||
return
|
||||
var autoplay = load("res://smoke/level_autoplay.gd")
|
||||
var t0 := Time.get_ticks_msec()
|
||||
print("开始:seed=%d atk+%d%% hp+%d%%" % [seed_val, atk_pct, hp_pct])
|
||||
var st = autoplay.run_level(db, seed_val, 1, atk_pct, hp_pct)
|
||||
print("结果: %s · 用时 %.2f 秒" % [st.result, float(Time.get_ticks_msec() - t0) / 1000.0])
|
||||
print(" 战斗节点=%d 轮数=%d 整关承伤=%d 剩余HP=%d 进BossHP=%d 踩雷=%d" % [st.battles, st.rounds, st.hero_dmg, st.hp_left, st.hp_at_boss, st.mine_hits])
|
||||
print(" 败北节点=%d(%s) 恢复点回复=%d(前HP %d)" % [st.defeat_node_index, st.defeat_node_type, st.rest_healed, st.hp_before_rest])
|
||||
quit(0)
|
||||
@@ -1 +0,0 @@
|
||||
uid://bflo5h8xysqsl
|
||||
@@ -1,169 +0,0 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Date: 2026-09-11
|
||||
# 关卡期望扫描(难度曲线与Boss 调参期关卡侧批量工具的原型——对应 棋盘生成 F4 / 战斗侧 monte_carlo.gd):
|
||||
# 扫描 atk% × hp% 网格(模拟天赋树数值提升档位),输出每档"通关率 / 整关承伤期望 / 败北节点分布 /
|
||||
# 通关剩余 HP 期望 / 进 Boss HP 期望 / 平均轮数"——把"多少数值能通关、通关要掉多少血"变成可测量数据。
|
||||
# 运行:Godot_console.exe --headless --path . --script res://smoke/level_monte_carlo.gd -- [N] [seed0] [normal|perfect|both] [level_index] [shared|unique]
|
||||
# 种子模式(2026-09-19 掷点测量修复批 A 案):shared(默认,旧行为)= 同批次全部格子×策略共用种子
|
||||
# seed0+i——同种子历史坐标可比,但同一 (种子, scope) 被反复消费,聚合掷点统计被头部窗口彩票放大
|
||||
# (验收重测批实证);unique = 逐局独立种子(seed0 + 全局递增序号),每个掷点恰消费一次 → 聚合
|
||||
# 掷点统计回归标准二项分布(±2σ gate 口径重新有效)。验收掷点面用 unique;同种子 A/B 对照用 shared。
|
||||
extends SceneTree
|
||||
|
||||
|
||||
var _run_cursor := 0 # unique 模式全局局序号(seed = seed0 + cursor)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var n := 30
|
||||
if args.size() >= 1 and args[0].is_valid_int():
|
||||
n = maxi(5, int(args[0]))
|
||||
var seed0 := 4200
|
||||
if args.size() >= 2 and args[1].is_valid_int():
|
||||
seed0 = int(args[1])
|
||||
# 关卡轴(分支节点图批 2026-09-17:L2 幽暗地域图版扫描——第 4 参 level_index,默认 1 零行为变更)
|
||||
var level_arg := 1
|
||||
if args.size() >= 4 and args[3].is_valid_int():
|
||||
level_arg = maxi(1, int(args[3]))
|
||||
var db = load("res://core/config_db.gd").new()
|
||||
if not db.load_tables("res://data/tables", false):
|
||||
print("表加载失败")
|
||||
quit(1)
|
||||
return
|
||||
# 共享日志会话(2026-09-12 用户要求:模拟运行与真人试玩同日志合规——操作/轮次/战斗/通关全量 JSONL,
|
||||
# 一文件多局,run 行携带种子/档位切分局次)
|
||||
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)
|
||||
print("模拟日志: %s" % log_path)
|
||||
var autoplay = load("res://smoke/level_autoplay.gd")
|
||||
var t0 := Time.get_ticks_msec()
|
||||
# 策略轴(2026-09-14 OQ7 完美策略档——技能轴上界测量):normal = 原理性玩家近似;
|
||||
# perfect = 零盲挖/全标旗/卡死刷新(auto_player.gd 完美策略档节);both = 双档对照。
|
||||
var policy_arg := "normal"
|
||||
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"]
|
||||
# 种子模式(掷点测量修复批 A 案——见文件头注记):缺省 shared = 旧行为零变更
|
||||
var seed_mode := "shared"
|
||||
if args.size() >= 5 and args[4] in ["shared", "unique"]:
|
||||
seed_mode = args[4]
|
||||
# 基线格模式(2026-09-20 重投批工具口径):第 6 参 baseline = 只跑 atk0/hp0 一格——
|
||||
# 全网格 10 格在大 N 下成本不可行(~1.35s/局),基线判读只需零加成格的大样本精度
|
||||
var baseline_only := false
|
||||
if args.size() >= 6 and args[5] == "baseline":
|
||||
baseline_only = true
|
||||
var atk_axis: Array = [0, 50, 100, 150, 200]
|
||||
var hp_axis: Array = [0, 100]
|
||||
if baseline_only:
|
||||
atk_axis = [0]
|
||||
hp_axis = [0]
|
||||
var seed_label := "种子 %d+%d" % [seed0, n] if seed_mode == "shared" else "种子 %d 起逐局独立" % seed0
|
||||
print("=== 关卡期望扫描:level %d · N=%d/格 · %s · 策略=%s · 种子模式=%s ===" % [level_arg, n, seed_label, policy_arg, seed_mode])
|
||||
print("%-6s %-10s %-8s %-14s %-14s %-14s %-10s %-12s %-12s %-10s %-10s %-8s %-8s" % ["策略", "atk%/hp%", "通关率", "整关承伤均/p90", "通关剩余HP均(胜)", "进BossHP均", "平均轮数", "踩雷/战", "普通承伤/场%", "跳战率", "Boss承伤%", "踩雷/局", "刷新/局"])
|
||||
var perfect_mine_total := 0 # 完美档构造保证 = 零踩雷(只挖已证安全格)——扫描末断言
|
||||
for policy in policies:
|
||||
var tag := "基线" if policy == "normal" else "完美"
|
||||
for atk_pct in atk_axis:
|
||||
for hp_pct in hp_axis:
|
||||
var wins := 0
|
||||
var dmg_all: Array = []
|
||||
var hp_left_win: Array = []
|
||||
var hp_boss: Array = []
|
||||
var rounds_all: Array = []
|
||||
var mines_per_battle: Array = []
|
||||
var normal_dmg_pct: Array = [] # 每普通战斗承伤占生效 max_hp 比(难度曲线与Boss F4 口径——≤20% 带)
|
||||
var boss_dmg_pct: Array = [] # Boss 战承伤占生效 max_hp 比(难度曲线与Boss F4 口径——≤50% 带)
|
||||
var wipes := 0 # 普通战斗 round-1 即胜数(首轮全灭跳战——拍板④观测面)
|
||||
var normal_battles := 0
|
||||
var death_nodes := {}
|
||||
var mine_per_run: Array = []
|
||||
var refresh_per_run: Array = []
|
||||
for i in range(n):
|
||||
var seed_val := seed0 + i if seed_mode == "shared" else seed0 + _run_cursor
|
||||
_run_cursor += 1
|
||||
var st = autoplay.run_level(db, seed_val, level_arg, atk_pct, hp_pct, true, logger, policy)
|
||||
dmg_all.append(st.hero_dmg)
|
||||
rounds_all.append(st.rounds)
|
||||
mine_per_run.append(int(st.mine_hits))
|
||||
refresh_per_run.append(int(st.refreshes))
|
||||
mines_per_battle.append(float(st.mine_hits) / float(maxi(1, st.battles)))
|
||||
var eff_max := maxi(1, int(round(float(db.hero.max_hp) * (100.0 + float(hp_pct)) / 100.0)))
|
||||
# 逐战斗承伤切分(round_index 回卷 = 新战斗;末段 = Boss 战——当且仅当本局抵达 Boss 节点)
|
||||
var segs := _damage_segments(st.rows)
|
||||
for k in segs.size():
|
||||
var pct := 100.0 * float(segs[k].dmg) / float(eff_max)
|
||||
if k == segs.size() - 1 and int(st.hp_at_boss) >= 0:
|
||||
boss_dmg_pct.append(pct)
|
||||
else:
|
||||
normal_dmg_pct.append(pct)
|
||||
normal_battles += 1
|
||||
if bool(segs[k].r1_win):
|
||||
wipes += 1
|
||||
if st.result == "victory":
|
||||
wins += 1
|
||||
hp_left_win.append(st.hp_left)
|
||||
hp_boss.append(st.hp_at_boss)
|
||||
elif st.result == "defeat":
|
||||
var key := "%d.%s" % [st.defeat_node_index, st.defeat_node_type]
|
||||
death_nodes[key] = int(death_nodes.get(key, 0)) + 1
|
||||
var death_txt := "—"
|
||||
if not death_nodes.is_empty():
|
||||
var parts: Array = []
|
||||
for k in death_nodes.keys():
|
||||
parts.append("%s×%d" % [k, death_nodes[k]])
|
||||
death_txt = " ".join(parts)
|
||||
var mine_mean := _mean(mine_per_run)
|
||||
if policy == "perfect":
|
||||
perfect_mine_total += int(round(mine_mean * float(n)))
|
||||
print("%-6s atk%+3d%% hp%+3d%% %5.0f%% %6.1f / %-6.1f %6.1f %6.1f %6.1f %5.2f %6.1f %5.0f%% %6.1f %6.2f %6.2f %s" % [
|
||||
tag, atk_pct, hp_pct, 100.0 * float(wins) / float(n),
|
||||
_mean(dmg_all), _p90(dmg_all), _mean(hp_left_win), _mean(hp_boss), _mean(rounds_all), _mean(mines_per_battle),
|
||||
_mean(normal_dmg_pct), 100.0 * float(wipes) / float(maxi(1, normal_battles)), _mean(boss_dmg_pct), mine_mean, _mean(refresh_per_run), death_txt])
|
||||
print("(F4 带:承伤 ≤20%%/普通场 · ≤50%%/Boss 场,占生效 max_hp · F5 带:基线通关率 L1 [35,55]%% / L2 [25,45]%%——2026-09-20 用户拍板分带 · 跳战率 = 普通战斗 round-1 即胜(首轮全灭)占比 · 承伤含败者至死 · 败北节点 0-3=战斗节点序 4=Boss · 踩雷/战=每战斗负向兑付触发期望(格面资源批口径) · 完美档 = 技能轴上界测量(零盲挖/全标旗/卡死刷新——口径见 auto_player.gd)· 耗时 %.1f 秒)" % [float(Time.get_ticks_msec() - t0) / 1000.0])
|
||||
if "perfect" in policies and perfect_mine_total > 0:
|
||||
push_error("[level_monte_carlo] 完美策略档出现踩雷 %d 次——构造保证被破坏(只挖已证安全格),求解器/驱动有缺陷" % perfect_mine_total)
|
||||
quit(1)
|
||||
return
|
||||
logger.close_session()
|
||||
quit(0)
|
||||
|
||||
|
||||
# 逐战斗段切分:round_index 回卷 = 新战斗(defense_scan 同口径)
|
||||
# 每段 = {dmg: 英雄承伤合计, r1_win: round-1 即胜(首轮全灭跳战签名——verdict 胜利行 value=1)}
|
||||
func _damage_segments(rows: Array) -> Array:
|
||||
var segs: Array = []
|
||||
var last_round := 0
|
||||
for row in rows:
|
||||
var r: int = int(row.get("round_index", 0))
|
||||
if segs.is_empty() or r < last_round:
|
||||
segs.append({"dmg": 0, "r1_win": false})
|
||||
last_round = r
|
||||
var seg: Dictionary = segs[segs.size() - 1]
|
||||
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action":
|
||||
seg.dmg = int(seg.dmg) + int(row.get("value", 0))
|
||||
if String(row.get("type", "")) == "verdict" and r == 1 and int(row.get("value", 0)) == 1:
|
||||
seg.r1_win = true
|
||||
return segs
|
||||
|
||||
|
||||
func _mean(a: Array) -> float:
|
||||
if a.is_empty():
|
||||
return 0.0
|
||||
var t := 0.0
|
||||
for v in a:
|
||||
t += float(v)
|
||||
return t / float(a.size())
|
||||
|
||||
|
||||
func _p90(a: Array) -> float:
|
||||
if a.is_empty():
|
||||
return 0.0
|
||||
var b := a.duplicate()
|
||||
b.sort()
|
||||
return float(b[mini(b.size() - 1, int(0.9 * b.size()))])
|
||||
@@ -1 +0,0 @@
|
||||
uid://h3mghae7yvvx
|
||||
@@ -124,7 +124,7 @@ func _smoke_adventure_table() -> void:
|
||||
func _smoke_adventure_api() -> void:
|
||||
print("--- 冒险结构 API 闸(重复开始拒绝 · 放弃两态受理)---")
|
||||
var adr = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(42))
|
||||
adr.set_hero_override(400, 200) # 首战速胜档(放弃的非战斗受理要走到选路态)
|
||||
adr.set_hero_override(900, 400) # 首战速胜档(放弃的非战斗受理要走到选路态)
|
||||
check(adr.start_adventure(42), "冒险启动受理(主种子注入恰一次——state=running)")
|
||||
check(not adr.start_adventure(43), "进行中重复 start_adventure 拒绝(重开须待结算完毕)")
|
||||
check(adr.runner().current_battle() != null, "首战在飞(接口①开始关卡直达战斗节点)")
|
||||
@@ -170,10 +170,10 @@ func _smoke_adventure() -> void:
|
||||
miss += 1
|
||||
check(miss == 0, "行集内每个战斗/Boss 节点在 board_gen + enemy 有精确行(缺失 %d)" % miss)
|
||||
# ② 满配通关 + ④ 大奖励兑付(恰 2 次 · 读数=表值 · 回满=新上限 · 覆盖值通道传播 L2/L3 首战上限)
|
||||
var st: Dictionary = autoplay.run_adventure(_db, 42, 400, 200)
|
||||
var st: Dictionary = autoplay.run_adventure(_db, 42, 900, 400)
|
||||
var sum: Dictionary = st["summary"]
|
||||
check(String(st["result"]) == "completed" and bool(sum.get("cleared", false)),
|
||||
"满配通关(42 / atk+400 / hp+200——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)")
|
||||
"满配通关(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)")
|
||||
check(int(sum.get("levels_cleared", -1)) == 3, "通关几关 = 3(levels_cleared)")
|
||||
check(int(sum.get("keystone_total", -1)) == 3 * int(_db.adventure.keystone_per_level),
|
||||
"钥石合计 = 3 × keystone_per_level(%d)" % int(sum.get("keystone_total", -1)))
|
||||
@@ -191,7 +191,7 @@ func _smoke_adventure() -> void:
|
||||
reward_ok = false
|
||||
check(reward_ok, "大奖励读数:hp_bonus=表值 · 新上限累加 · 回满=新上限 · 钥石逐关注账")
|
||||
var seq: Array = st["encounter_max_hp_seq"]
|
||||
var k := 3.0 # hp+200 ⇒ TunePanel 百分比后乘 ×3(加算先加、百分比后乘——生效次序口径)
|
||||
var k := 5.0 # hp+400 ⇒ TunePanel 百分比后乘 ×5(加算先加、百分比后乘——生效次序口径)
|
||||
var exp_base := maxi(1, int(round(float(int(_db.hero.max_hp)) * k)))
|
||||
var exp_l2 := maxi(1, int(round(float(int(_db.hero.max_hp) + hp_bonus) * k)))
|
||||
var exp_l3 := maxi(1, int(round(float(int(_db.hero.max_hp) + 2 * hp_bonus) * k)))
|
||||
@@ -202,7 +202,7 @@ func _smoke_adventure() -> void:
|
||||
check(idx_l2 > 0 and idx_l3 > idx_l2,
|
||||
"L2/L3 首战上限入参 = 基础+加算(%d→%d——大奖励经覆盖值通道传播)" % [exp_l2, exp_l3])
|
||||
# ③ 种子域可复现(同种子两局:summary + 上限序列 + 行进序列逐位一致)
|
||||
var st2: Dictionary = autoplay.run_adventure(_db, 42, 400, 200)
|
||||
var st2: Dictionary = autoplay.run_adventure(_db, 42, 900, 400)
|
||||
check(st2["summary"] == sum and st2["encounter_max_hp_seq"] == seq
|
||||
and st2["visited_ids"] == st["visited_ids"],
|
||||
"同种子两局逐位可复现(summary/上限序列/行进序列——主种子单点派生)")
|
||||
@@ -242,7 +242,7 @@ func _smoke_adventure() -> void:
|
||||
"L2 败北败退明细:层号=2 · 节点在案(数值验证取数面 2026-09-22)")
|
||||
# ⑥ 重复宣布拒绝(settle 后再调 _on_level_finished——摘要零变化)
|
||||
var adr6 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(7))
|
||||
adr6.set_hero_override(400, 200)
|
||||
adr6.set_hero_override(900, 400)
|
||||
check(adr6.start_adventure(7), "重复拒绝场景:冒险启动(seed 7)")
|
||||
var b6 = adr6.runner().current_battle()
|
||||
var st6 := _adv_st()
|
||||
@@ -273,7 +273,7 @@ func _smoke_adventure() -> void:
|
||||
var same := {}
|
||||
for s in range(42, 92):
|
||||
var a8 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(s))
|
||||
a8.set_hero_override(400, 200)
|
||||
a8.set_hero_override(900, 400)
|
||||
if not a8.start_adventure(s):
|
||||
continue
|
||||
if _adv_drive_to_shop(a8, _adv_st(), autoplay):
|
||||
@@ -313,7 +313,7 @@ func _smoke_adventure() -> void:
|
||||
"同一公式两个落点逐位一致(LevelRunner.rest_heal_amount 内置默认)")
|
||||
# ⑩ 放弃(战斗中拒绝 · 非战斗期受理同失败型结算)
|
||||
var a10 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(15))
|
||||
a10.set_hero_override(400, 200)
|
||||
a10.set_hero_override(900, 400)
|
||||
check(a10.start_adventure(15), "放弃场景:冒险启动(seed 15——首战在飞)")
|
||||
check(not a10.abandon(), "战斗中 abandon 拒绝(战斗域单事务在飞)")
|
||||
var b10 = a10.runner().current_battle()
|
||||
@@ -2907,7 +2907,7 @@ func _smoke_shop_room() -> void:
|
||||
var shop_run := {}
|
||||
for s in range(42, 92):
|
||||
var runner = load("res://core/level_runner.gd").new(_db, _fresh_rng(s))
|
||||
runner.set_hero_override(400, 200) # 行进段快速通关档(购买断言与百分比轴正交——加算区单独断言)
|
||||
runner.set_hero_override(900, 400) # 行进段快速通关档(购买断言与百分比轴正交——加算区单独断言)
|
||||
runner.start_level(2, s)
|
||||
var st := {"result": "", "rounds": 0, "refreshes": 0, "mine_hits": 0, "hero_dmg": 0, "policy": "normal"}
|
||||
var guard := 40
|
||||
@@ -3481,7 +3481,7 @@ func _smoke_save() -> void:
|
||||
|
||||
# ⑨ 摘要集成(冒险结构批产物驱动):run_adventure 真局摘要 → apply 原语 → 域读数 → 写盘读回保持
|
||||
var autoplay = load("res://smoke/level_autoplay.gd")
|
||||
var st9: Dictionary = autoplay.run_adventure(_db, 42, 400, 200)
|
||||
var st9: Dictionary = autoplay.run_adventure(_db, 42, 900, 400)
|
||||
var f9 = sm_script.new(paths[8])
|
||||
f9.apply_adventure_summary(st9.get("summary", {}))
|
||||
var kpl := int(_db.adventure.keystone_per_level)
|
||||
@@ -3492,7 +3492,7 @@ func _smoke_save() -> void:
|
||||
"集成三关首杀标记齐")
|
||||
check(f9.recent_summaries().size() == 1 and int(f9.recent_summaries()[0].get("master_seed", -1)) == 42,
|
||||
"集成 recent 1 条且 seed 42")
|
||||
var st9b: Dictionary = autoplay.run_adventure(_db, 42, 400, 200)
|
||||
var st9b: Dictionary = autoplay.run_adventure(_db, 42, 900, 400)
|
||||
f9.apply_adventure_summary(st9b.get("summary", {}))
|
||||
check(f9.clear_count(1) == 2 and f9.clear_count(2) == 2 and f9.clear_count(3) == 2, "第二局后通关次数全 2")
|
||||
check(f9.keystone_balance() == 6 * kpl, "第二局后余额翻倍(6 × keystone_per_level)")
|
||||
|
||||
@@ -24,7 +24,7 @@ var _tested_legend := false
|
||||
var _tested_review := false
|
||||
var _tested_refresh_chain := false
|
||||
var _tested_tooltip := false
|
||||
var _tested_l2 := false # 分支节点图批:L2 入口行为段(首页直入口 + 岔路选路真实点击)
|
||||
var _inter_confirms := 0 # 大奖励屏确认计数(冒险推进停点——首见确认推进 L2,再见 = L2 通关收口)
|
||||
var _route_clicks := 0 # 选路覆盖层点击计数(岔路使用率观测面)
|
||||
|
||||
|
||||
@@ -84,9 +84,9 @@ func _ready() -> void:
|
||||
var iso_reset = load("res://core/save_manager.gd").new("user://smoke_click_probe_reset.json")
|
||||
iso_reset.load_from_disk()
|
||||
main.meta = load("res://core/meta_progression.gd").new(main.db, iso_reset)
|
||||
main.tune_panel.set_sliders(200, 100)
|
||||
main.tune_panel.set_sliders(400, 200) # 高加成保 L1 可通关——L2 岔路选路段只能经冒险推进到达(单关直入口退役)
|
||||
var t1 := Time.get_ticks_msec()
|
||||
main._start_full_level()
|
||||
main._start_adventure_from_menu()
|
||||
var t_entry := Time.get_ticks_msec() - t1
|
||||
# 性能 AC:进入战斗屏到首格可点 ≤500ms(棋盘 install 完成口径);
|
||||
# 场景实例化冷启动总时长(含全部脚本首载编译——引擎启动域,非 AC 口径)另列观测
|
||||
@@ -98,7 +98,7 @@ func _ready() -> void:
|
||||
_problems.append("首格未可点(cells=0)")
|
||||
print("[click_probe] 首格可点计时 %dms(进入战斗屏口径,≤500ms AC)· 场景冷启动 %dms(含 UI 构建 %dms,观测项)" % [
|
||||
first_cell_ms, t_total, t_ui_ready])
|
||||
var action_budget := 9000
|
||||
var action_budget := 20000
|
||||
while not _done and action_budget > 0:
|
||||
action_budget -= 1
|
||||
await get_tree().process_frame
|
||||
@@ -117,24 +117,33 @@ func _ready() -> void:
|
||||
main.shop_overlay.leave_pressed.emit()
|
||||
_flags_placed.clear()
|
||||
continue
|
||||
if main._inter_overlay.visible:
|
||||
# 大奖励屏(冒险级停点——非末关通关恰一次):首见确认推进 L2(真实按钮信号路径);再见 = L2 已通关,收口
|
||||
if not main._input_locked():
|
||||
_problems.append("大奖励屏打开时输入未锁定")
|
||||
_inter_confirms += 1
|
||||
if _inter_confirms == 1:
|
||||
var next_btn: Button = _find_button_by_prefix(main._inter_overlay, "进入下一层")
|
||||
if next_btn == null:
|
||||
_problems.append("大奖励屏确认按钮缺失")
|
||||
else:
|
||||
next_btn.pressed.emit()
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
if main.runner == null or int(main.runner.level_row.get("level_index", -1)) != 2:
|
||||
_problems.append("大奖励确认后未推进到第二关(level_index != 2)")
|
||||
else:
|
||||
_done = true
|
||||
print("[click_probe] 第二关大奖励屏(L2 通关)——探针收口 · 选路口点击 %d 次" % _route_clicks)
|
||||
break
|
||||
_flags_placed.clear()
|
||||
continue
|
||||
if main._end_overlay.is_open():
|
||||
# 终局 overlay 模态断言(pattern 6:打开时输入锁定)
|
||||
# 冒险终局(失败/放弃/打穿)——单关直入口退役后无二段入口,见终局即收口(模态断言照旧)
|
||||
if not main._input_locked():
|
||||
_problems.append("终局 overlay 打开时输入未锁定")
|
||||
if not _tested_l2:
|
||||
# L1 终局 → 首页直入口切换 L2(分支节点图批行为段:真实"返回"路径 + start_level(2))
|
||||
_tested_l2 = true
|
||||
print("[click_probe] L1 结束:%s——切换关卡 2 试玩入口" % main._end_overlay.verdict_text().replace("\n", " "))
|
||||
main._on_battle_end_continue() # 终局出口(真实"返回"按钮处理器——全量清理回开始层)
|
||||
if not main._start_overlay.visible:
|
||||
_problems.append("终局返回后未回到开始覆盖层")
|
||||
main.tune_panel.set_sliders(200, 100)
|
||||
main._start_full_level(2)
|
||||
if main.runner == null or int(main.runner.level_row.get("level_index", -1)) != 2:
|
||||
_problems.append("关卡 2 直入口启动失败(level_index != 2)")
|
||||
continue
|
||||
_done = true
|
||||
print("[click_probe] 关卡结束:%s · 选路口点击 %d 次" % [
|
||||
print("[click_probe] 冒险终局:%s · 选路口点击 %d 次" % [
|
||||
main._end_overlay.verdict_text().replace("\n", " "), _route_clicks])
|
||||
break
|
||||
if main.settlement == null or main.runner == null:
|
||||
|
||||
@@ -10,7 +10,7 @@ func _ready() -> void:
|
||||
var main = load("res://presentation/main.tscn").instantiate()
|
||||
add_child(main)
|
||||
await get_tree().process_frame
|
||||
main._start_full_level()
|
||||
main._start_adventure_from_menu()
|
||||
for i in range(3):
|
||||
await get_tree().process_frame
|
||||
var win := get_window()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 实现 GDD:design/ux/battle.md(场景级 headless 探针——四区布局实例化断言;entry 17②③)。
|
||||
# Date: 2026-09-12(战斗屏改造批扩展;基础 = 2026-09-11 首战棋盘探针——既有断言全保留)
|
||||
# 项目运行模式(autoload 在位)实例化 main.tscn,直接驱动 _start_full_level(),
|
||||
# 项目运行模式(autoload 在位)实例化 main.tscn,直接驱动 _start_adventure_from_menu()(完整冒险——唯一试玩入口),
|
||||
# 断言四区真实构建(棋盘/进度条/主角站桩/敌方站桩/AP 点阵/明细带/数据框;E 区道具面板已删除;
|
||||
# 2026-09-20 全站桩拍板——英雄卡/敌人卡断言随站桩改造批换装)。
|
||||
# 运行:Godot_console.exe --headless --path . res://smoke/ui_probe.tscn
|
||||
@@ -14,9 +14,9 @@ func _ready() -> void:
|
||||
add_child(main)
|
||||
await get_tree().process_frame
|
||||
var problems: Array = []
|
||||
# ---- 放弃入口(2026-09-21 用户拍板——台账 119):默认隐藏(单关测试直入口无冒险可放弃)----
|
||||
# ---- 放弃入口(2026-09-21 用户拍板——台账 119):默认隐藏(非冒险模式无冒险可放弃——开始层/调试两层)----
|
||||
if main.route_overlay.abandon_btn == null or main.route_overlay.abandon_btn.visible:
|
||||
print("[ui_probe] FAIL — 放弃按钮默认应隐藏(单关测试直入口关)")
|
||||
print("[ui_probe] FAIL — 放弃按钮默认应隐藏(非冒险模式关)")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
# ---- 营地覆盖层(元进度实现批——主菜单入口/开合/余额文案/购买禁用态;
|
||||
@@ -58,7 +58,7 @@ func _ready() -> void:
|
||||
var iso_zero = load("res://core/save_manager.gd").new("user://smoke_ui_probe_camp.json")
|
||||
iso_zero.load_from_disk()
|
||||
main.meta = load("res://core/meta_progression.gd").new(main.db, iso_zero)
|
||||
main._start_full_level()
|
||||
main._start_adventure_from_menu()
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
# ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)----
|
||||
|
||||
@@ -61,7 +61,7 @@ func _ready() -> void:
|
||||
# (攻击 ×2 / 生命 +50%)——旧缺陷(残留值被当基线再乘)在 encounter 参数链上同样被抓获
|
||||
main.tune_panel.set_sliders(100, 50)
|
||||
main.tune_panel.press_apply_for_probe()
|
||||
main._start_full_level() # 完整关卡路径(_start_single 按语义清零滑条 = 恒基线,不承载加成断言)
|
||||
main._start_adventure_from_menu() # 完整冒险路径(唯一试玩入口;_start_single 按语义清零滑条 = 恒基线,不承载加成断言)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
var hero: Dictionary = main.settlement.presentation_snapshot().hero
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Date: 2026-09-22(数值验证工具批——判定端 tools/balance_verifier.py 的数据源,本文件零判定逻辑)
|
||||
# 三段一次采集,stdout 以「VJSON 」前缀行出机器读 JSON(其余行为人读进度/摘要):
|
||||
# level 段 直入口单关基线(level_autoplay.run_level 零加成)——L1/L2/L3 × 双画像 × n,
|
||||
# 供验收带判定(分带批 2026-09-20 口径:L1 普通 [35,55] / L2 普通 [25,45] / 完美 [35,65])
|
||||
# adventure 段 全冒险连探基线(run_adventure 零永久强化)× 双画像 × na——止步分布与败退节点
|
||||
# (摘要 defeat 明细 + 末场战斗敌方名),回答「零加成倒在第几层哪个节点」
|
||||
# 2026-09-23 单关直入口口径退役(用户拍板):原 level 段(直入口单关基线——满血零上下文进关,
|
||||
# 与真实冒险的条件通关率脱节,曾误导曲线调参)整段移除;逐关难度读数由 adventure 段行内
|
||||
# levels_cleared/stopped_at 派生(判定端 analyze_adventure_levels),零补采。
|
||||
# 2026-09-23 换算率标定批:① adventure 段增 inject 注入档(永久强化 flat OFAT——标定换算率表的
|
||||
# 边际读数:每档与白板同种子流配对,逐关条件通关率差 = 该档的带平移量实测);
|
||||
# ② campaign 段增 armor_first 花费序 + 行带 spend 标签(多轨迹合并采集按序分组)。
|
||||
# 两段一次采集,stdout 以「VJSON 」前缀行出机器读 JSON(其余行为人读进度/摘要):
|
||||
# adventure 段 全冒险连探基线(run_adventure 零永久强化)× 双画像 × na——止步分布、败退节点
|
||||
# (摘要 defeat 明细 + 末场战斗敌方名)与逐关条件通关率派生源;inject 档行带
|
||||
# inject_tag/override(flat 三键)
|
||||
# campaign 段 营地循环战役 × 双画像 × k 局——SaveManager 隔离档 + MetaProgression 真实账本:
|
||||
# 逐局 hero_override() 注入 → 冒险 → settle_adventure 入账 → 花费策略买锻造,
|
||||
# 回答「累计几次通关冒险后下一关可过」(元进度换算率标定的模拟读数源)
|
||||
# 2026-09-23 换算率标定批:① level 段增 inject 注入档(永久强化 flat OFAT——标定换算率表的
|
||||
# 边际读数:每档与基线同种子流配对,通关率差 = 该档的带平移量实测)+ levels 层裁剪;
|
||||
# ② campaign 段增 armor_first 花费序(甲轴真实账本轨迹——护甲行强条目重测读数源)。
|
||||
# 运行:Godot_console.exe --headless --path . --script res://smoke/verifier_batch.gd -- [k=v ...]
|
||||
# n=60(直入口每格局数) na=40(基线冒险局数) k=40(战役局数) seed0=8000
|
||||
# only=level,adventure,campaign(缺省全段) spend=hp_first|atk_first|armor_first(锻造花费优先序)
|
||||
# inject=max_hp:3,atk:1(直入口 flat 注入档——三键 {max_hp, atk, armor_class},缺省全零白板)
|
||||
# levels=1,2,3(直入口层集——标定批只收 1,2,第三层带未定零判定面)
|
||||
# na=40(基线冒险局数) k=40(战役局数) seed0=8000
|
||||
# only=adventure,campaign(缺省全段) spend=hp_first|atk_first|armor_first(锻造花费优先序)
|
||||
# inject=max_hp:3,atk:1(冒险段 flat 注入档——三键 {max_hp, atk, armor_class},缺省全零白板)
|
||||
# 种子纪律:unique 逐局独立(掷点测量修复批 A 案——seed0 + 全局游标,每掷点恰消费一次)。
|
||||
# 同 seed0 跨档复用 = 配对读数(同序种子流——分析侧按局序配对消方差)。
|
||||
# 同 seed0 跨档复用 = 配对读数(同序种子流——分析侧按种子配对消方差)。
|
||||
# 日志合规:全段共享 playtest_logger 会话(与真人试玩同日志),分析工具可直接消费。
|
||||
# 存档红线:战役档用 user://verifier_campaign_<画像>.json 隔离路径,段首清陈旧段尾清理——
|
||||
# 绝不碰 user://save.json(与 smoke 存档隔离纪律同源)。
|
||||
@@ -46,48 +47,37 @@ func _initialize() -> void:
|
||||
print("模拟日志: %s" % log_path)
|
||||
var autoplay = load("res://smoke/level_autoplay.gd")
|
||||
var t0 := Time.get_ticks_msec()
|
||||
var counts := {"level": 0, "adventure": 0, "campaign": 0}
|
||||
if "level" in opt.only:
|
||||
_seg_level(db, autoplay, logger, opt, counts)
|
||||
var counts := {"adventure": 0, "campaign": 0}
|
||||
if "adventure" in opt.only:
|
||||
_seg_adventure(db, autoplay, logger, opt, counts)
|
||||
if "campaign" in opt.only:
|
||||
_seg_campaign(db, autoplay, logger, opt, counts)
|
||||
logger.close_session()
|
||||
print("数值验证采集完成:level %d 局 · adventure %d 局 · campaign %d 局,耗时 %.1f 秒"
|
||||
% [counts.level, counts.adventure, counts.campaign, float(Time.get_ticks_msec() - t0) / 1000.0])
|
||||
print("数值验证采集完成:adventure %d 局 · campaign %d 局,耗时 %.1f 秒"
|
||||
% [counts.adventure, counts.campaign, float(Time.get_ticks_msec() - t0) / 1000.0])
|
||||
quit(0)
|
||||
|
||||
|
||||
func _parse_args(args: PackedStringArray) -> Dictionary:
|
||||
var opt := {"n": 60, "na": 40, "k": 40, "seed0": 8000, "only": "level,adventure,campaign",
|
||||
"spend": "hp_first", "inject": "", "levels": "1,2,3"}
|
||||
var opt := {"na": 40, "k": 40, "seed0": 8000, "only": "adventure,campaign",
|
||||
"spend": "hp_first", "inject": ""}
|
||||
for a in args:
|
||||
var kv := String(a).split("=", true, 1)
|
||||
if kv.size() != 2:
|
||||
continue
|
||||
var key := String(kv[0])
|
||||
if opt.has(key):
|
||||
if key in ["only", "spend", "inject", "levels"]:
|
||||
if key in ["only", "spend", "inject"]:
|
||||
opt[key] = String(kv[1])
|
||||
else:
|
||||
opt[key] = int(kv[1])
|
||||
var segs: Array = []
|
||||
for s in String(opt.only).split(","):
|
||||
if s in ["level", "adventure", "campaign"]:
|
||||
if s in ["adventure", "campaign"]:
|
||||
segs.append(s)
|
||||
opt["only"] = segs
|
||||
if not SPEND_ORDERS.has(String(opt.spend)):
|
||||
opt["spend"] = "hp_first"
|
||||
var lv: Array = []
|
||||
for s in String(opt.levels).split(","):
|
||||
var li := int(s)
|
||||
if li >= 1 and li <= 3 and not lv.has(li):
|
||||
lv.append(li)
|
||||
lv.sort()
|
||||
if lv.is_empty():
|
||||
lv = [1, 2, 3]
|
||||
opt["levels"] = lv
|
||||
opt["override"] = _parse_inject(String(opt.inject))
|
||||
return opt
|
||||
|
||||
@@ -102,6 +92,15 @@ func _parse_inject(raw: String) -> Dictionary:
|
||||
return out
|
||||
|
||||
|
||||
# 注入档人读标签(白板 = "白板";行键 inject_tag 供判定端分档聚合)
|
||||
func _inject_tag(ov: Dictionary) -> String:
|
||||
var parts: Array = []
|
||||
for k in OVERRIDE_KEYS:
|
||||
if int(ov.get(k, 0)) != 0:
|
||||
parts.append("%s+%d" % [k, int(ov[k])])
|
||||
return "白板" if parts.is_empty() else ",".join(parts)
|
||||
|
||||
|
||||
func _next_seed(seed0: int) -> int:
|
||||
var s: int = seed0 + _cursor
|
||||
_cursor += 1
|
||||
@@ -112,47 +111,22 @@ func _emit(row: Dictionary) -> void:
|
||||
print("VJSON " + JSON.stringify(row))
|
||||
|
||||
|
||||
# ---- level 段:直入口单关基线(零加成——分带批验收带判定的同口径读数);
|
||||
# inject 档 = 同种子流配对的 OFAT 边际读数(换算率标定批——行带 override flat 三键) ----
|
||||
# ---- adventure 段:全冒险连探基线(零永久强化——止步分布/败退节点归因/逐关条件通关率派生源);
|
||||
# inject 档 = 同种子流配对的 OFAT 边际读数(换算率标定批——行带 inject_tag/override flat 三键) ----
|
||||
|
||||
func _seg_level(db, autoplay, logger, opt: Dictionary, counts: Dictionary) -> void:
|
||||
func _seg_adventure(db, autoplay, logger, opt: Dictionary, counts: Dictionary) -> void:
|
||||
var ov: Dictionary = opt.override
|
||||
var tag := _inject_tag(ov)
|
||||
for policy in ["normal", "perfect"]:
|
||||
for li in opt.levels:
|
||||
for i in range(int(opt.n)):
|
||||
var seed_val := _next_seed(int(opt.seed0))
|
||||
var st: Dictionary = autoplay.run_level(db, seed_val, li, 0, 0, false, logger, policy, "min", ov)
|
||||
_emit({
|
||||
"seg": "level", "level": li, "policy": policy, "seed": seed_val,
|
||||
"inject_tag": tag, "override": ov.duplicate(),
|
||||
"result": String(st.result),
|
||||
"defeat_node_id": int(st.defeat_node_id), "defeat_node_type": String(st.defeat_node_type),
|
||||
"battles": int(st.battles), "rounds": int(st.rounds),
|
||||
})
|
||||
counts.level += 1
|
||||
print("进度 level 第%d层 %s %d/%d(%s)" % [li, policy, int(opt.n), int(opt.n), tag])
|
||||
|
||||
|
||||
# 注入档人读标签(白板 = "白板";行键 inject_tag 供判定端分档聚合)
|
||||
func _inject_tag(ov: Dictionary) -> String:
|
||||
var parts: Array = []
|
||||
for k in OVERRIDE_KEYS:
|
||||
if int(ov.get(k, 0)) != 0:
|
||||
parts.append("%s+%d" % [k, int(ov[k])])
|
||||
return "白板" if parts.is_empty() else ",".join(parts)
|
||||
|
||||
|
||||
# ---- adventure 段:全冒险连探基线(零永久强化——止步分布与败退节点归因) ----
|
||||
|
||||
func _seg_adventure(db, autoplay, logger, opt: Dictionary, counts: Dictionary) -> void:
|
||||
for policy in ["normal", "perfect"]:
|
||||
for i in range(int(opt.na)):
|
||||
var seed_val := _next_seed(int(opt.seed0))
|
||||
var st: Dictionary = autoplay.run_adventure(db, seed_val, 0, 0, false, logger, policy, "min")
|
||||
_emit(_adventure_row(st, policy, seed_val))
|
||||
var st: Dictionary = autoplay.run_adventure(db, seed_val, 0, 0, false, logger, policy, "min", ov)
|
||||
var row := _adventure_row(st, policy, seed_val)
|
||||
row["inject_tag"] = tag
|
||||
row["override"] = ov.duplicate()
|
||||
_emit(row)
|
||||
counts.adventure += 1
|
||||
print("进度 adventure %s %d/%d" % [policy, int(opt.na), int(opt.na)])
|
||||
print("进度 adventure %s %d/%d(%s)" % [policy, int(opt.na), int(opt.na), tag])
|
||||
|
||||
|
||||
# 冒险局共用取数行(adventure/campaign 两段同构——summary 缺席 = 编排异常局,原样带 result 供判定端计异常)
|
||||
|
||||
Reference in new issue
Block a user