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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ if(BUILD_TESTING)
termforge_add_python_test(windterm_raster_style_reference -m unittest research.ui.test_raster_style_reference -v)
termforge_add_python_test(windterm_raster_selection_proposal -m unittest research.ui.test_raster_selection_reference -v)
termforge_add_python_test(windterm_raster_raw_proposal -m unittest research.ui.test_raster_raw_reference -v)
termforge_add_python_test(windterm_sgr1_proposal -m unittest research.ui.test_sgr1_reference -v)
termforge_add_python_test(differential -m unittest discover -s experiments/differential -p test_*.py -v)
termforge_add_python_test(ime_inventory -m unittest research.ime.test_hosted_inventory research.ime.test_prepare_hosted research.ime.test_product_ime -v)
termforge_add_python_test(text_actions -m unittest experiments.shell.test_actions -v)
Expand Down
2 changes: 2 additions & 0 deletions docs/EVIDENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分

原版模式依据见 [1049 直达终端观察](research/windterm-alternate-reference-2026-10-28.md)与[验证身份](../research/ui/results/2026-10-28-alternate-reference/validation.json):26 阶段精确输入、DSR 和文档投影相互绑定,原版重复进入的光标行为与通用幂等假设不同;样式、完整 active grid 和本地 Shell 运输继续保留未知。

[#146 SGR1 固定样本调查](../research/ui/results/2026-10-29-sgr1-reference/README.md)取得五阶段/五 grab、28 对 H/space 完整有序像素签名;SGR0/SGR1 控制在采样条件下均不可分,进入/重复进入/返回无唯一分类。内部字重、SGR1 是否受字体/profile 偏好影响及全部非颜色属性仍未知;没有据此更改 C++ 策略或验收 #123/#126/#27。

原版输出自动化见 [cmd 正确性工具](../benchmarks/tools/WINDTERM-CMD.md)及[固定结果](../benchmarks/tools/results/2026-10-28-windterm-cmd/README.md):小 ASCII 全文、生产事件和 Job 回收已有无人值守闭环,路径元字符修复及各执行源码分列。它没有取得 C0、完整历史、消费/真实呈现端点或正式性能基线。

[#133 设置读取诊断](../benchmarks/tools/results/2026-10-29-c0-settings-reference/README.md)在独立受控配置差异下核对 536B 全文,并分别读到 producer console / 原版 view 窗口 120×40、document capacity 23;QWidget 字体仅为元数据,28 条 dot-key 设置仍不可读。完整 C0、glyph/字体请求有效性、10000 行历史、autocomplete、呈现及性能继续未知。
Expand Down
36 changes: 36 additions & 0 deletions docs/research/windterm-sgr1-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 1049 的 SGR1 输出签名:调查合同

对应 [#146](https://github.com/JavaZeroo/termforge/issues/146),接续 #123/#126 的未知非颜色属性。[离线 false 提案](../../research/ui/results/2026-10-29-sgr1-proposal/README.md)保留执行前身份;独立预审后的[唯一实际运行](../../research/ui/results/2026-10-29-sgr1-reference/README.md)取得五阶段完整栅格,但每帧 SGR0/SGR1 控制均不可分,进入/重复进入/返回的字重状态保持 unknown。旧 raw-capture-v1 的七个生产源、计划与证据保持字节不变。

目标是比较固定 WindTerm 2.7.0 在 1049 进入、重复进入与返回后,新写的 H/space 是否匹配同一张图中的 SGR0 或 SGR1 控制输出。匹配表示该固定样本的完整像素输出签名相同,不能直接解释为内部 `Style.bold`、QPainter pen 或实际字体字重相同。原版优先;重要独立差异仍需用户确认。

## 五阶段与可区分前态

| 阶段 | 精确输入的作用 | 采集后的源末态 |
|---|---|---|
| 0 | 主屏清空,SGR0/SGR1 各写两个 H 控制 | SGR1 |
| 1 | `1049h` 后先写无 SGR 的 `E0 H H`,再写两组控制 | SGR1 |
| 2 | 备用屏明确写 SGR0 样本和两组控制 | SGR1,为重复进入提供不同于 SGR0 的前态 |
| 3 | 重复 `1049h` 后先写 `R0 H H`,再写两组控制;最后追加不写字的 SGR0 | SGR0,与主屏原有源末态 SGR1 区分 |
| 4 | `1049l` 后先写 `M1 H H`,再写两组控制 | 不用于下一转换 |

这里的末态来自精确发送源的声明,并非已读出的原版内部属性。返回标记前保留一个显式空格,避免与主屏末尾文字拼接而失去完整 token 绑定。每阶段源后追加一次 `CSI 6n`,只在独立 UI 确认后前进。固定输入、长度与 SHA 见[不可执行提案](../../research/ui/results/2026-10-29-sgr1-proposal/plan.json)。

## 采集与分类

[工具](../../research/ui/sgr1_reference.py)复用已审 raw Qt 模板,只以受保护锚点替换五阶段 marker 表、case/status 名称和输出文件名。原生 sret、QPixmap/QImage 生命周期、选择端点、marker/ROI、DPR、caret 和每阶段单次 grab 的实现不变;生成模板 SHA 与实际注入文件 SHA 分别记录。REA 固定 6.7.0,准备计划记录 shim、package.json 与入口脚本身份;这不代表完整工具依赖树已重现。

每个未知样本有两个 H,SGR0 与 SGR1 控制也各有两个 H。采集保留完整有序 H/space QRgb 数组于私有目录,分类完全离线,不在采集中按颜色选择是否继续。结构、身份、空图、ROI/DPR、选择端点或 caret 失败停止;不移动 ROI、不补抓、不重试。

[离线审计](../../research/ui/sgr1_analysis.py)要求同帧同一文本行、唯一位置与严格递增的 glyph 矩形、相同 ROI 形状/样本数、各组内部的完整有序签名一致。SGR0/1 控制不可分、控制重复不一致或目标未唯一匹配时保持 unknown。颜色差异、主墨色并列不阻断已经成立的完整输出签名匹配,但只记录颜色关系;`font_weight_mechanism=null` 和 `internal_bold_verified=false` 保持不变。

[导出器](../../research/ui/export_sgr1_reference.py)核对执行计划、源/模块/注入脚本、全部 NAWS 事件、实际 DSR、原始消息与文件一致性、逐阶段确认和 Job/profile 回收。只输出合成源、白名单标量、派生签名和原件哈希;原始像素、资源、路径、PID、profile 留私有。早期停止不补造未冻结的网格或后续画面。

## 执行与停止边界

- 执行前固定生产提交、唯一新目录与单次 marker,最多一次原版启动、每阶段一次 grab;当前 false 提案不能创建 attempt 或启动目标。
- 仅官方固定 4047 文件清单、EXE/Qt 完整 SHA、自有副本、合成 loopback Telnet、非输入 station/desktop 和 OwnedJob;不修改个人桌面、剪贴板、DPI 或会话。
- 沿既有 REA capture 45 秒、CLI wait 60 秒与清理边界;启动在前、清理在后,没有整程 45/60 秒保证,不构成绩效测量。
- 实际运行使用独立预审后另建的单次 true 计划。该计划已经消费,原件与 false 提案保留;离线成功不批准重跑或模拟原版执行。

完整 style、内部笔刷、全网格、47/1047/1048、margin/origin/wrap、本地 Shell、物理呈现、性能及 R2 均未由本项验收。
131 changes: 131 additions & 0 deletions research/ui/export_sgr1_reference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""Validate one fixed SGR1 capture and publish only reviewed synthetic scalars/hashes."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import re
import sys

ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from research.ui.sgr1_reference import CASE_ID, MODULE_SHA256, MANIFEST_SHA256, SOURCE_FILES, AUDIT_FILES, TARGETS, phase_records, build_probe_fragment
from research.ui.sgr1_analysis import audit_capture, frame_comparisons, require
from research.ui.export_raster_raw_reference import REASONS

TOKENS = sorted({row["label"] for target in TARGETS for row in target["markers"]})
SYNTHETIC_LINE = re.compile(r" *(?:(?:" + "|".join(re.escape(token) for token in TOKENS) + r") *)*")
COMPLETE, PARTIAL = "captured-five-sgr1-raster-phases", "partial-sgr1-raster-capture"


def export(directory, plan_path, destination):
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"))
require(plan["issue"] == 146 and plan["case_id"] == CASE_ID and plan["execution_authorized"] is True and plan["maximum_original_runs"] == 1, "Plan scope or execution authorization differs")
require((plan["phase_count"], plan["maximum_grabs_per_phase"], plan["maximum_grabs"], plan["rea_capture_seconds"], plan["cli_wait_timeout_seconds"], plan["whole_lifecycle_bound_seconds"]) == (5, 1, 5, 45, 60, None), "Plan capture/lifecycle bounds differ")
require(re.fullmatch(r"[0-9a-f]{40}", plan["source_base_commit"]) is not None, "Plan source identity missing")
require(plan["module_sha256"] == MODULE_SHA256 and plan["files_manifest_sha256"] == MANIFEST_SHA256 and plan["phases"] == phase_records(), "Plan fixed identities or inputs differ")
tool = plan["rea_prepared_identity"]
require(set(tool) == {"version", "shim_sha256", "package_json_sha256", "entry_sha256"} and tool["version"] == "6.7.0" and all(re.fullmatch(r"[0-9a-f]{64}", tool[key]) for key in ("shim_sha256", "package_json_sha256", "entry_sha256")), "Prepared REA identity differs")
for key, names in (("source_files_sha256", SOURCE_FILES), ("offline_audit_files_sha256", AUDIT_FILES)):
require(set(plan[key]) == set(names) and plan[key] == {name: sha(ROOT / name) for name in names}, "Source inventory or bytes differ")
generated = hashlib.sha256(build_probe_fragment().encode("utf-8")).hexdigest()
require(plan["generated_capture_sha256"] == generated, "Generated capture differs")
run, peer, captured, outer = [read(name) for name in ("run.json", "peer.json", "sgr1-raster-result.json", "evidence.json")]
evidence = outer["data"]["evidence"]; normalized = evidence["normalized_result"]
require(run["sha256"] == MODULE_SHA256["WindTerm.exe"] and run["files_sha256"] == MANIFEST_SHA256, "Observed package identity differs")
template = (ROOT / "research/ui/qt_r1_text.js").read_text(encoding="utf-8").split("// Scenario driver", 1)[0] + build_probe_fragment()
require(run["template_sha256"] == hashlib.sha256(template.encode("utf-8")).hexdigest(), "Executed capture template differs")
require(normalized["source_sha256"] == run["script_sha256"] == sha(directory / "probe.js"), "Injected source differs")
require(outer["ok"] and not outer["data"]["cleanup_error"] and not normalized["cleanup_error"] and not normalized["messages_truncated"], "Provider/cleanup incomplete")
require(not any(row["type"] == "error" for row in normalized["messages"]), "Provider script error")
require(evidence["provider"] == {"id": "frida", "name": "Frida", "version": None} and evidence["confidence"] == "observed" and evidence["subject"] is None and evidence["environment"] is None, "Provider identity differs")
require(re.fullmatch(r"ev_[0-9a-f]{64}", evidence["evidence_id"]) is not None, "Provider evidence identity missing")
require(all(value in {"Artifact identity is unavailable for this observation.", "The target operating system and architecture were not reported by this observation."} for value in evidence["limitations"]), "Unexpected provider limitation text")
messages = [row["payload"] for row in normalized["messages"] if row["type"] == "send" and isinstance(row.get("payload"), dict)]
observed = [{key: value for key, value in row.items() if key != "kind"} for row in messages if row.get("kind") == "sgr1_raster_phase"]
finals = [{key: value for key, value in row.items() if key != "kind"} for row in messages if row.get("kind") == "sgr1_raster_result"]
require(observed == captured["captures"] and finals == [captured], "Provider messages and raw file disagree")
require(captured["status"] in (COMPLETE, PARTIAL) and captured["reason"] in REASONS, "Capture status/reason differs")
guards = captured["module_guards"]
require(len(guards) == 4 and {row["name"] for row in guards} == set(MODULE_SHA256) and all(row == {"name": row["name"], "path_guard_passed": True} for row in guards), "Module path guard failed")
count = len(observed); require(0 <= count <= 5, "Too many phases")
grid, freeze, history = peer.get("frozen_grid"), peer.get("freeze_event_count"), peer.get("naws_history", [])
require(len(history) <= 64 and all(isinstance(row, list) and len(row) == 2 and all(type(n) is int and 0 < n <= 65535 for n in row) for row in history), "NAWS shape differs")
if grid is None:
require(count == 0 and captured["status"] == PARTIAL and freeze is None, "Missing grid after source release")
else:
require(grid == [peer["columns"], peer["rows"]] and 40 <= peer["columns"] <= 160 and 10 <= peer["rows"] <= 60, "Frozen grid differs")
require(type(freeze) is int and 0 <= freeze <= len(history), "NAWS event bound differs")
require(all(row == grid for row in history[freeze:]), "NAWS changed after freeze")
wire = bytes.fromhex(peer["received_hex"])
replies = list(re.finditer(rb"\x1b\[(\??)(\d+);(\d+)R", wire))
require(len(wire) <= 65536 and not re.sub(rb"\x1b\[(\??)(\d+);(\d+)R", b"", wire), "Unexpected application bytes")
require(len(peer["records"]) == len(replies) == count, "Source/reply/capture counts differ")
require(captured["grab_phases"] == [row["phase"] for row in observed if row["grab_attempted"]] and len(set(captured["grab_phases"])) == len(captured["grab_phases"]), "Extra grab")
public_phases = []
for index, (capture, record, reply) in enumerate(zip(observed, peer["records"], replies)):
expected = phase_records(peer["rows"], peer["columns"])[index]
require(capture["phase"] == record["phase"] == index and capture["label"] == TARGETS[index]["label"], "Phase identity differs")
require(all(record[key] == expected[key] for key in ("label", "source_hex", "source_sha256")), "Exact source differs")
cursor = [int(reply[2]), int(reply[3])]
require(reply[1] == b"" and cursor == record["cursor_reply"] == capture["cursor_reply"], "DSR differs")
require(capture["reason"] in REASONS and capture["module_guards"] == guards, "Capture reason/module differs")
terminal = capture["terminal"]
require(terminal["protocol"] == "Telnet" and terminal["connected"] is True and terminal["capacity"] == peer["rows"], "Terminal binding differs")
require(terminal["line_count"] == len(terminal["lines"]) <= 200 and all(SYNTHETIC_LINE.fullmatch(line) for line in terminal["lines"]), "Non-synthetic terminal text")
require(type(terminal["is_alt"]) is bool and type(terminal["is_main"]) is bool and terminal["is_alt"] != terminal["is_main"], "Mode observation missing")
require(terminal["is_alt"] == (index in (1, 2, 3)), "Unexpected active buffer for fixed source")
value = audit_capture(capture)
value.update({"source_label": record["label"], "source_hex": record["source_hex"], "source_sha256": record["source_sha256"],
"dsr_request_hex": "1b5b366e", "cursor_reply_hex": reply[0].hex(), "is_alt": terminal["is_alt"], "is_main": terminal["is_main"]})
public_phases.append(value)
ack = directory / f"ack-{index}"
if capture["capture_valid"]:
require(ack.read_text() == "captured", "Capture acknowledgment missing")
else:
require(index == count-1 and not ack.exists(), "Invalid capture advanced source")
complete = captured["status"] == COMPLETE
require(not complete or (count == 5 and peer["complete"] is True and all(row["capture_valid"] for row in observed)), "Incomplete capture promoted")
require(complete or captured["reason"] is not None, "Partial capture has no reason")
cleanup, profile = read("cleanup.json"), read("profile-check.json")
require(set(cleanup) == {"active", "total", "terminated"} and all(type(n) is int and n >= 0 for n in cleanup.values()) and cleanup["active"] == 0, "Owned Job did not finish")
require(profile == {"only_synthetic_session": True, "session_count": 1}, "Synthetic profile failed")
require((directory / "stderr.txt").stat().st_size == 0, "Unexpected original stderr")
raw_names = ("run.json", "probe.js", "peer.json", "sgr1-raster-result.json", "evidence.json", "cleanup.json", "profile-check.json", "stderr.txt")
summary = {"schema": "termforge-original-sgr1-reference-v1", "issue": 146, "case_id": CASE_ID,
"status": captured["status"], "reason": captured["reason"], "raw_capture_complete": complete,
"source_base_commit": plan["source_base_commit"], "source_files_sha256": plan["source_files_sha256"],
"offline_audit_files_sha256": plan["offline_audit_files_sha256"], "generated_capture_sha256": generated,
"rea_prepared_identity": tool,
"plan_sha256": sha(plan_path), "module_sha256": MODULE_SHA256, "files_manifest_sha256": MANIFEST_SHA256,
"maximum_original_runs": 1, "actual_original_runs": 1, "rea_capture_seconds": 45,
"cli_wait_timeout_seconds": 60, "whole_lifecycle_bound_seconds": None,
"frozen_grid": grid, "naws_history": history, "freeze_event_count": freeze,
"planned_phase_count": 5, "observed_phase_count": count, "grab_phases": captured["grab_phases"],
"phases": public_phases, "same_frame_signature_comparisons": frame_comparisons(public_phases),
"runtime_module_path_guards": guards,
"provider": {key: evidence[key] for key in ("evidence_id", "provider", "confidence", "subject", "environment", "limitations")},
"lifecycle": {"outer_job": cleanup, "profile": profile, "deliberate_original_termination": True, "natural_gui_exit_verified": False},
"raw_files": {name: {"bytes": (directory/name).stat().st_size, "sha256": sha(directory/name)} for name in raw_names},
"internal_bold_verified": False, "full_style_verified": False, "full_grid_verified": False,
"local_shell_transport_verified": False, "presented_done": None, "r2_complete": False, "windterm_parity": False,
"limitations": ["Only exact same-frame SGR0/SGR1 output signatures are compared; this does not read an internal pen or prove general font weight.",
"Each group must repeat exactly with equal ROI shapes/counts. Indistinguishable or inconsistent controls stay unknown. Color differences/ties do not invalidate an exact output signature but never establish a font-weight mechanism.",
"Only the five fixed sources and one grab per phase are covered; no ROI movement, extra grab or retry.",
"QWidget raster and direct Telnet do not prove physical presentation, local-shell transport or performance.",
"Capture45/CLI60 exclude startup and later cleanup; no whole-lifecycle deadline is claimed.",
"Raw pixels, paths, PIDs, copied original resources and synthetic profile stay local. Unknowns do not establish a reset or inherited internal style."]}
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")
return summary


if __name__ == "__main__":
parser = argparse.ArgumentParser()
for name in ("directory", "plan", "destination"):
parser.add_argument("--" + name, type=Path, required=True)
args = parser.parse_args()
result = export(args.directory, args.plan, args.destination)
print(json.dumps({"exported": True, "status": result["status"], "observed_phases": result["observed_phase_count"]}))
Loading