Files
gplan-2/game/tests/bp_baseline_dump.gd
T
DKChan 4da7f3e2f2 主卡T3(拆件批wr4/wr5): 应用层收薄+构造注入+信号直连——adventure_runner六转发信号族与_forward_*方法删除(选路快照双职改核心侧受理_on_level_choices_presented直订level信号;stop信号中继删);level_runner构造注入一次给齐(_init收ctx十二键:hero/ledger瘦壳/adv_bucket/attunement/tree_rules活引用/shop_mode/treasure_unlocked/类型轴/凭证出箱/pool_wiring四参——后置setter群八件与set_hero/set_run_ledger退役,_bind_shop全仓仅构造一处,头注补关卡开始信号前零发射契约,八白盒属性内部改直指_shop);adv _start_current_level组包ctx(_inject_level_params退役改_emit_level_started只广播);呈现/测量信号直连(main_adventure_flow _connect_runner_once六信号level_started重取点is_connected守卫重连+三选一订stop();autoplay增订level_started同关守卫重连三信号;curio_interact_controller 14处adventure.stop()改址);测量面残留换代(bp_baseline_dump直连改level_started重连/ui_tune_probe与main_debug_flow keystone读hero());测试直连面换代(breakpoint_resume P5订stop()/adventure_feats信号序观测level_started回调内重连——bind句柄每回调新建不以Callable相等判重);sc3源码断言落hero_ledger_test(_bind_shop全仓仅构造一处/_forward_族零命中)〔拆件批 V5〕。验收:bp1对拍292dumps×27键逐位零漂移(改前/T1/T2/T3四时点一致)+十套件零红+csf集成绿+冒烟17项0失败〔拆件批 V4〕〔拆件批 V5〕
2026-10-27 03:51:35 +08:00

