iter9-numfix-c: 普通档 v3.2-v3.4 失误模型校准(卡死赌挖+概率启发+空闲早赌)

- v3.2:卡死赌挖替代刷新/停手(用户实证口径「看不到数字也盲挖」;普通档不刷新=用户自报全程
  未用刷新);choose_dig 行主序=概率最差格,实证 199/200 死第二战——退役
- gamble_pick(auto_player):概率启发选格=对邻接已证数字约束取(余雷/未知)比最小值——真人
  「挑看起来最安全的一格」单步近似;纯查询面零 RNG 确定性保回放
- v3.3:标雷误挖 0.22→0.10(22% 全旗误挖=保底命中非真人失误形态,普通档 0/100 全灭实证)
- v3.4:空闲提前赌挖 0.10(完美系把不确定性攒到残局 hazard→1=扫雷;真人早赌滚信息)+
  _gamble_action 组装抽出
- 读数轨迹(N=100,种子 910003 起,route min):v3.3=0%(L1 2%)/v3.4=0%(L1 2%)——
  死因改判:非失误率而是 L1 四连战无治疗夹缝(雷伤+垫刀双压),表值刀归调参批
This commit is contained in:
DKChan committed 2026-10-01 02:42:33 +08:00
1 parent a2f72f9d9e
commit cc2d75d396
2 files changed
+78 -8

No files matched your search

