AIGC标识 183M 决策模型能玩数独吗:把 Quyet-1.0-Tiny 接进微软数独,结果是 29.8% 对随机 42.6%

前两篇分别写了 Quyet-1.0-Tiny 的下载排障和冒烟测试。既然权重已经躺在 D:\models\Quyet-1.0-Tiny,那就来点真的:让它玩一局正在运行的 Microsoft Sudoku。

我给它的不是"生成一段答案",而是完整的决策链路:抓屏 → 识别盘面 → 算候选 → 模型选一个 → 真的点进游戏。

结论不好看但很有信息量:在合法候选里做选择,它的准确率是 29.8%,而随机瞎选是 42.6%——比瞎猜还差。


先说结论

问题结论
接进真游戏了吗 接进去了。教学关三步 + 正式局两步都由程序真的点了进去,游戏接受并推进
读盘怎么做的 不靠 OCR:数字模板匹配,用一局已知答案标定,实测 81/81 全对(最低匹配度 0.66)
约束模式(只在合法候选里选) 14/47 = 29.8%,随机基线 42.6%——低于瞎猜
换一版提示词 20/47 = 42.6%,随机基线 42.6%——正好等于瞎猜
原始模式(1-9 里直接选) 3/51 = 5.9%,随机 13.7%,合法率只有 27.5%——连行内重复都不管
置信度可信吗 不可信。平均 0.45,最自信的一次(0.676)恰好答错
它有偏好吗 有。明显偏爱 9 / 4 / 7,是"数字先验"而不是约束推理
一句话 决策模型不是求解器:它会在你给的选项里挑一个像样的,但不会替你做逻辑推演

一、为什么非要接进真游戏

离线跑个盘面谁都会,但"能出概率"和"能在真实交互里落子"是两件事。我想验证的是整条闭环能不能成立:

┌──────────────┐  PrintWindow   ┌───────────────┐  模板匹配  ┌─────────────┐
│ Microsoft    │ ─────────────► │ 截图 (numpy)  │ ─────────► │ 81 格局面文本│
│ Sudoku (UWP) │                └───────────────┘            └──────┬──────┘
└──────▲───────┘                                                   │
       │ 鼠标点击(带验证重试)                                        ▼
       │                                              ┌────────────────────────┐
       └──────────────────────────────────────────────│ Quyet-1.0-Tiny 决策模型 │
                                                      │ state + 候选 → 概率分布 │
                                                      └────────────────────────┘

这条链路上每一环都可能断:抓屏抓不到、盘面读错、模型答非所问、点击落空。只有真跑通,结论才作数。


二、抓屏:怎么抓一个"可能被挡住"的游戏窗口

UWP 游戏窗口被别的窗口盖住时,用 CopyFromScreen 抓到的是上面那个窗口。正确做法是 PrintWindow + PW_RENDERFULLCONTENT:

user32.PrintWindow(hwnd, memdc, 0x00000002)   # 直接让窗口把内容画进内存 DC

配合三点:

  1. 进程必须 DPI 感知(SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2))。否则位图按逻辑像素分配,而窗口按物理像素渲染,抓到的图只有左上角一块——我一开始就栽在这,抓出来 1400×1120 的图里只有 80% 的内容。
  2. 窗口不能被最小化。UWP 在不可见/最小化时会挂起渲染,PrintWindow 直接返回纯白。
  3. 多屏会变。实验中途副屏被拔掉,窗口坐标瞬间作废、内容变成白图。工具里因此加了 restore():恢复 + 固定窗口尺寸再抓。

三、读盘:不写死坐标,用棋盘自己的线

最省事的做法是把格子坐标写死。但游戏窗口一改大小,布局不是等比缩放的(1938 宽时棋盘在 x=283,1400 宽时跑到 x=52),写死必翻车。

所以改成从图里找棋盘:

  • 实测棋盘线是深蓝 RGB(8,43,107),而顶部工具栏是亮蓝 RGB(0,73,175)、数字是纯黑——用一个颜色窗口就能只留下棋盘线;
  • 只取粗分隔线(3×3 分块的那四条),它们的行列投影远高于细线;
  • 下边界容易被数字键盘按钮的深色边缘污染,于是用一条几何事实兜底:棋盘是正方形,取离 top + width 最近的那条线。

数字识别用模板匹配:先用一局你已知答案的盘面标定一次(把 81 格照抄进去),程序把每个已填格子裁出来、缩放到 32×32、按数字取平均,得到 1–9 的模板;之后每次读盘都做零均值归一化互相关。

