From dc5c631ffdf83dfbdfcb8b359137e0d87212ac6d Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:27:56 +0800 Subject: [PATCH] test(conpty): retain styled cursor comparison and blank foreground gap (#56) --- docs/EVIDENCE.md | 1 + docs/FEATURES.md | 2 +- .../research/conpty-source-margin-controls.md | 2 +- docs/research/conpty-source-margin-styles.md | 61 + experiments/conpty/margin_controls.py | 60 +- experiments/conpty/margin_controls_probe.cpp | 11 +- .../validation.json | 1571 +++++++++++++++++ experiments/conpty/source_margin_controls.py | 41 +- experiments/conpty/test_margin_controls.py | 86 + 9 files changed, 1811 insertions(+), 24 deletions(-) create mode 100644 docs/research/conpty-source-margin-styles.md create mode 100644 experiments/conpty/results/2026-10-27-source-margin-styles/validation.json diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index 657ef767..b0df9658 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -53,6 +53,7 @@ R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分 | 原生 margin 逐字段合同与历史修复候选(#56 仍开放) | [合同、源码与路径](research/conpty-native-margin-path.md)、[独立 oracle](../experiments/conpty/margin_contract.py)、[2026-10-26固定运行](../experiments/conpty/results/2026-10-26-native-margin-path/validation.json) | 当时系统与官方原生 final 仍红,官方80列两次原5秒生命周期失败保留;补丁当时仅可应用。后续真实同源构建/运行另列,不改写旧失败。 | | 固定源码 ConPTY margin 运行对照(#56 限定实验) | [构建身份、运行与复验](research/conpty-source-margin-runtime.md)、[白名单验证](../experiments/conpty/results/2026-10-27-source-margin-runtime/validation.json) | 同源baseline与patched,原175B两尺寸native red→green;四阶段文本/格属性/光标、末端文本和VT-off反例独立检查,共8次自然退出/EOF/Job0。15个API/身份场景及8个清单拒绝通过;显式样式、历史、resize、完整产品未验,未选择后端。 | | 源码 ConPTY 主屏与重复切屏对照(#56 限定实验) | [输入、预期与结果](research/conpty-source-margin-controls.md)、[白名单验证](../experiments/conpty/results/2026-10-27-source-margin-controls/validation.json) | 三场景×两尺寸×同源两版共12次;主屏均正确,默认备用屏/重复h/l为具体旧错误→原生全部正确,两版末端最终文字均正确;全部自然退出/EOF/Job0。独立输入不替代原175B,样式/历史/resize及产品未验。 | +| 源码 ConPTY 样式与非零光标对照(#56 限定实验) | [五阶段与空白格差异](research/conpty-source-margin-styles.md)、[原始/修正判据验证](../experiments/conpty/results/2026-10-27-source-margin-styles/validation.json) | 同源两版×两尺寸共4次,patched原生五阶段全字段通过,baseline仅final符合具体旧错;两版末端文字/背景/光标/current_style通过,但滚动空白前景色144/8格不同,完整属性仍失败。默认空格序列化判据错误已离线修正,原运行未重试;WindTerm及完整合同未知。 | | ConPTY 系统/固定官方后端的同源 margin A/B(#41、#47 未关闭) | [合同与第一方来源](research/conpty-margin-backends.md)、[固定 175B 与 16 case 结果](../experiments/conpty/results/2026-10-26-margin-backends/validation.json) | 相同 175B、两网格/两 mode/两 fresh:系统末端 margin 错,固定官方后端的末端正确;CHAR_INFO 仍错。禁用 VT 由窄屏反例拒绝;仅诊断可行性,不证明 PR #50 或 main 联合整合通过。 | | 实验输入 worker 持块与停止(#62) | [sender fence 合同](research/sender-fence.md)、[固定源码与实际字段](../experiments/shell/results/2026-10-26-sender-fence/validation.json) | `519a3f3` 的系统/官方各两进程、每次 stop/cancel/竞争,共 12 条实际 worker 记录,均 join/Job0;另有各一次原 focus 单测和各 2/2 纯 CTest。明确修正旧 never-read/output-first 不能保证 16KiB 的夹具假设,同 EXE 旧顺序仍红。实现仅在 PR #50 实验整合,main 未包含;probe 未链接候选 VT,纯 EOF 为模拟,#53 UI/3MB/768B/八会话及完整官方矩阵未验收。 | | 实验 UI 未完成粘贴(#53) | [机制与边界](research/ui-paste-fence.md)、[旧固定记录](../experiments/shell/results/2026-10-26-ui-paste-fence/validation.json)、[组合修复](research/resize-replay-cost.md)、[最终同源码记录](../experiments/shell/results/2026-10-26-replay-cost-and-close/validation.json) | `eae4b6a` 单窗口两轮、真实EOF/无GO负例及两轮原八会话data-only通过;行键复用及关闭期间不回复分别有诊断/因果回归,原completed/stage45/退出错误保留。完整CTest54/56仍有Vim59、resize43、main7/ui52与shell180秒期限;未计真实CF/IME、完整后端或性能通过。 | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 2e3401b0..3e4f52f8 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -16,7 +16,7 @@ | 显式候选 main:`termforge.4` + 系统 ConPTY | `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON`;按 [ADR-0003](adr/0003-core-reference-and-reuse.md)保留为可评估实验,正式核心选型待用户确认 | [固定来源](../experiments/candidates/alacritty/PROVENANCE.md)、[适配边界](../experiments/core/CANDIDATE.md)与 [#39 字素合同](research/streaming-graphemes.md);候选正确性通过不关闭系统 ConPTY 或 R2 父项 | | 实验分支:`termforge.5` + 固定官方 ConPTY | [PR #50(Draft)](https://github.com/JavaZeroo/termforge/pull/50),`.4` 联合源码 `3e5b96d` 的[真实基线](research/joint-conpty-baseline.md)保留,尚未合并 main 或通过产品整体验收 | [#51 原生 viewport 修复](research/native-viewport-resize.md)的原 resize/Unicode 及原生样本、[#88 回显完成判据](research/workspace-restore-transport-oracle.md)的原保存/恢复/钳制分别通过;[#87 完整记录检查点](research/workflow-record-checkpoint.md)有受控旧红/新绿及最终同源码两轮原八会话 data-only。最新官方完整回归 56/57、8 项内部 skip,[Vim #52](https://github.com/JavaZeroo/termforge/issues/52)仍红,[MSYS输入边界](research/vim-msys-input-boundary.md)已定位捕获样本的Ctrl-O丢失位置,后续[隔离运行库补丁](research/vim-msys-runtime-prototype.md)在重建基点red后原方法四轮通过;正式固定运行库验收未完成,[#63 工作区残尾](research/workspace-native-viewport.md)已由同EXE单变量与运输回放确认现有viewport修复覆盖,原UI/主区三轮重复两批及完整shell通过;更早缺运输样本保留未知。[#53 UI fence](research/ui-paste-fence.md)与[行键复用/关闭回复](research/resize-replay-cost.md)、[#62 worker 夹具](research/sender-fence.md)各自限定通过。终端 margin 与原生状态分列,#47 及完整后端仍开放;旧失败和历史运输缺口保留 | -另有默认关闭的[源码 ConPTY margin 探针实验](research/conpty-source-margin-runtime.md):固定同源 baseline/patched 对照在原175B两尺寸取得原生四阶段 red→green,VT-off反例保持一致。后续[主屏与重复切屏对照](research/conpty-source-margin-controls.md)以独立输入完成12次原生/末端验证,补充默认备用屏区域和重复h/l的限定证据。它只构建独立探针,尚未成为产品后端;样式、历史、resize及完整合同仍由 #56/#41 跟踪。 +另有默认关闭的[源码 ConPTY margin 探针实验](research/conpty-source-margin-runtime.md):固定同源 baseline/patched 对照在原175B两尺寸取得原生四阶段 red→green,VT-off反例保持一致。后续[主屏与重复切屏对照](research/conpty-source-margin-controls.md)以独立输入完成12次原生/末端验证,补充默认备用屏区域和重复h/l的限定证据。[样式/非零光标对照](research/conpty-source-margin-styles.md)的补丁版五阶段原生检查通过,但末端滚动空白前景色仍与严格判据不同,完整属性比较失败;初始默认空格序列化判据错误已单列修正。它只构建独立探针,尚未成为产品后端;其他样式、历史、resize及完整合同仍由 #56/#41 跟踪。 以下能力默认指已合并实现;某个配置的通过结果不能转用于另一个配置。R1 的真实系统 CF/拼音验收属于固定自研核心配置,不外推到候选 `.4` 或实验后端联合配置。 diff --git a/docs/research/conpty-source-margin-controls.md b/docs/research/conpty-source-margin-controls.md index 7805f167..e75a114d 100644 --- a/docs/research/conpty-source-margin-controls.md +++ b/docs/research/conpty-source-margin-controls.md @@ -41,4 +41,4 @@ python -m experiments.conpty.source_margin_controls --baseline local/source-base python -m unittest experiments.conpty.test_margin_controls -v ``` -本次只补无备用屏、默认备用屏区域、重复 h/l 的真实交叉。显式样式、非零光标保存恢复、历史隔离/压力、resize、查询/API 写入、完整产品工作流、性能及维护升级仍未验证;原 175B 的 VT-off 证据属于前一批,不能合并成同批更大矩阵。原系统/官方失败、#52、#97 和 #99 的独立失败继续保留。正式方案仍由 #34 提交证据及取舍,经用户确认后生效。 +本次只补无备用屏、默认备用屏区域、重复 h/l 的真实交叉。显式样式与非零光标的后续[独立对照](conpty-source-margin-styles.md)记录原生通过及末端空白前景色差异,不回写本批结果。历史隔离/压力、resize、查询/API 写入、完整产品工作流、性能及维护升级仍未验证;原 175B 的 VT-off 证据属于前一批,不能合并成同批更大矩阵。原系统/官方失败、#52、#97 和 #99 的独立失败继续保留。正式方案仍由 #34 提交证据及取舍,经用户确认后生效。 diff --git a/docs/research/conpty-source-margin-styles.md b/docs/research/conpty-source-margin-styles.md new file mode 100644 index 00000000..6f99b89b --- /dev/null +++ b/docs/research/conpty-source-margin-styles.md @@ -0,0 +1,61 @@ +# 源码 ConPTY:样式、光标恢复与空白格差异 + +固定同源 margin 补丁在本次五阶段原生检查中全部通过:备用屏退出后,主屏光标和红字蓝底属性恢复,后续滚动仍使用主屏 `2;3r`。未修补版前四阶段正确,最后一阶段出现预先固定的旧区域错误。不过,两版实际 transport 的候选末端都有滚动空白格的前景色元数据差异,**完整末端属性比较仍失败**。文字、背景色、光标和当前绘制属性通过,不代表完整合同通过。 + +这是 [#56](https://github.com/JavaZeroo/termforge/issues/56) 在[三个原生 controls](conpty-source-margin-controls.md)之后的独立实验。WindTerm 2.7.0 对该场景的实际行为尚未观测;本记录不选择产品后端,不批准候选核心策略,也不关闭 #56/#41。 + +## 运行前固定的场景 + +沿用[原 175B 实验](conpty-source-margin-runtime.md)已核验的 Terminal `dc8ae365847d41f62d1ebca93815bb00959bc12f` baseline/patched、构建 run `40449292015` 及双文件/补丁/summary receipt;上游源码与补丁未修改。独立 `--styled-cursor` 入口只执行一个 211B 场景,在 `80×24`、`12×7` 下分别运行两版,每格一次,共四次,无重试。原 175B 与默认三个 controls 保持原入口。 + +| 阶段 | 固定操作与原生判据 | +|---|---| +| before | 写入区域内外标记,主屏 `2;3r`,SGR `31;44`,光标第 3 行第 6 列 | +| alt | 进入备用屏,在 `4;5r` 内以 SGR `32;45` 滚动;光标第 5 行第 8 列 | +| restored | 离开备用屏,原主屏内容及第 3 行第 6 列光标恢复 | +| after-write | 直接写 `M`,原生第 3 行第 6 格为红字蓝底,光标前进一格 | +| final | 回到主屏区域滚动,保留区域外内容;`REGION_C`、`REGION_D` 及其尾部空白为红字蓝底;第 6 行写 `SGR_DONE` | + +全文、完整 `CHAR_INFO` 字符/属性、光标和 viewport 分别使用独立字面值检查。原生红字蓝底为 `0x14`、绿字紫底为 `0x52`;末端 trace 的 ANSI 索引另用 `fg=1/bg=4`。baseline 必须匹配具体旧状态,异常或任意失败不计为有效反例。 + +## 实际结果与判据修正 + +| 观测 | 两个尺寸的 baseline | 两个尺寸的 patched | +|---|---|---| +| 原生前四阶段 | 全字段通过 | 全字段通过 | +| 原生 final | 文本/格属性失败,符合旧区域预测:保留 `REGIOM_B` 并出现 `REGION_CIXED`;光标/viewport 通过 | 全字段通过 | +| 实际 transport 末端 final | 文字、主屏、光标及 current_style 通过;完整单元格比较失败 | 相同 | +| 生命周期 | 自然退出 37、EOF 109、flags=0、outer Job active0/total4/terminated0,无 force | 相同 | + +所有样本仍使用 5 秒 child wait、15 秒外期限、共享 5 秒 cleanup。四份源字节均为 211B / SHA `51d0f55717ff8fbe5e82040967edf2f9399a9b893fd8e8cb282f94c143919b62`;实际运输均为 254B / SHA `b584cd146f447c1e78e51d74e6d1c38c6a93d860d738a0afacdf959c8327d9bd`。未执行 GUI 产品流程或呈现测量。 + +首次判据把未触碰的默认空格错误地写为 `text=" " / width=1`,而已有 [trace 编码](../../experiments/candidates/alacritty/src/main.rs)对默认空单元使用 `text="" / width=0`,有背景色的空白才保留空格与宽度 1。离线审计发现这一测试错误,新增反例先失败,再修正编码;原四次运行、原 summary、原退出 1 结果全部保留,没有重跑原生程序。 + +修正后重新读取同四份捕获,80 列每次排除 1731 格、12 列每次排除 31 格的错误序列化预期;**显式前景/背景的预期没有放宽**。剩余差异只在零基第 1、2 行、第 8 列至行尾: + +- 80 列每次 144 格,12 列每次 8 格。 +- 文字均为一个空格、width=1、背景色均为 4、bold=false。 +- 期望显式前景色 1,实际为默认前景色 -1。写出的彩色文字及 current_style 仍为 1/4。 + +原始与修正后的完整比较都为失败。不能将默认空单元的判据错误算为产品缺陷,也不能将剩余空白前景色差异直接认定为用户可见错误。 + +固定 Alacritty 上游 `542147067dd016fc5f49120ef0ba605a98a4e3a7` 的 [`Cell::reset`](https://github.com/alacritty/alacritty/blob/542147067dd016fc5f49120ef0ba605a98a4e3a7/alacritty_terminal/src/term/cell.rs)只保留 template 的背景色,其余字段取默认值;当前 `.5` 候选保持该逻辑,滚动清行调用这条路径。这解释了所捕获的候选元数据结果。该源码观察不证明 WindTerm 的行为,也不构成改变产品合同或修改候选核心的批准。 + +## 身份、验证与复验 + +[脱敏清单](../../experiments/conpty/results/2026-10-27-source-margin-styles/validation.json)保留原运行身份、原始 summary 哈希、四份捕获身份、离线修正来源及两种失败结论。 + +- 实际原生运行源码 `485c3e253e88a2d83f72326c0ff9185b94d87269`,当时工作树干净;离线判据修正源码 `6e52e6957de10c14c570d3b7b83992c585fcc3a9`,不冒充重新运行。 +- Windows 11 x64 build 26200;VS17 / MSVC 19.42.34433 / SDK 26100,Release x64。独立探针 SHA `ca19645b65aea8ac74b56d1e59837cb7514c3acf5932d24112411725b92206d7`;复用前批 `.5` trace SHA `5941d4569d0ae146eb005f12ecfb15014bc06c18ee48d1c9460824ab65df93d3`。上游 pair 是 hosted VS18 构建,两者身份分列。 +- 初始纯判据 7/7;空单元编码反例 1 次失败后,修正判据 7/7、0.069 秒,包含错误前景/背景、光标、current_style、文字、宽度和主屏状态的拒绝。时长仅为测试诊断。 +- 前批 12 份 controls 捕获离线复查通过,未增加原生执行。此次完整 styled 矩阵仍失败,不能用纯测试成功替代。 +- 私有原件位于 `local/r56-style-controls/matrix-1`;公开清单只有合成输入/输出、结果、来源和哈希,不含二进制、用户状态或绝对路径。 + +按[前批构建与来源说明](conpty-source-margin-controls.md#复验与剩余范围)生成 controls 探针和候选 trace,并核验固定 baseline/patched;使用新输出目录: + +```powershell +python -m experiments.conpty.source_margin_controls --styled-cursor --baseline local/source-baseline --patched local/source-patched --probe local/source-controls-build/bin/Release/conpty_source_margin_controls.exe --candidate-trace experiments/candidates/alacritty/target/release/termforge-alacritty-trace.exe --output local/source-styles-new +python -m unittest experiments.conpty.test_margin_controls -v +``` + +保留失败判据,当前同配置预计矩阵退出 1;不得通过重试取绿。其他 SGR/真彩色、终端中间阶段、历史隔离/压力、resize、查询/API、完整产品、性能、升级维护及 WindTerm 对照仍待完成。`TERMFORGE_BUILD_SOURCE_CONPTY_EXPERIMENT` 默认 OFF,正式方案仍由 #34 按证据提案及用户确认决定。 diff --git a/experiments/conpty/margin_controls.py b/experiments/conpty/margin_controls.py index bcd9c106..a1b2e114 100644 --- a/experiments/conpty/margin_controls.py +++ b/experiments/conpty/margin_controls.py @@ -4,6 +4,12 @@ def expected_source(case, columns, rows): expected_native(case, columns, rows) # Fixed workload/grid validation. + if case == "styled-cursor": + return (b"\x1b[0m\x1b[2J\x1b[HTOP_FIXED\r\nREGION_A\r\nREGION_B\r\nBOTTOM_FIXED\r\n" + b"\x1b[2;3r\x1b[31;44m\x1b[3;6H" + b"\x1b[?1049h\x1b[0m\x1b[2J\x1b[HALT_OUTSIDE\r\n\x1b[4;1HALT_A\r\nALT_B" + b"\x1b[4;5r\x1b[32;45m\x1b[5;1H\nALT_C\nALT_D\x1b[5;8H" + b"\x1b[?1049lM\x1b[3;1H\nREGION_C\nREGION_D\x1b[6;1HSGR_DONE") parts = [b"\x1b[2J\x1b[HTOP_FIXED\r\nREGION_A\r\nREGION_B\r\nBOTTOM_FIXED\r\n\x1b[2;3r"] if case != "main-only": if case == "repeat-alt": @@ -15,12 +21,22 @@ def expected_source(case, columns, rows): def expected_native(case, columns, rows): - if (columns, rows) not in ((80, 24), (12, 7)) or case not in ("main-only", "default-alt", "repeat-alt"): + if (columns, rows) not in ((80, 24), (12, 7)) or case not in ("main-only", "default-alt", "repeat-alt", "styled-cursor"): raise ValueError("fixed native control and grid required") def state(lines, cursor): return {"lines": lines + [""] * (rows - len(lines)), "cursor": cursor} + if case == "styled-cursor": + before = state(["TOP_FIXED", "REGION_A", "REGION_B", "BOTTOM_FIXED"], [2, 5]) + alternate = state(["ALT_OUTSIDE", "", "", "ALT_C", "ALT_D"], [4, 7]) + alternate["attribute_spans"] = [(3, 0, columns, 0x52), (4, 0, columns, 0x52)] + after_write = state(["TOP_FIXED", "REGION_A", "REGIOM_B", "BOTTOM_FIXED"], [2, 6]) + after_write["attribute_spans"] = [(2, 5, 6, 0x14)] + final = state(["TOP_FIXED", "REGION_C", "REGION_D", "BOTTOM_FIXED", "", "SGR_DONE"], [5, 8]) + final["attribute_spans"] = [(1, 0, columns, 0x14), (2, 0, columns, 0x14), (5, 0, 8, 0x14)] + return {"before": before, "alt": alternate, "restored": before, "after-write": after_write, "final": final} + before = state(["TOP_FIXED", "REGION_A", "REGION_B", "BOTTOM_FIXED"], [0, 0]) result = {"before": before} if case != "main-only": @@ -39,14 +55,17 @@ def _evaluate(stages, expected, columns, rows): for capture in stages.values(): if (capture["columns"], capture["rows"]) != (columns, rows): raise ValueError("native control grid changed") - # No control selects SGR. Compare all cells against the initial attribute; - # this checks preservation, without choosing a portable default color. + # The initial default is observed, while explicit SGR colors below use + # independent Win32 attribute values, including erased trailing cells. attribute = stages["before"]["cells"][0][0][1] results = {} for name, target in expected.items(): actual = stages[name] wanted = [[(ord(character), attribute) for character in line.ljust(columns)] for line in target["lines"]] + for row, begin, end, explicit in target.get("attribute_spans", ()): + for column in range(begin, end): + wanted[row][column] = (wanted[row][column][0], explicit) fields = {"text": actual["lines"] == target["lines"], "cell_text": actual["cell_lines"] == target["lines"], "cells_and_attributes": actual["cells"] == wanted, @@ -61,9 +80,42 @@ def evaluate_native(stages, case, columns, rows): return _evaluate(stages, expected_native(case, columns, rows), columns, rows) +def evaluate_terminal(record, case, columns, rows): + target = expected_native(case, columns, rows)["final"] + screen = record["screen"] + cells = screen["cells"] + lines = ["".join(cell["text"] for cell in line if cell.get("width", 1)).rstrip() for line in cells] + alternate = bool(record["mode_bits"] & (1 << 12)) + fields = {"text": lines == target["lines"], "main_screen": not alternate} + if case == "styled-cursor": + # The terminal trace uses ANSI palette indices; CHAR_INFO uses Win32 + # foreground/background bits. Keep their literal oracles independent. + default = {"fg": -1, "bg": -1, "bold": False} + explicit = {"fg": 1, "bg": 4, "bold": False} + # Match the trace's existing empty-cell encoding, while still requiring + # explicit foreground and background on the colored scroll blanks. + wanted = [[{"text": character if column < len(line) else "", + "width": 1 if column < len(line) else 0, "style": default.copy()} + for column, character in enumerate(line.ljust(columns))] for line in target["lines"]] + for row, begin, end in ((1, 0, columns), (2, 0, columns), (5, 0, 8)): + for column in range(begin, end): + cell = wanted[row][column] + cell.update(text=cell["text"] or " ", width=1, style=explicit.copy()) + fields.update(cells_and_styles=cells == wanted, + cursor=[screen["row"], screen["column"]] == [5, 8], + current_style=screen["current_style"] == explicit) + return {"fields": fields, "passed": all(fields.values()), "lines": lines, "alternate": alternate} + + def matches_unpatched_prediction(stages, case, columns, rows): expected = expected_native(case, columns, rows) - if case != "main-only": + if case == "styled-cursor": + expected["final"] = { + "lines": ["TOP_FIXED", "REGION_A", "REGIOM_B", "REGION_CIXED", "REGION_D", "SGR_DONE"] + [""] * (rows - 6), + "cursor": [5, 8], + "attribute_spans": [(2, 5, 6, 0x14), (3, 0, 8, 0x14), (4, 0, 8, 0x14), (5, 0, 8, 0x14)], + } + elif case != "main-only": # With the main region still active, LF at the physical bottom clamps # instead of scrolling. ALT_DONE overwrites ALT_LAST; ALT_TOP remains. expected["alt2" if case == "repeat-alt" else "alt"] = { diff --git a/experiments/conpty/margin_controls_probe.cpp b/experiments/conpty/margin_controls_probe.cpp index 6e6b6069..122a09bf 100644 --- a/experiments/conpty/margin_controls_probe.cpp +++ b/experiments/conpty/margin_controls_probe.cpp @@ -24,7 +24,16 @@ static void write(const std::string& bytes) { "control output failed"); } static std::vector> stages(const std::wstring& name, int rows) { - require(name == L"main-only" || name == L"default-alt" || name == L"repeat-alt", "fixed control required"); + require(name == L"main-only" || name == L"default-alt" || name == L"repeat-alt" || name == L"styled-cursor", "fixed control required"); + if (name == L"styled-cursor") return { + {"before", "\x1b[0m\x1b[2J\x1b[HTOP_FIXED\r\nREGION_A\r\nREGION_B\r\nBOTTOM_FIXED\r\n" + "\x1b[2;3r\x1b[31;44m\x1b[3;6H"}, + {"alt", "\x1b[?1049h\x1b[0m\x1b[2J\x1b[HALT_OUTSIDE\r\n\x1b[4;1HALT_A\r\nALT_B" + "\x1b[4;5r\x1b[32;45m\x1b[5;1H\nALT_C\nALT_D\x1b[5;8H"}, + {"restored", "\x1b[?1049l"}, + {"after-write", "M"}, + {"final", "\x1b[3;1H\nREGION_C\nREGION_D\x1b[6;1HSGR_DONE"}, + }; std::vector> result{{"before", "\x1b[2J\x1b[HTOP_FIXED\r\nREGION_A\r\nREGION_B\r\nBOTTOM_FIXED\r\n\x1b[2;3r"}}; if (name != L"main-only") { diff --git a/experiments/conpty/results/2026-10-27-source-margin-styles/validation.json b/experiments/conpty/results/2026-10-27-source-margin-styles/validation.json new file mode 100644 index 00000000..503b5ecc --- /dev/null +++ b/experiments/conpty/results/2026-10-27-source-margin-styles/validation.json @@ -0,0 +1,1571 @@ +{ + "upstream_source": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "generated_utc": "2026-10-27T07:07:20.450780+00:00", + "build_run": "40449292015", + "source": "485c3e253e88a2d83f72326c0ff9185b94d87269", + "working_tree_dirty": false, + "source_files": { + "CMakeLists.txt": "9afd4590855f90d0e467824a49fc75a6f24b0d88ba7f4ff4ad92764239b682b3", + "experiments/conpty/backend.hpp": "af672718af8c05e90a26576e317b6ac9295fbee76f3f0298371060a9d167f5bb", + "experiments/conpty/session.hpp": "242ac8aa13f3fd56675b2ddefaaf207e5f4c8d3ef8e703260aaed2f0c41677aa", + "experiments/conpty/test_backend.py": "ab371ff7fbf196061451b96009a8260afb35473c1d05b2f99dda66e2b6a9bbba", + "experiments/conpty/margin_controls_probe.cpp": "5095dd3a113b799d417e522cbbe30aa0217a539dd46e1c096a8b707ab709b4c7", + "experiments/conpty/margin_controls.py": "e538503832c453cfd72d285969dcb1c4f78f6519fe06540c48f9a5a78d9c1138", + "experiments/conpty/source_margin_controls.py": "8476a975ac959468cf6177d3301cedd53e2a41d9c3ba029b1ac066c1999d2a99", + "experiments/conpty/margin_run.py": "99e6caf18224ce7b41307355066db70eacb810bc0626b15d3af09c9a89439ed2", + "experiments/conpty/source_margin_run.py": "26d07c9f912b1bb68b89d33a989ee80189230002869df83a572a22e7b2f6f657", + "experiments/conpty/run.py": "c8eef1e36b6d200b2d508cef91eaa5ca79a91fa87c5daf6265b2475c31dce329", + "experiments/candidates/alacritty/run.py": "2d83240506bf03b4da92f7b2dc85adf40446aff25cd0f0dbe08132b611c2627e", + "experiments/shell/workflow_fixture.hpp": "332d73699320a6f503fc212417a4d3820b82cc2d3feb3d1fffe3930660d57453", + "experiments/shell/workflow_checks.py": "f186ff0f5335c26fc5c4f0286961981dfd6af8fbaafc789e2bc50058486efa44" + }, + "platform": "Windows-11-10.0.26200-SP0", + "probe_sha256": "ca19645b65aea8ac74b56d1e59837cb7514c3acf5932d24112411725b92206d7", + "trace_sha256": "5941d4569d0ae146eb005f12ecfb15014bc06c18ee48d1c9460824ab65df93d3", + "pair_receipts": { + "baseline": { + "schema": "termforge-upstream-conpty-variant-v1", + "upstream_source": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "workflow_run": "40449292015", + "production_patch_applied": false, + "focused_adapter_result": "expected-failure", + "full_adapter_verified": false, + "patch_sha256": "dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81", + "files": { + "OpenConsole.exe": { + "sha256": "fabdc934d9755c405fe848251905e8c0e70a2bf7702b8ed7ccd104eec6591588", + "bytes": 1146880 + }, + "conpty.dll": { + "sha256": "9d165745cce4b29a5818a45c23f68e962b3a3140c2e00566bd17fee970fdd9d1", + "bytes": 108544 + } + } + }, + "patched": { + "schema": "termforge-upstream-conpty-variant-v1", + "upstream_source": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "workflow_run": "40449292015", + "production_patch_applied": true, + "focused_adapter_result": "passed", + "full_adapter_verified": true, + "patch_sha256": "dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81", + "files": { + "OpenConsole.exe": { + "sha256": "1c8f071f6e4779ec297fb041af4eaea39be29b6920cef625ab91d1cbc20c79cb", + "bytes": 1147392 + }, + "conpty.dll": { + "sha256": "9d165745cce4b29a5818a45c23f68e962b3a3140c2e00566bd17fee970fdd9d1", + "bytes": 108544 + } + } + } + }, + "deadlines": { + "child_seconds": 5, + "outer_seconds": 15, + "cleanup_seconds": 5 + }, + "cases": [ + "styled-cursor" + ], + "planned_executions": 4, + "comparisons": [ + { + "case": "styled-cursor", + "columns": 80, + "rows": 24, + "baseline_matches_prediction": true, + "patched_native_passed": true, + "both_terminal_finals_passed": false, + "passed": false + }, + { + "case": "styled-cursor", + "columns": 12, + "rows": 7, + "baseline_matches_prediction": true, + "patched_native_passed": true, + "both_terminal_finals_passed": false, + "passed": false + } + ], + "passed": false, + "full_r2_contract_verified": false, + "product_backend_selected": false, + "presented_done": null, + "unmeasured": [ + "history_stress", + "resize", + "queries_and_api_writes", + "terminal_intermediate_stages", + "other_SGR_and_truecolor", + "full_product", + "performance", + "WindTerm_equivalence" + ], + "schema": "termforge-source-margin-styled-observation-v1", + "scope": "Four original executions; native patch comparison passed, strict terminal cell-attribute comparison failed. No retry, default backend selection or WindTerm equivalence.", + "raw_summary": { + "bytes": 27676, + "sha256": "e87a2d689ed8e0b3b38efdde70a339be8eddf2e03f19883da75c83b63e57e9d3" + }, + "offline_reevaluation": { + "source": "6e52e6957de10c14c570d3b7b83992c585fcc3a9", + "oracle_file": { + "bytes": 7263, + "sha256": "f33bcded8366be3c66fbb7761957f62ba4d12b28b5a6d52249c2f9097ec56c37" + }, + "native_executions": 0, + "original_summary_unchanged": true, + "correction": "Untouched default blanks use text empty and width 0; colored blanks use a space and width 1. Explicit colored-blank foreground expectation remains 1.", + "original_gate_passed": false, + "corrected_gate_passed": false + }, + "samples": { + "baseline-80x24-styled-cursor": { + "status": "completed", + "case": "styled-cursor", + "columns": 80, + "rows": 24, + "source_bytes": 211, + "source_sha256": "51d0f55717ff8fbe5e82040967edf2f9399a9b893fd8e8cb282f94c143919b62", + "transport_bytes": 254, + "transport_sha256": "b584cd146f447c1e78e51d74e6d1c38c6a93d860d738a0afacdf959c8327d9bd", + "native_contract": { + "stages": { + "before": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "alt": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "restored": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "after-write": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "final": { + "text": false, + "cell_text": false, + "cells_and_attributes": false, + "cursor": true, + "viewport": true, + "passed": false + } + }, + "passed": false, + "initial_native_attribute": 7, + "full_r2_contract_verified": false + }, + "unpatched_prediction_matches": true, + "native_lines": { + "before": [ + "TOP_FIXED", + "REGION_A", + "REGION_B", + "BOTTOM_FIXED", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "alt": [ + "ALT_OUTSIDE", + "", + "", + "ALT_C", + "ALT_D", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "restored": [ + "TOP_FIXED", + "REGION_A", + "REGION_B", + "BOTTOM_FIXED", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "after-write": [ + "TOP_FIXED", + "REGION_A", + "REGIOM_B", + "BOTTOM_FIXED", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "final": [ + "TOP_FIXED", + "REGION_A", + "REGIOM_B", + "REGION_CIXED", + "REGION_D", + "SGR_DONE", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "native_cursor": { + "before": [ + 2, + 5 + ], + "alt": [ + 4, + 7 + ], + "restored": [ + 2, + 5 + ], + "after-write": [ + 2, + 6 + ], + "final": [ + 5, + 8 + ] + }, + "native_cell_sha256": { + "before": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651", + "alt": "d2e9a140a7b7a3c537b4cb25ea306c0b26fdf9694fc8c9d44218be173a99108e", + "restored": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651", + "after-write": "84708a877d836570697978465b4c2b788652b2de0a20a09d8a117111d3c82dc0", + "final": "68ea1c55640dde6862f56fc015df6821072a72d589d315aff42776d5b5f46938" + }, + "terminal_lines": [ + "TOP_FIXED", + "REGION_C", + "REGION_D", + "BOTTOM_FIXED", + "", + "SGR_DONE", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "terminal_alternate": false, + "terminal_cursor": [ + 5, + 8 + ], + "terminal_current_style": { + "bg": 4, + "bold": false, + "fg": 1 + }, + "terminal_contract": { + "text": true, + "main_screen": true, + "cells_and_styles": false, + "cursor": true, + "current_style": true + }, + "terminal_final_passed": false, + "lifecycle": { + "status": "completed", + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-baseline-run40449292015" + } + }, + "capture_files": { + "after-write.cells.bin": { + "bytes": 7680, + "sha256": "84708a877d836570697978465b4c2b788652b2de0a20a09d8a117111d3c82dc0" + }, + "after-write.console.bin": { + "bytes": 3840, + "sha256": "0463eaeda9c44a38ad9003ca14df549dcfc74ceb364308432aceadb7491fa007" + }, + "after-write.console.json": { + "bytes": 442, + "sha256": "e0c0890312f36d46be7aed7ec3ede6c37213f782790f309ff0509e6b8b2132ff" + }, + "alt.cells.bin": { + "bytes": 7680, + "sha256": "d2e9a140a7b7a3c537b4cb25ea306c0b26fdf9694fc8c9d44218be173a99108e" + }, + "alt.console.bin": { + "bytes": 3840, + "sha256": "f6492387ed4760be9851cccd504ac8f6f21624d55dce9671b3be4560ada0b5ac" + }, + "alt.console.json": { + "bytes": 434, + "sha256": "81304f5e1cd83bbf0039f4880b7fd44913ef877911075a69268f761f6589a29b" + }, + "before.cells.bin": { + "bytes": 7680, + "sha256": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651" + }, + "before.console.bin": { + "bytes": 3840, + "sha256": "4279b6605b0fb08728ce75e50e7b24eaf2d75e39e3d44b12b0f41d2e6ba8343b" + }, + "before.console.json": { + "bytes": 437, + "sha256": "6014972055b4eb00031ac07ff4abffef4adf62ca7033b192321e09ee5732a3db" + }, + "final.cells.bin": { + "bytes": 7680, + "sha256": "68ea1c55640dde6862f56fc015df6821072a72d589d315aff42776d5b5f46938" + }, + "final.console.bin": { + "bytes": 3840, + "sha256": "acea12c6e10ea9ad409f7c9df8015dd47ac1c4f22fbb3dfbae99fe064424807d" + }, + "final.console.json": { + "bytes": 436, + "sha256": "07ecbd646eec84165dc6bd007ab31ac962db93dd78c878e327d139e651dc4611" + }, + "restored.cells.bin": { + "bytes": 7680, + "sha256": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651" + }, + "restored.console.bin": { + "bytes": 3840, + "sha256": "4279b6605b0fb08728ce75e50e7b24eaf2d75e39e3d44b12b0f41d2e6ba8343b" + }, + "restored.console.json": { + "bytes": 439, + "sha256": "f6c9971ed7f756a1c0792a378167c871b6cb6fd17864bc8b65a1ba0bd27e9fc4" + }, + "source.bin": { + "bytes": 211, + "sha256": "51d0f55717ff8fbe5e82040967edf2f9399a9b893fd8e8cb282f94c143919b62" + }, + "transport.bin": { + "bytes": 254, + "sha256": "b584cd146f447c1e78e51d74e6d1c38c6a93d860d738a0afacdf959c8327d9bd" + }, + "transport.prefix.bin": { + "bytes": 238, + "sha256": "d46065b8507db6fc51f2f4cf1c232bbc39ca99b03405dd4a5c92509c741a4e10" + } + }, + "terminal_trace": { + "bytes": 234723, + "sha256": "ab0f259d147d4a517473f66bf593a30e180b7e29279832d913c670d54420825d" + }, + "terminal_blank_foreground_difference": { + "count": 144, + "zero_based_rows": [ + 1, + 2 + ], + "columns_begin": 8, + "columns_end_exclusive": 80, + "text": " ", + "width": 1, + "background": 4, + "bold": false, + "expected_foreground": 1, + "actual_foreground": -1, + "original_gate_passed": false, + "corrected_gate_passed": false + }, + "oracle_serialization_correction": { + "default_empty_cells": 1731, + "original_expected": { + "text": " ", + "width": 1 + }, + "corrected_expected": { + "text": "", + "width": 0 + }, + "attribute_expectations_unchanged": true + } + }, + "patched-80x24-styled-cursor": { + "status": "completed", + "case": "styled-cursor", + "columns": 80, + "rows": 24, + "source_bytes": 211, + "source_sha256": "51d0f55717ff8fbe5e82040967edf2f9399a9b893fd8e8cb282f94c143919b62", + "transport_bytes": 254, + "transport_sha256": "b584cd146f447c1e78e51d74e6d1c38c6a93d860d738a0afacdf959c8327d9bd", + "native_contract": { + "stages": { + "before": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "alt": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "restored": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "after-write": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "final": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + } + }, + "passed": true, + "initial_native_attribute": 7, + "full_r2_contract_verified": false + }, + "unpatched_prediction_matches": false, + "native_lines": { + "before": [ + "TOP_FIXED", + "REGION_A", + "REGION_B", + "BOTTOM_FIXED", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "alt": [ + "ALT_OUTSIDE", + "", + "", + "ALT_C", + "ALT_D", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "restored": [ + "TOP_FIXED", + "REGION_A", + "REGION_B", + "BOTTOM_FIXED", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "after-write": [ + "TOP_FIXED", + "REGION_A", + "REGIOM_B", + "BOTTOM_FIXED", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "final": [ + "TOP_FIXED", + "REGION_C", + "REGION_D", + "BOTTOM_FIXED", + "", + "SGR_DONE", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "native_cursor": { + "before": [ + 2, + 5 + ], + "alt": [ + 4, + 7 + ], + "restored": [ + 2, + 5 + ], + "after-write": [ + 2, + 6 + ], + "final": [ + 5, + 8 + ] + }, + "native_cell_sha256": { + "before": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651", + "alt": "d2e9a140a7b7a3c537b4cb25ea306c0b26fdf9694fc8c9d44218be173a99108e", + "restored": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651", + "after-write": "84708a877d836570697978465b4c2b788652b2de0a20a09d8a117111d3c82dc0", + "final": "be9fa7d2fa9870457043d00a2b97c17a43383ffcaed1094e29e8e229905748c1" + }, + "terminal_lines": [ + "TOP_FIXED", + "REGION_C", + "REGION_D", + "BOTTOM_FIXED", + "", + "SGR_DONE", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "terminal_alternate": false, + "terminal_cursor": [ + 5, + 8 + ], + "terminal_current_style": { + "bg": 4, + "bold": false, + "fg": 1 + }, + "terminal_contract": { + "text": true, + "main_screen": true, + "cells_and_styles": false, + "cursor": true, + "current_style": true + }, + "terminal_final_passed": false, + "lifecycle": { + "status": "completed", + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-patched-run40449292015" + } + }, + "capture_files": { + "after-write.cells.bin": { + "bytes": 7680, + "sha256": "84708a877d836570697978465b4c2b788652b2de0a20a09d8a117111d3c82dc0" + }, + "after-write.console.bin": { + "bytes": 3840, + "sha256": "0463eaeda9c44a38ad9003ca14df549dcfc74ceb364308432aceadb7491fa007" + }, + "after-write.console.json": { + "bytes": 442, + "sha256": "e0c0890312f36d46be7aed7ec3ede6c37213f782790f309ff0509e6b8b2132ff" + }, + "alt.cells.bin": { + "bytes": 7680, + "sha256": "d2e9a140a7b7a3c537b4cb25ea306c0b26fdf9694fc8c9d44218be173a99108e" + }, + "alt.console.bin": { + "bytes": 3840, + "sha256": "f6492387ed4760be9851cccd504ac8f6f21624d55dce9671b3be4560ada0b5ac" + }, + "alt.console.json": { + "bytes": 434, + "sha256": "81304f5e1cd83bbf0039f4880b7fd44913ef877911075a69268f761f6589a29b" + }, + "before.cells.bin": { + "bytes": 7680, + "sha256": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651" + }, + "before.console.bin": { + "bytes": 3840, + "sha256": "4279b6605b0fb08728ce75e50e7b24eaf2d75e39e3d44b12b0f41d2e6ba8343b" + }, + "before.console.json": { + "bytes": 437, + "sha256": "6014972055b4eb00031ac07ff4abffef4adf62ca7033b192321e09ee5732a3db" + }, + "final.cells.bin": { + "bytes": 7680, + "sha256": "be9fa7d2fa9870457043d00a2b97c17a43383ffcaed1094e29e8e229905748c1" + }, + "final.console.bin": { + "bytes": 3840, + "sha256": "3a57853c6ca7cc6a7fb53ea445a9967d8e92faf2ef6391d820d2e6d6fbaf3950" + }, + "final.console.json": { + "bytes": 436, + "sha256": "2c5cbccfa24897e74b2a8da7c3fd398ece2f8c1c91e4266661cb223ea2d2c988" + }, + "restored.cells.bin": { + "bytes": 7680, + "sha256": "fe9016196e03b264cce22f628f67b778fe1e6a3aeb160ed53effc153b1ff6651" + }, + "restored.console.bin": { + "bytes": 3840, + "sha256": "4279b6605b0fb08728ce75e50e7b24eaf2d75e39e3d44b12b0f41d2e6ba8343b" + }, + "restored.console.json": { + "bytes": 439, + "sha256": "f6c9971ed7f756a1c0792a378167c871b6cb6fd17864bc8b65a1ba0bd27e9fc4" + }, + "source.bin": { + "bytes": 211, + "sha256": "51d0f55717ff8fbe5e82040967edf2f9399a9b893fd8e8cb282f94c143919b62" + }, + "transport.bin": { + "bytes": 254, + "sha256": "b584cd146f447c1e78e51d74e6d1c38c6a93d860d738a0afacdf959c8327d9bd" + }, + "transport.prefix.bin": { + "bytes": 238, + "sha256": "d46065b8507db6fc51f2f4cf1c232bbc39ca99b03405dd4a5c92509c741a4e10" + } + }, + "terminal_trace": { + "bytes": 234723, + "sha256": "ab0f259d147d4a517473f66bf593a30e180b7e29279832d913c670d54420825d" + }, + "terminal_blank_foreground_difference": { + "count": 144, + "zero_based_rows": [ + 1, + 2 + ], + "columns_begin": 8, + "columns_end_exclusive": 80, + "text": " ", + "width": 1, + "background": 4, + "bold": false, + "expected_foreground": 1, + "actual_foreground": -1, + "original_gate_passed": false, + "corrected_gate_passed": false + }, + "oracle_serialization_correction": { + "default_empty_cells": 1731, + "original_expected": { + "text": " ", + "width": 1 + }, + "corrected_expected": { + "text": "", + "width": 0 + }, + "attribute_expectations_unchanged": true + } + }, + "baseline-12x7-styled-cursor": { + "status": "completed", + "case": "styled-cursor", + "columns": 12, + "rows": 7, + "source_bytes": 211, + "source_sha256": "51d0f55717ff8fbe5e82040967edf2f9399a9b893fd8e8cb282f94c143919b62", + "transport_bytes": 254, + "transport_sha256": "b584cd146f447c1e78e51d74e6d1c38c6a93d860d738a0afacdf959c8327d9bd", + "native_contract": { + "stages": { + "before": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "alt": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "restored": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "after-write": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "final": { + "text": false, + "cell_text": false, + "cells_and_attributes": false, + "cursor": true, + "viewport": true, + "passed": false + } + }, + "passed": false, + "initial_native_attribute": 7, + "full_r2_contract_verified": false + }, + "unpatched_prediction_matches": true, + "native_lines": { + "before": [ + "TOP_FIXED", + "REGION_A", + "REGION_B", + "BOTTOM_FIXED", + "", + "", + "" + ], + "alt": [ + "ALT_OUTSIDE", + "", + "", + "ALT_C", + "ALT_D", + "", + "" + ], + "restored": [ + "TOP_FIXED", + "REGION_A", + "REGION_B", + "BOTTOM_FIXED", + "", + "", + "" + ], + "after-write": [ + "TOP_FIXED", + "REGION_A", + "REGIOM_B", + "BOTTOM_FIXED", + "", + "", + "" + ], + "final": [ + "TOP_FIXED", + "REGION_A", + "REGIOM_B", + "REGION_CIXED", + "REGION_D", + "SGR_DONE", + "" + ] + }, + "native_cursor": { + "before": [ + 2, + 5 + ], + "alt": [ + 4, + 7 + ], + "restored": [ + 2, + 5 + ], + "after-write": [ + 2, + 6 + ], + "final": [ + 5, + 8 + ] + }, + "native_cell_sha256": { + "before": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3", + "alt": "b6955bf5bd843cb3acb16f862eabe4341e18526437e62adb9cc7e9afcea789e4", + "restored": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3", + "after-write": "9bb0cc966b92b1a1f86e7dbdde94a6e101e8512b271d2baecb8a3661e47b8687", + "final": "428cc23d0edbc831126dc5f1cf0044af45946a43a695b39dc95b5f15f2454046" + }, + "terminal_lines": [ + "TOP_FIXED", + "REGION_C", + "REGION_D", + "BOTTOM_FIXED", + "", + "SGR_DONE", + "" + ], + "terminal_alternate": false, + "terminal_cursor": [ + 5, + 8 + ], + "terminal_current_style": { + "bg": 4, + "bold": false, + "fg": 1 + }, + "terminal_contract": { + "text": true, + "main_screen": true, + "cells_and_styles": false, + "cursor": true, + "current_style": true + }, + "terminal_final_passed": false, + "lifecycle": { + "status": "completed", + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-baseline-run40449292015" + } + }, + "capture_files": { + "after-write.cells.bin": { + "bytes": 336, + "sha256": "9bb0cc966b92b1a1f86e7dbdde94a6e101e8512b271d2baecb8a3661e47b8687" + }, + "after-write.console.bin": { + "bytes": 168, + "sha256": "e44ad01ff79993fee760691a952b672cd8d53c1c2b6e4cb12e0c09af6182a6a1" + }, + "after-write.console.json": { + "bytes": 386, + "sha256": "46029fec056b0d1f56d62d850a99185625ef5b2614dbdea7cfade9ab4117ca3d" + }, + "alt.cells.bin": { + "bytes": 336, + "sha256": "b6955bf5bd843cb3acb16f862eabe4341e18526437e62adb9cc7e9afcea789e4" + }, + "alt.console.bin": { + "bytes": 168, + "sha256": "c2e8954d37be9bfc2b600951255d9410a9873e9259b7980ed41785de97e863c3" + }, + "alt.console.json": { + "bytes": 378, + "sha256": "56d31b92ef96caf3d12cf388d2f8491b9b719de3fb137956df77429a8e9d3bc5" + }, + "before.cells.bin": { + "bytes": 336, + "sha256": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3" + }, + "before.console.bin": { + "bytes": 168, + "sha256": "f3f613e2347734671af75bb4747b55ebc89ea0c2b1994b5efd7a86152aa80b10" + }, + "before.console.json": { + "bytes": 381, + "sha256": "6997410b5bb147cbcc80c7d70014e81e105f1e9f9b0553c2ff0fe1731d007677" + }, + "final.cells.bin": { + "bytes": 336, + "sha256": "428cc23d0edbc831126dc5f1cf0044af45946a43a695b39dc95b5f15f2454046" + }, + "final.console.bin": { + "bytes": 168, + "sha256": "82c05767aae71e19dbaef245ad930cc5847b725f27c52dc79c37c470a9904990" + }, + "final.console.json": { + "bytes": 380, + "sha256": "0afd964ba580b478680682a52cd9fc07313169b1891ccb56d76d3c7dbf3f1118" + }, + "restored.cells.bin": { + "bytes": 336, + "sha256": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3" + }, + "restored.console.bin": { + "bytes": 168, + "sha256": "f3f613e2347734671af75bb4747b55ebc89ea0c2b1994b5efd7a86152aa80b10" + }, + "restored.console.json": { + "bytes": 383, + "sha256": "34fd72b6596e93feb19578af411822d04b0cd8b3657da1909ee6cdb55c3b6aed" + }, + "source.bin": { + "bytes": 211, + "sha256": "51d0f55717ff8fbe5e82040967edf2f9399a9b893fd8e8cb282f94c143919b62" + }, + "transport.bin": { + "bytes": 254, + "sha256": "b584cd146f447c1e78e51d74e6d1c38c6a93d860d738a0afacdf959c8327d9bd" + }, + "transport.prefix.bin": { + "bytes": 238, + "sha256": "d46065b8507db6fc51f2f4cf1c232bbc39ca99b03405dd4a5c92509c741a4e10" + } + }, + "terminal_trace": { + "bytes": 10663, + "sha256": "91b97be296766a8cf6c7dc6bf2e36b27252d45e4e3d8875ada68ae75885201b7" + }, + "terminal_blank_foreground_difference": { + "count": 8, + "zero_based_rows": [ + 1, + 2 + ], + "columns_begin": 8, + "columns_end_exclusive": 12, + "text": " ", + "width": 1, + "background": 4, + "bold": false, + "expected_foreground": 1, + "actual_foreground": -1, + "original_gate_passed": false, + "corrected_gate_passed": false + }, + "oracle_serialization_correction": { + "default_empty_cells": 31, + "original_expected": { + "text": " ", + "width": 1 + }, + "corrected_expected": { + "text": "", + "width": 0 + }, + "attribute_expectations_unchanged": true + } + }, + "patched-12x7-styled-cursor": { + "status": "completed", + "case": "styled-cursor", + "columns": 12, + "rows": 7, + "source_bytes": 211, + "source_sha256": "51d0f55717ff8fbe5e82040967edf2f9399a9b893fd8e8cb282f94c143919b62", + "transport_bytes": 254, + "transport_sha256": "b584cd146f447c1e78e51d74e6d1c38c6a93d860d738a0afacdf959c8327d9bd", + "native_contract": { + "stages": { + "before": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "alt": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "restored": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "after-write": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + }, + "final": { + "text": true, + "cell_text": true, + "cells_and_attributes": true, + "cursor": true, + "viewport": true, + "passed": true + } + }, + "passed": true, + "initial_native_attribute": 7, + "full_r2_contract_verified": false + }, + "unpatched_prediction_matches": false, + "native_lines": { + "before": [ + "TOP_FIXED", + "REGION_A", + "REGION_B", + "BOTTOM_FIXED", + "", + "", + "" + ], + "alt": [ + "ALT_OUTSIDE", + "", + "", + "ALT_C", + "ALT_D", + "", + "" + ], + "restored": [ + "TOP_FIXED", + "REGION_A", + "REGION_B", + "BOTTOM_FIXED", + "", + "", + "" + ], + "after-write": [ + "TOP_FIXED", + "REGION_A", + "REGIOM_B", + "BOTTOM_FIXED", + "", + "", + "" + ], + "final": [ + "TOP_FIXED", + "REGION_C", + "REGION_D", + "BOTTOM_FIXED", + "", + "SGR_DONE", + "" + ] + }, + "native_cursor": { + "before": [ + 2, + 5 + ], + "alt": [ + 4, + 7 + ], + "restored": [ + 2, + 5 + ], + "after-write": [ + 2, + 6 + ], + "final": [ + 5, + 8 + ] + }, + "native_cell_sha256": { + "before": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3", + "alt": "b6955bf5bd843cb3acb16f862eabe4341e18526437e62adb9cc7e9afcea789e4", + "restored": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3", + "after-write": "9bb0cc966b92b1a1f86e7dbdde94a6e101e8512b271d2baecb8a3661e47b8687", + "final": "863a5472a5eb1208f304c4872bb580f7c74be4faf0d3eb8cac9f72918441f8af" + }, + "terminal_lines": [ + "TOP_FIXED", + "REGION_C", + "REGION_D", + "BOTTOM_FIXED", + "", + "SGR_DONE", + "" + ], + "terminal_alternate": false, + "terminal_cursor": [ + 5, + 8 + ], + "terminal_current_style": { + "bg": 4, + "bold": false, + "fg": 1 + }, + "terminal_contract": { + "text": true, + "main_screen": true, + "cells_and_styles": false, + "cursor": true, + "current_style": true + }, + "terminal_final_passed": false, + "lifecycle": { + "status": "completed", + "returncode": 0, + "outer_job_after": { + "active": 0, + "total": 4, + "terminated": 0 + }, + "forced_cleanup": false, + "job_assigned": true, + "process_reaped": true, + "streams_drained": true, + "cleanup_errors": [], + "native": { + "waited": true, + "side_by_side": true, + "creation_flags": 0, + "exit_code": 37, + "read_error": 109, + "capture_overflow": false, + "forced_reader_cancel": false, + "forced_process_cancel": false, + "cleanup_complete": true, + "active_children_after": 0, + "backend_identity": "source-dc8-r56-patched-run40449292015" + } + }, + "capture_files": { + "after-write.cells.bin": { + "bytes": 336, + "sha256": "9bb0cc966b92b1a1f86e7dbdde94a6e101e8512b271d2baecb8a3661e47b8687" + }, + "after-write.console.bin": { + "bytes": 168, + "sha256": "e44ad01ff79993fee760691a952b672cd8d53c1c2b6e4cb12e0c09af6182a6a1" + }, + "after-write.console.json": { + "bytes": 386, + "sha256": "46029fec056b0d1f56d62d850a99185625ef5b2614dbdea7cfade9ab4117ca3d" + }, + "alt.cells.bin": { + "bytes": 336, + "sha256": "b6955bf5bd843cb3acb16f862eabe4341e18526437e62adb9cc7e9afcea789e4" + }, + "alt.console.bin": { + "bytes": 168, + "sha256": "c2e8954d37be9bfc2b600951255d9410a9873e9259b7980ed41785de97e863c3" + }, + "alt.console.json": { + "bytes": 378, + "sha256": "56d31b92ef96caf3d12cf388d2f8491b9b719de3fb137956df77429a8e9d3bc5" + }, + "before.cells.bin": { + "bytes": 336, + "sha256": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3" + }, + "before.console.bin": { + "bytes": 168, + "sha256": "f3f613e2347734671af75bb4747b55ebc89ea0c2b1994b5efd7a86152aa80b10" + }, + "before.console.json": { + "bytes": 381, + "sha256": "6997410b5bb147cbcc80c7d70014e81e105f1e9f9b0553c2ff0fe1731d007677" + }, + "final.cells.bin": { + "bytes": 336, + "sha256": "863a5472a5eb1208f304c4872bb580f7c74be4faf0d3eb8cac9f72918441f8af" + }, + "final.console.bin": { + "bytes": 168, + "sha256": "d2fb3eaf337d2f63c74009b554d61c9eecae9dfbd6131726acb7cb643c9f7cf4" + }, + "final.console.json": { + "bytes": 380, + "sha256": "d4aa2f046ca74cc270c1ebb86d058ce918512b2726a3b3f38ca3ef2d6cc0afbe" + }, + "restored.cells.bin": { + "bytes": 336, + "sha256": "323107891dd2f32c2be1fecd7ae03379c96f8fe38eff938ece8f19eeebe6dbf3" + }, + "restored.console.bin": { + "bytes": 168, + "sha256": "f3f613e2347734671af75bb4747b55ebc89ea0c2b1994b5efd7a86152aa80b10" + }, + "restored.console.json": { + "bytes": 383, + "sha256": "34fd72b6596e93feb19578af411822d04b0cd8b3657da1909ee6cdb55c3b6aed" + }, + "source.bin": { + "bytes": 211, + "sha256": "51d0f55717ff8fbe5e82040967edf2f9399a9b893fd8e8cb282f94c143919b62" + }, + "transport.bin": { + "bytes": 254, + "sha256": "b584cd146f447c1e78e51d74e6d1c38c6a93d860d738a0afacdf959c8327d9bd" + }, + "transport.prefix.bin": { + "bytes": 238, + "sha256": "d46065b8507db6fc51f2f4cf1c232bbc39ca99b03405dd4a5c92509c741a4e10" + } + }, + "terminal_trace": { + "bytes": 10663, + "sha256": "91b97be296766a8cf6c7dc6bf2e36b27252d45e4e3d8875ada68ae75885201b7" + }, + "terminal_blank_foreground_difference": { + "count": 8, + "zero_based_rows": [ + 1, + 2 + ], + "columns_begin": 8, + "columns_end_exclusive": 12, + "text": " ", + "width": 1, + "background": 4, + "bold": false, + "expected_foreground": 1, + "actual_foreground": -1, + "original_gate_passed": false, + "corrected_gate_passed": false + }, + "oracle_serialization_correction": { + "default_empty_cells": 31, + "original_expected": { + "text": " ", + "width": 1 + }, + "corrected_expected": { + "text": "", + "width": 0 + }, + "attribute_expectations_unchanged": true + } + } + }, + "source_hex": "1b5b306d1b5b324a1b5b48544f505f46495845440d0a524547494f4e5f410d0a524547494f4e5f420d0a424f54544f4d5f46495845440d0a1b5b323b33721b5b33313b34346d1b5b333b36481b5b3f31303439681b5b306d1b5b324a1b5b48414c545f4f5554534944450d0a1b5b343b3148414c545f410d0a414c545f421b5b343b35721b5b33323b34356d1b5b353b31480a414c545f430a414c545f441b5b353b38481b5b3f313034396c4d1b5b333b31480a524547494f4e5f430a524547494f4e5f441b5b363b31485347525f444f4e45", + "transport_hex": "1b5b31741b5b631b5b3f31303034681b5b3f39303031681b5b306d1b5b324a1b5b48544f505f46495845440d0a524547494f4e5f410d0a524547494f4e5f420d0a424f54544f4d5f46495845440d0a1b5b323b33721b5b33313b34346d1b5b333b36481b5b3f31303439681b5b306d1b5b324a1b5b48414c545f4f5554534944450d0a1b5b343b3148414c545f410d0a414c545f421b5b343b35721b5b33323b34356d1b5b353b31480d0a414c545f430d0a414c545f441b5b353b38481b5b3f313034396c4d1b5b333b31480d0a524547494f4e5f430d0a524547494f4e5f441b5b363b31485347525f444f4e451b5b3f313030346c1b5b3f393030316c", + "validation": { + "initial_oracle_regressions": { + "passed": 7, + "total": 7, + "seconds": 0.09 + }, + "canonical_empty_cell_red": { + "failed": 1, + "total": 1, + "seconds": 0.001 + }, + "corrected_oracle_regressions": { + "passed": 7, + "total": 7, + "seconds": 0.069 + }, + "observed_blank_foreground_counterexample": "included in corrected regressions", + "old_capture_offline_recheck": { + "passed": 12, + "total": 12, + "native_executions": 0 + }, + "native_probe_build": "passed", + "new_product_regression": "not-run" + }, + "validation_logs": { + "configure.log": { + "bytes": 911, + "sha256": "6619d952f08570277c0d3e562a3dde1ad7a2d04aad54dba0a28152eba06daa1e" + }, + "build.log": { + "bytes": 335, + "sha256": "fc88a5ad4f07523f2e6aa8fabb6c4780c3d63f4be1d2a04bd505cf862278ab13" + }, + "matrix-1.log": { + "bytes": 389, + "sha256": "619b58e89b4d5538fb21d8cce317666211d526a4d2a0a60862a0086d0210ca24" + }, + "oracle-serialization-red.log": { + "bytes": 820, + "sha256": "aa6b26efeccd9fb0b19bc90e64dcb1b49a489921dd3e2de51a87a8e77e9ce049" + }, + "oracle-serialization-green.log": { + "bytes": 1594, + "sha256": "4a115ee9f4be09e5c96726f6430990d81df503f7bdc9d0531c3997cda1475f4d" + }, + "old-capture-recheck.json": { + "bytes": 1818, + "sha256": "f86bab6f7cda08abd490c5e476a269b35a1f594fbaf8d43b76ca9e46cf5489b5" + } + }, + "limitations": [ + "The candidate Cell::reset retains background and defaults foreground; source observation does not establish WindTerm behavior or approve a product policy.", + "The initial oracle incorrectly serialized untouched default blanks. Offline correction changes that encoding only; the expected explicit foreground remains 1 and both original and corrected matrix verdicts remain failed.", + "Native CHAR_INFO and terminal style are separate interfaces; current foreground and written colored text were preserved.", + "Other SGR, truecolor, intermediate terminal states, history, resize, queries/API, full product and maintenance remain unverified." + ] +} diff --git a/experiments/conpty/source_margin_controls.py b/experiments/conpty/source_margin_controls.py index 30955187..b8b1eba4 100644 --- a/experiments/conpty/source_margin_controls.py +++ b/experiments/conpty/source_margin_controls.py @@ -1,4 +1,4 @@ -"""One bounded source-pair comparison for three additional native controls.""" +"""One bounded source-pair comparison for fixed native control workloads.""" from __future__ import annotations import argparse @@ -10,7 +10,7 @@ from experiments.candidates.alacritty.run import run_trace from experiments.conpty.margin_controls import ( - evaluate_native, expected_native, expected_source, matches_unpatched_prediction, + evaluate_native, evaluate_terminal, expected_native, expected_source, matches_unpatched_prediction, ) from experiments.conpty.margin_run import sha256, validate_lifecycle from experiments.conpty.source_margin_run import identity, reviewed_pair, ROOT, RUN, UPSTREAM @@ -37,9 +37,7 @@ def inspect_case(directory, trace, case, columns, rows): if report["passed"] is not True: raise ValueError("control replay rejected") final = records[-1] - lines = ["".join(cell["text"] for cell in line if cell.get("width", 1)).rstrip() - for line in final["screen"]["cells"]] - alternate = bool(final["mode_bits"] & (1 << 12)) + terminal = evaluate_terminal(final, case, columns, rows) return {"source_bytes": len(source), "source_sha256": sha256(directory / "source.bin"), "transport_bytes": len(transport), "transport_sha256": sha256(directory / "transport.bin"), "native_contract": evaluate_native(states, case, columns, rows), @@ -47,14 +45,16 @@ def inspect_case(directory, trace, case, columns, rows): "native_lines": {stage: row["lines"] for stage, row in states.items()}, "native_cursor": {stage: [row["cursor_row"], row["cursor_column"]] for stage, row in states.items()}, "native_cell_sha256": {stage: sha256(directory / (stage + ".cells.bin")) for stage in expected}, - "terminal_lines": lines, "terminal_alternate": alternate, - "terminal_final_passed": lines == expected["final"]["lines"] and not alternate} + "terminal_lines": terminal["lines"], "terminal_alternate": terminal["alternate"], + "terminal_cursor": [final["screen"]["row"], final["screen"]["column"]], + "terminal_current_style": final["screen"]["current_style"], + "terminal_contract": terminal["fields"], "terminal_final_passed": terminal["passed"]} -def compare(samples): +def compare(samples, cases=CASES): results = [] for columns, rows in GRIDS: - for case in CASES: + for case in cases: baseline = samples[f"baseline-{columns}x{rows}-{case}"] patched = samples[f"patched-{columns}x{rows}-{case}"] completed = all(row.get("status") == "completed" for row in (baseline, patched)) @@ -69,7 +69,8 @@ def compare(samples): return results -def run(baseline, patched, probe, trace, output): +def run(baseline, patched, probe, trace, output, *, styled_cursor=False): + cases = ("styled-cursor",) if styled_cursor else CASES pairs = {name: reviewed_pair(folder, name) for name, folder in (("baseline", baseline), ("patched", patched))} probe, trace = Path(probe).resolve(strict=True), Path(trace).resolve(strict=True) @@ -83,7 +84,8 @@ def run(baseline, patched, probe, trace, output): "experiments/conpty/margin_run.py", "experiments/conpty/source_margin_run.py", "experiments/conpty/run.py", "experiments/candidates/alacritty/run.py", "experiments/shell/workflow_fixture.hpp", "experiments/shell/workflow_checks.py"] - summary = {"schema": "termforge-source-margin-controls-v1", "upstream_source": UPSTREAM, + schema = "termforge-source-margin-styled-controls-v1" if styled_cursor else "termforge-source-margin-controls-v1" + summary = {"schema": schema, "upstream_source": UPSTREAM, "generated_utc": datetime.now(timezone.utc).isoformat(), "build_run": RUN, "source": subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip(), "working_tree_dirty": bool(subprocess.check_output(["git", "status", "--porcelain"], cwd=ROOT)), @@ -91,11 +93,13 @@ def run(baseline, patched, probe, trace, output): "platform": platform.platform(), "probe_sha256": sha256(probe), "trace_sha256": sha256(trace), "pair_receipts": {name: receipt for name, (_, receipt) in pairs.items()}, "deadlines": {"child_seconds": 5, "outer_seconds": 15, "cleanup_seconds": 5}, - "planned_executions": 12, "samples": {}, "comparisons": [], "passed": False, + "cases": list(cases), "planned_executions": len(cases) * len(GRIDS) * len(pairs), + "samples": {}, "comparisons": [], "passed": False, "full_r2_contract_verified": False, "product_backend_selected": False, "presented_done": None, - "unmeasured": ["styled_source", "history_stress", "resize", "queries_and_api_writes", - "terminal_stage_cursors", "full_product", "performance", "WindTerm_equivalence"]} + "unmeasured": ["history_stress", "resize", "queries_and_api_writes", "terminal_intermediate_stages", + "other_SGR_and_truecolor", "full_product", "performance", "WindTerm_equivalence"] + + ([] if styled_cursor else ["styled_source", "terminal_final_styles_and_cursor"])} def save(): (output / "summary.json").write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8") @@ -103,7 +107,7 @@ def save(): try: # Exactly one execution per planned cell; failed cells remain failed. for columns, rows in GRIDS: - for case in CASES: + for case in cases: for variant, (directory, _) in pairs.items(): name = f"{variant}-{columns}x{rows}-{case}" folder = output / "_private" / name @@ -128,7 +132,7 @@ def save(): sample["failure_stage"] = stage summary["samples"][name] = sample save() - summary["comparisons"] = compare(summary["samples"]) + summary["comparisons"] = compare(summary["samples"], cases) summary["passed"] = all(row["passed"] for row in summary["comparisons"]) finally: save() @@ -139,8 +143,11 @@ def main(): parser = argparse.ArgumentParser(description=__doc__) for name in ("baseline", "patched", "probe", "candidate-trace", "output"): parser.add_argument("--" + name, type=Path, required=True) + parser.add_argument("--styled-cursor", action="store_true", + help="Run only the fixed four-execution SGR/cursor matrix") args = parser.parse_args() - result = run(args.baseline, args.patched, args.probe, args.candidate_trace, args.output) + result = run(args.baseline, args.patched, args.probe, args.candidate_trace, args.output, + styled_cursor=args.styled_cursor) print(json.dumps({"passed": result["passed"], "comparisons": result["comparisons"]})) return int(not result["passed"]) diff --git a/experiments/conpty/test_margin_controls.py b/experiments/conpty/test_margin_controls.py index 1a72c8ac..0d196d09 100644 --- a/experiments/conpty/test_margin_controls.py +++ b/experiments/conpty/test_margin_controls.py @@ -13,6 +13,28 @@ def capture(lines, cursor, columns=12, rows=7): "cursor_row": cursor[0], "cursor_column": cursor[1]} +def styled_capture(columns=12, rows=7): + def state(lines, cursor): + return capture(lines, cursor, columns, rows) + + sample = { + "before": state(["TOP_FIXED", "REGION_A", "REGION_B", "BOTTOM_FIXED"], (2, 5)), + "alt": state(["ALT_OUTSIDE", "", "", "ALT_C", "ALT_D"], (4, 7)), + "restored": state(["TOP_FIXED", "REGION_A", "REGION_B", "BOTTOM_FIXED"], (2, 5)), + "after-write": state(["TOP_FIXED", "REGION_A", "REGIOM_B", "BOTTOM_FIXED"], (2, 6)), + "final": state(["TOP_FIXED", "REGION_C", "REGION_D", "BOTTOM_FIXED", "", "SGR_DONE"], (5, 8)), + } + # Independent literal Win32 color values: green/magenta, red/blue. + for row in (3, 4): + sample["alt"]["cells"][row] = [(character, 0x52) for character, _ in sample["alt"]["cells"][row]] + sample["after-write"]["cells"][2][5] = (ord("M"), 0x14) + for row in (1, 2): + sample["final"]["cells"][row] = [(character, 0x14) for character, _ in sample["final"]["cells"][row]] + for column, character in enumerate("SGR_DONE"): + sample["final"]["cells"][5][column] = (ord(character), 0x14) + return sample + + class NativeMarginControlTests(unittest.TestCase): def main_only(self): return {"before": capture(["TOP_FIXED", "REGION_A", "REGION_B", "BOTTOM_FIXED"], (0, 0)), @@ -65,6 +87,20 @@ def test_repeated_alternate_and_main_requests_keep_the_hidden_main_region(self): old["restored"]["cells"][0][0] = (ord("T"), 4) self.assertFalse(matches_unpatched_prediction(old, "repeat-alt", 12, 7)) + def test_styled_main_cursor_restores_and_scroll_retains_explicit_attributes(self): + for columns, rows in ((12, 7), (80, 24)): + sample = styled_capture(columns, rows) + self.assertTrue(evaluate_native(sample, "styled-cursor", columns, rows)["passed"]) + for stage, row, column in (("after-write", 2, 5), ("alt", 3, columns - 1), ("final", 1, columns - 1)): + changed = copy.deepcopy(sample) + character, _ = changed[stage]["cells"][row][column] + changed[stage]["cells"][row][column] = (character, 7) + with self.subTest(stage=stage, columns=columns): + self.assertFalse(evaluate_native(changed, "styled-cursor", columns, rows)["passed"]) + changed = copy.deepcopy(sample) + changed["restored"]["cursor_column"] = 0 + self.assertFalse(evaluate_native(changed, "styled-cursor", columns, rows)["passed"]) + def test_missing_stage_and_wrong_grid_are_rejected(self): with self.assertRaises(ValueError): evaluate_native({"before": self.main_only()["before"]}, "main-only", 12, 7) @@ -73,6 +109,56 @@ def test_missing_stage_and_wrong_grid_are_rejected(self): with self.assertRaises(ValueError): evaluate_native(self.main_only(), "unknown", 12, 7) + def test_styled_baseline_requires_the_specific_old_margin_cells_and_attributes(self): + sample = styled_capture() + sample["final"] = capture(["TOP_FIXED", "REGION_A", "REGIOM_B", "REGION_CIXED", "REGION_D", "SGR_DONE"], (5, 8)) + sample["final"]["cells"][2][5] = (ord("M"), 0x14) + for row in (3, 4, 5): + for column in range(8): + character, _ = sample["final"]["cells"][row][column] + sample["final"]["cells"][row][column] = (character, 0x14) + result = evaluate_native(sample, "styled-cursor", 12, 7) + self.assertEqual([name for name, stage in result["stages"].items() if not stage["passed"]], ["final"]) + self.assertTrue(matches_unpatched_prediction(sample, "styled-cursor", 12, 7)) + for stage, row, column in (("before", 0, 1), ("after-write", 2, 5), ("final", 3, 9)): + changed = copy.deepcopy(sample) + character, _ = changed[stage]["cells"][row][column] + changed[stage]["cells"][row][column] = (character, 0x52) + self.assertFalse(matches_unpatched_prediction(changed, "styled-cursor", 12, 7)) + + def test_actual_terminal_final_requires_colors_cursor_and_restored_current_style(self): + from experiments.conpty.margin_controls import evaluate_terminal + + lines = ["TOP_FIXED", "REGION_C", "REGION_D", "BOTTOM_FIXED", "", "SGR_DONE", ""] + default = {"fg": -1, "bg": -1, "bold": False} + explicit = {"fg": 1, "bg": 4, "bold": False} + # The trace represents untouched default blanks as empty/zero-width; + # a blank with an explicit background is a space/one-width cell. + cells = [[{"text": character, "width": 1, "style": default.copy()} for character in line] + + [{"text": "", "width": 0, "style": default.copy()} for _ in range(12 - len(line))] + for line in lines] + for row in (1, 2): + for cell in cells[row]: + cell.update(text=cell["text"] or " ", width=1, style=explicit.copy()) + for cell in cells[5][:8]: + cell["style"] = explicit.copy() + record = {"mode_bits": 0, "screen": {"cells": cells, "row": 5, "column": 8, "current_style": explicit.copy()}} + self.assertTrue(evaluate_terminal(record, "styled-cursor", 12, 7)["passed"]) + changes = ( + lambda row: row["screen"].update(column=0), + lambda row: row.update(mode_bits=1 << 12), + lambda row: row["screen"]["current_style"].update(fg=2), + lambda row: row["screen"]["cells"][1][-1]["style"].update(fg=-1), + lambda row: row["screen"]["cells"][1][-1]["style"].update(bg=-1), + lambda row: row["screen"]["cells"][0][0].update(text="X"), + lambda row: row["screen"]["cells"][2][0].update(width=2), + lambda row: row["screen"]["cells"][4][0].update(text=" ", width=1), + ) + for change in changes: + changed = copy.deepcopy(record) + change(changed) + self.assertFalse(evaluate_terminal(changed, "styled-cursor", 12, 7)["passed"]) + if __name__ == "__main__": unittest.main()