Files
gplan-2/game/sim/calibrate_boards.gd

108 lines
4.0 KiB
GDScript
Raw Permalink 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
# 棋盘口径校准(分析会话取数工具——冒险零加成完成度分母实证):
# 逐 board_gen 行真实生成 N 板,统计 实际雷数/安全格数/R₀ 初始展开占比——
# 生成期「先布后删+可推理率重试」使实际雷数远低于表口径 round(d×(W×H−9)),
# 日志侧只有清图板可反推 safe_total(未清图深度无样本),本工具给全网格精确分母。
# 运行:Godot_console.exe --headless --path . --script res://sim/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:
class _FakeBoardGen:
var row: Dictionary
var spawn_edge: int = 0 # 表头成员(generate 直索引面——真值由 _initialize 逐行注入)
func resolve_row(_L: int, _D: int) -> Dictionary:
return row
class _FakeCombat:
var board_gen := _FakeBoardGen.new()
var combat := _FakeCombat.new()
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,r0_sd,r0_p90,r0_max,inf_mean,cap_rej_mean")
for row in db.combat.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),
# 初始展开收窄两参数透传(.get 缺省与生成器防御口径一致)
"shave_ring_radius": int(row.get("shave_ring_radius", 1)),
"r0_max_cap": int(row.get("r0_max_cap", 0)),
"spawn_edge": int(db.combat.board_gen.spawn_edge), # 出生区边长透传(读真表头、零字面量——组E FakeDB 模式不动)
}
var fdb := FakeDB.new()
fdb.row = rd
fdb.combat.board_gen.spawn_edge = int(db.combat.board_gen.spawn_edge) # 表头注入(零字面量——读真表头)
var mines: Array = []
var safes: Array = []
var r0s: Array = []
var infs: Array = []
var cap_rejs: 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))
cap_rejs.append(int(b.metrics.get("r0_cap_rejected", 0)))
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,%.2f,%d,%d,%.3f,%.2f" % [
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),
_sd(r0s), _p90(r0s), _max(r0s), _mean(infs), _mean(cap_rejs)])
quit(0)
func _mean(a: Array) -> float:
var t := 0.0
for v in a:
t += float(v)
return t / float(a.size())
func _sd(a: Array) -> float:
var m := _mean(a)
var t := 0.0
for v in a:
t += (float(v) - m) * (float(v) - m)
return sqrt(t / float(a.size()))
func _p90(a: Array) -> int:
var b := a.duplicate()
b.sort()
return int(b[mini(a.size() - 1, int(ceil(a.size() * 0.9)) - 1)])
func _max(a: Array) -> int:
var b := a.duplicate()
b.sort()
return int(b[b.size() - 1])
func _med(a: Array) -> int:
var b := a.duplicate()
b.sort()
return int(b[b.size() / 2])