178 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
# 断点基线对拍采集脚本(迭代 39 拆件批 T0 载体,批末随中间产物退役——非长期测试件)。
# 固定种子集驱动完整冒险至各停点(节点入口/选路/三选一在途/兑付后待推进),逐停点
# dump 断点 Dictionary 全键值(直调 _bp_parts 写侧取值面)→ JSON 落盘,供改前/改后
# 逐键逐位比对(bp1:同种子改前改后 27 键逐位一致)。
# 运行:BP_DUMP_OUT=<json 路径> godot --headless --path . --script res://tests/bp_baseline_dump.gd
# 缺省输出 user://bp_baseline_dump.json。
# 驱动与 sim/level_autoplay 同构(完美档战斗驱动 + 确定性选件/选路)——零随机流自开,
# 同种子逐位可复放;dump 时点由驱动循环状态判定,确定性时点。
extends SceneTree
const AdventureRunnerScript := preload("res://core/adventure_runner.gd")
const Autoplay := preload("res://sim/level_autoplay.gd")
const SEEDS: Array[int] = [1234, 4321, 910042, 20260101, 20260102, 20260103]
const PARAMS := {
"hero_override": {},
"treasure_unlocked": {"wide_scout_band": true},
"attunement": ["sky_eye", "incense_stick", "refresh_practice"], # 充能账非零形态+刷新存量加仓(attunement_charges/refresh_stock 键覆盖)
"attune_refresh_y": 2, # Y 池非零形态(attune_refresh_y 键覆盖)
"shop_mode": "pool", # 共享池货架面(shop_shelf/shop_visit_buys/shop_purchased 覆盖)
"relics_seed": ["twin_pick"], # 特性持有集+adventure_counters 非空形态(adv_twin_digs 计数器用户)
}
func _init() -> void:
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
print("[bp_baseline_dump] FAIL — 数值表加载失败")
quit(1)
return
var runs: Array = []
for seed_val in SEEDS:
var run := _drive_dump_run(db, int(seed_val))
runs.append({"seed": int(seed_val), "result": String(run["result"]), "dumps": run["dumps"]})
print("[bp_baseline_dump] seed=%d result=%s dumps=%d" % [int(seed_val), String(run["result"]), (run["dumps"] as Array).size()])
var out_path := OS.get_environment("BP_DUMP_OUT")
if out_path.is_empty():
out_path = "user://bp_baseline_dump.json"
var f := FileAccess.open(out_path, FileAccess.WRITE)
if f == null:
print("[bp_baseline_dump] FAIL — 输出文件打不开: %s" % out_path)
quit(1)
return
f.store_string(JSON.stringify({"generated_by": "bp_baseline_dump", "seeds": SEEDS, "runs": runs}, " "))
f.close()
print("[bp_baseline_dump] DONE — %s" % out_path)
quit(0)
# 单局驱动+停点 dump(返回 {result, dumps};dumps 元素 = {seq, tag, level_index, node_id, bp})
func _drive_dump_run(db, seed_val: int) -> Dictionary:
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(seed_val)
var adr = AdventureRunnerScript.new(db, rng)
var ctx := {"result": "", "dumps": [] as Array, "seq": 0, "adr": adr, "db": db}
# 信号直连(拆件批 T3——转发族退役):runner 信号在 level_started 重取点重连(同关守卫恰一次);
# 停点件信号面订 stop()(每冒险一次)
adr.level_started.connect(_on_dump_level_started.bind(ctx))
adr.stop().curio_resolved.connect(_on_curio_resolved.bind(ctx))
if not adr.start_adventure(seed_val, PARAMS):
ctx["result"] = "start_failed"
return ctx
var guard := 2000
while String(ctx["result"]) == "" and guard > 0:
guard -= 1
if not (adr.stop()._node_pending as Dictionary).is_empty():
_dump(ctx, "node_curio_pending")
var ncands: Array = (adr.stop()._node_pending.get("candidates", []) as Array)
var npick: Dictionary = Autoplay._pick_token(ncands, "off")
if not adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))):
ctx["result"] = "node_advance_failed"
continue
if not (adr.stop()._pending as Dictionary).is_empty():
_dump(ctx, "curio_pending")
var cands: Array = (adr.stop()._pending.get("candidates", []) as Array)
var pick: Dictionary = Autoplay._pick_token(cands, "off")
if not adr.pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("feat_id", ""))):
ctx["result"] = "advance_failed"
continue
if bool(adr._pending_next_level):
_dump(ctx, "resolved_wait_continue")
if not adr.continue_to_next_level():
ctx["result"] = "advance_failed"
continue
var runner = adr.runner()
if runner == null:
break # 冒险已收束或停点态未覆盖(防御)
if runner.awaiting_choice():
_dump(ctx, "route_choice")
var ids: Array = runner.next_candidate_ids()
if ids.is_empty():
ctx["result"] = "no_candidates"
break
if not runner.pick_next_node(int(ids[0])):
ctx["result"] = "route_failed"
continue
var settlement = runner.current_battle()
if settlement != null and String(settlement.battle_result) == "":
_drive_battle(ctx, runner, settlement)
continue
# 收束态分派:结算受理后(state=idle/settling)按 summary 定局;其余态无战无停点无选路 = 死循环防护
if String(ctx["result"]) == "":
var sum: Dictionary = adr.summary if adr.summary != null else {}
if not sum.is_empty():
ctx["result"] = String(sum.get("result", "settled"))
break
if String(adr.state) in ["idle", "settling"]:
ctx["result"] = "settled_no_summary"
break
if guard == 1:
ctx["result"] = "timeout"
return ctx
# 完美档战斗驱动(复用 sim 同款决策——上界策略;st 键集满足 _autoplay_battle 消费面)
func _drive_battle(ctx: Dictionary, runner, settlement) -> void:
var st := {"result": "", "rounds": 0, "refreshes": 0, "seal_placed": 0,
"policy": "perfect", "adv": false}
Autoplay._autoplay_battle(runner, settlement, st, null)
# 停点 dump(写侧取值面直调;_runner 为 null 的停点态节点域字段按缺省落档——与真机同构)
static func _dump(ctx: Dictionary, tag: String) -> void:
var adr = ctx["adr"]
var bp: Dictionary = adr._bp_parts()
var runner = adr.runner()
var dumps: Array = ctx["dumps"]
ctx["seq"] = int(ctx["seq"]) + 1
dumps.append({
"seq": int(ctx["seq"]),
"tag": String(tag),
"level_index": int(adr.level_index),
"node_id": int(runner.current_node_id()) if runner != null else -1,
"bp": bp,
})
# 每关开始(dump 直连面):重取 runner 重连 node_entered(同关守卫——level_started 每关恰一次发射)
static func _on_dump_level_started(_li: int, _dn: String, ctx: Dictionary) -> void:
var adr = ctx["adr"]
if String(ctx.get("wired_level", "")) == "%s|%d" % [str(adr.master_seed), int(adr.level_index)]:
return
ctx["wired_level"] = "%s|%d" % [str(adr.master_seed), int(adr.level_index)]
var r = adr.runner()
if r == null:
return
r.node_entered.connect(_on_node_entered.bind(ctx))
# 节点入口驱动(信号时点):入口 dump 覆盖节点域字段形态;rest/shop 直通继续,reward 呈现三选一
static func _on_node_entered(node: Dictionary, _info: Dictionary, ctx: Dictionary) -> void:
var adr = ctx["adr"]
var runner = adr.runner()
if runner == null:
return
var t := String(node.get("type", ""))
_dump(ctx, "node_" + t)
if t == "rest" or t == "shop":
runner.continue_after_node()
elif t == "reward":
var ndraw: Dictionary = adr.node_curio_candidates()
var ncands: Array = ndraw.get("candidates", []) as Array
if ncands.is_empty():
runner.continue_after_node() # 牌库空合法态直通(池耗即枯竭)
else:
adr.present_node_curio(ncands)
# 节点三选一兑付完成回图(context=reward_node → continue_after_node 回选路)
static func _on_curio_resolved(result: Dictionary, ctx: Dictionary) -> void:
if String(result.get("context", "")) != "reward_node":
return
var adr = ctx["adr"]
var runner = adr.runner()
if runner != null and not runner.continue_after_node():
ctx["result"] = "node_advance_failed"