From a2f72f9d9e22f60c0a67c039ae43e7659a929a9b Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 1 Oct 2026 01:06:49 +0800 Subject: [PATCH] =?UTF-8?q?iter9-numfix-b:=20=E6=A8=A1=E6=8B=9F=E6=A1=A3?= =?UTF-8?q?=E4=BD=8D=E9=87=8D=E6=9E=84=E2=80=94=E2=80=94=E6=99=AE=E9=80=9A?= =?UTF-8?q?=E6=A1=A3=20v3=3D=E5=AE=8C=E7=BE=8E+=E5=A4=B1=E8=AF=AF=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=C2=B7=E5=AE=8C=E7=BE=8E=E6=A1=A3=E4=BF=AE=E4=B8=8A?= =?UTF-8?q?=E7=95=8C=EF=BC=88=E7=94=A8=E6=88=B7=E6=8B=8D=E6=9D=BF=E3=80=8C?= =?UTF-8?q?=E6=99=AE=E9=80=9A=E6=A1=A3=E2=89=A0=E6=96=B0=E6=89=8B=E7=94=BB?= =?UTF-8?q?=E5=83=8F=E3=80=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 依据:真人实证推翻 v2 普通档——零加成真人首通三关踩 23 雷 vs 新手画像 L2 条件通关率 10.6% (尺子比真人弱 20 倍);完美档选路机械取最小编号+商店零购=不是元游戏上界(真人两店全购) - 普通档 v3:完美策略(subset 解算/标旗/卡死刷新)+受控失误注入——标雷误挖 NORMAL_MINE_SLIP_P 0.22(目标踩雷期望=完美+2/板,用户规格)+安全挖跳过 NORMAL_SKIP_DIG_P 0.05(每板少挖几个); 失误流=独立策略 RNG(局种子派生,逐局可回放;核心七流零消费零扰动) - 完美档上界修正:选路机械最小编号→目标价值评分(rest 伤重/shop 买得起/reward 占位/boss 缓冲, 同分 tie-break 沿 route 方向——verifier 双窗轴保持);商店零购→贪心买齐(GP 局内死钱) - 夹具保护:v3 行为只挂 run_adventure 测量面(st.adv 旗标)——run_level 单关机制夹具保持旧语义 (机械选路+novice 画像+商店零购),smoke 断言零扰动 - novice_action 留库不删(历史读数对账面);轻闸=冒烟 596/0 全绿(夹具零漂移实证) --- game/smoke/level_autoplay.gd | 142 ++++++++++++++++++++++++++++++++--- 1 file changed, 130 insertions(+), 12 deletions(-) diff --git a/game/smoke/level_autoplay.gd b/game/smoke/level_autoplay.gd index 5c2ead70..e7d4d76b 100644 --- a/game/smoke/level_autoplay.gd +++ b/game/smoke/level_autoplay.gd @@ -18,12 +18,27 @@ # 大奖励立即确认 continue_to_next_level、adventure_finished 置 result/summary;外层每关重取 # runner 实例经 _drive_level 复用单关驱动)。run_level 驱动段抽成 _drive_level——签名与 # 返回结构零变化,行为逐位不变。 +# 2026-10-01 模拟档位重构(用户拍板「普通档≠新手画像」——普通档 v2 被真人实证推翻:零加成真人 +# 首通三关踩 23 雷 vs 新手画像 L2 条件通关率 10.6%,尺子比真人弱 20 倍): +# ①普通档 v3 = 完美策略 + 受控失误注入(标雷误挖 NORMAL_MINE_SLIP_P / 安全挖跳过 +# NORMAL_SKIP_DIG_P;失误流走独立策略 RNG——种子派生自局种子,逐局确定性/可回放保持, +# 核心七流零消费零扰动);v2「新手级单步推理+盲挖兜底」口径退役(novice_action 留库不删—— +# 历史读数对账面)。 +# ②完美档修上界:选路从机械最小编号改目标价值评分(rest 伤重优先/shop 有钱优先/boss 缓冲), +# 商店从零购离开改贪心买齐(GP 局内死钱——不购物=玩的不是同一个游戏,真人实证两店全购)。 +# ③route 轴保持:评分同分 tie-break 仍按 min/max 编号方向——verifier 双窗轴不塌。 # 运行前提:db 已 load_tables;run_level/run_adventure 均须全新 RngService(同种子 ⇒ 全程逐步可回放)。 # 实现注:信号处理体全部为静态方法 + bind 传状态——GDScript 多行 lambda 内嵌 match/控制流的解析不可靠。 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 +# 安全挖跳过率:每板安全格 ~70-90 × p ≈ 3-5 次漏挖(信息与推进双损失) +const NORMAL_SKIP_DIG_P := 0.05 + 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: var own_session := logger == null @@ -42,6 +57,7 @@ static func run_level(db, seed_val: int, level_index: int, atk_pct: int, hp_pct: "boss_depth": -1, "hp_at_boss": -1, "rows": [], "timeout": false, "log_path": "", "policy": policy, "refreshes": 0, "mine_hits": 0, "visited_ids": [], "choices": 0, "last_battle_enemies": [], + "prng": _policy_rng(seed_val), "mine_slips": 0, "skip_digs": 0, "shop_buys": 0, } runner.step_row.connect(_on_step_row.bind(runner, st, collect_rows)) runner.node_entered.connect(_on_node_entered.bind(runner, st, logger)) @@ -80,7 +96,7 @@ static func _drive_level(runner, st: Dictionary, logger, policy: String, route: # 选路态(入口停点 / 兑付确认 / 战斗胜利后的零自动推进停点)——route 策略显式选下一跳 if runner.node_index >= 0: st.choices = int(st.choices) + 1 - runner.pick_next_node(_route_pick(runner, route)) + runner.pick_next_node(_route_pick(runner, route, st)) continue guard_battles -= 1 var this_settlement = runner.current_battle() @@ -118,6 +134,8 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_ "visited_ids": [], "choices": 0, "last_battle_enemies": [], "summary": {}, "wallet_events": [], "encounter_max_hp_seq": [], "reward_events": [], "shop_gp_seq": [], # 商店房到达余额序(逐关 1 次——GP 跨关延续验收取数面) + "prng": _policy_rng(seed_val), "mine_slips": 0, "skip_digs": 0, "shop_buys": 0, + "adv": true, # v3 档位行为开关(选路评分/失误注入/商店购买只挂冒险测量面——单关夹具保持旧语义零扰动) "level_templates": [], # 逐关图模板名(关卡节点图生成 v0.4 分层出数) } @@ -175,7 +193,8 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr, r.continue_after_node() elif t == "shop": st.shop_gp_seq.append(int(info.get("gp", -1))) - r.continue_after_node() # 商店房零购离开(自动路径直通 Boss——购买行为归冒烟专项/真人试玩) + _autoplay_shop(r, st) # 贪心买齐(2026-10-01——旧零购离开退役:GP 局内死钱,真人实证两店全购) + r.continue_after_node() if logger != null: logger.log_node(node, info, r.node_index) @@ -195,6 +214,28 @@ static func _on_adventure_reward(reward: Dictionary, st: Dictionary, adr) -> voi st.result = "advance_failed" +# 商店贪心买齐(2026-10-01 模拟档位重构):GP 局内死钱——买得起就买( affordable 集里挑最便宜的 +# 反复买到达不上任何商品为止,guard 防死循环)。确定性(零随机流);购买出账/加算区入账全走 +# runner.buy_product 核心编排(与呈现层购买同一出账路径,wallet_changed 信号照常采集)。 +static func _autoplay_shop(r, st: Dictionary) -> void: + var guard := 30 + while guard > 0: + guard -= 1 + var affordable: Array = [] + for p in r.shop_products(): + if r.gp >= int(p.get("price", 1 << 30)): + affordable.append(p) + if affordable.is_empty(): + return + var pick: Dictionary = affordable[0] + for p in affordable: + if int(p.get("price", 1 << 30)) < int(pick.get("price", 1 << 30)): + pick = p + if not r.buy_product(String(pick.get("product_id", ""))): + return + st.shop_buys = int(st.get("shop_buys", 0)) + 1 + + # 冒险终局(completed/failed/abandoned——result 载 summary.result,外层驱动循环随之收敛) static func _on_adventure_finished(summary: Dictionary, st: Dictionary, logger) -> void: st.summary = summary.duplicate(true) @@ -203,14 +244,58 @@ static func _on_adventure_finished(summary: Dictionary, st: Dictionary, logger) logger.log_level_end(summary) -# 路径轴策略(确定性——候选编号最小/最大;min 侧 = 岔路恒取小编号支)。 -# 编号取 runner.next_candidate_ids()(编号空间解析单一出口——单链下标 / 图 node_depth) -static func _route_pick(runner, route: String) -> int: +# 策略流 RNG(2026-10-01 模拟档位重构):种子派生自局种子 × 黄金比率常数 + 偏移——与核心统一流 +# (rng_service 单盐派生)零共享零扰动;逐局确定性保持(同种子 ⇒ 同决策序列 ⇒ 同回放) +static func _policy_rng(seed_val: int) -> RandomNumberGenerator: + var prng := RandomNumberGenerator.new() + prng.seed = seed_val * 2654435761 + 912931 + return prng + + +# 路径轴选路(2026-10-01 目标价值评分制——机械最小编号退役):候选按节点类型评分取最高, +# 同分 tie-break 沿 route 方向(min=小编号/max=大编号——verifier 双窗轴保持)。 +# 评分语义(确定性,零随机流):rest=伤重高值(缺口比例×100)/shop=买得起高值(GP 局内死钱, +# 真人实证两店全购)/reward=占位低值(MC 夹具解锁空集=零兑付直通)/boss=缓冲低值(有店有_rest +# 先补给再打)/battle=基线。编号取 runner.next_candidate_ids()(编号空间解析单一出口)。 +static func _route_pick(runner, route: String, st: Dictionary) -> int: var ids: Array = runner.next_candidate_ids() if ids.is_empty(): return -1 - ids.sort() - return ids[0] if route != "max" else ids[ids.size() - 1] + if not bool(st.get("adv", false)): # 单关机制夹具:旧机械选路保持(smoke 断言零扰动) + ids.sort() + return int(ids[0]) if route != "max" else int(ids[ids.size() - 1]) + var nodes: Array = runner.next_candidates() + var scored: Array = [] + for i in nodes.size(): + var node: Dictionary = nodes[i] + var t := String(node.get("type", "")) + var s := 30.0 + if t == "rest": + var max_hp := float(runner.effective_max_hp()) + s = 5.0 + (1.0 - float(runner.hero_hp) / maxf(max_hp, 1.0)) * 100.0 # 满血 rest=5 低于 battle(白 rest 不去);伤重可到 105 + elif t == "shop": + s = 10.0 + var cheapest := 1 << 30 + for p in runner.shop_products(): + cheapest = mini(cheapest, int(p.get("price", 1 << 30))) + if cheapest < (1 << 30) and runner.gp >= cheapest: + s = 80.0 + elif t == "reward": + s = 5.0 + elif t == "boss": + s = 20.0 + scored.append({"id": int(ids[i]), "s": s}) + var best: Dictionary = scored[0] + for i in range(1, scored.size()): + var c: Dictionary = scored[i] + var better: bool + if absf(c.s - best.s) > 0.0001: + better = c.s > best.s + else: + better = (c.id < best.id) if route != "max" else (c.id > best.id) + if better: + best = c + return int(best.id) static func _on_step_row(row: Dictionary, _runner, st: Dictionary, collect_rows: bool) -> void: @@ -255,7 +340,8 @@ static func _on_node_entered(node: Dictionary, info: Dictionary, runner, st: Dic st.hp_before_rest = int(info.get("hp", 0)) - st.rest_healed runner.continue_after_node() elif t == "shop": - runner.continue_after_node() # 商店房零购离开(GP+商店房批——自动路径直通 Boss;购买行为归冒烟商店专项断言/真人试玩) + runner.continue_after_node() # 单关机制夹具(run_level)零购离开保持——smoke 商店/战斗断言面 + # 不掺购买行为;冒险口径(run_adventure)已在 _on_adventure_node_entered 改贪心买齐 if logger != null: logger.log_node(node, info, runner.node_index) @@ -274,9 +360,10 @@ static func _autoplay_battle(runner, settlement, st: Dictionary, logger) -> void if String(st.get("policy", "normal")) == "perfect": _autoplay_battle_perfect(runner, settlement, st, logger) return - # 普通策略档 v2(2026-09-19 用户拍板):基础定式画像——单步决策循环(move/dig/flag 经 settle_batch 提交, - # 推理分级见 auto_player.gd 普通策略档节:新手级单数字满足规则 + 盲挖边界兜底、无 refresh); - # 旧"无推理挖最近格"口径随批退役。连续拒绝 ≥3 = 编排违约防御(同完美档 fail-fast 语义)。 + # 普通策略档 v3(2026-10-01 用户重定义):完美策略 + 受控失误注入——v2「新手级单步推理+盲挖 + # 兜底」画像被真人实证推翻(零加成真人首通三关踩 23 雷 vs v2 普通档 L2 条件通关率 10.6%)。 + # v3 语义 = 会玩的普通玩家:推理与完美档同源(subset 解算/标旗/卡死刷新),失误只注入两手—— + # 标雷误挖(NORMAL_MINE_SLIP_P)+ 安全挖跳过(NORMAL_SKIP_DIG_P)。连续拒绝 ≥3 = 编排违约防御。 var round_guard := 60 while st.result == "" and runner.current_battle() == settlement and settlement.battle_result == "" and round_guard > 0: round_guard -= 1 @@ -287,7 +374,11 @@ static func _autoplay_battle(runner, settlement, st: Dictionary, logger) -> void while runner.current_battle() == settlement and settlement.battle_result == "" and settlement.action_economy.phase == "Active" \ and settlement.round_index == start_round and action_guard > 0 and rejects < 3: action_guard -= 1 - var act := AutoPlayer.novice_action(settlement.minesweeper) + var act: Dictionary + if bool(st.get("adv", false)): + act = _apply_normal_slips(AutoPlayer.perfect_action(settlement.minesweeper, settlement), st) # v3:完美+失误 + else: + act = AutoPlayer.novice_action(settlement.minesweeper) # 单关机制夹具:v2 新手画像保持 var kind := String(act.get("kind", "stop")) if kind == "stop": break @@ -298,7 +389,13 @@ static func _autoplay_battle(runner, settlement, st: Dictionary, logger) -> void ok = _submit_intent(settlement, "dig", act.get("cell"), logger, start_round) elif kind == "flag": ok = _submit_intent(settlement, "flag_place", act.get("cell"), logger, start_round) + elif kind == "refresh": + ok = bool(settlement.player_request_refresh()) + if logger != null: + logger.log_action("refresh", Vector2i(-1, -1), ok, {"round": start_round}) if ok: + if kind == "refresh": + st.refreshes = int(st.refreshes) + 1 rejects = 0 else: rejects += 1 @@ -308,9 +405,30 @@ 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 用户规格):只在完美策略的两类动作上出手—— +# ①标雷误挖:把「标旗已证雷」动作直接转成「挖该格」(真人手滑原型:知道是雷、位置点错, +# 踩雷吃伤且该雷奖励报废);期望踩雷增量 ≈ 每板已证雷数 × NORMAL_MINE_SLIP_P(目标完美+2/板) +# ②安全挖跳过:把「挖已证安全格」动作转成本轮停手(少挖几个正确的雷——信息与推进双损失) +# 失误流 = st.prng(策略流 RNG,种子派生自局种子——同种子逐位可回放;核心七流零消费零扰动) +static func _apply_normal_slips(act: Dictionary, st: Dictionary) -> Dictionary: + var prng: RandomNumberGenerator = st.get("prng") + if prng == null: + return act + var kind := String(act.get("kind", "stop")) + 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"} + return act + + # 完美策略档战斗驱动(2026-09-14):单步决策循环——move/dig/flag 经 排雷玩法 settle_batch 提交, # refresh 经 settlement.player_request_refresh(战斗结算 核心编排:行动经济 扣 RAP + 退役 + 当场换新板), # stop 出内环走统一停手提交。连续拒绝 ≥3 = 编排违约防御(fail-fast 语义,停手防死循环)。 +# 2026-10-01 上界修正:选路改目标价值评分(_route_pick)+ 商店贪心买齐(_autoplay_shop)—— +# 「完美」须是元游戏意义上的上界(会购物会补给),不再只是扫雷子游戏上界。 static func _autoplay_battle_perfect(runner, settlement, st: Dictionary, logger) -> void: var round_guard := 60 while st.result == "" and runner.current_battle() == settlement and settlement.battle_result == "" and round_guard > 0: