@windsland52/maafw-live 0.0.0-stage → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +293 -2
- package/bin/maafw-live.mjs +25 -0
- package/docs/daemon-protocol.md +121 -0
- package/docs/keyframe-retention-contract-v0.md +225 -0
- package/docs/session-unify-v0.md +115 -0
- package/lib/client/daemon.js +276 -0
- package/lib/commands/annotate.js +179 -0
- package/lib/commands/calibrate.js +61 -0
- package/lib/commands/color.js +73 -0
- package/lib/commands/crop.js +228 -0
- package/lib/commands/dispatch.js +121 -0
- package/lib/commands/env.js +409 -0
- package/lib/commands/index.js +70 -0
- package/lib/commands/input.js +257 -0
- package/lib/commands/kf.js +167 -0
- package/lib/commands/probe.js +70 -0
- package/lib/commands/reco.js +440 -0
- package/lib/commands/repl.js +1501 -0
- package/lib/commands/roi-check.js +273 -0
- package/lib/commands/roi-compare.js +388 -0
- package/lib/commands/runtime.js +718 -0
- package/lib/commands/skill.js +169 -0
- package/lib/commands/timing.js +247 -0
- package/lib/commands/version.js +66 -0
- package/lib/daemon/framed.mjs +3680 -0
- package/lib/daemon/reco_child.mjs +287 -0
- package/lib/exec.js +96 -0
- package/lib/flags.js +44 -0
- package/lib/image.js +90 -0
- package/lib/index.js +143 -0
- package/lib/interface/hotkey.js +142 -0
- package/lib/interface/index.js +12 -0
- package/lib/interface/jsonc.js +95 -0
- package/lib/interface/load.js +397 -0
- package/lib/interface/override.js +287 -0
- package/lib/interface/plan.js +279 -0
- package/lib/option-args.js +29 -0
- package/lib/pkg.js +54 -0
- package/lib/protocol.js +94 -0
- package/lib/runtime/actions.js +225 -0
- package/lib/runtime/keyframes.js +73 -0
- package/lib/runtime/log-watch.js +160 -0
- package/lib/runtime/node-source.js +100 -0
- package/lib/runtime/ocr-model.js +45 -0
- package/lib/runtime/pi-env.js +34 -0
- package/lib/runtime/pretask.js +217 -0
- package/lib/runtime/prune.js +28 -0
- package/lib/runtime/receipts.js +57 -0
- package/lib/runtime/reco-args.js +38 -0
- package/lib/runtime/reco-verdict.js +82 -0
- package/lib/runtime/roi-compare.js +198 -0
- package/lib/runtime/session-forward.js +79 -0
- package/lib/runtime/session-ipc.js +471 -0
- package/lib/runtime/session.js +135 -0
- package/lib/runtime/signal-guard.js +35 -0
- package/lib/types/client/daemon.d.ts +86 -0
- package/lib/types/commands/annotate.d.ts +3 -0
- package/lib/types/commands/calibrate.d.ts +7 -0
- package/lib/types/commands/color.d.ts +9 -0
- package/lib/types/commands/crop.d.ts +3 -0
- package/lib/types/commands/dispatch.d.ts +22 -0
- package/lib/types/commands/env.d.ts +21 -0
- package/lib/types/commands/index.d.ts +13 -0
- package/lib/types/commands/input.d.ts +17 -0
- package/lib/types/commands/kf.d.ts +10 -0
- package/lib/types/commands/probe.d.ts +15 -0
- package/lib/types/commands/reco.d.ts +27 -0
- package/lib/types/commands/repl.d.ts +10 -0
- package/lib/types/commands/roi-check.d.ts +3 -0
- package/lib/types/commands/roi-compare.d.ts +3 -0
- package/lib/types/commands/runtime.d.ts +66 -0
- package/lib/types/commands/skill.d.ts +17 -0
- package/lib/types/commands/timing.d.ts +64 -0
- package/lib/types/commands/version.d.ts +3 -0
- package/lib/types/exec.d.ts +31 -0
- package/lib/types/flags.d.ts +62 -0
- package/lib/types/image.d.ts +27 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/interface/hotkey.d.ts +36 -0
- package/lib/types/interface/index.d.ts +11 -0
- package/lib/types/interface/jsonc.d.ts +9 -0
- package/lib/types/interface/load.d.ts +190 -0
- package/lib/types/interface/override.d.ts +78 -0
- package/lib/types/interface/plan.d.ts +68 -0
- package/lib/types/option-args.d.ts +19 -0
- package/lib/types/pkg.d.ts +14 -0
- package/lib/types/protocol.d.ts +124 -0
- package/lib/types/runtime/actions.d.ts +248 -0
- package/lib/types/runtime/keyframes.d.ts +43 -0
- package/lib/types/runtime/log-watch.d.ts +28 -0
- package/lib/types/runtime/node-source.d.ts +25 -0
- package/lib/types/runtime/ocr-model.d.ts +8 -0
- package/lib/types/runtime/pi-env.d.ts +24 -0
- package/lib/types/runtime/pretask.d.ts +53 -0
- package/lib/types/runtime/prune.d.ts +4 -0
- package/lib/types/runtime/receipts.d.ts +7 -0
- package/lib/types/runtime/reco-args.d.ts +18 -0
- package/lib/types/runtime/reco-verdict.d.ts +50 -0
- package/lib/types/runtime/roi-compare.d.ts +117 -0
- package/lib/types/runtime/session-forward.d.ts +10 -0
- package/lib/types/runtime/session-ipc.d.ts +117 -0
- package/lib/types/runtime/session.d.ts +77 -0
- package/lib/types/runtime/signal-guard.d.ts +19 -0
- package/lib/types/update-check.d.ts +20 -0
- package/lib/types/usage.d.ts +3 -0
- package/lib/update-check.js +97 -0
- package/lib/usage.js +50 -0
- package/package.json +94 -4
- package/skills/README.md +51 -0
- package/skills/maafw-live/SKILL.md +293 -0
- package/skills/maafw-live/references/examples.md +238 -0
- package/skills/maafw-live/references/pitfalls.md +211 -0
- package/skills/maafw-live/references/workflows.md +164 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# 反模式与真实教训
|
|
2
|
+
|
|
3
|
+
每条都是踩过的:症状 → 原因 → 怎么做。协议字段见
|
|
4
|
+
[daemon-protocol.md](https://github.com/Windsland52/maafw-live/blob/main/docs/daemon-protocol.md)。
|
|
5
|
+
留存结论怎么读见[留存契约 §7](https://github.com/Windsland52/maafw-live/blob/main/docs/keyframe-retention-contract-v0.md)。
|
|
6
|
+
|
|
7
|
+
## 1. 判定与结果消费
|
|
8
|
+
|
|
9
|
+
| 症状 | 原因 | 怎么做 |
|
|
10
|
+
| :--- | :--- | :--- |
|
|
11
|
+
| "任务成功了"但游戏没动 | 拿退出码或信封外层 `ok` 当业务结果 | 任务级结果只看 `data.record.ok`;`record.nodes[]` 看每节点状态 |
|
|
12
|
+
| 看不出走了哪条分支 | 节点名只说明"执行了哪个节点",不说明"当时有哪些出口可选";而且**没命中的候选不产生节点记录** | 两份记录合起来读:`record.nextCandidates`(候选轮询序列)+ `record.nodes[].name`(结果)。序列第一条是任务入口本身、不是轮询;同一候选**连续重复**才折叠成 `polls`,多候选分支里是交替出现、`polls` 恒 1——那时按条数数轮次 |
|
|
13
|
+
| `record.nodes[].name` 两次同名、看不出走了哪条分支 | 框架在 `PipelineNode.*` 顶层填的是**任务入口名**;工具已改为优先取内嵌 `node_details.name`,并用 `nameSource` 标注来源 | 按 `nameSource` 判:`node_details` = 权威;`entry` = 顶层兜底、**未确认**(节点还没跑完时会出现),这时别拿它当"跑了哪个节点",用屏幕/留存帧复核 |
|
|
14
|
+
| 识别"命中"了,框却是全零 | miss 时框架仍回 detail 对象 | 只认 `hit`;零尺寸 box 一律当未命中 |
|
|
15
|
+
| 任务失败但退出 0 | 旧版 CLI 按外层 `ok` 定退出码(已修) | 现在 `record.ok=false` → 退出 1;仍要按字段判定,不要只信码 |
|
|
16
|
+
| 把 `FINDINGS(3)` 当失败 | 3 的语义是"跑成了,但发现了问题" | 未命中、裁剪警告、帧不可解析都属此类;按 `error.code` 分支 |
|
|
17
|
+
| 节点事件对不上画面 | 节点事件的 seq 是**回调时最新帧序**,且可能被后续回调更新 | 关联强度按 `adjacent` 计;转移级结论不能只靠邻近帧 |
|
|
18
|
+
| `startSeq` 当"开跑前截图" | 它不是严格的开跑前截图,`framesCaptured` 是进程累计值 | 要"动作前/后"的画面就用 `input` 返回的 `retention.before/after` |
|
|
19
|
+
|
|
20
|
+
## 2. 会话与生命周期
|
|
21
|
+
|
|
22
|
+
- **同一台设备并发两个控制器**(面板 daemon + 一次性命令 / 两个终端)= 互扰,属红线。要并发就换设备或串行化。
|
|
23
|
+
- **一次性命令的环是空的、`seq` 恒为 0**:每次命令 spawn 自己的 daemon,连接与帧流都是进程态。
|
|
24
|
+
帧序对齐、历史帧取用、`reco --seq` 只在 `repl` / 客户端订阅路径有效。
|
|
25
|
+
- **命令退出即断开**:一次命令里想"连接 → 看 → 点 → 再看",必须写进同一个 `repl` 会话(或客户端脚本)。
|
|
26
|
+
- **"另一个时刻"冒充不了"当时那一次"**:`screencap` 拿的是现在;已淘汰的 L0 帧就是没有了——
|
|
27
|
+
升格失败要如实接受,改走 L1 参考帧并说明它不作证据本体。
|
|
28
|
+
- adb 的 `destroy` 偶发阻塞:客户端超时后会硬杀 daemon 自愈,这属预期,不是崩溃。
|
|
29
|
+
|
|
30
|
+
### 2.1 agent 侧(custom action / recognition 里)
|
|
31
|
+
|
|
32
|
+
- **`context.tasker.controller` 不能缓存到变量里复用**。这是 MaaFramework 的语义:`controller` 属性
|
|
33
|
+
每次访问都新建一个远程句柄并把旧的顶替掉——缓存的句柄再调用会撞 access violation
|
|
34
|
+
(读 0xFFFFFFFFFFFFFFFF)。每次要用时**重新取**(`context.tasker.controller` 现取现用),
|
|
35
|
+
别在 action 开头存一份然后整个循环复用。
|
|
36
|
+
- **`context.tasker.controller` / `context.tasker` 的 reverse request 依赖桥接侧注册 sink**。
|
|
37
|
+
`controller not found`(`controller_id` 为空)说明桥接没注册对应 sink,不是"回调里不能截图"——
|
|
38
|
+
maafw-live ≥0.3.1 的 `run` 会注册 resource/controller/tasker 三个 sink,回执 `agent.sinks` 可见;
|
|
39
|
+
自己写的桥接脚本必须同样注册(参照 M9A `agent_client` 的完整 PI-client 装配),否则 agent 内
|
|
40
|
+
一切 controller/tasker 反查都会静默失败。
|
|
41
|
+
|
|
42
|
+
## 3. 坐标与图像空间
|
|
43
|
+
|
|
44
|
+
- **历史帧 roi 必须按该帧捕获时尺寸换算**(每帧带 `fw/fh`)。按当前全局尺寸换算会在重连/改分辨率后静默错剪。
|
|
45
|
+
- **L1 小图上的 box 不是 pipeline 坐标**:小图空间 → 控制器空间要换算;识别单测用小图时结果要标注来源尺寸。
|
|
46
|
+
- **`annotate` 的候选同时给 `box` 与 `ctrl`,别拿错**:会话内路径的 `box` 是**工作图空间**(缺省走 L1 小图,
|
|
47
|
+
720p 下缩放 0.375),`ctrl` 才是控制器坐标;库帧路径(`--from-kf`)两者相同,所以"库帧上量出来的数"
|
|
48
|
+
直接照用不会出错,**换成实时会话就错 2.67 倍**。要点击、要写 roi、要填 `--point`,一律用 `ctrl`
|
|
49
|
+
(人读表格里印的就是 `ctrl=`)。
|
|
50
|
+
- **`--short-side` / `--long-side` / `--raw` 只在手动连法生效**;项目模式按 `interface.json` 的
|
|
51
|
+
`display_short_side` 走。改了缩放,项目里所有 roi 与模板都要重算。
|
|
52
|
+
- **文字按钮走 OCR,不要硬裁模板**:实测一条约 85×32 的文字按钮「全部领取」——`crop --point` 裁出的模板
|
|
53
|
+
**自匹配就位置错误**(best 落到别处、得分 0.848、21 次评估),拿它做 TemplateMatch 在同一 ROI 上**未中**;
|
|
54
|
+
同一区域改用 `{"recognition":"OCR","expected":"全部领取"}` **命中 1.0**。代价:OCR 单次 1.1–1.7s,
|
|
55
|
+
TemplateMatch 约 0.22s(慢 5–8 倍)——**图标用模板、文字按钮用 OCR**。裁模板时自匹配报"位置错误"
|
|
56
|
+
就是这条路不通的信号,别硬用。
|
|
57
|
+
- **模板空间 = 源帧捕获时尺寸**。从旧帧裁的模板用在当前空间会失效——裁剪结果里的 `spaceCheck.match:false`
|
|
58
|
+
就是这个信号(工具同时会警告并退出 3)。
|
|
59
|
+
- 模拟器上 `adb shell wm size` 改物理分辨率**不传导**到框架截图(实测 MuMu:原生 screencap 已变,
|
|
60
|
+
控制器仍按初始化尺寸交付)。要触发分辨率变化,用重连改 `shortSide`。
|
|
61
|
+
|
|
62
|
+
## 4. 识别与模板
|
|
63
|
+
|
|
64
|
+
- **未命中先问"目标在不在帧里"**,再谈 roi / 阈值 / 模板。得分 < 0.5 是"不匹配",不是"阈值太高"。
|
|
65
|
+
- **纯色/低纹理模板的得分是噪声**:CCOEFF_NORMED 下大面积同色会让 best 落到别处且得分不低。
|
|
66
|
+
判据是**位置正确优先于得分**——`positionOk:false` 的模板不要用。
|
|
67
|
+
- **参数用例别只测一档**:同一模板在不同阈值/roi 下表现差很多;`--sweep` 出曲线再定起点(实测 − 0.1)。
|
|
68
|
+
- **SoM 候选表是"截断表",不是"全部区域"**:候选上限 30,实测 6 帧里 4 帧打满、去重后真实规模中位 54
|
|
69
|
+
(默认上限只放出约 41%)。没出现在表里 ≠ 画面上没有那个东西;回执里 `mergedTotal` 与 `count` 不等就是截断了。
|
|
70
|
+
截断**按横向分带轮转**(带内保持源优先级),所以不会出现"某一段整体消失"的假象——
|
|
71
|
+
**若某个带确实为 0,那是供给真的没有**,不是被截掉的。
|
|
72
|
+
- **别把"表里没有"当成"那里不能点"**:候选表要按源理解——OCR 框贴文字、连通域偏亮连通区、
|
|
73
|
+
边缘密度看梯度;暗底图标、无文字的纯色按钮可能任何源都不给。找不到就走点选路径(`crop --point`)。
|
|
74
|
+
- **别指望 edge 源,也别去调它**:实测把边缘门槛放松(z 1.2→0.6、地板 2000→600)后,edge 的**独立候选反而
|
|
75
|
+
更少**(17→15)——阈值一松,热块聚成更大的边缘簇,然后被优先级更高的连通域在合并阶段吸收掉;
|
|
76
|
+
覆盖率上升只是框变大。回执里的 `filtered.edge` 说明了机制:**绝对地板(2000)从不触发**(`belowMin` 全为 0),
|
|
77
|
+
门槛全在 **z** 上(`belowZ` 三五千量级)——所以"调 edge"实际只有 z 一个杠杆,而它调不动结果。
|
|
78
|
+
暗底、无文字的按钮该走点选路径(`crop --point`),不是调阈值。
|
|
79
|
+
- **"候选少"不等于"工具看不见"**:`filtered.conn` 显示面积窗下界(120px)会拒掉约 **97%** 的连通域
|
|
80
|
+
(实测三帧:通过 27–53 个、`tooSmall` 1460–1698 个)。供给从来不是瓶颈——判断某处"能不能被找到",
|
|
81
|
+
要看 `sources` + `filtered` + 该带的分带计数,而不是凭候选总数猜。
|
|
82
|
+
- **`annotate --from-kf` 没有 diff 源**:库帧路径没有会话事件,工具如实报 `sourcesUnavailable.diff`。
|
|
83
|
+
要看"哪里变了"必须用实时会话的 `annotate`(配 `stream` 的变化事件),库帧路径只回答"画面里有哪些区域"。
|
|
84
|
+
- **识别框默认就是动作框**:`Click` 的落点是 **target 框内的随机点**,没给 `target` 时 target 就是识别框。
|
|
85
|
+
所以"框里有一片空地"= 有一定概率点在空地上、什么都不发生(实测:宽 115px 的入口模板里右侧 40% 是空地,
|
|
86
|
+
一次 `run` 的点击就这么丢了,而回执照样报 `Action.Succeeded`——**动作成功 ≠ 点到了东西**)。
|
|
87
|
+
两条对策:**把模板裁成按钮本体**(`crop --point` 点图标中心,框即按钮),或显式给 `target` 收窄落点区域。
|
|
88
|
+
- **`positionOk` 不是"框完美"的证明**:它只说明"模板在同帧上落回你给的那块区域"(IoU 重叠判据)。
|
|
89
|
+
宽松框给太大时 snap 会偏,而它照样为真——实测图鉴图标 110×110 输入、snap 收到 57×69,真值是 69×62,
|
|
90
|
+
宽度少了 12px,`positionOk` 却是 `true`。要判框准不准:**给贴边的框**,或再裁一遍比 `box` 变化。
|
|
91
|
+
- **动态区域模板跨帧不稳**:同帧自匹配 1.0 只证明"模板与源帧一致";要在新帧上复测(`crop` 自动做跨帧验证)。
|
|
92
|
+
- **不要凭直觉调 snap 常数**(`snapTol` / `snapFrac`,默认 90 / 0.12):36 例 A/B 实测,把阈值调"敏感"
|
|
93
|
+
(60 / 0.2)会让 snap 更勤快(触发 61%→69%)却让**位置正确率从 91.7% 掉到 83.3%**——过度收紧后,
|
|
94
|
+
候选池的外扩档会落到更不独特的区域。默认值不是随手写的,改之前先跑 `npm run survey:crop` 拿数据。
|
|
95
|
+
- **塌陷不是失败**:snap 把框收到原面积 35% 以下时,只要 `positionOk` 为真就照用——同一批数据里塌陷例
|
|
96
|
+
位置全部正确。反过来,**耗时翻倍才是警告**:健康区域 9 次评估 ≈10s,退化区域 17-21 次 ≈20s。
|
|
97
|
+
- **从降采样小图裁模板**(或上采样冒充原图)会得到尺寸对不上的模板:模板尺寸必须与识别空间一致。
|
|
98
|
+
|
|
99
|
+
## 5. 留存与关键帧库
|
|
100
|
+
|
|
101
|
+
- **攒到最后再升格**:L0 是有界 FIFO,等这段做完,那次捕获早已淘汰——一次性状态就此没有证据。
|
|
102
|
+
- **升格要在捕获它的那个 daemon 会话里做**:换个进程的 L0 是空的。
|
|
103
|
+
- **库副本按单写者管理**(v0):同 ID 冲突内容必须报错,不能任选一份;复制库保留身份,新建库生成新身份。
|
|
104
|
+
- **裸序号不是引用**:`kf:0142` 只能当展示简称,跨库持久引用要完整 `kf:<库UUID>:<序号>`。
|
|
105
|
+
- **库配额满额是拒绝新增并报告**,不后台静默删除已升格帧——清理策略由人决定。
|
|
106
|
+
- **L2 出处别漏**:模板提交进仓库时带上 `<out>.prov.json`;脱敏裁剪可能去掉识别上下文,
|
|
107
|
+
裁后要重核 hits/box 断言,不要继承原图的全部验证结论。
|
|
108
|
+
|
|
109
|
+
## 6. 框架原生坑(会崩,不只是结果不对)
|
|
110
|
+
|
|
111
|
+
- **节点回调里禁止发任何控制器任务**(截图/点击)。实测:`add_sink` 回调中并发 `post_screencap` 与识别回路
|
|
112
|
+
抢控制器,触发原生崩溃 `0xC0000005`(同任务关/开必现对照)。要用帧就读 `cached_image`(识别回路自己截的图,
|
|
113
|
+
零新任务),并按 `adjacent` 报关联强度,不冒称 `bound`。
|
|
114
|
+
- **agent 子进程与 maa-node 必须同版本**(协议号握手):通道能建立但握手被拒——连接后要复核
|
|
115
|
+
`connected && alive`,不能只看"没报错"。
|
|
116
|
+
- 无效模板会让 beta 绑定原生崩溃:识别单测在**一次性子进程**里跑,子进程炸掉只损失一次测试。
|
|
117
|
+
|
|
118
|
+
## 7. 本工具的已知边界(会随时间收敛,遇到先按"如实报告"处理)
|
|
119
|
+
|
|
120
|
+
- **`crop` / `annotate` 只加载 `resource.path[0]`**(或显式 `--resource-dir`)。`reco --project` /
|
|
121
|
+
`--node-name` 才加载整条链。OCR 模型或 pipeline 在后面的 bundle / `attach_resource_path` 里时,
|
|
122
|
+
`annotate` 会像画面上没有字,`crop` 的自匹配会报「没跑起来」。验节点用 `reco`;要让这两条命令用到
|
|
123
|
+
那一段资源,显式给 `--resource-dir`。
|
|
124
|
+
- **`--act` 不接受 `--node-name`**。按资源原名验识别用 `--node-name`;动作半要 `--node` / `--node-file`,
|
|
125
|
+
或直接 `run`(才会桥接 agent 子进程)。
|
|
126
|
+
- 自匹配要加载资源:`crop` 必须能解析到资源目录(`--project` 或 `--resource-dir`);
|
|
127
|
+
库帧路径可以用 `--project` 离线解析资源(不连设备)。
|
|
128
|
+
- **资源包里没有任何 pipeline(或资源链根本加载不出来)时,`crop` 会明确报"自匹配没跑起来"**
|
|
129
|
+
(带上具体原因:识别子进程一个结果都没回 / 资源链加载不出来),
|
|
130
|
+
而不是"低纹理/不独特"——这两件事的处置相反(去修资源 vs 去换框)。此时回执里 `positionOk=false`、
|
|
131
|
+
**这次的框未经自匹配验证**,不要拿它去写 pipeline。所以**先有能加载的资源、再信自匹配结论**;
|
|
132
|
+
`survey:*` 脚本为此会现造一个最小资源。
|
|
133
|
+
- 裁剪耗时(实测口径,别按"卡死"处理):健康区域 **9 次评估 ≈10.5s**,退化区域 **17 次 ≈20s**;
|
|
134
|
+
同元素在 1920×1080 上同样 9 次、11.9s(+13%)。**耗时翻倍本身就是"这块内容不独特"的信号**。
|
|
135
|
+
- 库帧路径的跨帧验证**缺省关闭**是有意的:留存帧对应的状态通常已不在画面上,硬比会产生假警告。
|
|
136
|
+
- 修过的两个静默错误(避免旧印象):解码后未归一化 RGB 会导致 RGBA 帧错剪;多条裁剪警告曾互相覆盖,
|
|
137
|
+
现在回执给 `warns[]`。
|
|
138
|
+
|
|
139
|
+
## 8. Windows 的 shell / 编码 / 路径边界
|
|
140
|
+
|
|
141
|
+
工具本身在 Windows 上很少出问题;踩坑几乎都发生在**参数与文本穿过这三层边界**的时候。
|
|
142
|
+
下面每条都有实测,且容易被误判成"工具坏了"——先按这里排除,再去怀疑工具。
|
|
143
|
+
|
|
144
|
+
### 8.1 参数:npm 的 `.cmd` shim 会重写它
|
|
145
|
+
|
|
146
|
+
| 症状 | 原因 | 怎么做 |
|
|
147
|
+
| :--- | :--- | :--- |
|
|
148
|
+
| 改了 `expected`/正则,识别结果却一模一样 | 参数先过 `cmd.exe`:`^` 是转义符,被吞掉 | 程序化调用一律 `node <repo>/bin/maafw-live.mjs …`,绕开 `.cmd` / `.ps1` |
|
|
149
|
+
| 带 `!` 的参数字符丢失 | `cmd.exe` 延迟展开 | 同上;bash 下的裸 `maafw-live` shim 也可以 |
|
|
150
|
+
| 报 JSON 非法 / 参数串断了 | `%` 触发变量展开、引号被重解释 | 同上;且**不要把 JSON 拼进 shell 字符串**,用 argv 列表传 |
|
|
151
|
+
|
|
152
|
+
实测(2026-10):`--node '{"…","expected":"^解锁"}'` 经 `.cmd` 到达进程时已变成 `"解锁"`,
|
|
153
|
+
于是本不该命中的「未解锁」被判命中;改用 `node bin/maafw-live.mjs` 后结果正确。
|
|
154
|
+
**症状是"三种不同正则给出完全相同的识别结果"时,先怀疑这一条。**
|
|
155
|
+
|
|
156
|
+
不想和 shell 搏斗就走文件通道:`reco --node-file node.json`、`reco --param-file -`(stdin)。
|
|
157
|
+
文件按 JSONC 解析(pipeline 常带 `//` 注释),`^`、`"`、`%` 都不经过 shell。
|
|
158
|
+
|
|
159
|
+
### 8.2 编码:控制台是 GBK,数据是 UTF-8
|
|
160
|
+
|
|
161
|
+
- **回显乱码不是数据错**:管道里出现 `Ħ����˾` 是代码页 936 在解 UTF-8 字节。文件与 JSON 本身是好的。
|
|
162
|
+
- **真的会抛异常**:父进程用 `encoding="utf-8"` 读子进程 **stderr** 时可能
|
|
163
|
+
`UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd5`(stderr 是 GBK 字节)。
|
|
164
|
+
- 怎么做:统一 `--json` 落文件,读文件用 `encoding="utf-8", errors="replace"`;
|
|
165
|
+
不靠控制台回显判断中文对不对。自己的脚本要打印中文,先设 `PYTHONIOENCODING=utf-8`。
|
|
166
|
+
|
|
167
|
+
### 8.3 路径:`/tmp` 在 bash 与原生进程里不是同一个地方
|
|
168
|
+
|
|
169
|
+
- git-bash 的 `/tmp` 与原生 Python / Node 解析出的 `/tmp` 落在不同根(后者常成 `C:\tmp` 之类)。
|
|
170
|
+
bash 写入、原生进程读取 → `FileNotFoundError`。
|
|
171
|
+
- **产物就放项目里**(相对 cwd 即可):用项目里已 gitignore 的目录(如 `.live/`)或自建工作目录。
|
|
172
|
+
多个工具共用同一个 cwd 时,相对路径最省事。
|
|
173
|
+
- 只有**跨 cwd 传参**(把路径交给一个在别的目录启动的进程)时才写成 `C:/…` 正斜杠绝对路径——
|
|
174
|
+
这种写法 bash 与 node 都认。**不要**把"一律用绝对路径"当规矩,也不要用 `/tmp` 当中转站。
|
|
175
|
+
|
|
176
|
+
### 8.4 其他两个不是 Windows 专有、但同样会被误判的
|
|
177
|
+
|
|
178
|
+
- `maa-node` 是**原生模块**,版本钉在工具自带的那份(如 5.14.2),可能与被测项目依赖的 MaaFW
|
|
179
|
+
版本有代差。识别分数与框一致时可直接用,但**下结论前先标出这个版本差**。
|
|
180
|
+
- 本工具是 **Node CLI,不是 Python 包**:`import maafw_live` 必然失败,别把这个当成环境损坏。
|
|
181
|
+
|
|
182
|
+
### 8.5 进程树清理(`uv run` / 宿主死了 daemon 还活着)
|
|
183
|
+
|
|
184
|
+
这层坑全是 Windows 平台行为 + uv 的重 exec 模型叠出来的,改 cleanup / stop 逻辑前过一遍:
|
|
185
|
+
|
|
186
|
+
- **`process.kill(pid)` = `TerminateProcess`,管道不破。** 宿主被它杀死时,daemon 的 stdin
|
|
187
|
+
既不 `end` 也不 `close` 也不 `error`——`rl.on('close')` / `stdin 'end'` / `'close'`
|
|
188
|
+
全不触发,`readableEnded` 恒 false。**宿主死亡探测只能靠 `process.ppid` 心跳**
|
|
189
|
+
(`process.kill(ppid, 0)` 抛错即宿主已死),500ms 一拍 `unref`。
|
|
190
|
+
- **`taskkill /T` 在 `uv run` 的重 exec 链上漏中层。** 链是
|
|
191
|
+
`uv.exe → venv-python → uv-python`:`uv` 把中层 re-parent,`taskkill` 只按杀时的
|
|
192
|
+
直接父子关系递归,中层一脱离就打不到——实测收掉 `uv.exe` 和末级 `uv-python`,
|
|
193
|
+
中间 `venv-python` 留成孤儿。**只 `taskkill /T` 不够**,得再按命令行特征
|
|
194
|
+
(agent 每层 argv 都带同一个 `identifier`)点名扫一遍兜底。
|
|
195
|
+
- **`uv run` 的孙级在 kill 途中还在生。** `taskkill /T` 返回不等于树已死完——
|
|
196
|
+
`uv.exe` 死的瞬间它重 exec 出的新子进程已脱离父链。快照、递归、等时长都救不了,
|
|
197
|
+
只能靠命令行特征兜底。
|
|
198
|
+
- **先杀树、后 disconnect。** `disconnect()` 会让 `uv.exe` 自己优雅退出,python 子孙
|
|
199
|
+
瞬间断链,`taskkill /T` 再够不到——顺序反过来必留两层 python 孤儿(实测)。
|
|
200
|
+
- **powershell 兜底脚本会被 `-like` 自杀。** `CommandLine -like '%ident%'` 会命中这条
|
|
201
|
+
powershell 自己的 `CommandLine`(字面量里就带着 ident),`Stop-Process` 把自己
|
|
202
|
+
干掉在中途,python 照样留下。必须 `$PID` 排除自身。
|
|
203
|
+
- **`Get-CimInstance` 在冷 powershell 里可能慢到被超时杀。** 逐层递归查询会堆叠冷启动
|
|
204
|
+
成本(实测 15s+);单次全表扫描 ~1s。查询设计先想清楚递归深度。
|
|
205
|
+
- **Node `process.on('exit')` 里 `spawn`/`execFile` 不可靠。** exit 回调里异步 spawn 的
|
|
206
|
+
子进程可能根本起不来;要同步 `spawnSync`。即便如此,`spawnSync` 在 exit 里也不保证
|
|
207
|
+
能 fork 新进程——exit 兜底只做信号级 `child.kill()`(POSIX 负 pid 杀组),
|
|
208
|
+
树杀只能放在 async 的 `cleanupAgents` 路径里。
|
|
209
|
+
- **验收"无孤儿"用 `Get-Process` 数,不用 `Get-CimInstance`。** WMI 有快照滞后:进程已死
|
|
210
|
+
但行还在("survivor" 名单全是假阳)。`Get-Process -Id <pid>` 返回空才是真的死。
|
|
211
|
+
同理,"还有 N 个"的结论要给 sweep 至少 30s 跑完再下。
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# 动线细节:命令级选择与实测经验
|
|
2
|
+
|
|
3
|
+
本文件是 [SKILL.md](../SKILL.md) 四条动线的展开。字段与参数以
|
|
4
|
+
[daemon-protocol.md](https://github.com/Windsland52/maafw-live/blob/main/docs/daemon-protocol.md) 为准;
|
|
5
|
+
这里只写"为什么这么选、什么时候不该这么选、实测值是多少"。
|
|
6
|
+
|
|
7
|
+
## A. 观测:什么时候用事件、什么时候用轮询
|
|
8
|
+
|
|
9
|
+
| 目标 | 手段 | 为什么 |
|
|
10
|
+
| :--- | :--- | :--- |
|
|
11
|
+
| 知道画面何时开始/停止变化 | `stream start` + 订阅 `event`(change / stable) | 事件带变化区域与帧序,不用自己比像素 |
|
|
12
|
+
| 确认某个状态现在在不在 | `reco`(对当前帧/缓冲帧) | 识别比"看图判断"确定 |
|
|
13
|
+
| 拿"变化前后"的画面 | 事件锚点帧 + `frame get --seq` | 锚点帧已在 L0/锚区,不需要重截 |
|
|
14
|
+
| 找"某个东西在哪" | `annotate` → 选号 → 得 ctrl 坐标 | 候选区域的坐标是算出来的,不是猜的 |
|
|
15
|
+
| 找"这里是什么颜色" | `color --roi` | 实测均值/HSV/主色,避免模型报色值 |
|
|
16
|
+
|
|
17
|
+
候选表的分辨率预期(实测,同一画面两种交付尺寸):
|
|
18
|
+
|
|
19
|
+
| | 720p(6 帧) | 1080p(1 帧) |
|
|
20
|
+
| :--- | :--- | :--- |
|
|
21
|
+
| 去重后候选 | 中位 54 / 最大 135 | 96 |
|
|
22
|
+
| 进表数(上限 30) | 30(4/6 帧打满) | 30(打满) |
|
|
23
|
+
| **画面覆盖率** | 中位 **19%** | **6.2%** |
|
|
24
|
+
|
|
25
|
+
上限 30 是固定的(面向模型上下文预算),所以**分辨率越高,"被标记的画面比例"越低**——1080p 下候选表
|
|
26
|
+
更像"取样"而不是"覆盖"。高分辨率场景不要指望它覆盖画面:优先 OCR(语义最强),其次点选(`crop --point`)。
|
|
27
|
+
|
|
28
|
+
同一条纪律也适用于采集:项目跑在高分辨率时,采集要在同一空间里做——
|
|
29
|
+
`maafw-live repl --kind adb --target <addr> --short-side 1080`,一次会话里 `screencap` + `kf promote latest`
|
|
30
|
+
(已在 1920×1080 上验过);项目模式跟 `interface.json` 的 `display_short_side` 走,别手动改。
|
|
31
|
+
|
|
32
|
+
节奏与容量:
|
|
33
|
+
|
|
34
|
+
- `stream` 的 fps 与 scale 决定 CPU 与环内容量;活跃场景 5–10 fps 够用,静态等待降到 1–2 fps。
|
|
35
|
+
- 环形缓冲(L1 小图)与 L0 原图缓存是两条链:**环里的小图能看不能当证据**,原图才能裁模板(见动线 C/D)。
|
|
36
|
+
- L0 有双重上限(张数 + 字节预算),先淘汰滚动区;`kf status` 看实时占用与 seq 区间——升格前拿不准就先看它。
|
|
37
|
+
|
|
38
|
+
校准(写进 pipeline 之前做一次,换场景再复测):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
$ maafw-live calibrate --project <dir> # 静止画面采样,给 blockThresh / changeGlobal 推荐值(REPL 无此命令)
|
|
42
|
+
maa> stream start --fps 6 --block-thresh <推荐> --change-global <推荐>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## B. 识别实测:怎么选图像来源
|
|
46
|
+
|
|
47
|
+
| 来源 | 命令 | 用途 |
|
|
48
|
+
| :--- | :--- | :--- |
|
|
49
|
+
| 离线 PNG | `reco --image f.png` | 复现历史帧上的识别;做参数扫描 |
|
|
50
|
+
| 缓冲帧 | `reco --seq <n>` | 对"刚刚那一帧"复测(需 `repl` 且已 `stream start`) |
|
|
51
|
+
| 当前画面 + 真机动作 | `reco --act --node <json>` | 验证 target 框语义与落点(**会真的动设备**) |
|
|
52
|
+
|
|
53
|
+
要点:
|
|
54
|
+
|
|
55
|
+
- 整节点透传(`--node`)只在"节点自己没写模板、而调用方提供了模板图"时补模板引用;其余字段一律原样交给框架,
|
|
56
|
+
V1 扁平与 V2 嵌套不做转换。字段语义有问题就查 MaaLLMWiki 的协议原文,不要在本工具层猜。
|
|
57
|
+
- `--sweep` 用来找阈值起点,不是用来"调到达标":扫描结果的最高分若 < 0.5,说明模板与画面不匹配。
|
|
58
|
+
- `--act` 会先识别、再动作;识别未命中时动作半不执行(如实报在 `stage`)——不要把"没执行"读成"执行了但没效果"。
|
|
59
|
+
它只接受 `--node` / `--node-file`,不接受 `--node-name`。按资源原名验识别用 `--node-name`;要连动作一起验,用 `run`。
|
|
60
|
+
- `connect project` 之后,REPL 的 `run` 与一次性 `run --project` 叠同一套四级 `pipeline_override` 和 `--preset`。
|
|
61
|
+
`--resource-dir` 只换资源来源;和 `--preset` 同给时 preset 不生效,命令会说明。
|
|
62
|
+
- 图像是判据的一部分:小图空间里算出的 box **不能**直接抄成 pipeline 的 roi。
|
|
63
|
+
|
|
64
|
+
## C. 模板裁剪:输入怎么给、结果怎么判
|
|
65
|
+
|
|
66
|
+
输入选择(**框该给多大有实测结论**,15 例真实 UI 目标):
|
|
67
|
+
|
|
68
|
+
- 知道大致范围 → `--roi x,y,w,h`(**宽松框**)。实测:**每边留 6–10px 最稳**;把上一次 snap 的输出直接
|
|
69
|
+
当输入也可以(文字类目标第二遍只差 ±1px)。**余量给到元素尺寸的 60–80% 就开始出问题**——
|
|
70
|
+
图鉴图标给 110×110 时 snap 收到 57×69(真值 69×62,宽度少了 12px),物品卡片给 180×180 时高度被拉到 152,
|
|
71
|
+
**而 `positionOk` 不会报警**(框仍与真值大幅重叠)。所以别用大余量,也别把 `positionOk` 当"框完美"的证明。
|
|
72
|
+
- 想知道框稳不稳 → **同一目标再裁一遍比 `box`**:文字类 ±1px;内部有渐变的图形可能再收一档
|
|
73
|
+
(实测物品卡片 120×120 → 104×113),那就把第二遍的结果当最终框。
|
|
74
|
+
- 只知道一个点 → `--point x,y [--pad n]`(默认外扩 24px),点→ROI 派生与裁剪共享同一条 snap 链。
|
|
75
|
+
- 边界贴太紧、目标撑满框、大面积纯色 → snap 可能撤不动;工具会把收紧框、加边距、外扩档、原框都进候选池,
|
|
76
|
+
位置正确者优先。仍不达标就换框,或改走 `annotate` 选点。
|
|
77
|
+
|
|
78
|
+
结果判读:
|
|
79
|
+
|
|
80
|
+
| 字段 | 怎么读 |
|
|
81
|
+
| :--- | :--- |
|
|
82
|
+
| `positionOk` | **第一判据**:模板在同帧上是否落回裁剪处。false = 模板不独特,别用 |
|
|
83
|
+
| `score` | 第二判据:位置对但得分 < 0.7 时提示注意跨帧稳定性 |
|
|
84
|
+
| `snapped` | snap 是否收紧成功;false 表示用了原宽松框 |
|
|
85
|
+
| `cross` | 跨帧验证(另抓一帧再匹配);`score` 大幅衰减或 `posOk=false` = 对动态区域敏感 |
|
|
86
|
+
| `spaceCheck` | 源帧空间 vs 当前控制器尺寸;`match:false` 时模板与当前识别空间不一致 |
|
|
87
|
+
| `provPath` | L2 出处侧车路径;`--no-prov` 时为 null |
|
|
88
|
+
|
|
89
|
+
耗时预期:自匹配是"逐候选起一个识别子进程"。**实测(MuMu v5 + maa-node 5.14.2,1280×720)**:
|
|
90
|
+
健康区域 **9 次评估 / 约 10.5s**(首个候选就位置正确、得分 ≥0.99 → 早停);退化区域 **17 次评估 / 约 20s**
|
|
91
|
+
(跑满候选池 + 两轮 ±2px 精修)。所以"数十秒"是上限口径,**耗时翻倍本身就是"这块内容不独特"的信号**。
|
|
92
|
+
分辨率影响很小:同元素在 1920×1080 上同样 9 次评估、11.9s(+13%)——成本由起子进程主导,不由像素数主导
|
|
93
|
+
(`snapTol` 是通道差之和、`snapFrac` 是比例,两者都与分辨率无关,实测 4/4 通过)。
|
|
94
|
+
不要误判为卡死;一次裁剪的成本要算进动线预算。
|
|
95
|
+
|
|
96
|
+
关于 snap 的预期(同一批实测):**框给得越大,snap 越可能不起作用**——480×270 这种占屏 1/6 的宽松框,
|
|
97
|
+
24 例里只有 1 例真的改变了最终框,其余都被候选池退回原框(`snapped:true` 只说明"算出了收紧框",
|
|
98
|
+
不代表最终用了它)。要 snap 出力,宽松框应当**贴着目标放大一圈**(例如目标 70×60、`--roi` 给 78×76),
|
|
99
|
+
而不是整屏网格。判据始终是 `positionOk` 与得分,不指望 snap 救场。
|
|
100
|
+
|
|
101
|
+
裁剪产物(L2)与出处:
|
|
102
|
+
|
|
103
|
+
```jsonc
|
|
104
|
+
// tpl.png.prov.json(schema 1)
|
|
105
|
+
{
|
|
106
|
+
"level": "L2",
|
|
107
|
+
"derivedFrom": { "kind": "kf", "id": "kf:<库UUID>:0142", "sha256": "...", "ctrlW": 1280, "ctrlH": 720 },
|
|
108
|
+
"transform": { "op": "crop", "resize": false, "scale": 1, "loose": [...], "crop": [...] },
|
|
109
|
+
"selfMatch": { "score": 1, "positionOk": true, "box": [...], "tries": 9 },
|
|
110
|
+
"cross": null
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
出处里**没有**设备地址/目标——L2 是要提交进仓库的交付物,设备来源留在本地 manifest。
|
|
115
|
+
|
|
116
|
+
`--project` 时自匹配资源只取 `resource.path[0]`。要测的 pipeline 在后面的 bundle 里,显式给 `--resource-dir`。
|
|
117
|
+
|
|
118
|
+
## D. 留存与离线裁剪:时间线
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
观测到状态 ──┬─ kf promote latest ← 必须在捕获它的那个 daemon 会话里做
|
|
122
|
+
│ (身份在接受请求时固定;已淘汰则失败,不取新帧冒充)
|
|
123
|
+
│
|
|
124
|
+
├─ kf list / kf resolve ← 纯离线,不依赖 daemon 存活
|
|
125
|
+
│
|
|
126
|
+
└─ crop --from-kf <ID> ← 离线裁剪:不需要设备,也不需要那次会话还活着
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
判断表:
|
|
130
|
+
|
|
131
|
+
| 情形 | 结论 |
|
|
132
|
+
| :--- | :--- |
|
|
133
|
+
| `kf resolve` → `available` | 文件在、哈希对、元数据全,可作裁剪源 |
|
|
134
|
+
| `missing` | 本机不可复核(文件丢失/无此 ID/裸序号);**不代表历史从未观测** |
|
|
135
|
+
| `corrupt` | sha256 不匹配(文件被改/损坏),拒绝裁剪 |
|
|
136
|
+
| `unsupported` | manifest 版本不认识或结构不符;不默认重建 |
|
|
137
|
+
| L0 已淘汰、只剩 L1 | 只能作参考帧,**不作 state 证据本体** |
|
|
138
|
+
|
|
139
|
+
裁剪源选择:
|
|
140
|
+
|
|
141
|
+
- 状态还在画面上 → 直接 `crop --roi`(热缓存路径,快,且能顺带跨帧验证)。
|
|
142
|
+
- 状态已消失/设备不在手上/换了会话 → `crop --from-kf`(离线;跨帧验证缺省关,`--cross` 才做)。
|
|
143
|
+
- 两边都要(状态可复现时):`--from-kf ... --cross`,同时得到"来源固定"与"新帧仍稳定"两个结论。
|
|
144
|
+
|
|
145
|
+
升格容量纪律:L0 缓存有界,`kf promote` 时机会决定你还有没有证据。动线纪律是**看到即升格,不攒到最后**;
|
|
146
|
+
库满额时工具会**拒绝新增并报告**(不会后台静默删除已升格帧),清理或扩容由人决定。
|
|
147
|
+
|
|
148
|
+
## Z. 经验值速查(全部来自实测;改动与否都写清)
|
|
149
|
+
|
|
150
|
+
| 项 | 值 | 实测依据 | 状态 |
|
|
151
|
+
| :--- | :--- | :--- | :--- |
|
|
152
|
+
| 宽松框余量 | **每边 6–10px**(或上一次 `snap` 输出直接当输入) | 15 例真实 UI 目标:14/15 位置正确;大余量(60–80%)时 snap 会偏而 `positionOk` 不报警 | 使用纪律 |
|
|
153
|
+
| 模板判据 | 位置正确 → 跨帧稳定 → **空间一致** | 网格 91.7% / 真实 UI 14/15;空间不符时退出 `FINDINGS(3)` | 判据链 |
|
|
154
|
+
| 裁剪耗时 | 健康 **9 次评估 ≈10.5s**;退化 **17 次 ≈20s** | 24+36+15 例;1080p 仅 +13% | 预期口径 |
|
|
155
|
+
| 裁剪成本与分辨率 | 基本无关(`snapTol` 是通道差、`snapFrac` 是比例) | 1920×1080 同元素 9 次、1.1× 耗时 | 已验 |
|
|
156
|
+
| `snapTol` / `snapFrac` | 90 / 0.12 | A/B:调"敏感"(60 / 0.2)触发更多但**位置正确率 91.7% → 83.3%** | **未改**(数据不支持) |
|
|
157
|
+
| SoM 候选上限 | 30,截断**按横向分带轮转** | 6 帧 4 次打满、真实规模中位 54、默认上限只放出 41%;改前大厅 63 个底部候选全被截掉 | **已按数据改** |
|
|
158
|
+
| 候选合并 IoU | 0.6 | 0.4 / 0.6 / 0.8 → 合并总数 289 / 310 / 328(±6%,平坦区) | **未改** |
|
|
159
|
+
| edge z / 地板 | 1.2 / 2000 | 放松到 0.6 / 600 后 edge 独立候选反而更少(17 → 15,大簇被 conn 吸收) | **未改**(别调它) |
|
|
160
|
+
| 候选覆盖率预期 | 720p 约 19%;**1080p 仅 6.2%** | 上限固定、分辨率越高覆盖越低 | 使用预期 |
|
|
161
|
+
| 识别阈值起点 | 实测最高置信度 **− 0.1**;得分 < 0.5 视为不匹配 | 参数扫描 + 假命中修复后的实测 | 使用纪律 |
|
|
162
|
+
|
|
163
|
+
要动上面任何一个常数:先跑 `npm run survey:crop` / `npm run survey:som`(本仓 `scripts/`,离线、不需要设备),
|
|
164
|
+
拿 A/B 数据再改——本表里"未改"的项都是量过之后**决定不动**的,不是没人管。
|