+42
View File
@@ -208,3 +208,45 @@ static func _row_major(cells: Dictionary) -> Array:
var out: Array = cells.keys()
out.sort_custom(func(a, b): return a.y * 1000 + a.x < b.y * 1000 + b.x)
return out
# 卡死赌挖选格(普通档 v3.1 2026-10-01——模拟档位重构):概率启发近似真人「挑看起来最安全的一格」。
# 危险度 = 该格对每个邻接已证数字约束取(余雷数/未知数)比值中的最小值(= 任一邻数字视角下的
# 最低雷占比——single 已证安全格在进入本函数前已被 perfect_action 消化,故卡死态比值恒 >0);
# 同危险度行主序先到先得。纯查询面零 RNG(确定性保回放);寻路同 choose_dig(目标已开 8 邻
# 首个可达者为 hero_cell)。
static func gamble_pick(minesweeper) -> Dictionary:
var cons := _constraints(minesweeper, minesweeper.current_board(), {})
var best_h := 2.0
var best := Vector2i(-1, -1)
var revealed: Array = minesweeper.revealed_cells()
var revealed_set := {}
for c in revealed:
revealed_set[c] = true
var board = minesweeper.current_board()
for y in board.height:
for x in board.width:
var c := Vector2i(x, y)
if minesweeper.is_revealed(c) or minesweeper.has_flag(c):
continue
var adj := false
for n in board.neighbors8(c):
if revealed_set.has(n):
adj = true
break
if not adj:
continue
var h := 1.0
for con in cons:
if (con["unknown"] as Dictionary).has(c):
var ratio := float(con["mines"]) / float((con["unknown"] as Dictionary).size())
h = minf(h, ratio)
if h < best_h - 0.0001:
best_h = h
best = c
if best.x < 0:
return {}
for n in board.neighbors8(best):
if minesweeper.is_revealed(n) and (n == minesweeper.hero_pos() or not minesweeper.compute_move_path(n).is_empty()):
return {"dig_cell": best, "hero_cell": n}
return {}
+36 -8
View File
@@ -34,10 +34,14 @@ extends RefCounted
const AutoPlayer := preload("res://smoke/auto_player.gd") # O4 收口:自动玩家策略单一实现
# ── 普通档 v3 失误注入参数(2026-10-01 用户规格:踩雷期望=完美+2/板、每板少挖几个正确格)──
# 标雷→误挖转化率:每板已证雷 ~9-14 颗 × p ≈ 2-3 次踩雷(目标完美+2/板±0.3——首测读数后校准此值)
const NORMAL_MINE_SLIP_P := 0.22
# 标雷→误挖转化率:v3.2 实证 0.22 时普通档 0/100 全灭(首轮大量已证旗=保底命中,非真人失误
# 形态——真人几乎不挖自己插旗的格);降 0.10 = 偶发真手滑,主踩雷源=卡死赌挖(不确定性概率命中)
const NORMAL_MINE_SLIP_P := 0.10
# 安全挖跳过率:每板安全格 ~70-90 × p ≈ 3-5 次漏挖(信息与推进双损失)
const NORMAL_SKIP_DIG_P := 0.05
# 空闲提前赌挖率(v3.4):已证安全格在手时小概率先赌一格——完美系把不确定性攒到残局
# (残局 hazard→1=扫雷),真人早赌滚信息(低 hazard 期吃掉不确定性);p×~40 安全 AP/板 ≈ 4 次
const NORMAL_IDLE_GAMBLE_P := 0.10
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", template_pick: int = -1) -> Dictionary:
@@ -376,7 +380,7 @@ static func _autoplay_battle(runner, settlement, st: Dictionary, logger) -> void
action_guard -= 1
var act: Dictionary
if bool(st.get("adv", false)):
act = _apply_normal_slips(AutoPlayer.perfect_action(settlement.minesweeper, settlement), st) # v3:完美+失误
act = _apply_normal_slips(AutoPlayer.perfect_action(settlement.minesweeper, settlement), settlement, st) # v3:完美+失误
else:
act = AutoPlayer.novice_action(settlement.minesweeper) # 单关机制夹具:v2 新手画像保持
var kind := String(act.get("kind", "stop"))
@@ -405,12 +409,15 @@ static func _autoplay_battle(runner, settlement, st: Dictionary, logger) -> void
logger.log_action("stop", Vector2i(-1, -1), _accepted(out_s), {"round": start_round})
# 普通档 v3 失误注入(2026-10-01 用户规格):只在完美策略的两类动作上出手——
# 普通档 v3 失误注入(2026-10-01 用户规格):三类——
# ①标雷误挖:把「标旗已证雷」动作直接转成「挖该格」(真人手滑原型:知道是雷、位置点错,
# 踩雷吃伤且该雷奖励报废);期望踩雷增量 ≈ 每板已证雷数 × NORMAL_MINE_SLIP_P(目标完美+2/板)
# ②安全挖跳过:把「挖已证安全格」动作转成本轮停手(少挖几个正确的雷——信息与推进双损失)
# ③卡死赌挖:完美档无已证动作时刷新/停手拖轮次吃敌方垫刀(实证=完美档 41% 通关全败于慢玩
# 垫刀非踩雷)——真人会按概率赌一个边界格(用户实证口径「有些地方因为我看不到数字,也是盲挖
# 的」);普通档不刷新(用户自报全程没用刷新),赌挖替代刷新/停手
# 失误流 = st.prng(策略流 RNG,种子派生自局种子——同种子逐位可回放;核心七流零消费零扰动)
static func _apply_normal_slips(act: Dictionary, st: Dictionary) -> Dictionary:
static func _apply_normal_slips(act: Dictionary, settlement, st: Dictionary) -> Dictionary:
var prng: RandomNumberGenerator = st.get("prng")
if prng == null:
return act
@@ -418,12 +425,33 @@ static func _apply_normal_slips(act: Dictionary, st: Dictionary) -> Dictionary:
if kind == "flag" and prng.randf() < NORMAL_MINE_SLIP_P:
st.mine_slips = int(st.get("mine_slips", 0)) + 1
return {"kind": "dig", "cell": act.get("cell")}
if kind == "dig" and prng.randf() < NORMAL_SKIP_DIG_P:
st.skip_digs = int(st.get("skip_digs", 0)) + 1
return {"kind": "stop"}
if kind == "dig":
var roll := prng.randf()
if roll < NORMAL_SKIP_DIG_P:
st.skip_digs = int(st.get("skip_digs", 0)) + 1
return {"kind": "stop"}
if roll < NORMAL_SKIP_DIG_P + NORMAL_IDLE_GAMBLE_P:
var idle := AutoPlayer.gamble_pick(settlement.minesweeper)
if not idle.is_empty():
return _gamble_action(idle, settlement)
if kind == "refresh" or kind == "stop":
var plan := AutoPlayer.gamble_pick(settlement.minesweeper) # v3.2:概率启发赌挖(choose_dig 行主序=概率最差格,实证 199/200 死第二战——退役)
if not plan.is_empty():
return _gamble_action(plan, settlement)
return act
# 赌挖动作组装(v3.4 抽出):目标邻接=直接挖,否则先移到目标的已开邻格(下一拍再挖)
static func _gamble_action(plan: Dictionary, settlement) -> Dictionary:
var ms = settlement.minesweeper
var hero: Vector2i = ms.hero_pos()
var target: Vector2i = plan.dig_cell
var d := Vector2i(absi(target.x - hero.x), absi(target.y - hero.y))
if maxi(d.x, d.y) == 1:
return {"kind": "dig", "cell": target}
return {"kind": "move", "cell": plan.hero_cell}
# 完美策略档战斗驱动(2026-09-14):单步决策循环——move/dig/flag 经 排雷玩法 settle_batch 提交,
# refresh 经 settlement.player_request_refresh(战斗结算 核心编排:行动经济 扣 RAP + 退役 + 当场换新板),
# stop 出内环走统一停手提交。连续拒绝 ≥3 = 编排违约防御(fail-fast 语义,停手防死循环)。