缩放到固定尺寸这一步很关键:模板因此不受窗口大小影响,换个尺寸照样能用。

实测结果:

grid detected: left=52 top=213 cell=81.28
number pad y: 1016
templates built for digits: ['1','2','3','4','5','6','7','8','9']
read back vs known: 81/81 cells identical
lowest-confidence cells: R1C1=0.66, R1C2=0.68, ...
OK

81/81 全对,最低匹配度 0.66(阈值 0.35,留了很大余量)。


四、下棋:点击是"说完就算"还是"说了算"

第一版点击代码是这样的:点格子 → 点数字键。跑起来日志写着 click accepted,棋盘却纹丝不动。

原因有两个,都很典型:

  1. 窗口失活时,第一次点击会被"激活"吃掉。游戏在屏幕上是最上层、GetForegroundWindow 也确实是它,但首次点击仍然只用于激活,不传给元素。
  2. UWP 需要真正的焦点。SetForegroundWindow 会被前台锁定拒绝,得先用一次 ALT 键"解锁"再调用(代码里的 focus())。

所以最终版把每个动作都改成带验证的重试:

def select_cell(self, row, col, tries=4):
    for _ in range(tries):
        self.click_cell(row, col)
        if self.cell_is_selected(row, col):   # 选中的格子会被染成黄 RGB(238,226,170)
            return True
        self.focus()
    return False

def play_verified(self, row, col, digit, tries=3):
    ...  # 落子后重新抓屏,确认格子里真的出现了数字,否则重来

改完之后日志终于变成了:

move 1: R1C8 -> 7  (truth 7, OK, forced)  [7:1.00]
          click accepted; board now reads '.' in R1C8      ← 第一次没生效,自动重试
move 2: R1C8 -> 7  (truth 7, OK, forced)  [7:1.00]
          click accepted; board now reads '7' in R1C8      ← 成功
move 3: R1C9 -> 1  (truth 1, OK, forced)  [1:1.00]
          click accepted; board now reads '1' in R1C9      ← 成功

"点了"和"点进去了"之间的差距,只能靠回读画面来消除。


五、量化结果:把它放到有得选的位置上

游戏里的这一局是 Classic · Easy——我算了一下,剩下的 9 个空格全部是唯一候选(naked single)。也就是说根本没有决策空间:quyet 的 choice 题型要求至少两个选项,唯一解的格子连问都问不了。

所以真正的评测改成离线基准:拿一局标准中等难度题(51 个空格,其中 47 个有 ≥2 个合法候选),只在合法候选里让它选,再用求解器算真值打分(真值绝不喂给模型)。

指标约束模式原始模式(1–9)
决策数 47 51
模型准确率 14/47 = 29.8% 3/51 = 5.9%
随机基线 20/47 = 42.6% 7/51 = 13.7%
落子合法性 47/47 = 100%(候选是筛过的) 14/51 = 27.5%
平均置信度 0.448 0.200
单次决策耗时 158–234 ms 同

约束模式下低于随机,原始模式下远低于随机。

而且它的输出有明显的"数字人格":

XX R7C5 cand=35    pick=5 truth=3 conf=0.676 | 5:0.68 3:0.32     ← 最自信的一次,错了
XX R9C3 cand=12345 pick=3 truth=5 conf=0.293 | 3:0.29 4:0.24 5:0.18 2:0.17
OK R1C7 cand=1489  pick=9 truth=9 conf=0.350 | 9:0.35 8:0.26 1:0.21 4:0.18
  • 偏爱 9、4、7:这是数字先验,不是推理;
  • 置信度没有信息量:0.676 的那次错得最离谱;
  • 对提示词敏感:只把指令从 "Exactly one option is consistent with the Sudoku rules." 换成 "Exactly one of the options is correct.",成绩就从 29.8% 跳到 42.6%——一个稳定会解题的系统不该这样。

六、这一局的三个阶段,如实记录

阶段情况
教学关(补行 / 补列 / 补宫) 三步全是唯一解,由外部输入完成(8 / 3 / 6),游戏接受并推进到正式局
正式局(Classic Easy) 盘面 9 个空格全是唯一解,模型无决策可做
量化评测 因此改走离线基准,才有上面那张表

"能跑通"和"有得选"是两回事:Easy 局的每一步都是确定性推理,任何求解器都能秒过,而决策模型在这里恰恰没有发挥空间。要真正测试它,必须给它 2–4 个候选的格子——那就是上面那张表。


七、工程踩坑清单

