# Validation ## 0.2.1 / protocol 4 acceptance This refactor has offline controller/adapter/transaction regression evidence. Historical gameplay below belongs to earlier implementations and is not evidence of new-version speed or win rate. Fresh live gameplay has not been resumed during this refactor. The state envelope reports the installed core version and protocol. # 验证记录 此文件区分实际游戏、离线测试与尚未验证的能力。正在进行的整局试玩不会提前记为通过。 ## 核心决策路径重构(仅离线,follow-up) - 精确推演只覆盖完整白名单:纯数值伤害/格挡、已知费用、无未建模状态/遗物。玩家触发器、X 费、空白 Attack 意图均为 unknown,不是 0。 - 本地斩杀/保命走同一套 `projectPlay` 证明,不用正则命中代替。 - 真实桥接 run 只有 act/floor/ascension。持久策略仍要求 run identity;无 identity 时只能把策略作为当次 `battle` 参数,绑定 `expectedStateId`,调用结束即失效。 - 策略只约束菜单,不代选牌。未匹配则交回。 - 候选包含全部单步(含结束回合与未知牌)和有上限的已证明前缀。`max_steps` 限制前缀长度。 - 单步与多卡共用 `dispatch` 事务。发送出错保留停止。协议号 4。 - 默认置信度 0.5。失败请求会计数;失败用量标为 unavailable。 ## 核心决策路径重构(仅离线) - 已知效果与未知效果共用一套战斗算术:格挡先于生命;8 HP + 3 格挡不会被 6 点伤害算成斩杀。 - 单步动作与多卡前缀走同一套已验证发送:两卡候选为一次模型请求、两次核对后的发送;预测偏离会拦住第二次发送并保持停止。 - 成功发送后可见状态不变视为未知结果:停止标记保留,不能重发。任何发送错误都不能仅凭错误文字证明无副作用;发送后异常保留停止。 - 未匹配的策略不会在仍有可打牌时默认结束回合;缺少 run identity 或未知失效条件时策略不生效。 - 升级牌的英文效果用当前牌面数值,不再沿用种子里的固定 6 点。 - 上述均有离线回归。真实对局兼容由后续有界运行时检查证明;离线通过不等于实战通过。 ## 基线实战(原型) - macOS arm64,Slay the Spire 2 v0.107.1。 - 铁甲战士,第一幕第 3 层,3 只史莱姆,5 回合获胜。 - 11 手牌:Jev 自动执行 8 手,主模型接管 3 手,另有 4 次程序结束回合。 - Jev 共 12 次调用(包含 dry-run 与暂停);28,060 输入、583 输出 token。 - 6 次同进程后续推理中位约 0.365 秒;各进程首个请求约 1.0–1.36 秒。 - 8 次自动出牌从决策开始到确认,中位约 1.40 秒;结束回合需约 3.6–4.7 秒。 - 从第一手确认到胜利约 2 分 20 秒,包含检查和主模型接管时间。 - 奖励状态与原生游戏画面一致,未把一次 HTTP 成功当作完成。 这些是一次小样本,并非通关率或泛化性能。 ## 独立项目 - 二十七项离线测试已通过:合法选项、相同牌合并、药水目标、分工、旧状态、单写锁、未知动作结果停止、单战斗边界、模型输出校验、选牌弹窗、多卡计划及延迟伤害。 - 用固定上游版本从全新下载目录编译,零警告零错误。 - 支持完整战斗状态读取、出牌、结束回合和奖励等菜单操作;实际覆盖将随整局试验更新。 - **本机 DeepSeek Harness(DSH)已实测通过**:沿用已有配置,官方 DeepSeek provider 真实调用 `state` 观察战斗;在移交写入权后,真实调用 `act` 从攻击药水选择头槌,读回头槌以零费进入手牌。会话记录包含实际 `tool/call` / `tool/result`,并由主控再次读取游戏验证。没有依赖图像或鼠标。 - 前期误用 Reasonix 的认证/额度失败不代表 DSH 不可用;那条路线已停止,不是本项目的默认接入方式。 - 实战发现仅等待“手牌已移除”可能早于伤害结算。新的桥接补丁加入动作执行器空闲与队列清空条件,已编译、在第9层商店安全重启并续同局读回。第11层精英战真实执行两卡计划“防御→突破”,两步分别约584ms、777ms;能量2→0、格挡0→5、自HP63→62、敌HP73→62均吻合,队列空闲字段读回成功。两张牌之间没有模型调用。多卡计划等待预期伤害/格挡/意图等全部满足,不把移牌当完成。 ## 操作者偏差(本轮实测的失败面) 接管后有相当一段推进是**主模型手动逐张出牌**,而不是让 Jev 主导。这不是设计意图,记录在此以免被当作已完成的自动化: - 该时段事件来源统计为 planner 21 张 / Jev 0 张 / 程序局部 0 张。 - 原因是当时 CLI 单步接口过于笨重,加上 `resolve` 规则写得过窄(只认"数值明确的攻击牌"),遇到纯防御/抽牌回合就直接交回主模型。 - 随后补的规则(`fill` 自足格挡或抽牌、已行动回合的安全收尾)修掉了这个缺口,但**这些新规则只经过离线测试**,尚未在真实对局中复验。 - 低置信度占 Jev 调用的 49%(90/182),其中一部分属于"模型看不到虚无/回合结束"的信息缺口,而不是真正的策略不确定。 结论:本轮不能宣称"常规战斗已由 Jev 主导"。下一步应以 Jev 优先的循环开新局并重新统计来源比例。 ## 新局入口与"程序主导"复验 - 实现并实测了受支持的开新局路径:`game_over → main_menu → singleplayer → standard → character_select → embark`。角色选择界面暴露 `IRONCLAD`/`SILENT` 与 `confirm`/`embark`/`back`;`abandon_run`、`quit`、`multiplayer`、`daily`、`custom` 不进入动作列表。 - 开新局时误触发过一次停止标记:点选角色时 state_id 未变化,`settled` 判定"没有稳定跃迁"。核实后发现是**假警报**(角色本来就已选中),按接口 `clear-halt` 清除,未重放动作。 - 新增防守:奖励界面把 `proceed` 放到最后并标注"放弃剩余奖励",避免再次出现"直接离开而漏领奖励"。此前确实漏领过一次第 1 层奖励。 - 新规则下的第一场战斗(第 1 层,毛绒伏地虫):**12 步 16 秒结束,程序决定全部动作,Jev 调用 0 次、主模型 0 次**。 - 新局前 17 个回合的来源统计:程序 `local` 80、程序确定动作 24、Jev 2、主模型(我的手动干预)38;`local` 明细为 resolve 34、kill 3、guard 2;无停止、无预测偏离。 - 回合指标:`turn_ms` 中位 7.9s、`action_ms`(动画结算)中位 4.9s、`agent_gap_ms` 中位 0ms。对照上一局的 36.6s / 7.1s / 18.7s,模型往返基本消失,剩余耗时主要是动画。 - 仍需注意:`cards` 统计在新局明显偏低(72 个动作只记到 4 张牌),说明按预期状态匹配的套牌计数在程序连续出牌下并不准确,不能用来代替来源统计。 ## 短计划与机械推进(protocol 3 窗口) - `SPIRE_CANDIDATES=1` 已在真实战斗跑通:同一回合内程序先本地收尾、再走候选决策(`planned=true`),单场 8 步推进、我方 80→34、无预测偏离。 - 修掉三个真实缺陷: 1. **候选路径绕过置信度门槛**:模型给 0.01 置信度、且它自己的独立判断自相矛盾(选斩杀线却给 safe_c0=0.31 / safe_c1=0.66),程序仍直接采用。现在候选答案同样受门槛约束。 2. **非致命回合不该问"是否安全"**:进伤 12、满血 80 时程序本就能证明每条线都能活。现在只有程序算出该回合会死的候选才附带安全判断。 3. **上一局的策略污染新局**:旧 `.runtime/strategy.json` 仍在生效,把候选强行导向"打击"。策略现在绑定创建时的楼层,低于当前楼层即判定为上一局并清除。 - 阈值按风险分级(官方 guidance):致命回合/未知信息用 0.5,程序已证明全部候选可存活的低风险回合用 0.25;安全从不依赖这个数字,survival 约束与合法性始终在代码里。 - 低风险且模型仍不确定时,追加一次**二选一风格问题**(进攻线 / 防守线 / 不确定),让模型能明确说"不清楚"而不是被迫猜。 - `advance` 机械推进实测:在事件处正确停下,未代选战略。 ## 整局结果(第 2、3 局,均已终局) - 第 2 局:铁甲战士,**第 1 幕第 11 层阵亡**于雾菇战(`ENCOUNTER.FOGMOG_NORMAL`,1345 秒,未放弃)。死因是进入该战时仅 24/80 血,策略续行仍执行了 12 步。 - 第 3 局:铁甲战士,**第 1 幕第 6 层阵亡**于毛绒伏地虫(`ENCOUNTER.FUZZY_WURM_CRAWLER_WEAK`,584 秒,未放弃)。21/80 血进入长血怪消耗战,策略续行 38 步后耗尽。 - 三局全部未通关。结论:当前策略在**低血量进入长消耗战**时不足;这是策略问题,不是执行机制问题(机制侧动作全部合法、无重放、无预测偏离)。 ## 策略续行与护栏(实测) - 策略续行已在两局中反复生效:单场最长 **38 步连续执行**、期间 0 次模型调用;`local_decision kind=strategy` 累计 62 次(第 3 局窗口)。 - 低血量护栏改为"同一血量档 + 同一敌人集合只提示一次",并新增 `guard_repeat` 事件;重复时**明确交回并说明需要显式策略**,不再逐牌打断、也不静默空转。 - 药水审查同样标记为护栏,可被策略续行:否则药水位会变成逐回合接管来源。 - 修正:`--protocol` 参数双自增导致窗口过滤失效;`find` 局部变量遮蔽数组方法;批次协议标签改为按多数票。 - `act` 现在记录决策者标签(`caller`/`agent`/`plan`),因此"调用方自己推进"不再被算作主模型接管。 ## 第 4 局(人工接管,不用快模型) - 结果:铁甲战士,**第 2 幕第 24 层阵亡**于感染棱柱精英(`ENCOUNTER.INFESTED_PRISMS_ELITE`,2196 秒,未放弃)。 - 首次**击杀第 1 幕首领**(仪式兽 252HP):8HP 时用完美打击 23 伤斩杀 17HP 的它,抢在它 18 伤攻击之前。 - 全部操作由调用方逐张决定,快模型参与 0 次;每一步都经 `act`/`seq` 的合法性与结算校验。 - 死因是**资源耗尽**而非机制缺陷:25/80 血、0 药水进入第 2 幕精英,该精英 161HP 且会给玩家的牌附加"污染"。 - 局内被验证的机制(实测,非理论):打击木偶(名字含"打击"的牌 +3 伤,基础打击 6→9);苦无(每回合 3 张攻击牌 +1 敏捷,防御 5→6);地狱狂徒(抽到"打击"类牌自动对随机敌人打出);开信刀(每回合 3 张技能牌 → 全体 5 伤);毒素(回合结束仍在手牌则受 5 伤,需花 1 能量清掉);昏眩(手牌全体 `BlockedByHook`,整回合不能出牌);振翅(按数值减伤,32 伤实际打出 15);逃脱大师(倒计时结束敌人逃跑);顺走(偷牌,胜利后可取回)。 - 关键教训:**进精英前的血量与药水储备决定成败**,不是战斗内的操作。第 2 幕两次精英战都是低血进入。 ## 第 5 局(人工接管,不用快模型)—— 打满两幕 - 结果:铁甲战士,**第 2 幕第 33 层阵亡**于知识恶魔 Boss(`ENCOUNTER.KNOWLEDGE_DEMON_BOSS`,3921 秒,未放弃,seed `72W8PMZY6E`)。 - **通关第 1 幕**,Boss 是同族神官 190 + 信徒 58/59(307HP 三敌阵容):12HP 时用暴走+(24,被虚弱削过) + 打击(6) 凑够 30 斩杀 25HP 的神官,爪牙随之消失。 - **第 2 幕**推进到 Boss:击杀 2 只精英(感染棱柱 161、蜂群术士 145),拿到 8 件遗物。 - 死因是**资源耗尽**:进 Boss 时只有 37/80 血、0 药水;Boss 379HP 且会逼你二选一诅咒。选了「心灵腐化」(每回合少抽 1 张)而非「瓦解」(每回合结束 6 伤),但少抽牌让后续几回合摸不到格挡——最后一回合手上零格挡牌,靠剑柄打击抽到的 1 张防御把 24 点来袭压到 19,从 20 血剩 1 血,下一回合 7 格挡 < 11 来袭而亡。 - 局内实测的机制:**人工制品**(吸收减益,战栗的易伤会被吃掉);**知识恶魔的诅咒二选一**;**心灵腐化**(每回合少抽 1 张);**棘刺蟾蜍**无荆棘(扣血来自别处);**敌人 id 会在同伴死亡后重编号**(CHOMPER_1 → CHOMPER_0,用旧 id 会被正确拒绝)。 ## 尚未验证 - 整局胜利、持续无人接管及不同角色的通关率。 - 非 macOS 平台与其他游戏版本。 - 所有事件、特殊选牌、遗物选择、宠物目标、多人游戏。 - 多用户部署、远程暴露或恶意本地进程隔离。 原始逐步日志留在本地私有运行目录。公开结果应为去除个人路径、存档、身份信息与凭据后的汇总。 ## 进行中的整局 原型首战之后,独立控制器已新增验证第4层单敌、第5层四敌、第6层药水选牌与弃牌选择、第8层多段/虚弱战;并走过休息、商店和宝箱。 ## 整局结果(已终局,非通关) 铁甲战士一局在**第三幕第43层三骑士精英战阵亡**结束,`state_type: game_over`、无可用选项、HP 0/86。全程未重开、未改生命/能量、未覆盖存档。 - 阵亡回合是**算术上已注定**的:第5回合 33 HP、抽牌堆为空、敌方意图 15+35=50,手牌最多只能产生 20 格挡与 22 伤害,既无斩杀线也无足够格挡。程序算术在出牌前就标出"缺口 50"。 - 因此这一局的结论是**未通关**,不能当作决策质量或通关率的证据。整局覆盖:三幕、普通战、精英、两个幕首领、事件、商店、休息、宝藏、多选牌与选牌弹窗、随机效果。 - 全日志动作来源(含前一位操作者的时段):planner 737、Jev 178、程序确定动作 90、程序局部决策 18。 ## 决策粒度改版实测(第三幕) - 第33层首领无厌沙虫:明确沙坑计时=2、归零即被吞下;实测确认「狂乱逃离」是**增加**沙坑计数(延长存活),与网上指南“Frantic Escape buys time”一致。 - 第35、38、39层:第三幕普通战与长血怪。第39层史莱姆狂战士 261 HP 打到胜利(HP 59→34,含纸伤难愈与黏液入牌)。 - 新增程序侧 `guard` / `resolve` 在真实回合内触发:事件日志出现 `local_decision kind=resolve`(连续 14/6/6 伤害三连,中间无模型调用)与 `kind=guard`(5 格挡挡下 15 点显示伤害)。 - Jev 在同一场长战里多次以 0.17–0.36 置信度交回;未下调阈值,改为收窄选项重问与程序保底。整局长血的自动推进仍不完整。 - 沙盒实测:`spire_act` 结束回合 + `spire_battle` 真实调用 Jev,低置信度时返回交接包且**没有出牌、没有误停**。 - 一次 `spire_battle` 只走一步就交回属正常收敛,但这也是当前最大的未消除往返来源。 ## 回合级指标(新增观测) `node scripts/metrics-jsonl.mjs .runtime/events.jsonl --turns 6` 从私有事件日志读出,不触碰游戏: - 全量样本(原型+整局,77 个按 act:floor:round 切分的回合):动作与结算中位约 7.1s,**代理等待中位约 18.7s**,即时间主要花在模型往返而不是动画。 - 终局四个回合的代理等待分别为 20.3s、59.7s、50.4s、10.6s;其中两个高值来自主模型接管,不是动画。 - Jev 单次推断中位约 0.9s;`decision` 只记录 Jev,主模型调用数需由调用方 harness 提供。 - 已知缺陷并已修正:`battle.round` 跨战斗不重置,早期按裸回合号统计会把多场战斗合并成一个“回合”;现按 `act:floor:round` 复合键切分。 - 已知缺陷并已修正:单次推断统计曾误用“回合内推断总和”的中位数。 ## 尚未验证 - 整局胜利、持续无人接管及不同角色的通关率。 - 非 macOS 平台与其他游戏版本。 - 所有事件、特殊选牌、遗物选择、宠物目标、多人游戏。 - 整回合无人接管的自动重排(新牌出现后由程序重排剩余动作)仍未完成;当前只覆盖 guard/resolve 两类确定性动作。 - 优化前后对照尚未在相同局面下完成(需要同一存档局面重放,当前只有同规则的历史样本)。 原始逐步日志留在本地私有运行目录。公开结果应为去除个人路径、存档、身份信息与凭据后的汇总。 ## 发布包检查 从打包文件解压到独立临时目录运行离线测试和语法检查通过;没有 npm 运行依赖。Git 跟踪文件经密钥值、个人绝对路径、游戏程序集和存档检查,均未检出。旧运行目录锁/停止标记的迁移保护已补齐;实际在第9层安全点确认旧操作者结束后切换了共享锁。 ## 新发现:多选卡读回 第二幕熔合者事件需要移除两张牌。第一次选择的青色选中边框已出现,但上游旧JSON没有包含选中集合,导致状态不变并触发停止。经一次GUI核实后,DSH原生插件补选第二张普通防御,预览确认两张普通牌且保留升级牌。桥接补丁现输出选中计数、索引和逐牌选中标志;已编译并在第23层安全重启保留同局进度,后续商店移除牌已实际读回 selected_count 0→1、selected_indices []→[0] 和 is_selected,再确认移除成功。完整的双选中间态仍待再次出现时复验。该次排障用过截图,不计为完全无GUI覆盖。 商店退出兼容:实际API先关闭商品面板再点击离开,旧的 can_proceed 是关闭前按钮状态。核心已按这一复合动作语义修正,避免把可执行的退出误挡住。