Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
*.json text eol=lf
*.yml text eol=lf
*.py text eol=lf
research/ui/*.js text eol=lf
research/ui/reference_sources/*.py.txt text eol=lf
*.ps1 text eol=crlf
experiments/core/fixtures/*.bin binary
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ if(BUILD_TESTING)
termforge_add_python_test(benchmark_tools -m unittest discover -s benchmarks/tests -v)
termforge_add_python_test(windterm_alternate_export -m unittest research.ui.test_export_alternate_reference -v)
termforge_add_python_test(windterm_alternate_style_reference -m unittest research.ui.test_export_alternate_style_reference research.ui.test_alternate_style_peer -v)
termforge_add_python_test(windterm_raster_style_reference -m unittest research.ui.test_raster_style_reference -v)
termforge_add_python_test(windterm_raster_selection_proposal -m unittest research.ui.test_raster_selection_reference -v)
termforge_add_python_test(differential -m unittest discover -s experiments/differential -p test_*.py -v)
termforge_add_python_test(ime_inventory -m unittest research.ime.test_hosted_inventory research.ime.test_prepare_hosted research.ime.test_product_ime -v)
termforge_add_python_test(text_actions -m unittest experiments.shell.test_actions -v)
Expand Down
57 changes: 57 additions & 0 deletions docs/research/windterm-1049-raster-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# 1049 当前样式:一次受控 Qt 栅格调查计划

状态:[#126](https://github.com/JavaZeroo/termforge/issues/126) 的一次执行已结束,在阶段 0 抓图前因 selectionText 守卫停止;颜色仍未知,见[实际结果与边界](windterm-1049-raster-reference-2026-10-28.md)。以下保留执行前计划,不把计划内容视为已观察结果。计划基点为 `72f40e10a8db0ef7585f8bec82f2be72f78c3d33`,并已接入 LF 属性守卫。

## 固定目标与边界

- 官方 WindTerm 2.7.0 Windows x64 EXE SHA:`7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`;4,047 文件清单 SHA:`e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203`。
- Qt5Widgets SHA:`3788c669d4b645e5a576de9fc77fca776bf516d43c89143dc2ca28291ba14358`;Qt5Gui SHA:`5e7d2d41b8b92a880e83b8cc0ca173f5da61218604186196787ee1600956be1e`。导出、局部指令和 Qt 5.15.2 官方头文件已只读核对。
- 继续使用独立 WindowStation/Desktop、OwnedJob、独立 HOME/profile 和一个 `127.0.0.1` 合成 Telnet 会话。用户桌面、剪贴板、DPI/显示设置均不参与。
- **最多新启动原版一次**。新生产脚本、新执行身份和新唯一输出目录,不复用旧批结果。REA capture 45 秒、CLI wait timeout 60 秒;原版启动在 CLI 之前,整程时限另列为未知,不能称 45 秒整程保证。
- Qt 的 QWidget grab 是受控栅格,不是物理显示/真实 presented。动态注入仅用于正确性;不计时、不声明性能或完整终端样式等价。

## 五个最小阶段

`ESC` 为 `0x1b`。每阶段末尾追加 `ESC[6n`,收到回复、模式值和栅格记录后才确认下一阶段。标签只用于从独立原版文档 API 定位,比较的字形始终为 `H`。每阶段只抓一次 viewport;第一阶段两个 H 属于同一张校准图。

| 阶段 | 精确合成源(不含末尾 DSR) | 目的 |
|---|---|---|
| 0 主屏校准 | `ESC[0mESC[2JESC[HESC[2;3HESC[31;44mM0 H H` | 在固定 SGR31/44 下取得两个同字形 H 与各自前面的显式空格,校准位置、背景和墨迹 |
| 1 首次进入 | `ESC[?1049hA0 H` | 不插入 SGR 或 CUP,观察进入后的当前样式 |
| 2 改变备用样式 | `ESC[4;5HESC[32;45mA1 H` | 取得另一组 H 的前景/背景颜色参照 |
| 3 重复进入 | `ESC[?1049hA2 H` | 不补 SGR,观察重复进入后的当前样式 |
| 4 退出后写入 | `ESC[?1049lM1 H` | 不补 SGR,观察主屏恢复后新写 H 的当前样式 |

为消除表格排版歧义,阶段 0 的文字载荷严格是 `M0 H H`,阶段 2 严格是 `A1 H`;控制序列之间、控制序列与载荷之间没有额外空格。实际来源十六进制及 SHA 必须在执行前由新脚本导出并固定。进入、重复进入、退出后均不补发清屏/样式恢复命令。

此次使用修订后的 peer,在每条完整 NAWS 解析时立即记录与检查,不能只看 `recv` 块末态。等 UI capacity 与 NAWS 稳定后才冻结源与坐标;冻结后尺寸改变、模式 getter 失败或阶段确认缺失均停止。新批事件级守卫结果只属于新执行,不回填旧 #123。

## Qt 调用合同

全部在 GUI 线程、原版阶段完成后调用。借用的自有 viewport 必须由父链、名称及模块路径守卫绑定。QRect 输入为 **16B 的 inclusive x1/y1/x2/y2**,全 viewport 为 `[0,0,width-1,height-1]`。

| 固定导出 | RVA | 已核对的调用 |
|---|---:|---|
| `?grab@QWidget@@QEAA?AVQPixmap@@AEBVQRect@@@Z`(Widgets) | `0x39520` | `grab(widget, result, rect)`;返回 result |
| `?toImage@QPixmap@@QEBA?AVQImage@@XZ`(Gui) | `0xad6f0` | `toImage(pixmap, result)`;返回 result |
| `?pixel@QImage@@QEBAIHH@Z` | `0x8e560` | `uint32 pixel(image, int x, int y)`,x/y 为 EDX/R8D |
| `?width@QImage@@QEBAHXZ` / `?height@QImage@@QEBAHXZ` | `0x94770` / `0x8d1a0` | `int(pointer)` |
| `?isNull@QImage@@QEBA_NXZ` | `0x8da30` | `bool(pointer)` |
| `?devicePixelRatio@QPixmap@@QEBANXZ` / `?devicePixelRatio@QImage@@QEBANXZ` | `0xabe80` / `0x8c2c0` | `double(pointer)` |
| `??1QImage@@UEAA@XZ` / `??1QPixmap@@UEAA@XZ` | `0x86350` / `0xab020` | 普通析构 `void(pointer)`,不是删除析构 |

QPixmap 与 QImage 各分配 32B,**不预先构造**,由正常返回的 sret 调用构造。只在相应调用正常返回后标记对象已构造;`finally` 按 image→pixmap 顺序调用普通析构。用 `QImage::pixel` 读取 QRgb,不解释内部 format、stride 或像素内存布局。官方依据:[QWidget](https://raw.githubusercontent.com/qt/qtbase/v5.15.2/src/widgets/kernel/qwidget.h)、[QPixmap](https://raw.githubusercontent.com/qt/qtbase/v5.15.2/src/gui/image/qpixmap.h)、[QImage](https://raw.githubusercontent.com/qt/qtbase/v5.15.2/src/gui/image/qimage.h)、[QPaintDevice](https://raw.githubusercontent.com/qt/qtbase/v5.15.2/src/gui/painting/qpaintdevice.h)。

## 坐标、DPR 与墨迹校准

1. 独立读取原版 `lineText`/`lineStart`,按唯一标签定位 H 与其前面的显式空格,调用 `visibleRectFromPosition` 获取真实矩形;同时主动调用 `isAltScreen/isMainScreen`。不沿用旧批的 x=147、8×18 或具体屏幕大小作为假定。
2. 记录实际 viewport 几何、pixmap/image DPR、非空状态、image width/height 和标记矩形。必须能用这组实测量建立逻辑坐标到图像坐标的一致对应,ROI 完全在图像内;非整数边界或尺寸关系不明就保持未知,不平移 ROI 搜索。
3. 只对 H 和显式空格的小 ROI 用 `QImage::pixel` 采样。校准空间格需给出一致背景;两个校准 H 需有非背景墨迹,且其背景及主要墨迹颜色一致。背景不明确、无墨迹或颜色分布不能稳定区分时停止样式判断。
4. 记录 `background_rgba`、`dominant_ink_rgba`、有限颜色直方图、ROI 尺寸及私有 ROI 数据哈希。抗锯齿产生的墨迹色仅按栅格统计描述,不冒称 QPainter pen 的原始 RGB。
5. 阶段 1/3/4 与阶段 0、阶段 2 的同字形 H 比较前景/背景标量。仅在标记绑定、DPR/坐标和墨迹校准均成立时报告具体一致/差异;歧义保持 `null`。这不是容差、性能预算或全颜色空间的验收。

空图、空 ROI、无墨迹、坐标/DPR 校准失败、原版异常或失去阶段握手,均保存失败/未知后结束这一次运行。不能改变假设、重新抓取试到绿或额外启动原版。完整栅格/ROI 原始数据、插桩、路径、PID 和原版资源留在 `local/`;公开仅发布可审查合成源、身份与脱敏颜色/尺寸标量,生产校验用显式 `require/ValueError`。

## 执行前出口

执行项为 #126。[生产探针](../../research/ui/raster_style_reference.py)及[GUI 驱动](../../research/ui/raster_style_reference.js)已先提交固定身份,再生成含每阶段 hex/SHA、完整 Qt DLL 哈希、脚本哈希和唯一输出目录的私有计划,并由单次 marker 防止重复启动。本计划不解除现有样式未知项;实际执行结果另列,不重跑。离线边界回归已注册为 `windterm_raster_style_reference`,不使用原版资源。
52 changes: 52 additions & 0 deletions docs/research/windterm-1049-raster-reference-2026-10-28.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 1049 栅格样式调查:阶段 0 守卫停止,颜色仍未知

[#126](https://github.com/JavaZeroo/termforge/issues/126) 的一次已授权原版执行在 **抓图之前**结束。阶段 0 的 `selectionText` 返回非空字符串,执行脚本把它当作停止条件;没有调用 QWidget grab、QPixmap toImage 或 QImage pixel,没有生成栅格、ROI 像素、颜色或 presented 结果。当前样式继承与恢复仍未知。

这是调查守卫的语义假定有误,不能称为原版存在选区遮挡或样式错误。原执行源码和原始记录保持不变,没有改条件再抓取、补跑阶段或第二次启动原版。

## 固定执行身份

- 生产源码:`15c736b9274ed6bd2ba93842d27da8bc2eb87008`;[五阶段计划](windterm-1049-raster-plan.md)。在启动前提交源码,再生成唯一计划,计划 SHA 为 `deb78215e2a12108d73b0fc82fa6f5bf8e979f28ba71013d72f28818ef3e085c`。
- 固定官方 2.7.0 Windows x64 EXE SHA:`7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`;4,047 文件清单 SHA:`e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203`。
- 启动前核对 WindTerm/Qt5Core/Qt5Gui/Qt5Widgets 的**完整文件 SHA**,复制后完整清单再次核对。离线重审复制文件,四个完整 SHA 均与计划一致;运行中四个模块实际路径及所需导出 RVA 守卫通过。这不等于实际调用过栅格函数。
- 独立 WindowStation/Desktop、OwnedJob、独立 HOME/profile 和一个合成 loopback Telnet 会话;没有用户输入桌面、剪贴板或 DPI 变更。计划的单次 marker 保留,实际仅启动原版一次。
- REA capture=45 秒、CLI wait timeout=60 秒;原版更早启动并另有 2 秒等待,回收在 CLI 返回后进行,不提供整程 45/60 秒保证。动态插桩只用于正确性,不计性能。

REA Evidence ID 为 `ev_cedd36bf326d1769fa8d0d7b1cc508314eb22da43ba97faf80debbcf9b085e63`。完整脱敏标量及原件哈希见[白名单结果](../../research/ui/results/2026-10-28-raster-style-reference/validation.json)。

## 本次实际发生的步骤

五阶段分别计划主屏 SGR31/44 同图双 H 校准、首次 1049 进入、备用 SGR32/45、重复进入及退出后写 H。实际只发送了第一阶段的 31B 源及 `CSI 6n`,没有确认文件释放后四阶段。

阶段 0 精确源为 `ESC[0mESC[2JESC[HESC[2;3HESC[31;44mM0 H H`;DSR 回复为 `(2,9)`,主/备用状态为 `false/true`。NAWS 完整事件历史为 `91×24 → 91×25`,源冻结时事件数为 2;这次使用逐条完整 NAWS 检查的修订 peer,没有观察到冻结后的改变。

原版文档 API 返回了两个 H 标签位置。`selectionText` 返回 string,长度 10;`caretPosition=9`,caret rect 为 `x=211,y=18,width=0,height=18`。执行中的判断是 `selectionText === ''`,因此以原码 `selection-overlaps-observation` 停止:

| 指标 | 实际结果 |
|---|---|
| 已观察输入阶段 | 1 / 计划 5 |
| grab 尝试 | 0 |
| QPixmap/QImage 已构造与析构 | 均为 0 |
| 栅格与 H/space ROI 像素 | 无 |
| 颜色校准、比较 | 未执行,保持未知 |
| 原版真实选区或遮挡 | 未证实 |

原停止码保留原样,但导出另列 `actual_selection_or_overlap_proven=false`。非空 `selectionText` 不是这里可靠的选区判据,也没有因为未读到像素就声称 H 无墨迹。

## 既有原件对守卫假定的反证

停止后只读复核[既有原版样式/区域观察](https://github.com/JavaZeroo/termforge/blob/dce2ca799a25cf32f3cf2bacf1b9f7c915945a57/docs/research/windterm-styled-margin-reference.md)的本地原件,未运行原版。其 Evidence ID 为 `ev_8fbd9aead9aa5d60f49e05112c31eb542febf65084643c025eb8a52ffd590048`,`evidence.json` SHA 为 `be349abb34cf28f42b634ccf65bd0749e1e9940856059156b794e7968b7ef83c`。

该原件的 shell-ready、actual80x24 快照均为 `caret=anchor=97`,但 `selectionText` 长度为 5;styled-final 为 `caret=anchor=400`,但文本长度为 10。这是“非空返回值足以证明有选区”的直接反例。它不替代本批未采集的 anchor,也不证明本批实际选区已折叠;只说明本批使用的守卫判据不成立。下一次调查应先按原版实际 anchor/caret 合同明确选区状态,再决定是否抓取。

[两条脱敏反例](../../research/ui/results/2026-10-28-raster-style-reference/selection-getter-counterexample.json)只发布 caret、anchor 和 selection_length 三个值,并绑定原 Evidence ID、文件 SHA 与固定 EXE 身份;没有文字或路径。[专用离线导出器](../../research/ui/export_selection_counterexample.py)从固定原件核对并投影这些值,不启动原版。

## 离线审计与剩余边界

REA 正常结束,无脚本错误、消息截断、清理错误或 stderr;Job 最终 `active=0,total=3,terminated=0`,合成 profile 仍为一个会话。启动器主动结束原版,不以 Job 计数声称自然退出。provider 的 subject/environment 为 null,包身份由独立准备及模块路径守卫核对。

[生产探针](../../research/ui/raster_style_reference.py)、[GUI 脚本](../../research/ui/raster_style_reference.js)和原计划中的执行 SHA 未变。[离线导出](../../research/ui/export_raster_style_reference.py)使用显式 `require/ValueError`,重新核对源字节、消息、对象生命周期、像素派生值及单次抓取边界;本批为 partial,所以没有填充任何颜色结论。普通及 `python -O` 对同一原件导出字节相同。

[回归](../../research/ui/test_raster_style_reference.py)在普通/优化解释器下各 11 项通过,覆盖完整文件哈希尾部变更、模块路径、32B sret 正常返回后的析构顺序、ROI/墨迹边界、空图、额外抓取、私有文本拒绝、反例仅投影端点/长度,以及守卫停止不得冒充实际选区/像素证据。原生调用边界使用合成对象验证,不能转记成这次未执行的 Qt 栅格调用已通过。CTest `windterm_raster_style_reference` 单独通过。

原始日志、路径、PID、profile、复制资源和可能的 ROI 原始数据均留在 `local/r126-raster-0`;公开只有合成源、身份、停止原因、标量和哈希。进入/退出当前样式、受控栅格、完整网格及真实 presented 均未验证;本次不关闭 #126、#123、#27 或 R2。
Loading