From 60ef1eb70b487208fa790a882737dfe63da4bb7a Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:22:52 +0800 Subject: [PATCH 1/3] research: retain original 1049 state and unmatched style observation (#123) --- ...rm-alternate-style-reference-2026-10-28.md | 56 + research/ui/alternate_style_reference.js | 98 ++ research/ui/alternate_style_reference.py | 114 ++ .../ui/export_alternate_style_reference.py | 120 ++ .../validation.json | 1142 +++++++++++++++++ .../test_export_alternate_style_reference.py | 96 ++ 6 files changed, 1626 insertions(+) create mode 100644 docs/research/windterm-alternate-style-reference-2026-10-28.md create mode 100644 research/ui/alternate_style_reference.js create mode 100644 research/ui/alternate_style_reference.py create mode 100644 research/ui/export_alternate_style_reference.py create mode 100644 research/ui/results/2026-10-28-alternate-style-reference/validation.json create mode 100644 research/ui/test_export_alternate_style_reference.py diff --git a/docs/research/windterm-alternate-style-reference-2026-10-28.md b/docs/research/windterm-alternate-style-reference-2026-10-28.md new file mode 100644 index 00000000..8862c2fb --- /dev/null +++ b/docs/research/windterm-alternate-style-reference-2026-10-28.md @@ -0,0 +1,56 @@ +# WindTerm 2.7.0:1049 状态、标记和样式观测缺口 + +[#123](https://github.com/JavaZeroo/termforge/issues/123) 在固定原版上完成一次 11 阶段取证:实际调用主/备用屏状态接口、核对四个边界标记和一个延迟换行往返。**样式观测未成功,进入时继承、退出后当前样式恢复仍未知。** 精确 QPainter 入口经过受限设备过滤后没有匹配,不能把它解释成原版没有样式或样式符合某种默认规则。 + +完整合成源、DSR、文档投影、标记矩形、真实模式返回值和失败边界见[脱敏摘要](../../research/ui/results/2026-10-28-alternate-style-reference/validation.json)。它补充[首次模式观察](windterm-alternate-reference-2026-10-28.md),不替代本地 Shell 运输、完整 active grid、颜色或 R2 验收。 + +## 固定身份与一次执行 + +基点为 `d91eb589f04be6b54a29d237b87c5b05ad243908`。官方 2.7.0 Windows x64 EXE SHA-256 为 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`;复制的 4,047 个文件由既有完整清单 SHA `e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203` 守卫核对。动态探针另核对 WindTerm/Qt 模块路径属于自有复制包。 + +复用独立 WindowStation/Desktop、挂起创建后分配 OwnedJob、独立 HOME 和一个合成 Telnet profile,只监听 `127.0.0.1`。没有切换用户输入桌面、访问剪贴板或修改 DPI。固定计划在运行前保存于 ignored 的 `local/alternate-style-plan.json`,规定最多一次原版执行;实际一次,无重试。REA Frida Evidence ID 为 `ev_8c052576b1180d02c769570300f871cce11cdd828903f04952a3367627115639`。 + +本次保留 NAWS 历史 `91×24 → 91×25`,等待 UI capacity 与 NAWS 连续稳定后才释放源并冻结 **91×25**,冻结后任何网格改变均由 peer 报错。11 条精确源各发送一次,再追加 `CSI 6n`;下一阶段必须等待独立 UI 确认文件。收到 11 条无 `?` 前缀的 DSR、11 份 UI 快照和 11 次确认,没有其他应用回复字节。不声称覆盖任意 TCP 分块或逐字节解析。 + +`rea_capture_seconds=45` 是 REA 捕获时长,`cli_wait_timeout_seconds=60` 是复用启动器的 CLI 等待超时。原版更早恢复运行,中间有单独 2 秒等待,CLI 返回后还会回收资源;没有 45 秒或 60 秒整程生命周期保证。动态插桩只用于正确性,不计时或比较性能。 + +## 模式与标记的实际观察 + +`isAltScreen()`、`isMainScreen()` 本次均主动调用,所有结果为 bool 且无异常。主屏阶段得到 `false/true`;首次、重新和重复 `1049h` 后得到 `true/false`;`1049l` 后恢复 `false/true`。这弥补了首次观察只枚举接口名的缺口。 + +| 固定动作 | 观察 | +|---|---| +| 主屏 `2;3H` 写 `TFM0` | DSR `(2,7)`,`TFM0` 的原版 `visibleRectFromPosition` 为 `x=163,y=18,w=8,h=18` | +| `1049h` 后不改 SGR 写 `TFA0` | DSR `(1,5)`;状态为备用屏。`TFA0` 矩形 y=0,保留文档中的 `TFM0` 矩形 y=-18 | +| 备用屏 `4;5H` 改 SGR 后写 `TFA1`,`1049l` 后不改 SGR 写 `TFM1` | 主屏文字为 `TFM0TFM1`,DSR `(2,11)`;这些是文字/光标事实,颜色未知 | +| 重入后,在第一行、第二行、倒数第二行及最后一行写 `TFC0…3` | 四标记矩形 y 分别为 `0/18/414/432`;最后一个从第 88 列开始占到第 91 列,DSR `(25,91)` | +| 不退出,重复 `1049h` 后写 `TFA3` | 状态仍为备用屏,DSR `(1,5)`;四个 TFC 标记均从完整已读文档投影中消失,`TFA3` 矩形 y=0;保留主屏标记仍在 y=-18 | +| 再退出并写 `TFM2` | 主屏 `TFM0TFM1TFM2`,DSR `(2,15)`,主屏状态恢复 | + +矩形是原版接口返回的逻辑坐标,宽度 8 表示标记首字符位置的矩形,不是四字符整体宽度。负 y 与保留文档、scrollTop 一起说明:备用屏期间能从 `lineText` 读到旧主屏文字,不等于主屏仍是 active。它不是截图、像素证据或真实呈现端点;没有把矩形当成完整网格映射。 + +四个边界标记消失提供了更广的清除观察,仍不证明所有格均清空、空白格带何种样式或所有重复/混合模式转换符合原版。`ESC` 源的完整十六进制及每步返回均在摘要中,未用标准行为补造未知项。 + +## 延迟换行的有限合同 + +第 8 阶段先定位主屏 `(3,91)`,写 `W`,紧接 `1049h` 和备用标记 `TFA4`。第 9 阶段为 `1049l` 紧接 `X`,返回 DSR **`(4,2)`**。`W` 与进入备用屏之间、退出备用屏与 `X` 之间均没有插入 DSR。独立控制在 `(6,91)` 连续写 `VZ`,得到 DSR **`(7,2)`**。 + +这支持该固定输入中退出备用屏后保留延迟换行的可观察效果。原版文档把 `WX`、`VZ` 分别投影为相连文字;协议坐标用于判断换到下一物理行,不能拿逻辑文档行号代替。它不证明内部用了同名 pending-wrap 字段,也不外推到 margin、origin、宽字、resize 或其他保存恢复序列。 + +## 颜色观测为何保持未知 + +准备阶段只读核对了固定 Qt5Gui SHA `5e7d2d41b8b92a880e83b8cc0ca173f5da61218604186196787ee1600956be1e` 的精确导出和局部指令。官方 Qt 5.15.2 [QPainter 头文件](https://raw.githubusercontent.com/qt/qtbase/v5.15.2/src/gui/painting/qpainter.h)给出借用 `const QPen&` 和具体 drawText 签名,[QPen](https://raw.githubusercontent.com/qt/qtbase/v5.15.2/src/gui/painting/qpen.h) 与 [QColor](https://raw.githubusercontent.com/qt/qtbase/v5.15.2/src/gui/painting/qcolor.h)给出颜色返回接口。固定包内 `QPen::color`(RVA `0x274420`)使用 `this=rcx,result=rdx`,写入结果后返回 rdx;探针预备 16B 临时值,没有猜测聚合返回位置。QString::size/constData 和 QBrush::color 也由该包导出与局部指令核对。既有字体观察提供 QWidget→QPaintDevice `+0x10` 的来源。 + +本次尝试把 `QPainter::drawText(QPointF,QString)` 的已知合成标记、QPainter 设备身份及 pen RGBA 绑定;背景只观测同设备四种 `fillRect(QRect/QRectF,QColor/QBrush)`。每阶段请求自有 viewport update,但设备过滤后的文本绘制调用、标记、填充匹配均为 **0**。因此颜色读取链没有实际取得任何标记颜色,样式字段全部为 `null`。 + +当前数据不能区分原版用了另一 overload、重定向的 backing-store 设备、未触发该绘制路径或其他原因。不能把 `0` 次匹配解释成 `0` 样式,也没有基于猜测更改探针重跑。没有调用未核实的 QWidget grab/QPixmap-toImage ABI,没有生成栅格图;完整 active-grid/受控栅格/真实 presented 分别保持未验证。下一次颜色调查应先定向确认实际绘制入口或取得经核实的受控栅格路径。 + +## 离线审计与交付边界 + +REA 正常返回,无脚本错误、消息截断、清理错误或 stderr;Job 最终 `active=0,total=3,terminated=0`,profile 仍只有一个合成会话。启动器主动结束原版进程,不以 Job 计数声明自然退出。provider subject/environment 为 null,固定包身份由独立守卫提供。 + +[导出器](../../research/ui/export_alternate_style_reference.py)用显式 `require/ValueError`,在普通与 `python -O` 下对同一原件导出完全相同的 `partial-mode-and-wrap-observation`;校验失败前不创建公开文件。仅导出固定合成源、真实模式值、矩形和脱敏 scalar。原始包、profile、绝对路径、PID、插桩与日志保留在 `local/alternate-style-reference-0`。 + +[公共回归](../../research/ui/test_export_alternate_style_reference.py)以自造记录验证普通/优化 CLI:无匹配必须导出为样式未知的 partial,非夹具文字、未调用模式 getter、错误原版身份必须拒绝。没有使用原版资源或新增原版执行。 + +本次可为 C++ 切片提供模式状态、四标记消失和该延迟换行往返的原版合同;进入继承/退出当前样式、完整清屏及其他交叉仍未解除。#123、#27 和 R2 不能由本次部分取证关闭;本地 Shell 的原生 margin 问题继续由各自任务处理。 diff --git a/research/ui/alternate_style_reference.js b/research/ui/alternate_style_reference.js new file mode 100644 index 00000000..5813d40b --- /dev/null +++ b/research/ui/alternate_style_reference.js @@ -0,0 +1,98 @@ +// Exact known-marker paint observations on the owned original Qt viewport only. +let stage=0,busy=false,finished=false,due=0,lastPhase=-1,stable=null,stableSince=0; +let paintPhase=-1,paintBusy=false,paintCount=0,requested=false,readyGrid=null,readySince=0; +const started=Date.now(),observations=[],draws=[],fills=[],knownMarkers=['TFM0','TFM1','TFM2','TFA0','TFA1','TFA2','TFA3','TFA4','TFC0','TFC1','TFC2','TFC3']; +for(const module of [wind,core,gui,widgets]){ + const expected=(__RUN_DIRECTORY_JSON__+'/app/'+module.name).replace(/\\/g,'/').toLowerCase(); + if(module.path.replace(/\\/g,'/').toLowerCase()!==expected)throw Error('pinned Qt module path guard'); +} +const painterDevice=fn(gui,'?device@QPainter@@QEBAPEAVQPaintDevice@@XZ','pointer',['pointer']); +const painterPen=fn(gui,'?pen@QPainter@@QEBAAEBVQPen@@XZ','pointer',['pointer']); +const penColor=fn(gui,'?color@QPen@@QEBA?AVQColor@@XZ','pointer',['pointer','pointer']); +const brushColor=fn(gui,'?color@QBrush@@QEBAAEBVQColor@@XZ','pointer',['pointer']); +const colorParts=['red','green','blue','alpha'].map(name=>fn(gui,'?'+name+'@QColor@@QEBAHXZ','int',['pointer'])); +const stringSize=fn(core,'?size@QString@@QEBAHXZ','int',['pointer']); +const updateWidget=fn(widgets,'?update@QWidget@@QEAAXXZ','void',['pointer']); +let paintDevices=new Map(); +function rgba(color){const value=colorParts.map(read=>read(color));if(value.some(x=>x<0||x>255))throw Error('QColor component bound');return value;} +function currentTerminal(){ + const raw=evalText(`JSON.stringify((function(){var t=window.activeText;if(!t)return null;function get(k){try{var v=typeof t[k]==='function'?t[k]():t[k];return v===undefined?null:v;}catch(e){return null;}}function mode(k){var type=typeof t[k];if(type!=='function')return {callable:false,value:null,error:'not-callable'};try{return {callable:true,value:t[k](),error:null};}catch(e){return {callable:true,value:null,error:String(e)};}}var n=get('visibleLineCount');var lines=[],markers=[];if(typeof n==='number'&&n>=0&&n<=200){for(var i=0;i64)return; + const text=constData(args[2]).readUtf16String(size),markers=knownMarkers.filter(marker=>text.indexOf(marker)>=0); + if(!markers.length||! /^(?:TF[MAC][0-4]| )+$/.test(text))return; + const point=[args[1].readDouble(),args[1].add(8).readDouble()]; + if(point.some(x=>!Number.isFinite(x)||Math.abs(x)>4096))throw Error('text coordinate bound'); + const color=Memory.alloc(16);penColor(painterPen(args[0]),color); + const record={phase:paintPhase,role,api:'QPainter::drawText(QPointF,QString)',text,markers,point,pen_rgba:rgba(color)}; + const key=JSON.stringify(record);if(!draws.some(row=>JSON.stringify(row)===key)){if(draws.length>=256)throw Error('draw record bound');draws.push(record);send({kind:'known_marker_draw',...record});} + }catch(e){finish('failed',String(e));}finally{paintBusy=false;} +}}); +for(const floating of [false,true])for(const brush of [false,true]){ + const rectType=floating?'QRectF':'QRect',colorType=brush?'QBrush':'QColor'; + const api='?fillRect@QPainter@@QEAAXAEBV'+rectType+'@@AEBV'+colorType+'@@@Z'; + Interceptor.attach(gui.getExportByName(api),{onEnter(args){ + if(finished||paintBusy||paintPhase<0||Process.getCurrentThreadId()!==uiThread)return; + paintBusy=true; + try{ + const role=deviceRole(args[0]);if(!role)return; + let rectangle;if(floating)rectangle=[0,8,16,24].map(offset=>args[1].add(offset).readDouble()); + else{const r=[0,4,8,12].map(offset=>args[1].add(offset).readS32());rectangle=[r[0],r[1],r[2]-r[0]+1,r[3]-r[1]+1];} + if(rectangle.some(x=>!Number.isFinite(x)||Math.abs(x)>4096))throw Error('fill coordinate bound'); + const record={phase:paintPhase,role,api:'QPainter::fillRect('+rectType+','+colorType+')',rectangle,rgba:rgba(brush?brushColor(args[2]):args[2])}; + const key=JSON.stringify(record);if(!fills.some(row=>JSON.stringify(row)===key)){if(fills.length>=1024)throw Error('fill record bound');fills.push(record);} + }catch(e){finish('failed',String(e));}finally{paintBusy=false;} + }}); +} +send({kind:'probe_ready',station_isolated:true,scenario:'alternate-style-reference',paint_contract:'verified-Qt5-export-calls'}); +Interceptor.attach(user.getExportByName('DispatchMessageW'),{onEnter(){ + if(busy||finished||Process.getCurrentThreadId()!==uiThread||Date.now()41000)throw Error('bounded scenario deadline'); + if(stage===0){if(!ready('ui'))return;resize(main,1360,860);stage=1;} + else if(stage===1){scan();expandAll(tree);focus(tree,7);key(tree,0x1000011);stage=2;} + else if(stage===2){key(tree,0x1000004);stage=3;} + else{ + const peer=peerState();if(!peer||!peer.connected)return; + if(peer.error)throw Error('peer '+peer.error); + if(peer.complete){finish('observed-all-style-phases',null);return;} + const terminal=currentTerminal();if(!terminal||!terminal.connected||terminal.protocol!=='Telnet')return; + if(stage===3){ + const grid=JSON.stringify([peer.columns,peer.rows,terminal.capacity]); + if(!peer.columns||peer.rows!==terminal.capacity)return; + if(grid!==readyGrid){readyGrid=grid;readySince=Date.now();return;} + if(Date.now()-readySince<600)return; + scan();const viewport=viewPort();paintDevices=new Map([[viewport.add(0x10).toString(),'owned-terminal-viewport'],[editor.add(0x10).toString(),'owned-terminal-editor']]); + const flag=new File(__RUN_DIRECTORY_JSON__+'/fixture-ready','w');flag.write('stable-NAWS-capacity');flag.close();stage=4;return; + } + if(peer.phase===null)return; + if(peer.phase!==lastPhase){lastPhase=peer.phase;stable=null;stableSince=Date.now();requested=false;} + const record=peer.records[peer.phase];if(!record.cursor_reply)return; + const state=JSON.stringify(terminal); + if(state!==stable){stable=state;stableSince=Date.now();requested=false;return;} + if(Date.now()-stableSince<400)return; + if(!requested){paintPhase=peer.phase;updateWidget(viewPort());requested=true;stableSince=Date.now();return;} + if(Date.now()-stableSince<450)return; + const observation={phase:peer.phase,label:peer.phase_label,terminal,cursor_reply:record.cursor_reply,paint_requested:true,matching_marker_draws:draws.filter(row=>row.phase===peer.phase).length}; + observations.push(observation);send({kind:'style_phase',...observation}); + const ack=new File(__RUN_DIRECTORY_JSON__+'/ack-'+peer.phase,'w');ack.write('observed');ack.close(); + stableSince=Date.now()+100000; + } + due=Date.now()+75; + }catch(e){finish('failed',String(e));}finally{busy=false;} +}}); +setTimeout(()=>finish('failed','bounded observation timer'),42000); diff --git a/research/ui/alternate_style_reference.py b/research/ui/alternate_style_reference.py new file mode 100644 index 00000000..a5295a0f --- /dev/null +++ b/research/ui/alternate_style_reference.py @@ -0,0 +1,114 @@ +"""One bounded original-terminal style observation, with owned Qt paint hooks.""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def phases(rows: int, columns: int): + esc = b"\x1b[" + cup = lambda row, column: f"\x1b[{row};{column}H".encode("ascii") + return [ + ("main-red-blue", esc+b"0m"+esc+b"2J"+esc+b"H"+esc+b"?7h"+cup(2,3)+esc+b"31;44mTFM0"), + ("1049-enter-inherited-style", esc+b"?1049hTFA0"), + ("alternate-green-magenta", cup(4,5)+esc+b"32;45mTFA1"), + ("1049-exit-main-style", esc+b"?1049lTFM1"), + ("1049-second-enter-style", esc+b"?1049hTFA2"), + ("alternate-four-corners", cup(1,1)+esc+b"33;46mTFC0"+cup(2,1)+b"TFC1"+cup(rows-1,1)+b"TFC2"+cup(rows,columns-3)+b"TFC3"), + ("1049-duplicate-enter-style", esc+b"?1049hTFA3"), + ("1049-second-exit-main-style", esc+b"?1049lTFM2"), + # No inserted DSR between the last-column W and the alternate entry. + ("main-pending-wrap-enter", cup(3,columns)+esc+b"31;44mW"+esc+b"?1049hTFA4"), + # No inserted DSR between the alternate exit and the following X. + ("main-pending-wrap-exit-write", esc+b"?1049lX"), + ("main-direct-wrap-control", cup(6,columns)+esc+b"31;44mVZ"), + ] + + +def peer_class(): + """Reuse the fixed Telnet parser; delay source freezing until UI readiness.""" + from research.ui import alternate_reference + original = Path(alternate_reference.__file__) + source = original.read_text(encoding="utf-8") + replacements = { + 'if self.state["rows"] and selected is None:': 'if self.state["rows"] and selected is None and (self.directory / "fixture-ready").exists():', + 'selected = phases(self.state["rows"])': 'self.state["frozen_grid"] = [self.state["columns"], self.state["rows"]]\n selected = phases(self.state["rows"], self.state["columns"])', + 'if len(received) > 65536:': 'if self.state.get("frozen_grid") and self.state["frozen_grid"] != [self.state["columns"], self.state["rows"]]:\n raise RuntimeError("NAWS changed after fixture freeze")\n if len(received) > 65536:', + 'def _record(self):\n temporary': 'def _record(self):\n grid = [self.state["columns"], self.state["rows"]]\n history = self.state.setdefault("naws_history", [])\n if grid[0] and (not history or history[-1] != grid):\n history.append(grid)\n temporary', + } + for old, new in replacements.items(): + require(source.count(old) == 1, "Existing protected peer source changed") + source = source.replace(old, new) + namespace = {"__file__": str(original), "__name__": "termforge_style_peer"} + exec(compile(source, str(original), "exec"), namespace) + namespace["phases"] = phases + return namespace["Peer"] + + +Peer = peer_class() + + +def run(source: Path, out: Path, rea: Path): + from research.ui import r1_launch + launcher = Path(r1_launch.__file__) + text = launcher.read_text(encoding="utf-8") + replacements = { + "from research.ui.r1_vt_fixture import Peer": "from research.ui.alternate_style_reference import Peer", + 'Path(__file__).with_name("r1_menu_wrap.js").read_text(encoding="utf-8")': 'Path(__file__).with_name("alternate_style_reference.js").read_text(encoding="utf-8")', + } + for old, new in replacements.items(): + require(text.count(old) == 1, "Existing protected launcher source changed") + text = text.replace(old, new) + namespace = {"__file__": str(launcher), "__name__": "termforge_style_launcher"} + exec(compile(text, str(launcher), "exec"), namespace) + namespace["run"](source, out, rea, 45, False, "menu-wrap") + + +def write_plan(destination: Path): + sha = lambda path: hashlib.sha256(path.read_bytes()).hexdigest() + plan = { + "issue": 123, "maximum_original_runs": 1, + "reference_exe_sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "reference_files_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", + "source_base_commit": subprocess.check_output(["git","rev-parse","HEAD"],cwd=ROOT,text=True).strip(), + "rea_capture_seconds": 45, "cli_wait_timeout_seconds": 60, + "whole_lifecycle_bound_seconds": None, + "route": "Original terminal, owned loopback Telnet, isolated station/desktop/profile and OwnedJob", + "source_freeze": "UI confirms stable NAWS/capacity before releasing the peer; any later NAWS change fails the run", + "phase_count": len(phases(25,91)), + "example_grid": [91,25], + "example_sources": [{"phase":index,"label":label,"hex":content.hex(),"sha256":hashlib.sha256(content).hexdigest()} for index,(label,content) in enumerate(phases(25,91))], + "source_suffix": "Each phase is followed by CSI 6n and an independent UI acknowledgment", + "probe_files_sha256": {name:sha(ROOT/name) for name in ("research/ui/alternate_style_reference.py","research/ui/alternate_style_reference.js","research/ui/alternate_reference.py","research/ui/r1_launch.py","research/ui/qt_r1_text.js")}, + "actions": ["Open only synthetic loopback session on owned non-input station", "Call isAltScreen/isMainScreen and record actual returns", "Observe known-marker drawText and same-device fillRect colors using verified Qt5 exports", "Request an owned viewport update to collect paint calls", "Record source, DSR, document projection and marker rectangles", "Stop on failure and reclaim only owned resources; no retry"], + "limitations": ["QPainter calls are correctness evidence, not pixels or presented frames", "Unknown/unmatched paint paths remain null", "No clipboard, user desktop, personal profile or DPI changes", "No inserted DSR between pending W/1049h or 1049l/X", "45 is capture duration; CLI wait is60 and target starts before the separate2-second delay; no whole-lifecycle45-second guarantee", "Complete clear-grid/pending-wrap equivalence requires evidence, not assumed standards"], + } + destination.parent.mkdir(parents=True,exist_ok=True) + require(not destination.exists(),"Plan output must be new") + destination.write_text(json.dumps(plan,indent=2)+"\n",encoding="utf-8",newline="\n") + print(json.dumps({"plan":destination.as_posix(),"phases":plan["phase_count"],"maximum_original_runs":1})) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--plan",type=Path) + parser.add_argument("--source",type=Path) + parser.add_argument("--out",type=Path) + parser.add_argument("--rea",type=Path) + args = parser.parse_args() + if args.plan: + write_plan(args.plan) + else: + require(args.source and args.out and args.rea,"Source, output and REA are required") + run(args.source,args.out,args.rea) diff --git a/research/ui/export_alternate_style_reference.py b/research/ui/export_alternate_style_reference.py new file mode 100644 index 00000000..c9599dd6 --- /dev/null +++ b/research/ui/export_alternate_style_reference.py @@ -0,0 +1,120 @@ +"""Export the fixed partial style observation; unmatched painting stays unknown.""" +from __future__ import annotations +import argparse +import hashlib +import json +import math +from pathlib import Path +import re +import sys + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) +from research.ui.alternate_style_reference import phases, require + +SYNTHETIC_LINE = re.compile(r" *(?:(?:TFM[0-2]|TFA[0-4]|TFC[0-3])+|WX?|X|VZ?|Z)? *") +MARKERS = {f"TF{kind}{index}" for kind, count in (("M",3),("A",5),("C",4)) for index in range(count)} + + +def export(directory: Path, plan_path: Path, destination: Path): + sha = lambda path: hashlib.sha256(path.read_bytes()).hexdigest() + read = lambda name: json.loads((directory/name).read_text(encoding="utf-8")) + plan = json.loads(plan_path.read_text(encoding="utf-8")) + run, peer, captured = read("run.json"), read("peer.json"), read("style-probe-result.json") + outer = read("evidence.json") + evidence = outer["data"]["evidence"] + normalized = evidence["normalized_result"] + cleanup, profile = read("cleanup.json"), read("profile-check.json") + require(plan["issue"] == 123 and plan["maximum_original_runs"] == 1, "Unexpected observation plan") + require(plan["source_base_commit"] == "d91eb589f04be6b54a29d237b87c5b05ad243908", "Observation base differs") + require(plan["rea_capture_seconds"] == 45 and plan["cli_wait_timeout_seconds"] == 60 and plan["whole_lifecycle_bound_seconds"] is None and plan["phase_count"] == 11, "Plan durations or phase count differ") + require(set(plan["probe_files_sha256"]) == {"research/ui/alternate_style_reference.py","research/ui/alternate_style_reference.js","research/ui/alternate_reference.py","research/ui/r1_launch.py","research/ui/qt_r1_text.js"}, "Probe source inventory differs") + for name, expected in plan["probe_files_sha256"].items(): + require(name in {"research/ui/alternate_style_reference.py","research/ui/alternate_style_reference.js","research/ui/alternate_reference.py","research/ui/r1_launch.py","research/ui/qt_r1_text.js"}, "Unexpected probe source path") + require(sha(ROOT/name) == expected, "Executed probe source changed after its plan") + require(outer["ok"] and not outer["data"]["cleanup_error"], "REA capture failed") + require(captured["status"] == "observed-all-style-phases" and not captured["error"], "Input phases did not finish") + require(peer["complete"] and not peer.get("error"), "Owned peer did not finish") + require(run["sha256"] == plan["reference_exe_sha256"] == "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", "Original executable identity differs") + require(run["files_sha256"] == plan["reference_files_manifest_sha256"] == "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", "Original package identity differs") + require(normalized["source_sha256"] == run["script_sha256"] == sha(directory/"probe.js"), "Injected script identity differs") + require(not normalized["messages_truncated"] and not normalized["cleanup_error"], "Capture is truncated or cleanup failed") + require(not any(message["type"] == "error" for message in normalized["messages"]), "Capture contains a script error") + messages = [message["payload"] for message in normalized["messages"] if message["type"] == "send" and isinstance(message.get("payload"),dict)] + observed = [{key:value for key,value in message.items() if key != "kind"} for message in messages if message.get("kind") == "style_phase"] + finals = [{key:value for key,value in message.items() if key != "kind"} for message in messages if message.get("kind") == "style_reference_result"] + require(observed == captured["observations"] and finals == [captured], "Provider and retained observations differ") + require(cleanup["active"] == 0 and profile == {"only_synthetic_session":True,"session_count":1}, "Owned cleanup/profile scope failed") + columns, rows = peer["frozen_grid"] + require([columns,rows] == [peer["columns"],peer["rows"]] == [91,25], "Frozen observation grid differs") + require(peer["naws_history"] == [[91,24],[91,25]], "Retained NAWS history differs") + require((directory/"fixture-ready").read_text() == "stable-NAWS-capacity", "UI source-freeze acknowledgment differs") + received = bytes.fromhex(peer["received_hex"]) + replies = list(re.finditer(rb"\x1b\[(\??)(\d+);(\d+)R",received)) + require(not re.sub(rb"\x1b\[(\??)(\d+);(\d+)R",b"",received), "Unexpected application reply bytes") + expected = phases(rows,columns) + require(len(expected) == len(replies) == len(observed) == len(peer["records"]) == 11, "Phase/reply count differs") + exported = [] + for index, ((label,source), record, observation, reply) in enumerate(zip(expected,peer["records"],observed,replies)): + require(record["phase"] == observation["phase"] == index and record["label"] == observation["label"] == label, "Phase identity differs") + require(record["source_hex"] == source.hex() and record["source_sha256"] == hashlib.sha256(source).hexdigest(), "Synthetic source differs") + cursor = [int(reply[2]),int(reply[3])] + require(cursor == record["cursor_reply"] == observation["cursor_reply"], "Cursor reply differs") + require(reply[1] == b"" and (directory/f"ack-{index}").read_text() == "observed", "Reply prefix/stage acknowledgment differs") + terminal = observation["terminal"] + require(terminal["protocol"] == "Telnet" and terminal["connected"] is True and terminal["capacity"] == rows, "Terminal identity/capacity differs") + require(terminal["line_count"] == len(terminal["lines"]) <= 200, "Document projection bound differs") + require(all(isinstance(line,str) and SYNTHETIC_LINE.fullmatch(line) for line in terminal["lines"]), "Only fixed synthetic text is publishable") + for mode in (terminal["is_alt"],terminal["is_main"]): + require(mode == {"callable":True,"value":mode["value"],"error":None} and type(mode["value"]) is bool, "Mode getter was not successfully called") + require(terminal["is_alt"]["value"] != terminal["is_main"]["value"], "Mode getter values are not complementary") + marker_values = [] + for marker in terminal["markers"]: + require(marker["marker"] in MARKERS, "Unexpected marker") + line, column = marker["document_line"], marker["column"] + require(type(line) is int and 0 <= line < terminal["line_count"] and type(column) is int and column >= 0, "Marker location bound differs") + require(terminal["lines"][line][column:column+4] == marker["marker"], "Marker is not bound to retained text") + require(type(marker["position"]) is int and 0 <= marker["position"] <= 32768, "Marker position bound differs") + rectangle = marker["rect"] + require(set(rectangle) == {"x","y","width","height","left","right","top","bottom"}, "Unexpected rectangle fields") + require(all(type(value) in (int,float) and math.isfinite(value) and abs(value) <= 4096 for value in rectangle.values()), "Marker rectangle bound differs") + require(rectangle["width"] > 0 and rectangle["height"] > 0, "Invalid marker rectangle") + marker_values.append({"marker":marker["marker"],"document_line":line,"column":column,"position":marker["position"],"rect":dict(rectangle)}) + exported.append({"phase":index,"label":label,"source_hex":source.hex(),"source_bytes":len(source),"source_sha256":hashlib.sha256(source).hexdigest(),"dsr_suffix_hex":"1b5b366e","cursor_reply":cursor,"cursor_reply_hex":reply[0].hex(),"cursor_reply_private_prefix":"","document_projection":{"lines":terminal["lines"],"line_count":terminal["line_count"],"scroll":terminal["scroll"],"capacity":terminal["capacity"],"markers":marker_values},"is_alt":dict(terminal["is_alt"]),"is_main":dict(terminal["is_main"]),"paint_requested":observation["paint_requested"],"matching_marker_draws":observation["matching_marker_draws"]}) + # This frozen sample found no matching draw/device path. Preserve this failed + # observation surface instead of publishing an empty list as style success. + require(captured["draws"] == [] and captured["fills"] == [] and captured["paint_calls_seen"] == 0, "Frozen unmatched-paint result differs") + require(all(row["matching_marker_draws"] == 0 and row["paint_requested"] is True for row in exported), "Paint-attempt records differ") + raw_names = ("evidence.json","run.json","probe.js","style-probe-result.json","peer.json","fixture-ready","cleanup.json","profile-check.json","stderr.txt") + summary = { + "schema":"termforge-original-alternate-style-reference-v1","issue":123, + "status":"partial-mode-and-wrap-observation","source_base_commit":plan["source_base_commit"], + "scope":"One original direct-terminal Telnet observation. Mode getter calls and text/cursor behavior observed; the attempted QPainter style surface did not match.", + "reference_identity":{"exe_sha256":run["sha256"],"files_manifest_sha256":run["files_sha256"],"files_count":4047}, + "actual_original_runs":1,"maximum_original_runs":1,"rea_capture_seconds":45,"cli_wait_timeout_seconds":60,"whole_lifecycle_bound_seconds":None, + "naws_history":peer["naws_history"],"frozen_grid":peer["frozen_grid"],"naws_changed_after_freeze":False, + "source_freeze":"Owned UI acknowledgment after stable NAWS/capacity; any later NAWS change fails the peer.", + "plan_sha256":sha(plan_path),"probe_files_sha256":dict(plan["probe_files_sha256"]),"exporter_sha256":sha(Path(__file__)), + "phase_transport":"Each exact synthetic source is followed by CSI 6n and an independent UI acknowledgment; TCP chunking is not prescribed.", + "dsr_request_hex":"1b5b366e","dsr_reply_count":len(replies),"unmatched_application_reply_hex":"","phases":exported, + "mode_getters_observed":True,"marker_rectangles_observed":True, + "paint_observation_attempt":{"surface":"Exact QPainter::drawText(QPointF,QString) and four fillRect exports, filtered to owned viewport/editor QPaintDevice identities","matching_device_text_calls":0,"matching_marker_draws":0,"matching_fills":0,"result":"unmatched","entry_style_inheritance":None,"exit_style_restoration":None,"repeated_entry_style":None,"cell_colors":None}, + "pending_wrap_sample":{"enter_phase":8,"exit_and_write_phase":9,"exit_reply":exported[9]["cursor_reply"],"direct_control_phase":10,"direct_control_reply":exported[10]["cursor_reply"],"dsr_between_W_and_enter":False,"dsr_between_exit_and_X":False}, + "provider":{"evidence_id":evidence["evidence_id"],"provider":evidence["provider"],"confidence":evidence["confidence"],"subject":evidence["subject"],"environment":evidence["environment"],"messages_truncated":False,"cleanup_error":None,"limitations":evidence["limitations"]}, + "lifecycle":{"outer_job":cleanup,"profile":profile,"deliberate_original_termination":True,"natural_gui_exit_verified":False,"stderr_bytes":(directory/"stderr.txt").stat().st_size}, + "raw_files":{name:{"bytes":(directory/name).stat().st_size,"sha256":sha(directory/name)} for name in raw_names}, + "terminal_style_contract_verified":False,"raster_image_verified":False,"full_active_grid_verified":False,"local_shell_transport_verified":False,"presented_done":None,"windterm_parity":False,"r2_complete":False, + "limitations":["Unmatched QPainter hooks do not imply that the original has no styles; overload, redirected device, repaint scheduling or another path remain unseparated possibilities.","This run did not read pixels or obtain an image. Paint-call attempts cannot prove presented frames or pixel equivalence.","Mode getter booleans and marker rectangles establish the sampled original states; retained document lines are not a complete active grid.","Four corner markers disappear from the retained projection after duplicate1049h; entire-grid clearing and blank-cell styles remain unknown.","Pending-wrap evidence is limited to this exact 91x25 direct-terminal input and its control, not all mode/resize crossings.","The attempted entry/current-style/exit restoration observations remain null. No generic-standard behavior substitutes for them.","Telnet does not establish local-shell WinPTY/ConPTY semantics or resolve41/56.","45seconds is REA capture duration,60seconds is CLI wait timeout; target launch precedes a separate2-second delay and cleanup follows CLI wait. No whole-lifecycle bound is asserted.","Instrumentation is correctness-only. No latency, throughput, presented endpoint, reliability repetition or performance claim.","Provider subject/environment remain null; executable/package identity is independently enforced by preparation and module-path guards.","The original GUI is deliberately ended by the owned launcher. Job active0 is cleanup proof, not natural exit proof.","Copied original resources, raw probes, paths, identifiers and synthetic profile remain local; only fixed synthetic inputs and audited scalar observations are published."] + } + destination.parent.mkdir(parents=True,exist_ok=True) + destination.write_text(json.dumps(summary,ensure_ascii=False,indent=2)+"\n",encoding="utf-8",newline="\n") + print(json.dumps({"exported":destination.as_posix(),"status":summary["status"],"phases":len(exported),"styles_verified":False})) + + +if __name__ == "__main__": + parser=argparse.ArgumentParser() + parser.add_argument("--directory",type=Path,required=True) + parser.add_argument("--plan",type=Path,required=True) + parser.add_argument("--destination",type=Path,required=True) + args=parser.parse_args() + export(args.directory,args.plan,args.destination) diff --git a/research/ui/results/2026-10-28-alternate-style-reference/validation.json b/research/ui/results/2026-10-28-alternate-style-reference/validation.json new file mode 100644 index 00000000..99c74ebc --- /dev/null +++ b/research/ui/results/2026-10-28-alternate-style-reference/validation.json @@ -0,0 +1,1142 @@ +{ + "schema": "termforge-original-alternate-style-reference-v1", + "issue": 123, + "status": "partial-mode-and-wrap-observation", + "source_base_commit": "d91eb589f04be6b54a29d237b87c5b05ad243908", + "scope": "One original direct-terminal Telnet observation. Mode getter calls and text/cursor behavior observed; the attempted QPainter style surface did not match.", + "reference_identity": { + "exe_sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "files_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", + "files_count": 4047 + }, + "actual_original_runs": 1, + "maximum_original_runs": 1, + "rea_capture_seconds": 45, + "cli_wait_timeout_seconds": 60, + "whole_lifecycle_bound_seconds": null, + "naws_history": [ + [ + 91, + 24 + ], + [ + 91, + 25 + ] + ], + "frozen_grid": [ + 91, + 25 + ], + "naws_changed_after_freeze": false, + "source_freeze": "Owned UI acknowledgment after stable NAWS/capacity; any later NAWS change fails the peer.", + "plan_sha256": "ca5f4d79a4c9c607056b2a4553fd6ae408c79f1efac133c2ef4a945ed4f02c9d", + "probe_files_sha256": { + "research/ui/alternate_style_reference.py": "c129cbebe78a216b3f3593fc5e9ec4a1b1694ce190d047497de55e7953a8728d", + "research/ui/alternate_style_reference.js": "7ba1ff46a606c6c2bac4a65c7cab8a43b3a996be00d2dd050cd8bf5c0c6b7292", + "research/ui/alternate_reference.py": "94b85d49347e0e9813f7acddf0244f58520a7d1fce14084677c91cf4ddafffea", + "research/ui/r1_launch.py": "54fb41872c6abcc48242a0938044184854b016785cc13659c66b1bf404da0d8a", + "research/ui/qt_r1_text.js": "f5820d413fdd72c13dffcefbf4d0ade615abfe15754c6b0b52e1e098814eea6f" + }, + "exporter_sha256": "709b54a31e5ea77541cbebbd050e594e1976126c28efd3efe627d047676f7667", + "phase_transport": "Each exact synthetic source is followed by CSI 6n and an independent UI acknowledgment; TCP chunking is not prescribed.", + "dsr_request_hex": "1b5b366e", + "dsr_reply_count": 11, + "unmatched_application_reply_hex": "", + "phases": [ + { + "phase": 0, + "label": "main-red-blue", + "source_hex": "1b5b306d1b5b324a1b5b481b5b3f37681b5b323b33481b5b33313b34346d54464d30", + "source_bytes": 34, + "source_sha256": "7d7ee54c42f1f30ef86849dd0afb9a4f4e61e2d3098055a1dd80d62662fc98fd", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 2, + 7 + ], + "cursor_reply_hex": "1b5b323b3752", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0" + ], + "line_count": 2, + "scroll": 0, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": 18, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": 18, + "bottom": 35 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": false, + "error": null + }, + "is_main": { + "callable": true, + "value": true, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + }, + { + "phase": 1, + "label": "1049-enter-inherited-style", + "source_hex": "1b5b3f313034396854464130", + "source_bytes": 12, + "source_sha256": "5ba270f55aac7a5add74df84c65db1a81d47b7852958287366eb190c69043917", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 5 + ], + "cursor_reply_hex": "1b5b313b3552", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0", + "TFA0" + ], + "line_count": 3, + "scroll": 2, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": -18, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": -18, + "bottom": -1 + } + }, + { + "marker": "TFA0", + "document_line": 2, + "column": 0, + "position": 8, + "rect": { + "x": 147, + "y": 0, + "width": 8, + "height": 18, + "left": 147, + "right": 154, + "top": 0, + "bottom": 17 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": true, + "error": null + }, + "is_main": { + "callable": true, + "value": false, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + }, + { + "phase": 2, + "label": "alternate-green-magenta", + "source_hex": "1b5b343b35481b5b33323b34356d54464131", + "source_bytes": 18, + "source_sha256": "4a080d35cf0341271b425c511d3b1cf42639e455eef1484f2fea808e0dc5c810", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 4, + 9 + ], + "cursor_reply_hex": "1b5b343b3952", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0", + "TFA0", + "", + "", + " TFA1" + ], + "line_count": 6, + "scroll": 2, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": -18, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": -18, + "bottom": -1 + } + }, + { + "marker": "TFA0", + "document_line": 2, + "column": 0, + "position": 8, + "rect": { + "x": 147, + "y": 0, + "width": 8, + "height": 18, + "left": 147, + "right": 154, + "top": 0, + "bottom": 17 + } + }, + { + "marker": "TFA1", + "document_line": 5, + "column": 4, + "position": 19, + "rect": { + "x": 179, + "y": 54, + "width": 8, + "height": 18, + "left": 179, + "right": 186, + "top": 54, + "bottom": 71 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": true, + "error": null + }, + "is_main": { + "callable": true, + "value": false, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + }, + { + "phase": 3, + "label": "1049-exit-main-style", + "source_hex": "1b5b3f313034396c54464d31", + "source_bytes": 12, + "source_sha256": "1cfc9112024cf6d8805056ee55bfac54f93e27a025796575e9379b0c00787084", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 2, + 11 + ], + "cursor_reply_hex": "1b5b323b313152", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0TFM1" + ], + "line_count": 2, + "scroll": 0, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": 18, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": 18, + "bottom": 35 + } + }, + { + "marker": "TFM1", + "document_line": 1, + "column": 6, + "position": 7, + "rect": { + "x": 195, + "y": 18, + "width": 8, + "height": 18, + "left": 195, + "right": 202, + "top": 18, + "bottom": 35 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": false, + "error": null + }, + "is_main": { + "callable": true, + "value": true, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + }, + { + "phase": 4, + "label": "1049-second-enter-style", + "source_hex": "1b5b3f313034396854464132", + "source_bytes": 12, + "source_sha256": "4ca7c6119d698cf9ccbc5dee1c16c119439e9cddb83290c0243b3557a67feb5d", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 5 + ], + "cursor_reply_hex": "1b5b313b3552", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0TFM1", + "TFA2" + ], + "line_count": 3, + "scroll": 2, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": -18, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": -18, + "bottom": -1 + } + }, + { + "marker": "TFM1", + "document_line": 1, + "column": 6, + "position": 7, + "rect": { + "x": 195, + "y": -18, + "width": 8, + "height": 18, + "left": 195, + "right": 202, + "top": -18, + "bottom": -1 + } + }, + { + "marker": "TFA2", + "document_line": 2, + "column": 0, + "position": 12, + "rect": { + "x": 147, + "y": 0, + "width": 8, + "height": 18, + "left": 147, + "right": 154, + "top": 0, + "bottom": 17 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": true, + "error": null + }, + "is_main": { + "callable": true, + "value": false, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + }, + { + "phase": 5, + "label": "alternate-four-corners", + "source_hex": "1b5b313b31481b5b33333b34366d544643301b5b323b3148544643311b5b32343b3148544643321b5b32353b38384854464333", + "source_bytes": 51, + "source_sha256": "a1445c8f7de95905b05b235a3835c350188925d44519eed1a075a7ce9d9e70e3", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 25, + 91 + ], + "cursor_reply_hex": "1b5b32353b393152", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0TFM1", + "TFC0", + "TFC1", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "TFC2", + " TFC3" + ], + "line_count": 27, + "scroll": 2, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": -18, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": -18, + "bottom": -1 + } + }, + { + "marker": "TFM1", + "document_line": 1, + "column": 6, + "position": 7, + "rect": { + "x": 195, + "y": -18, + "width": 8, + "height": 18, + "left": 195, + "right": 202, + "top": -18, + "bottom": -1 + } + }, + { + "marker": "TFC0", + "document_line": 2, + "column": 0, + "position": 12, + "rect": { + "x": 147, + "y": 0, + "width": 8, + "height": 18, + "left": 147, + "right": 154, + "top": 0, + "bottom": 17 + } + }, + { + "marker": "TFC1", + "document_line": 3, + "column": 0, + "position": 17, + "rect": { + "x": 147, + "y": 18, + "width": 8, + "height": 18, + "left": 147, + "right": 154, + "top": 18, + "bottom": 35 + } + }, + { + "marker": "TFC2", + "document_line": 25, + "column": 0, + "position": 43, + "rect": { + "x": 147, + "y": 414, + "width": 8, + "height": 18, + "left": 147, + "right": 154, + "top": 414, + "bottom": 431 + } + }, + { + "marker": "TFC3", + "document_line": 26, + "column": 87, + "position": 135, + "rect": { + "x": 843, + "y": 432, + "width": 8, + "height": 18, + "left": 843, + "right": 850, + "top": 432, + "bottom": 449 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": true, + "error": null + }, + "is_main": { + "callable": true, + "value": false, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + }, + { + "phase": 6, + "label": "1049-duplicate-enter-style", + "source_hex": "1b5b3f313034396854464133", + "source_bytes": 12, + "source_sha256": "4ecf74b0d6914191efda7bfc321e4373322d2fe77d2d45c1e99e50f72d0efaaa", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 5 + ], + "cursor_reply_hex": "1b5b313b3552", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0TFM1", + "TFA3" + ], + "line_count": 3, + "scroll": 2, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": -18, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": -18, + "bottom": -1 + } + }, + { + "marker": "TFM1", + "document_line": 1, + "column": 6, + "position": 7, + "rect": { + "x": 195, + "y": -18, + "width": 8, + "height": 18, + "left": 195, + "right": 202, + "top": -18, + "bottom": -1 + } + }, + { + "marker": "TFA3", + "document_line": 2, + "column": 0, + "position": 12, + "rect": { + "x": 147, + "y": 0, + "width": 8, + "height": 18, + "left": 147, + "right": 154, + "top": 0, + "bottom": 17 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": true, + "error": null + }, + "is_main": { + "callable": true, + "value": false, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + }, + { + "phase": 7, + "label": "1049-second-exit-main-style", + "source_hex": "1b5b3f313034396c54464d32", + "source_bytes": 12, + "source_sha256": "ee226cbc78650aa1a71560a2aae106eb0eef89c9957bb622e6cd218c1bbe77d9", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 2, + 15 + ], + "cursor_reply_hex": "1b5b323b313552", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0TFM1TFM2" + ], + "line_count": 2, + "scroll": 0, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": 18, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": 18, + "bottom": 35 + } + }, + { + "marker": "TFM1", + "document_line": 1, + "column": 6, + "position": 7, + "rect": { + "x": 195, + "y": 18, + "width": 8, + "height": 18, + "left": 195, + "right": 202, + "top": 18, + "bottom": 35 + } + }, + { + "marker": "TFM2", + "document_line": 1, + "column": 10, + "position": 11, + "rect": { + "x": 227, + "y": 18, + "width": 8, + "height": 18, + "left": 227, + "right": 234, + "top": 18, + "bottom": 35 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": false, + "error": null + }, + "is_main": { + "callable": true, + "value": true, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + }, + { + "phase": 8, + "label": "main-pending-wrap-enter", + "source_hex": "1b5b333b3931481b5b33313b34346d571b5b3f313034396854464134", + "source_bytes": 28, + "source_sha256": "2cb52459163b68916c5cb972e70470de38cddb864bc1aebc588745076c3119eb", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 1, + 5 + ], + "cursor_reply_hex": "1b5b313b3552", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0TFM1TFM2", + " W", + "TFA4" + ], + "line_count": 4, + "scroll": 3, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": -36, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": -36, + "bottom": -19 + } + }, + { + "marker": "TFM1", + "document_line": 1, + "column": 6, + "position": 7, + "rect": { + "x": 195, + "y": -36, + "width": 8, + "height": 18, + "left": 195, + "right": 202, + "top": -36, + "bottom": -19 + } + }, + { + "marker": "TFM2", + "document_line": 1, + "column": 10, + "position": 11, + "rect": { + "x": 227, + "y": -36, + "width": 8, + "height": 18, + "left": 227, + "right": 234, + "top": -36, + "bottom": -19 + } + }, + { + "marker": "TFA4", + "document_line": 3, + "column": 0, + "position": 108, + "rect": { + "x": 147, + "y": 0, + "width": 8, + "height": 18, + "left": 147, + "right": 154, + "top": 0, + "bottom": 17 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": true, + "error": null + }, + "is_main": { + "callable": true, + "value": false, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + }, + { + "phase": 9, + "label": "main-pending-wrap-exit-write", + "source_hex": "1b5b3f313034396c58", + "source_bytes": 9, + "source_sha256": "e673f150b57de599e70be6834703f6b7d19f376a344c950dbe61ab87455160f6", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 4, + 2 + ], + "cursor_reply_hex": "1b5b343b3252", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0TFM1TFM2", + " WX", + "" + ], + "line_count": 4, + "scroll": 0, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": 18, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": 18, + "bottom": 35 + } + }, + { + "marker": "TFM1", + "document_line": 1, + "column": 6, + "position": 7, + "rect": { + "x": 195, + "y": 18, + "width": 8, + "height": 18, + "left": 195, + "right": 202, + "top": 18, + "bottom": 35 + } + }, + { + "marker": "TFM2", + "document_line": 1, + "column": 10, + "position": 11, + "rect": { + "x": 227, + "y": 18, + "width": 8, + "height": 18, + "left": 227, + "right": 234, + "top": 18, + "bottom": 35 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": false, + "error": null + }, + "is_main": { + "callable": true, + "value": true, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + }, + { + "phase": 10, + "label": "main-direct-wrap-control", + "source_hex": "1b5b363b3931481b5b33313b34346d565a", + "source_bytes": 17, + "source_sha256": "2324248f4473acc793d56dc8eac68c8e125af0756ce1dbebb22a32e3ae6349d0", + "dsr_suffix_hex": "1b5b366e", + "cursor_reply": [ + 7, + 2 + ], + "cursor_reply_hex": "1b5b373b3252", + "cursor_reply_private_prefix": "", + "document_projection": { + "lines": [ + "", + " TFM0TFM1TFM2", + " WX", + "", + " VZ", + "", + "" + ], + "line_count": 7, + "scroll": 0, + "capacity": 25, + "markers": [ + { + "marker": "TFM0", + "document_line": 1, + "column": 2, + "position": 3, + "rect": { + "x": 163, + "y": 18, + "width": 8, + "height": 18, + "left": 163, + "right": 170, + "top": 18, + "bottom": 35 + } + }, + { + "marker": "TFM1", + "document_line": 1, + "column": 6, + "position": 7, + "rect": { + "x": 195, + "y": 18, + "width": 8, + "height": 18, + "left": 195, + "right": 202, + "top": 18, + "bottom": 35 + } + }, + { + "marker": "TFM2", + "document_line": 1, + "column": 10, + "position": 11, + "rect": { + "x": 227, + "y": 18, + "width": 8, + "height": 18, + "left": 227, + "right": 234, + "top": 18, + "bottom": 35 + } + } + ] + }, + "is_alt": { + "callable": true, + "value": false, + "error": null + }, + "is_main": { + "callable": true, + "value": true, + "error": null + }, + "paint_requested": true, + "matching_marker_draws": 0 + } + ], + "mode_getters_observed": true, + "marker_rectangles_observed": true, + "paint_observation_attempt": { + "surface": "Exact QPainter::drawText(QPointF,QString) and four fillRect exports, filtered to owned viewport/editor QPaintDevice identities", + "matching_device_text_calls": 0, + "matching_marker_draws": 0, + "matching_fills": 0, + "result": "unmatched", + "entry_style_inheritance": null, + "exit_style_restoration": null, + "repeated_entry_style": null, + "cell_colors": null + }, + "pending_wrap_sample": { + "enter_phase": 8, + "exit_and_write_phase": 9, + "exit_reply": [ + 4, + 2 + ], + "direct_control_phase": 10, + "direct_control_reply": [ + 7, + 2 + ], + "dsr_between_W_and_enter": false, + "dsr_between_exit_and_X": false + }, + "provider": { + "evidence_id": "ev_8c052576b1180d02c769570300f871cce11cdd828903f04952a3367627115639", + "provider": { + "id": "frida", + "name": "Frida", + "version": null + }, + "confidence": "observed", + "subject": null, + "environment": null, + "messages_truncated": false, + "cleanup_error": null, + "limitations": [ + "Artifact identity is unavailable for this observation.", + "The target operating system and architecture were not reported by this observation." + ] + }, + "lifecycle": { + "outer_job": { + "active": 0, + "total": 3, + "terminated": 0 + }, + "profile": { + "only_synthetic_session": true, + "session_count": 1 + }, + "deliberate_original_termination": true, + "natural_gui_exit_verified": false, + "stderr_bytes": 0 + }, + "raw_files": { + "evidence.json": { + "bytes": 22604, + "sha256": "455a8cab10351a7d3b3934a85b89d1c18cf9abada1e2109705b0e34ee92586d5" + }, + "run.json": { + "bytes": 620, + "sha256": "bf682afb981a2cf8bd12461e486d7dde40e228886c22f695286e91afd54709be" + }, + "probe.js": { + "bytes": 23749, + "sha256": "96979b26fb3b9f12a9ee9fcf4ec339c93b7836c01d8c7c5423d459b198cae6d4" + }, + "style-probe-result.json": { + "bytes": 9742, + "sha256": "bde3669dcd0e5cc519f0eac1a6bcdac77998ab8b442b70bb42ea830005631174" + }, + "peer.json": { + "bytes": 3328, + "sha256": "21ff66c8899b56c11ffc7ebd6bc49639ccaf6d49360c49b813695829986c22eb" + }, + "fixture-ready": { + "bytes": 20, + "sha256": "622d3895d17f825845b8a486e94c55b2db0fa84777cb6a4d3c2f72414fd5469f" + }, + "cleanup.json": { + "bytes": 42, + "sha256": "8400f93b6a4a4a50cde35b5d474660590ed215732f55772a9d8afac138da4f1d" + }, + "profile-check.json": { + "bytes": 52, + "sha256": "5e522a64e98197722ccd084639b1ce50258b238e03be2f016e81c8af023b5ee1" + }, + "stderr.txt": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + "terminal_style_contract_verified": false, + "raster_image_verified": false, + "full_active_grid_verified": false, + "local_shell_transport_verified": false, + "presented_done": null, + "windterm_parity": false, + "r2_complete": false, + "limitations": [ + "Unmatched QPainter hooks do not imply that the original has no styles; overload, redirected device, repaint scheduling or another path remain unseparated possibilities.", + "This run did not read pixels or obtain an image. Paint-call attempts cannot prove presented frames or pixel equivalence.", + "Mode getter booleans and marker rectangles establish the sampled original states; retained document lines are not a complete active grid.", + "Four corner markers disappear from the retained projection after duplicate1049h; entire-grid clearing and blank-cell styles remain unknown.", + "Pending-wrap evidence is limited to this exact 91x25 direct-terminal input and its control, not all mode/resize crossings.", + "The attempted entry/current-style/exit restoration observations remain null. No generic-standard behavior substitutes for them.", + "Telnet does not establish local-shell WinPTY/ConPTY semantics or resolve41/56.", + "45seconds is REA capture duration,60seconds is CLI wait timeout; target launch precedes a separate2-second delay and cleanup follows CLI wait. No whole-lifecycle bound is asserted.", + "Instrumentation is correctness-only. No latency, throughput, presented endpoint, reliability repetition or performance claim.", + "Provider subject/environment remain null; executable/package identity is independently enforced by preparation and module-path guards.", + "The original GUI is deliberately ended by the owned launcher. Job active0 is cleanup proof, not natural exit proof.", + "Copied original resources, raw probes, paths, identifiers and synthetic profile remain local; only fixed synthetic inputs and audited scalar observations are published." + ] +} diff --git a/research/ui/test_export_alternate_style_reference.py b/research/ui/test_export_alternate_style_reference.py new file mode 100644 index 00000000..c2d1b530 --- /dev/null +++ b/research/ui/test_export_alternate_style_reference.py @@ -0,0 +1,96 @@ +"""The public partial-observation exporter must not turn unknown painting green.""" +from __future__ import annotations +import copy +import hashlib +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0,str(ROOT)) +from research.ui.alternate_style_reference import phases + + +def fixture(root): + directory=root/"raw" + directory.mkdir() + script=b"// Synthetic unit fixture; no original process is launched.\n" + (directory/"probe.js").write_bytes(script) + (directory/"stderr.txt").write_bytes(b"") + (directory/"fixture-ready").write_text("stable-NAWS-capacity") + observations=[] + records=[] + for index,(label,source) in enumerate(phases(25,91)): + observations.append({"phase":index,"label":label,"cursor_reply":[1,1],"paint_requested":True,"matching_marker_draws":0, + "terminal":{"protocol":"Telnet","connected":True,"capacity":25,"line_count":1,"lines":["TFM0"],"markers":[],"scroll":0, + "is_alt":{"callable":True,"value":False,"error":None},"is_main":{"callable":True,"value":True,"error":None}}}) + records.append({"phase":index,"label":label,"source_hex":source.hex(),"source_sha256":hashlib.sha256(source).hexdigest(),"cursor_reply":[1,1]}) + (directory/f"ack-{index}").write_text("observed") + captured={"status":"observed-all-style-phases","error":None,"observations":observations,"draws":[],"fills":[],"paint_calls_seen":0} + names=("research/ui/alternate_style_reference.py","research/ui/alternate_style_reference.js","research/ui/alternate_reference.py","research/ui/r1_launch.py","research/ui/qt_r1_text.js") + plan={"issue":123,"maximum_original_runs":1,"source_base_commit":"d91eb589f04be6b54a29d237b87c5b05ad243908", + "rea_capture_seconds":45,"cli_wait_timeout_seconds":60,"whole_lifecycle_bound_seconds":None,"phase_count":11, + "probe_files_sha256":{name:hashlib.sha256((ROOT/name).read_bytes()).hexdigest() for name in names}, + "reference_exe_sha256":"7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "reference_files_manifest_sha256":"e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203"} + data={"run.json":{"sha256":plan["reference_exe_sha256"],"files_sha256":plan["reference_files_manifest_sha256"],"script_sha256":hashlib.sha256(script).hexdigest()}, + "peer.json":{"complete":True,"frozen_grid":[91,25],"columns":91,"rows":25,"naws_history":[[91,24],[91,25]],"received_hex":(b"\x1b[1;1R"*11).hex(),"records":records}, + "style-probe-result.json":captured,"cleanup.json":{"active":0},"profile-check.json":{"only_synthetic_session":True,"session_count":1}, + "evidence.json":{"ok":True,"data":{"cleanup_error":None,"evidence":{"evidence_id":"synthetic-test-only","provider":{"name":"synthetic"},"confidence":"test-fixture","subject":None,"environment":None,"limitations":["Not runtime evidence."],"normalized_result":{"source_sha256":hashlib.sha256(script).hexdigest(),"messages_truncated":False,"cleanup_error":None}}}}} + write_records(directory,data) + plan_path=root/"plan.json" + plan_path.write_text(json.dumps(plan),encoding="utf-8") + return directory,plan_path,data + + +def write_records(directory,data): + captured=data["style-probe-result.json"] + messages=[{"type":"send","payload":{"kind":"style_phase",**copy.deepcopy(observation)}} for observation in captured["observations"]] + messages.append({"type":"send","payload":{"kind":"style_reference_result",**copy.deepcopy(captured)}}) + data["evidence.json"]["data"]["evidence"]["normalized_result"]["messages"]=messages + for name,value in data.items(): + (directory/name).write_text(json.dumps(value),encoding="utf-8") + + +class PartialStyleExportTests(unittest.TestCase): + def check_cli(self,change=None): + for optimized in (False,True): + with self.subTest(optimized=optimized),tempfile.TemporaryDirectory() as temporary: + root=Path(temporary) + directory,plan,data=fixture(root) + if change: + change(data) + write_records(directory,data) + destination=root/"public/validation.json" + result=subprocess.run([sys.executable,*(["-O"] if optimized else []),str(ROOT/"research/ui/export_alternate_style_reference.py"),"--directory",str(directory),"--plan",str(plan),"--destination",str(destination)],cwd=ROOT,capture_output=True,text=True,timeout=10) + if change: + self.assertNotEqual(result.returncode,0,result.stdout) + self.assertFalse(destination.exists()) + self.assertFalse(destination.parent.exists()) + else: + self.assertEqual(result.returncode,0,result.stderr) + summary=json.loads(destination.read_text(encoding="utf-8")) + self.assertEqual(summary["status"],"partial-mode-and-wrap-observation") + self.assertFalse(summary["terminal_style_contract_verified"]) + self.assertIsNone(summary["paint_observation_attempt"]["entry_style_inheritance"]) + self.assertIsNone(summary["paint_observation_attempt"]["exit_style_restoration"]) + self.assertEqual(summary["paint_observation_attempt"]["result"],"unmatched") + + def test_unmatched_painting_exports_only_as_partial_unknown(self): + self.check_cli() + + def test_nonfixture_text_is_rejected(self): + self.check_cli(lambda data:data["style-probe-result.json"]["observations"][0]["terminal"]["lines"].__setitem__(0,"PRIVATE_REVIEW_MARKER/host/path")) + + def test_uncalled_mode_getter_cannot_be_published_as_observed(self): + self.check_cli(lambda data:data["style-probe-result.json"]["observations"][0]["terminal"]["is_alt"].update(callable=False)) + + def test_changed_original_identity_is_rejected(self): + self.check_cli(lambda data:data["run.json"].update(sha256="0"*64)) + + +if __name__=="__main__": + unittest.main() From 72f40e10a8db0ef7585f8bec82f2be72f78c3d33 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:35:40 +0800 Subject: [PATCH 2/3] fix: guard each complete NAWS event and preserve prior evidence limits (#123) --- CMakeLists.txt | 1 + ...rm-alternate-style-reference-2026-10-28.md | 10 +- research/ui/alternate_style_reference.py | 4 +- .../ui/export_alternate_style_reference.py | 10 +- .../alternate_style_reference_20261028.py.txt | 114 ++++++++++++++++++ .../validation.json | 17 ++- research/ui/test_alternate_style_peer.py | 103 ++++++++++++++++ .../test_export_alternate_style_reference.py | 5 +- 8 files changed, 251 insertions(+), 13 deletions(-) create mode 100644 research/ui/reference_sources/alternate_style_reference_20261028.py.txt create mode 100644 research/ui/test_alternate_style_peer.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 73207ac2..59e80484 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -298,6 +298,7 @@ if(BUILD_TESTING) termforge_add_python_test(conpty_margin_contract -m unittest experiments.conpty.test_margin_contract -v) 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(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) diff --git a/docs/research/windterm-alternate-style-reference-2026-10-28.md b/docs/research/windterm-alternate-style-reference-2026-10-28.md index 8862c2fb..e542b51a 100644 --- a/docs/research/windterm-alternate-style-reference-2026-10-28.md +++ b/docs/research/windterm-alternate-style-reference-2026-10-28.md @@ -10,7 +10,9 @@ 复用独立 WindowStation/Desktop、挂起创建后分配 OwnedJob、独立 HOME 和一个合成 Telnet profile,只监听 `127.0.0.1`。没有切换用户输入桌面、访问剪贴板或修改 DPI。固定计划在运行前保存于 ignored 的 `local/alternate-style-plan.json`,规定最多一次原版执行;实际一次,无重试。REA Frida Evidence ID 为 `ev_8c052576b1180d02c769570300f871cce11cdd828903f04952a3367627115639`。 -本次保留 NAWS 历史 `91×24 → 91×25`,等待 UI capacity 与 NAWS 连续稳定后才释放源并冻结 **91×25**,冻结后任何网格改变均由 peer 报错。11 条精确源各发送一次,再追加 `CSI 6n`;下一阶段必须等待独立 UI 确认文件。收到 11 条无 `?` 前缀的 DSR、11 份 UI 快照和 11 次确认,没有其他应用回复字节。不声称覆盖任意 TCP 分块或逐字节解析。 +本次保留的 NAWS **接收块末态**为 `91×24 → 91×25`,等待 UI capacity 与 NAWS 末态连续稳定后才释放源,以 **91×25** 作为源生成尺寸。执行用的 peer 在整个 `recv` 块解析后才记录并检查尺寸;同一块内先改变再恢复可能漏记,因此不能证明冻结后逐事件不变。摘要保留原记录,`naws_changed_after_freeze=null`、`all_naws_events_captured=false`;11 份 UI 快照的 capacity 均为 25,但不是缺失 NAWS 事件的替代品。 + +11 条精确源各发送一次,再追加 `CSI 6n`;下一阶段必须等待独立 UI 确认文件。收到 11 条无 `?` 前缀的 DSR、11 份 UI 快照和 11 次确认,没有其他应用回复字节。不声称覆盖任意 TCP 分块或逐字节解析。 `rea_capture_seconds=45` 是 REA 捕获时长,`cli_wait_timeout_seconds=60` 是复用启动器的 CLI 等待超时。原版更早恢复运行,中间有单独 2 秒等待,CLI 返回后还会回收资源;没有 45 秒或 60 秒整程生命周期保证。动态插桩只用于正确性,不计时或比较性能。 @@ -35,7 +37,7 @@ 第 8 阶段先定位主屏 `(3,91)`,写 `W`,紧接 `1049h` 和备用标记 `TFA4`。第 9 阶段为 `1049l` 紧接 `X`,返回 DSR **`(4,2)`**。`W` 与进入备用屏之间、退出备用屏与 `X` 之间均没有插入 DSR。独立控制在 `(6,91)` 连续写 `VZ`,得到 DSR **`(7,2)`**。 -这支持该固定输入中退出备用屏后保留延迟换行的可观察效果。原版文档把 `WX`、`VZ` 分别投影为相连文字;协议坐标用于判断换到下一物理行,不能拿逻辑文档行号代替。它不证明内部用了同名 pending-wrap 字段,也不外推到 margin、origin、宽字、resize 或其他保存恢复序列。 +这支持该固定输入中退出备用屏后保留延迟换行的可观察效果。原版文档把 `WX`、`VZ` 分别投影为相连文字;协议坐标用于判断换到下一物理行,不能拿逻辑文档行号代替。它不证明内部用了同名 pending-wrap 字段,也不外推到 margin、origin、宽字、resize 或其他保存恢复序列。由于旧 peer 没有保留全部 NAWS 事件,该结论只对应已读接收块末态和快照,不额外证明两个快照间未发生瞬时尺寸改变。 ## 颜色观测为何保持未知 @@ -53,4 +55,8 @@ REA 正常返回,无脚本错误、消息截断、清理错误或 stderr;Job [公共回归](../../research/ui/test_export_alternate_style_reference.py)以自造记录验证普通/优化 CLI:无匹配必须导出为样式未知的 partial,非夹具文字、未调用模式 getter、错误原版身份必须拒绝。没有使用原版资源或新增原版执行。 +审查后,[当前 peer](../../research/ui/alternate_style_reference.py)已在每条完整 NAWS 解码时立即记录并检查,保留重复及往返事件。[自有 loopback 反例](../../research/ui/test_alternate_style_peer.py)用一个受控 `recv` 批次递交 `91×25 → 92×25 → 91×25`:旧代码漏报、只记录最终值的两项用例先复现失败;修订后冻结时在 `92×25` 立即拒绝,冻结前保留全部三条事件。普通及 `python -O` 的两项 loopback+四项导出回归均通过,已注册到 `windterm_alternate_style_reference` CTest。测试仅创建自有 TCP peer,不启动原版。 + +修订后的 peer **没有用于本次原版运行**。执行时的旧生产源已按原字节[归档](../../research/ui/reference_sources/alternate_style_reference_20261028.py.txt),SHA 与原计划一致;导出用该归档核对执行来源,原 `probe_files_sha256`、注入脚本 SHA、所有原始文件及 11 阶段内容保持不变。普通/优化离线重导一致,不给旧原件补造 NAWS 事件,也不把新 peer 的通过转记成旧运行的逐事件尺寸保证。 + 本次可为 C++ 切片提供模式状态、四标记消失和该延迟换行往返的原版合同;进入继承/退出当前样式、完整清屏及其他交叉仍未解除。#123、#27 和 R2 不能由本次部分取证关闭;本地 Shell 的原生 margin 问题继续由各自任务处理。 diff --git a/research/ui/alternate_style_reference.py b/research/ui/alternate_style_reference.py index a5295a0f..99d3c154 100644 --- a/research/ui/alternate_style_reference.py +++ b/research/ui/alternate_style_reference.py @@ -44,8 +44,8 @@ def peer_class(): replacements = { 'if self.state["rows"] and selected is None:': 'if self.state["rows"] and selected is None and (self.directory / "fixture-ready").exists():', 'selected = phases(self.state["rows"])': 'self.state["frozen_grid"] = [self.state["columns"], self.state["rows"]]\n selected = phases(self.state["rows"], self.state["columns"])', - 'if len(received) > 65536:': 'if self.state.get("frozen_grid") and self.state["frozen_grid"] != [self.state["columns"], self.state["rows"]]:\n raise RuntimeError("NAWS changed after fixture freeze")\n if len(received) > 65536:', - 'def _record(self):\n temporary': 'def _record(self):\n grid = [self.state["columns"], self.state["rows"]]\n history = self.state.setdefault("naws_history", [])\n if grid[0] and (not history or history[-1] != grid):\n history.append(grid)\n temporary', + 'self.state["rows"] = int.from_bytes(sub[3:5], "big")': 'self.state["rows"] = int.from_bytes(sub[3:5], "big")\n self._on_naws()', + 'def _record(self):\n temporary': 'def _on_naws(self):\n grid = [self.state["columns"], self.state["rows"]]\n history = self.state.setdefault("naws_history", [])\n if len(history) >= 64:\n raise RuntimeError("NAWS event bound")\n history.append(grid)\n self._record()\n if self.state.get("frozen_grid") and self.state["frozen_grid"] != grid:\n raise RuntimeError("NAWS changed after fixture freeze")\n\n def _record(self):\n temporary', } for old, new in replacements.items(): require(source.count(old) == 1, "Existing protected peer source changed") diff --git a/research/ui/export_alternate_style_reference.py b/research/ui/export_alternate_style_reference.py index c9599dd6..37005ab4 100644 --- a/research/ui/export_alternate_style_reference.py +++ b/research/ui/export_alternate_style_reference.py @@ -14,6 +14,7 @@ SYNTHETIC_LINE = re.compile(r" *(?:(?:TFM[0-2]|TFA[0-4]|TFC[0-3])+|WX?|X|VZ?|Z)? *") MARKERS = {f"TF{kind}{index}" for kind, count in (("M",3),("A",5),("C",4)) for index in range(count)} +EXECUTED_SOURCE_ARCHIVES = {"research/ui/alternate_style_reference.py":"research/ui/reference_sources/alternate_style_reference_20261028.py.txt"} def export(directory: Path, plan_path: Path, destination: Path): @@ -31,7 +32,7 @@ def export(directory: Path, plan_path: Path, destination: Path): require(set(plan["probe_files_sha256"]) == {"research/ui/alternate_style_reference.py","research/ui/alternate_style_reference.js","research/ui/alternate_reference.py","research/ui/r1_launch.py","research/ui/qt_r1_text.js"}, "Probe source inventory differs") for name, expected in plan["probe_files_sha256"].items(): require(name in {"research/ui/alternate_style_reference.py","research/ui/alternate_style_reference.js","research/ui/alternate_reference.py","research/ui/r1_launch.py","research/ui/qt_r1_text.js"}, "Unexpected probe source path") - require(sha(ROOT/name) == expected, "Executed probe source changed after its plan") + require(sha(ROOT/EXECUTED_SOURCE_ARCHIVES.get(name,name)) == expected, "Archived executed probe source differs from its plan") require(outer["ok"] and not outer["data"]["cleanup_error"], "REA capture failed") require(captured["status"] == "observed-all-style-phases" and not captured["error"], "Input phases did not finish") require(peer["complete"] and not peer.get("error"), "Owned peer did not finish") @@ -92,9 +93,10 @@ def export(directory: Path, plan_path: Path, destination: Path): "scope":"One original direct-terminal Telnet observation. Mode getter calls and text/cursor behavior observed; the attempted QPainter style surface did not match.", "reference_identity":{"exe_sha256":run["sha256"],"files_manifest_sha256":run["files_sha256"],"files_count":4047}, "actual_original_runs":1,"maximum_original_runs":1,"rea_capture_seconds":45,"cli_wait_timeout_seconds":60,"whole_lifecycle_bound_seconds":None, - "naws_history":peer["naws_history"],"frozen_grid":peer["frozen_grid"],"naws_changed_after_freeze":False, - "source_freeze":"Owned UI acknowledgment after stable NAWS/capacity; any later NAWS change fails the peer.", + "naws_history":peer["naws_history"],"naws_history_scope":"Distinct recv-block-end states only; complete intermediate NAWS events were not retained by the executed peer.","frozen_grid":peer["frozen_grid"],"naws_changed_after_freeze":None,"all_naws_events_captured":False, + "source_freeze":"Owned UI acknowledgment after stable NAWS/capacity. The executed peer compared only each recv block's final NAWS; intermediate changes within a block remain unknown.", "plan_sha256":sha(plan_path),"probe_files_sha256":dict(plan["probe_files_sha256"]),"exporter_sha256":sha(Path(__file__)), + "executed_source_archives":{name:{"path":archive,"sha256":plan["probe_files_sha256"][name]} for name,archive in EXECUTED_SOURCE_ARCHIVES.items()}, "phase_transport":"Each exact synthetic source is followed by CSI 6n and an independent UI acknowledgment; TCP chunking is not prescribed.", "dsr_request_hex":"1b5b366e","dsr_reply_count":len(replies),"unmatched_application_reply_hex":"","phases":exported, "mode_getters_observed":True,"marker_rectangles_observed":True, @@ -104,7 +106,7 @@ def export(directory: Path, plan_path: Path, destination: Path): "lifecycle":{"outer_job":cleanup,"profile":profile,"deliberate_original_termination":True,"natural_gui_exit_verified":False,"stderr_bytes":(directory/"stderr.txt").stat().st_size}, "raw_files":{name:{"bytes":(directory/name).stat().st_size,"sha256":sha(directory/name)} for name in raw_names}, "terminal_style_contract_verified":False,"raster_image_verified":False,"full_active_grid_verified":False,"local_shell_transport_verified":False,"presented_done":None,"windterm_parity":False,"r2_complete":False, - "limitations":["Unmatched QPainter hooks do not imply that the original has no styles; overload, redirected device, repaint scheduling or another path remain unseparated possibilities.","This run did not read pixels or obtain an image. Paint-call attempts cannot prove presented frames or pixel equivalence.","Mode getter booleans and marker rectangles establish the sampled original states; retained document lines are not a complete active grid.","Four corner markers disappear from the retained projection after duplicate1049h; entire-grid clearing and blank-cell styles remain unknown.","Pending-wrap evidence is limited to this exact 91x25 direct-terminal input and its control, not all mode/resize crossings.","The attempted entry/current-style/exit restoration observations remain null. No generic-standard behavior substitutes for them.","Telnet does not establish local-shell WinPTY/ConPTY semantics or resolve41/56.","45seconds is REA capture duration,60seconds is CLI wait timeout; target launch precedes a separate2-second delay and cleanup follows CLI wait. No whole-lifecycle bound is asserted.","Instrumentation is correctness-only. No latency, throughput, presented endpoint, reliability repetition or performance claim.","Provider subject/environment remain null; executable/package identity is independently enforced by preparation and module-path guards.","The original GUI is deliberately ended by the owned launcher. Job active0 is cleanup proof, not natural exit proof.","Copied original resources, raw probes, paths, identifiers and synthetic profile remain local; only fixed synthetic inputs and audited scalar observations are published."] + "limitations":["Unmatched QPainter hooks do not imply that the original has no styles; overload, redirected device, repaint scheduling or another path remain unseparated possibilities.","This run did not read pixels or obtain an image. Paint-call attempts cannot prove presented frames or pixel equivalence.","Mode getter booleans and marker rectangles establish the sampled original states; retained document lines are not a complete active grid.","The executed peer checked NAWS only after parsing an entire receive block. A transient change and return within one block could be missed; this old run proves only retained block-end states and11 UI capacities, not event-level grid stability. Revised per-message handling was not used in this run.","Four corner markers disappear from the retained projection after duplicate1049h; entire-grid clearing and blank-cell styles remain unknown.","Pending-wrap evidence is limited to the sampled91x25 capacities and this direct-terminal input/control; transient unrecorded NAWS events and all mode/resize crossings remain unverified.","The attempted entry/current-style/exit restoration observations remain null. No generic-standard behavior substitutes for them.","Telnet does not establish local-shell WinPTY/ConPTY semantics or resolve41/56.","45seconds is REA capture duration,60seconds is CLI wait timeout; target launch precedes a separate2-second delay and cleanup follows CLI wait. No whole-lifecycle bound is asserted.","Instrumentation is correctness-only. No latency, throughput, presented endpoint, reliability repetition or performance claim.","Provider subject/environment remain null; executable/package identity is independently enforced by preparation and module-path guards.","The original GUI is deliberately ended by the owned launcher. Job active0 is cleanup proof, not natural exit proof.","Copied original resources, raw probes, paths, identifiers and synthetic profile remain local; only fixed synthetic inputs and audited scalar observations are published."] } destination.parent.mkdir(parents=True,exist_ok=True) destination.write_text(json.dumps(summary,ensure_ascii=False,indent=2)+"\n",encoding="utf-8",newline="\n") diff --git a/research/ui/reference_sources/alternate_style_reference_20261028.py.txt b/research/ui/reference_sources/alternate_style_reference_20261028.py.txt new file mode 100644 index 00000000..a5295a0f --- /dev/null +++ b/research/ui/reference_sources/alternate_style_reference_20261028.py.txt @@ -0,0 +1,114 @@ +"""One bounded original-terminal style observation, with owned Qt paint hooks.""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def phases(rows: int, columns: int): + esc = b"\x1b[" + cup = lambda row, column: f"\x1b[{row};{column}H".encode("ascii") + return [ + ("main-red-blue", esc+b"0m"+esc+b"2J"+esc+b"H"+esc+b"?7h"+cup(2,3)+esc+b"31;44mTFM0"), + ("1049-enter-inherited-style", esc+b"?1049hTFA0"), + ("alternate-green-magenta", cup(4,5)+esc+b"32;45mTFA1"), + ("1049-exit-main-style", esc+b"?1049lTFM1"), + ("1049-second-enter-style", esc+b"?1049hTFA2"), + ("alternate-four-corners", cup(1,1)+esc+b"33;46mTFC0"+cup(2,1)+b"TFC1"+cup(rows-1,1)+b"TFC2"+cup(rows,columns-3)+b"TFC3"), + ("1049-duplicate-enter-style", esc+b"?1049hTFA3"), + ("1049-second-exit-main-style", esc+b"?1049lTFM2"), + # No inserted DSR between the last-column W and the alternate entry. + ("main-pending-wrap-enter", cup(3,columns)+esc+b"31;44mW"+esc+b"?1049hTFA4"), + # No inserted DSR between the alternate exit and the following X. + ("main-pending-wrap-exit-write", esc+b"?1049lX"), + ("main-direct-wrap-control", cup(6,columns)+esc+b"31;44mVZ"), + ] + + +def peer_class(): + """Reuse the fixed Telnet parser; delay source freezing until UI readiness.""" + from research.ui import alternate_reference + original = Path(alternate_reference.__file__) + source = original.read_text(encoding="utf-8") + replacements = { + 'if self.state["rows"] and selected is None:': 'if self.state["rows"] and selected is None and (self.directory / "fixture-ready").exists():', + 'selected = phases(self.state["rows"])': 'self.state["frozen_grid"] = [self.state["columns"], self.state["rows"]]\n selected = phases(self.state["rows"], self.state["columns"])', + 'if len(received) > 65536:': 'if self.state.get("frozen_grid") and self.state["frozen_grid"] != [self.state["columns"], self.state["rows"]]:\n raise RuntimeError("NAWS changed after fixture freeze")\n if len(received) > 65536:', + 'def _record(self):\n temporary': 'def _record(self):\n grid = [self.state["columns"], self.state["rows"]]\n history = self.state.setdefault("naws_history", [])\n if grid[0] and (not history or history[-1] != grid):\n history.append(grid)\n temporary', + } + for old, new in replacements.items(): + require(source.count(old) == 1, "Existing protected peer source changed") + source = source.replace(old, new) + namespace = {"__file__": str(original), "__name__": "termforge_style_peer"} + exec(compile(source, str(original), "exec"), namespace) + namespace["phases"] = phases + return namespace["Peer"] + + +Peer = peer_class() + + +def run(source: Path, out: Path, rea: Path): + from research.ui import r1_launch + launcher = Path(r1_launch.__file__) + text = launcher.read_text(encoding="utf-8") + replacements = { + "from research.ui.r1_vt_fixture import Peer": "from research.ui.alternate_style_reference import Peer", + 'Path(__file__).with_name("r1_menu_wrap.js").read_text(encoding="utf-8")': 'Path(__file__).with_name("alternate_style_reference.js").read_text(encoding="utf-8")', + } + for old, new in replacements.items(): + require(text.count(old) == 1, "Existing protected launcher source changed") + text = text.replace(old, new) + namespace = {"__file__": str(launcher), "__name__": "termforge_style_launcher"} + exec(compile(text, str(launcher), "exec"), namespace) + namespace["run"](source, out, rea, 45, False, "menu-wrap") + + +def write_plan(destination: Path): + sha = lambda path: hashlib.sha256(path.read_bytes()).hexdigest() + plan = { + "issue": 123, "maximum_original_runs": 1, + "reference_exe_sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "reference_files_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", + "source_base_commit": subprocess.check_output(["git","rev-parse","HEAD"],cwd=ROOT,text=True).strip(), + "rea_capture_seconds": 45, "cli_wait_timeout_seconds": 60, + "whole_lifecycle_bound_seconds": None, + "route": "Original terminal, owned loopback Telnet, isolated station/desktop/profile and OwnedJob", + "source_freeze": "UI confirms stable NAWS/capacity before releasing the peer; any later NAWS change fails the run", + "phase_count": len(phases(25,91)), + "example_grid": [91,25], + "example_sources": [{"phase":index,"label":label,"hex":content.hex(),"sha256":hashlib.sha256(content).hexdigest()} for index,(label,content) in enumerate(phases(25,91))], + "source_suffix": "Each phase is followed by CSI 6n and an independent UI acknowledgment", + "probe_files_sha256": {name:sha(ROOT/name) for name in ("research/ui/alternate_style_reference.py","research/ui/alternate_style_reference.js","research/ui/alternate_reference.py","research/ui/r1_launch.py","research/ui/qt_r1_text.js")}, + "actions": ["Open only synthetic loopback session on owned non-input station", "Call isAltScreen/isMainScreen and record actual returns", "Observe known-marker drawText and same-device fillRect colors using verified Qt5 exports", "Request an owned viewport update to collect paint calls", "Record source, DSR, document projection and marker rectangles", "Stop on failure and reclaim only owned resources; no retry"], + "limitations": ["QPainter calls are correctness evidence, not pixels or presented frames", "Unknown/unmatched paint paths remain null", "No clipboard, user desktop, personal profile or DPI changes", "No inserted DSR between pending W/1049h or 1049l/X", "45 is capture duration; CLI wait is60 and target starts before the separate2-second delay; no whole-lifecycle45-second guarantee", "Complete clear-grid/pending-wrap equivalence requires evidence, not assumed standards"], + } + destination.parent.mkdir(parents=True,exist_ok=True) + require(not destination.exists(),"Plan output must be new") + destination.write_text(json.dumps(plan,indent=2)+"\n",encoding="utf-8",newline="\n") + print(json.dumps({"plan":destination.as_posix(),"phases":plan["phase_count"],"maximum_original_runs":1})) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--plan",type=Path) + parser.add_argument("--source",type=Path) + parser.add_argument("--out",type=Path) + parser.add_argument("--rea",type=Path) + args = parser.parse_args() + if args.plan: + write_plan(args.plan) + else: + require(args.source and args.out and args.rea,"Source, output and REA are required") + run(args.source,args.out,args.rea) diff --git a/research/ui/results/2026-10-28-alternate-style-reference/validation.json b/research/ui/results/2026-10-28-alternate-style-reference/validation.json index 99c74ebc..e0cccae4 100644 --- a/research/ui/results/2026-10-28-alternate-style-reference/validation.json +++ b/research/ui/results/2026-10-28-alternate-style-reference/validation.json @@ -24,12 +24,14 @@ 25 ] ], + "naws_history_scope": "Distinct recv-block-end states only; complete intermediate NAWS events were not retained by the executed peer.", "frozen_grid": [ 91, 25 ], - "naws_changed_after_freeze": false, - "source_freeze": "Owned UI acknowledgment after stable NAWS/capacity; any later NAWS change fails the peer.", + "naws_changed_after_freeze": null, + "all_naws_events_captured": false, + "source_freeze": "Owned UI acknowledgment after stable NAWS/capacity. The executed peer compared only each recv block's final NAWS; intermediate changes within a block remain unknown.", "plan_sha256": "ca5f4d79a4c9c607056b2a4553fd6ae408c79f1efac133c2ef4a945ed4f02c9d", "probe_files_sha256": { "research/ui/alternate_style_reference.py": "c129cbebe78a216b3f3593fc5e9ec4a1b1694ce190d047497de55e7953a8728d", @@ -38,7 +40,13 @@ "research/ui/r1_launch.py": "54fb41872c6abcc48242a0938044184854b016785cc13659c66b1bf404da0d8a", "research/ui/qt_r1_text.js": "f5820d413fdd72c13dffcefbf4d0ade615abfe15754c6b0b52e1e098814eea6f" }, - "exporter_sha256": "709b54a31e5ea77541cbebbd050e594e1976126c28efd3efe627d047676f7667", + "exporter_sha256": "3815e955c157462d83e141d823dc2a75639c4b7e00a49531c534285e07edc6b2", + "executed_source_archives": { + "research/ui/alternate_style_reference.py": { + "path": "research/ui/reference_sources/alternate_style_reference_20261028.py.txt", + "sha256": "c129cbebe78a216b3f3593fc5e9ec4a1b1694ce190d047497de55e7953a8728d" + } + }, "phase_transport": "Each exact synthetic source is followed by CSI 6n and an independent UI acknowledgment; TCP chunking is not prescribed.", "dsr_request_hex": "1b5b366e", "dsr_reply_count": 11, @@ -1129,8 +1137,9 @@ "Unmatched QPainter hooks do not imply that the original has no styles; overload, redirected device, repaint scheduling or another path remain unseparated possibilities.", "This run did not read pixels or obtain an image. Paint-call attempts cannot prove presented frames or pixel equivalence.", "Mode getter booleans and marker rectangles establish the sampled original states; retained document lines are not a complete active grid.", + "The executed peer checked NAWS only after parsing an entire receive block. A transient change and return within one block could be missed; this old run proves only retained block-end states and11 UI capacities, not event-level grid stability. Revised per-message handling was not used in this run.", "Four corner markers disappear from the retained projection after duplicate1049h; entire-grid clearing and blank-cell styles remain unknown.", - "Pending-wrap evidence is limited to this exact 91x25 direct-terminal input and its control, not all mode/resize crossings.", + "Pending-wrap evidence is limited to the sampled91x25 capacities and this direct-terminal input/control; transient unrecorded NAWS events and all mode/resize crossings remain unverified.", "The attempted entry/current-style/exit restoration observations remain null. No generic-standard behavior substitutes for them.", "Telnet does not establish local-shell WinPTY/ConPTY semantics or resolve41/56.", "45seconds is REA capture duration,60seconds is CLI wait timeout; target launch precedes a separate2-second delay and cleanup follows CLI wait. No whole-lifecycle bound is asserted.", diff --git a/research/ui/test_alternate_style_peer.py b/research/ui/test_alternate_style_peer.py new file mode 100644 index 00000000..91a32475 --- /dev/null +++ b/research/ui/test_alternate_style_peer.py @@ -0,0 +1,103 @@ +"""Owned loopback counterexamples for individual complete NAWS events.""" +from __future__ import annotations +from pathlib import Path +import socket +import sys +import tempfile +import time +import unittest + +ROOT=Path(__file__).resolve().parents[2] +sys.path.insert(0,str(ROOT)) +from research.ui.alternate_style_reference import Peer + + +def naws(columns,rows): + return bytes([255,250,31])+columns.to_bytes(2,"big")+rows.to_bytes(2,"big")+bytes([255,240]) + + +class OneBatchConnection: + """Keep the tested receive boundary deterministic over a real loopback TCP.""" + def __init__(self,connection): + self.connection=connection + self.batched=False + + def __getattr__(self,name): + return getattr(self.connection,name) + + def recv(self,size): + if self.batched: + return self.connection.recv(size) + received=bytearray() + deadline=time.monotonic()+1 + while len(received)<27 and time.monotonic()=3): + break + time.sleep(.01) + state=dict(peer.state) + finally: + peer.close() + return state + + def test_transient_change_in_same_receive_is_rejected_after_freeze(self): + state=self.run_peer(frozen=True) + self.assertEqual(state.get("error"),"NAWS changed after fixture freeze") + self.assertEqual(state.get("naws_history"),[[91,25],[92,25]]) + self.assertEqual([state["columns"],state["rows"]],[92,25]) + + def test_every_complete_event_is_retained_before_freeze(self): + state=self.run_peer(frozen=False) + self.assertIsNone(state.get("error")) + self.assertEqual(state.get("naws_history"),[[91,25],[92,25],[91,25]]) + self.assertEqual([state["columns"],state["rows"]],[91,25]) + + +if __name__=="__main__": + unittest.main() diff --git a/research/ui/test_export_alternate_style_reference.py b/research/ui/test_export_alternate_style_reference.py index c2d1b530..8dfbff92 100644 --- a/research/ui/test_export_alternate_style_reference.py +++ b/research/ui/test_export_alternate_style_reference.py @@ -12,6 +12,7 @@ ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0,str(ROOT)) from research.ui.alternate_style_reference import phases +from research.ui.export_alternate_style_reference import EXECUTED_SOURCE_ARCHIVES def fixture(root): @@ -33,7 +34,7 @@ def fixture(root): names=("research/ui/alternate_style_reference.py","research/ui/alternate_style_reference.js","research/ui/alternate_reference.py","research/ui/r1_launch.py","research/ui/qt_r1_text.js") plan={"issue":123,"maximum_original_runs":1,"source_base_commit":"d91eb589f04be6b54a29d237b87c5b05ad243908", "rea_capture_seconds":45,"cli_wait_timeout_seconds":60,"whole_lifecycle_bound_seconds":None,"phase_count":11, - "probe_files_sha256":{name:hashlib.sha256((ROOT/name).read_bytes()).hexdigest() for name in names}, + "probe_files_sha256":{name:hashlib.sha256((ROOT/EXECUTED_SOURCE_ARCHIVES.get(name,name)).read_bytes()).hexdigest() for name in names}, "reference_exe_sha256":"7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", "reference_files_manifest_sha256":"e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203"} data={"run.json":{"sha256":plan["reference_exe_sha256"],"files_sha256":plan["reference_files_manifest_sha256"],"script_sha256":hashlib.sha256(script).hexdigest()}, @@ -78,6 +79,8 @@ def check_cli(self,change=None): self.assertIsNone(summary["paint_observation_attempt"]["entry_style_inheritance"]) self.assertIsNone(summary["paint_observation_attempt"]["exit_style_restoration"]) self.assertEqual(summary["paint_observation_attempt"]["result"],"unmatched") + self.assertIsNone(summary["naws_changed_after_freeze"]) + self.assertFalse(summary["all_naws_events_captured"]) def test_unmatched_painting_exports_only_as_partial_unknown(self): self.check_cli() From 63bac877aa5ba20618c780ad7c816cebbdf86949 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:44:26 +0800 Subject: [PATCH 3/3] fix: preserve recorded probe bytes across Windows checkouts (#123) --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index d80940da..d71be01b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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