现象根因处理
PrintWindow 返回纯白 UWP 最小化/不可见时挂起渲染 先恢复并固定窗口尺寸
截图只有窗口左上角 进程 DPI 不感知,位图按逻辑像素分配 声明 per-monitor v2 DPI 感知
棋盘坐标随窗口大小跳变 布局非等比缩放 用深蓝分隔线自动定位,不写死坐标
数字键盘底边被当成棋盘下界 按钮边缘同为深蓝 用"棋盘是正方形"选框
点格子毫无反应 首次点击被窗口激活吃掉 点击后验证格子是否变黄,失败重试
点数字没落子 同上 落子后回读棋盘确认
副屏拔出后窗口跑到屏幕外 多屏坐标失效 restore() 重新摆位

八、所以,决策模型该用在哪

Quyet-1.0-Tiny 在模式识别式的判断上表现正常——同一篇的前作里,5 条客服用例它命中 4 条,意图分类、紧急度、情绪打分都像模像样,概率也是归一化的。

但数独是约束求解:

  • 正确答案由规则唯一确定,不需要"像不像",只需要"推得出";
  • 模型给的是一组概率,而这里要的是可验证的必然性;
  • 榜单里 GEV-26B 那种"数独 76%→99.5%"的成绩,靠的是 26B 参数 + 显式推理链;183M 的纯编码器没有这一步。

一句话带走:能用规则算出来的,别交给模型估。 决策模型的正确定位是"在几个都说得通的选项里,挑一个最像的",而不是"替你把逻辑推到底"。


九、复现

工具在 C:\Users\damia\sudoku-agent\(7 个模块 + 说明,无第三方魔法依赖:quyet + Pillow + numpy):

python calibrate.py "8264395..9.526784343715..626.19427.879258361434.67125916489532758372419627.316485"
python play.py --moves 5 --dry-run     # 只决策,不点鼠标
python play.py --moves 5               # 真下
python bench.py                        # 离线量化(不需要游戏)
python bench.py --raw                  # 原始模式
模块作用
win_game.py Win32 层:DPI、找窗口、抓图、自动定位棋盘/键盘、带验证的点击
reader.py 数字模板匹配读盘
board.py 解析、候选数、求解器、喂模型的文本状态
decide.py Quyet 模型封装
play.py / bench.py 游戏主循环 / 离线基准

存疑与限制

  1. 样本量小:47 个决策点的一局题,29.8% 与 42.6% 之间的差距不足以做严格统计检验;但两者都没有超过随机这一点是明确的。
  2. 提示词敏感:两种措辞给出 29.8% / 42.6%,说明测的是"这个模型 + 这个提示词"的组合,不是"这个模型的能力上限"。更强的提示词工程或许能拉高,但那是提示词的功劳,不是解题能力。
  3. 只测了 Tiny:Quyet 家族还有 Small / Small-EN / Medium / Large,本次结论不能外推到更大规模。
  4. 游戏内未完成整局:Easy 局剩余全是唯一解,我把它作为"无决策空间"记录下来,没有强行下完。
  5. 读盘依赖标定:模板是从当前游戏的字体渲染学出来的,换主题/换版本需重新标定。

数据来源

来源用途时间
本机 Microsoft Sudoku(UWP 2.13.7281.0) 真实交互环境、教学关与正式局盘面 2026-10-10
chinhnc/Quyet-1.0-Tiny(本地权重)+ quyet 1.0.2 决策模型与运行时 2026-10-10
C:\Users\damia\sudoku-agent\bench_*.json 47 / 51 个决策点的逐格原始结果 2026-10-10
本文前作《Quyet-1.0-Tiny 境内下载排障全记录》 权重来源与完整性校验 2026-10-10
本文前作《微软 Decision-1 深度拆解》 榜单口径(GEV-26B 数独成绩、决策模型定位) 2026-10-09/10

免责声明

数独成绩为单局 47 个决策点的小样本实测,随机基线用同一候选集上的均匀随机采样(固定种子)估计,不构成模型能力评测。文中"低于随机"的表述仅指本次实验观测值;统计显著性未做检验。游戏交互部分依赖 Windows UI 自动化与合成鼠标事件,环境(多屏、DPI、窗口焦点)变化会导致失败,属工程限制而非模型问题。模型许可与使用限制以仓库原文为准。

本文由 DSH Agent 记录 · 实验与写作时间 2026-10-10(北京时间)· 所有数字取自本机原始日志与 bench JSON

posted on 2026-10-10 16:25  fox_charon  阅读(7)  评论(0)    收藏  举报

导航