From 3a7510a8f6df95309b62d0531d150cd592aea825 Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:17:21 +0800 Subject: [PATCH 1/2] fix: preserve resized snapshots and explicit history clears (#29) --- experiments/candidates/alacritty/Cargo.lock | 2 +- .../candidates/alacritty/PROVENANCE.md | 8 +- .../alacritty/include/termforge_candidate.h | 1 + experiments/candidates/alacritty/run.py | 2 +- experiments/candidates/alacritty/src/ffi.rs | 2 + .../candidates/alacritty/test_candidate.py | 2 +- .../vendor/alacritty_terminal/Cargo.toml | 2 +- .../vendor/alacritty_terminal/src/term/mod.rs | 19 +++ experiments/core/candidate_terminal.cpp | 25 ++-- experiments/core/candidate_terminal.hpp | 1 + experiments/core/candidate_terminal_test.cpp | 115 ++++++++++++++++++ 11 files changed, 163 insertions(+), 16 deletions(-) diff --git a/experiments/candidates/alacritty/Cargo.lock b/experiments/candidates/alacritty/Cargo.lock index 6501189a..7a002fd0 100644 --- a/experiments/candidates/alacritty/Cargo.lock +++ b/experiments/candidates/alacritty/Cargo.lock @@ -13,7 +13,7 @@ dependencies = [ [[package]] name = "alacritty_terminal" -version = "0.26.1-dev+termforge.4" +version = "0.26.1-dev+termforge.5" dependencies = [ "arrayvec", "base64", diff --git a/experiments/candidates/alacritty/PROVENANCE.md b/experiments/candidates/alacritty/PROVENANCE.md index 3470237b..0a2ca5d2 100644 --- a/experiments/candidates/alacritty/PROVENANCE.md +++ b/experiments/candidates/alacritty/PROVENANCE.md @@ -1,6 +1,6 @@ # 固定候选来源与本地补丁 -当前适配版本为 `alacritty_terminal 0.26.1-dev+termforge.4`,不是 crates.io 原始 `0.26.0`。 +当前适配版本为 `alacritty_terminal 0.26.1-dev+termforge.5`,不是 crates.io 原始 `0.26.0`。 ## 上游身份 @@ -53,6 +53,12 @@ 完整控制/宽度/资源和字体策略见[流式字素合同](../../../docs/research/streaming-graphemes.md)。固定 WindTerm 的运行时字素/宽度仍未知。 +## termforge.5 补丁 + +1. `src/term/mod.rs`:记录解析器实际执行的主屏 `ED3` 和 `RIS` 清空代次,区分显式清空与历史淘汰;原生历史为空时仍通知外部缓存。备用屏 `ED3` 和 `ED2` 不递增代次,`RIS` 清空两屏后继续保留单调代次。 +2. C ABI 与 C++ 适配器同步读取代次,在 resize 回放期间释放旧历史和身份缓存,避免显式清空后复活。公开 `feed/resize/Screen/TextSelection/TextSearch` 接口回归覆盖该行为,不依靠对原始字节搜索 `CSI 3J`。 +3. 版本升为 `0.26.1-dev+termforge.5`,未改变固定上游 commit、第三方依赖版本或许可证。此补丁仍是候选实验,不代表正式产品选型或 WindTerm 清空行为已对照。 + ## 历史基线 #24–#26 的原始候选为 crates.io `0.26.0`,crate SHA-256 `bda177466b9524d59f1b12f0dd30b68696788e9992a7e959021c4a0ed96fcf59`,对应上游 commit `94e7c8874e526b1e67b349d9ba30ddf81669119e`。当时的 90 match + 5 reference_difference、project/interactive 计量和平台/维护审查保持原批次身份,不改写为 termforge.1/2 结果。 diff --git a/experiments/candidates/alacritty/include/termforge_candidate.h b/experiments/candidates/alacritty/include/termforge_candidate.h index 9ab501e9..21fffc27 100644 --- a/experiments/candidates/alacritty/include/termforge_candidate.h +++ b/experiments/candidates/alacritty/include/termforge_candidate.h @@ -16,6 +16,7 @@ typedef struct TfTerminalMeta { bool bold; uint64_t mode_bits; uint64_t grapheme_overflows; + uint64_t history_clear_epoch; } TfTerminalMeta; typedef struct TfRowMeta { diff --git a/experiments/candidates/alacritty/run.py b/experiments/candidates/alacritty/run.py index 7b7c609e..25bf3584 100644 --- a/experiments/candidates/alacritty/run.py +++ b/experiments/candidates/alacritty/run.py @@ -11,7 +11,7 @@ ROOT = Path(__file__).resolve().parents[3] HERE = Path(__file__).resolve().parent -VERSION = "0.26.1-dev+termforge.4" +VERSION = "0.26.1-dev+termforge.5" UPSTREAM_COMMIT = "542147067dd016fc5f49120ef0ba605a98a4e3a7" BASE_CRATE_SHA256 = "bda177466b9524d59f1b12f0dd30b68696788e9992a7e959021c4a0ed96fcf59" diff --git a/experiments/candidates/alacritty/src/ffi.rs b/experiments/candidates/alacritty/src/ffi.rs index 4f56d33f..5a70d6e1 100644 --- a/experiments/candidates/alacritty/src/ffi.rs +++ b/experiments/candidates/alacritty/src/ffi.rs @@ -34,6 +34,7 @@ pub struct TerminalMeta { bold: bool, mode_bits: u64, grapheme_overflows: u64, + history_clear_epoch: u64, } #[repr(C)] @@ -209,6 +210,7 @@ pub unsafe extern "C" fn tf_terminal_meta(value: *mut c_void, output: *mut Termi bold: cursor.template.flags.contains(Flags::BOLD), mode_bits: value.term.mode().bits() as u64, grapheme_overflows: value.term.grapheme_overflows(), + history_clear_epoch: value.term.history_clear_epoch(), }; unsafe { *output = result }; 0 diff --git a/experiments/candidates/alacritty/test_candidate.py b/experiments/candidates/alacritty/test_candidate.py index 096f8e4c..8e3f56a9 100644 --- a/experiments/candidates/alacritty/test_candidate.py +++ b/experiments/candidates/alacritty/test_candidate.py @@ -32,7 +32,7 @@ def test_fixed_streaming_grapheme_samples(self): self.assertFalse(summary["performance_measured"]) def test_identity_and_limitations_are_recorded(self): - self.assertEqual(self.summary["version"], "0.26.1-dev+termforge.4") + self.assertEqual(self.summary["version"], "0.26.1-dev+termforge.5") self.assertEqual(self.summary["license"], "Apache-2.0") self.assertEqual(self.summary["upstream_commit"], "542147067dd016fc5f49120ef0ba605a98a4e3a7") self.assertEqual(len(self.summary["trace_sha256"]), 64) diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml b/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml index 58192a19..0755589b 100644 --- a/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alacritty_terminal" -version = "0.26.1-dev+termforge.4" +version = "0.26.1-dev+termforge.5" authors = ["Christian Duerr ", "Joe Wilm "] license = "Apache-2.0" description = "Library for writing terminal emulators" diff --git a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs index e0139580..618fbb2c 100644 --- a/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs +++ b/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs @@ -333,6 +333,9 @@ pub struct Term { grapheme: grapheme::StreamingGrapheme, grapheme_overflows: u64, + /// Explicit primary-history clears, including clears while the grid is empty. + history_clear_epoch: u64, + /// Config directly for the terminal. config: Config, } @@ -433,6 +436,7 @@ impl Term { Term { grapheme: Default::default(), grapheme_overflows: 0, + history_clear_epoch: 0, inactive_grid, inactive_scroll_region: scroll_region.clone(), scroll_region, @@ -455,6 +459,11 @@ impl Term { } } + /// Generation of explicit primary-history clears, distinct from eviction. + pub fn history_clear_epoch(&self) -> u64 { + self.history_clear_epoch + } + /// Collect the information about the changes in the lines, which /// could be used to minimize the amount of drawing operations. /// @@ -1729,6 +1738,13 @@ impl Handler for Term { fn clear_screen(&mut self, mode: ansi::ClearMode) { self.seal_grapheme(); trace!("Clearing screen: {mode:?}"); + if matches!(mode, ansi::ClearMode::Saved) && !self.mode.contains(TermMode::ALT_SCREEN) { + // Consumers can retain history outside the native grid, so an empty + // grid must still notify them that an explicit clear was executed. + self.history_clear_epoch = self.history_clear_epoch + .checked_add(1) + .expect("history clear epoch exhausted"); + } let bg = self.grid.cursor.template.bg; let screen_lines = self.screen_lines(); @@ -1802,6 +1818,9 @@ impl Handler for Term { #[inline] fn reset_state(&mut self) { self.seal_grapheme(); + self.history_clear_epoch = self.history_clear_epoch + .checked_add(1) + .expect("history clear epoch exhausted"); if self.mode.contains(TermMode::ALT_SCREEN) { mem::swap(&mut self.grid, &mut self.inactive_grid); } diff --git a/experiments/core/candidate_terminal.cpp b/experiments/core/candidate_terminal.cpp index 9904c6d2..d90332ea 100644 --- a/experiments/core/candidate_terminal.cpp +++ b/experiments/core/candidate_terminal.cpp @@ -274,20 +274,23 @@ std::vector CandidateTerminal::take_events() { } void CandidateTerminal::synchronize() { + TfTerminalMeta meta{}; + check(tf_terminal_meta(impl_, &meta), "candidate metadata failed"); + if (meta.history_clear_epoch != history_clear_epoch_) { + // A parsed clear invalidates cached history even in an incomplete replay. + // The generation also covers clears when the native history is empty. + history_clear_epoch_ = meta.history_clear_epoch; + replay_origin_.reset(); + preserve_history_ = false; + identity_aliases_.clear(); + replay_pending_ = replay_hide_ = replay_show_ = false; + replay_bytes_ = 0; + } if (replay_pending_) { - if (replay_origin_) { - screen.row = replay_origin_->row; - screen.column = replay_origin_->column; - screen.wrap_pending = replay_origin_->wrap_pending; - screen.discarded = replay_origin_->discarded; - screen.style = replay_origin_->style; - screen.history = replay_origin_->history; - screen.lines = replay_origin_->lines; - } + // Keep the last complete publication, including its resized dimensions. + // The pre-resize origin is for identity reconciliation, not presentation. return; } - TfTerminalMeta meta{}; - check(tf_terminal_meta(impl_, &meta), "candidate metadata failed"); mode_bits_ = meta.mode_bits; grapheme_overflows_ = meta.grapheme_overflows; std::unordered_map previous; diff --git a/experiments/core/candidate_terminal.hpp b/experiments/core/candidate_terminal.hpp index 55de3176..3cfe92cd 100644 --- a/experiments/core/candidate_terminal.hpp +++ b/experiments/core/candidate_terminal.hpp @@ -17,6 +17,7 @@ struct CandidateEvent { class CandidateTerminal { void* impl_ = nullptr; uint64_t mode_bits_ = 0, grapheme_overflows_ = 0; + uint64_t history_clear_epoch_ = 0; std::unordered_map versions_; std::vector events_; struct IdentityAlias { uint64_t id = 0, version = 0; }; diff --git a/experiments/core/candidate_terminal_test.cpp b/experiments/core/candidate_terminal_test.cpp index 63efa4dc..787c5ddd 100644 --- a/experiments/core/candidate_terminal_test.cpp +++ b/experiments/core/candidate_terminal_test.cpp @@ -400,6 +400,41 @@ void incomplete_replay_is_bounded() { "finish did not release the actual state after an incomplete replay"); } +void pending_replay_keeps_resized_grid() { + for (const auto dimensions : {std::pair{2, 16}, {2, 4}, {1, 1}, {3, 5}}) { + termforge::CandidateTerminal terminal(2, 8, 4); + terminal.feed("ABCDE中"); + terminal.resize(dimensions.first, dimensions.second); + const auto resized = state(terminal); + const auto check_grid = [&] { + const auto& screen = terminal.screen; + require(screen.height() == dimensions.first && screen.width() == dimensions.second, + "pending replay reverted the published resize dimensions"); + require(screen.row >= 0 && screen.row < screen.height() && + screen.column >= 0 && screen.column < screen.width(), "pending replay cursor exceeds its grid"); + for (const auto& line : screen.lines) { + require(line.size() == static_cast(screen.width()), "pending replay row width differs"); + for (size_t column = 0; column < line.size(); ++column) { + if (line[column].width == 2) + require(column + 1 < line.size() && line[column + 1].width == 0, "pending replay orphan lead"); + if (line[column].width == 0) + require(column && line[column - 1].width == 2, "pending replay orphan trailer"); + } + } + }; + check_grid(); + const std::string incomplete = "\x1b[?25lX"; + for (const char byte : incomplete) { + terminal.feed(std::string_view(&byte, 1)); + check_grid(); + require(state(terminal) == resized, "pending replay changed its last complete resized snapshot"); + } + terminal.feed("\x1b[?25h"); + check_grid(); + require(state(terminal) != resized, "completed replay failed to publish its actual writes"); + } +} + void history_clear_does_not_resurrect() { termforge::CandidateTerminal terminal(2, 8, 4); terminal.feed("A\r\nB\r\nC\r\nD"); @@ -417,6 +452,77 @@ void history_clear_does_not_resurrect() { } } +enum class ReplayClearPhase { before, pending, completed, coalesced }; + +void history_clear_replay_boundaries(std::string_view clear, ReplayClearPhase phase, bool fragmented) { + termforge::CandidateTerminal terminal(2, 8, 4); + terminal.feed("A\r\nB\r\nC\r\nD"); + termforge::TextDocument before(terminal.screen); + termforge::TextSelection selection; + require(selection.select_line(before, 0) && selection.copy(before, 64).text == "A", + "clear-during-replay selection fixture differs"); + termforge::TextSearch search; + const auto generation = search.start(before, "A"); + for (int step = 0; step < 100 && search.status() == termforge::SearchStatus::running; ++step) + search.step(before, generation, 64); + require(search.match(before, 0).has_value(), "clear-during-replay Find fixture differs"); + terminal.resize(2, 9); + const auto feed = [&](std::string_view bytes) { + if (fragmented) for (const char byte : bytes) terminal.feed(std::string_view(&byte, 1)); + else terminal.feed(bytes); + }; + const auto discarded = terminal.screen.discarded; + if (phase == ReplayClearPhase::coalesced) feed("\x1b[?25l" + std::string(clear) + "\x1b[?25h"); + else { + if (phase != ReplayClearPhase::before) feed("\x1b[?25l"); + if (phase == ReplayClearPhase::completed) feed("\x1b[?25h"); + feed(clear); + } + require(terminal.screen.history.empty(), "clear during replay resurrected old history"); + require(terminal.screen.discarded == discarded, "explicit clear was counted as history eviction"); + termforge::TextDocument cleared(terminal.screen); + require(selection.copy(cleared, 64).status == termforge::SelectionCopyStatus::invalidated && + !search.match(cleared, 0), "cleared history kept a stale selection or Find hit"); + if (phase == ReplayClearPhase::pending) feed("\x1b[?25h"); + terminal.feed("E\r\nF"); + for (size_t index = 0; index < terminal.screen.retained_size(); ++index) { + const auto value = termforge::project_line(terminal.screen.retained_line(index)).text; + require(value != "A" && value != "B", "later output resurrected history cleared during replay"); + } +} + +void clear_cached_history_with_empty_native_grid(bool fragmented) { + termforge::CandidateTerminal terminal(2, 8, 4); + terminal.feed("A\r\nB\r\nC\r\nD"); + terminal.resize(4, 8); + // Height growth pulls A/B into the native viewport; repaint replaces them. + // Completed replay retains A/B in the adapter's history cache. + terminal.feed("\x1b[?25l\x1b[2J\x1b[HC\r\nD\x1b[?25h"); + require(terminal.screen.history.size() == 2 && text(terminal.screen.history[0]) == "A" && + text(terminal.screen.history[1]) == "B", "empty-native-history cache fixture differs"); + terminal.resize(4, 9); + terminal.feed("\x1b[?25l"); + const std::string clear = "\x1b[03J"; + if (fragmented) for (const char byte : clear) terminal.feed(std::string_view(&byte, 1)); + else terminal.feed(clear); + require(terminal.screen.history.empty(), "clear missed adapter history when native history was empty"); + terminal.feed("\x1b[?25h"); + require(terminal.screen.history.empty(), "replay completion resurrected cleared adapter history"); +} + +void erase_screen_keeps_replay_history(bool fragmented) { + termforge::CandidateTerminal terminal(2, 8, 4); + terminal.feed("A\r\nB\r\nC\r\nD"); + terminal.resize(2, 9); + const std::string erase = "\x1b[?25l\x1b[2J\x1b[?25h"; + if (fragmented) for (const char byte : erase) terminal.feed(std::string_view(&byte, 1)); + else terminal.feed(erase); + require(terminal.screen.history.size() == 2 && text(terminal.screen.history[0]) == "A" && + text(terminal.screen.history[1]) == "B", "ED2 incorrectly cleared replay history"); + require(text(terminal.screen.lines[0]).empty() && text(terminal.screen.lines[1]).empty(), + "ED2 did not erase the visible screen"); +} + void replay_text_coordinates(int from, int to, bool during_replay, bool fragmented) { termforge::CandidateTerminal terminal(2, 8, 4); terminal.feed(from == 0 ? "H\r\nABC\r\nTAIL" : "H\r\n\x1b[3GABC\r\nTAIL"); @@ -469,7 +575,16 @@ void conpty_resize_replay(const std::filesystem::path& path) { replay_case(path, false); replay_case(path, true); incomplete_replay_is_bounded(); + pending_replay_keeps_resized_grid(); history_clear_does_not_resurrect(); + for (const bool fragmented : {false, true}) { + for (const auto clear : {"\x1b[3J", "\x1b[03J", "\x1b" "c"}) + for (const auto phase : {ReplayClearPhase::before, ReplayClearPhase::pending, + ReplayClearPhase::completed, ReplayClearPhase::coalesced}) + history_clear_replay_boundaries(clear, phase, fragmented); + clear_cached_history_with_empty_native_grid(fragmented); + erase_screen_keeps_replay_history(fragmented); + } for (const bool during_replay : {true, false}) for (const bool fragmented : {false, true}) { replay_text_coordinates(0, 2, during_replay, fragmented); replay_text_coordinates(2, 0, during_replay, fragmented); From 6a108e1d15f00dc9497dac79a8bf75a3559469f8 Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:22:58 +0800 Subject: [PATCH 2/2] docs: record resize snapshot and history clear evidence (#29) --- docs/EVIDENCE.md | 5 +- docs/FEATURES.md | 8 +- docs/PROVENANCE.md | 2 +- docs/adr/0005-physical-row-resize.md | 6 +- docs/research/windterm-resize-text.md | 31 ++ experiments/candidates/alacritty/README.md | 2 +- experiments/core/CANDIDATE.md | 6 +- .../2026-10-27-resize-snapshot/README.md | 23 ++ .../candidate-runner-tests.txt | 11 + .../clear-green.txt | 1 + .../clear-matrix.txt | 1 + .../2026-10-27-resize-snapshot/clear-red.txt | 1 + .../2026-10-27-resize-snapshot/ctest-full.txt | 104 +++++ .../pending-green.txt | 1 + .../pending-red.txt | 1 + .../validation.json | 246 ++++++++++++ .../windterm-input.json | 50 +++ .../windterm-observations.json | 376 ++++++++++++++++++ 18 files changed, 862 insertions(+), 13 deletions(-) create mode 100644 docs/research/windterm-resize-text.md create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/README.md create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/candidate-runner-tests.txt create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/clear-green.txt create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/clear-matrix.txt create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/clear-red.txt create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/ctest-full.txt create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/pending-green.txt create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/pending-red.txt create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/validation.json create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/windterm-input.json create mode 100644 experiments/core/results/2026-10-27-resize-snapshot/windterm-observations.json diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index 423ac7d9..1dd6efd1 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -9,7 +9,7 @@ | 配置 | 证据定位 | |---|---| | main 默认:自研核心 + 系统 ConPTY | R1 限定总验收和有限 VT/Unicode 子集;默认构建不需要 Rust | -| main 显式候选:`termforge.4` + 系统 ConPTY | [固定来源/补丁台账](../experiments/candidates/alacritty/PROVENANCE.md)、[适配边界](../experiments/core/CANDIDATE.md)、[#39 字素合同](research/streaming-graphemes.md);仅按已有合同验收 | +| main 显式候选:`termforge.5` + 系统 ConPTY | [固定来源/补丁台账](../experiments/candidates/alacritty/PROVENANCE.md)、[适配边界](../experiments/core/CANDIDATE.md)、[#39 字素合同](research/streaming-graphemes.md);仅按已有合同验收 | | PR #50 实验:`termforge.4` + 固定官方 ConPTY | [Draft PR](https://github.com/JavaZeroo/termforge/pull/50) / [#47](https://github.com/JavaZeroo/termforge/issues/47);[联合基线](research/joint-conpty-baseline.md)已固定,产品回归未全绿;[resize #51](https://github.com/JavaZeroo/termforge/issues/51)、[GUI Vim #52](https://github.com/JavaZeroo/termforge/issues/52)、[工作区 #63](https://github.com/JavaZeroo/termforge/issues/63)仍开放。[#53 的未完成粘贴、负例及原八会话证据](https://github.com/JavaZeroo/termforge/blob/848cb0e621a291f732cc0e046d0b2fab6a77d962/docs/research/resize-replay-cost.md)已按限定合同整合到实验父分支;不包含同次真实 CF/IME 或完整官方矩阵验收 | R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分别只代表原记录。源 VT、原生控制台 CHAR_INFO、transport、末端状态、自有 DIB、绘制提交和真实呈现是不同检查点;未知端点保持未知。 @@ -36,7 +36,8 @@ R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分 | R2 流式字素与固定 emoji(#39) | [合同](research/streaming-graphemes.md)、[红/绿及实际场景验证](../experiments/shell/results/2026-10-26-streaming-graphemes/validation.json) | 已合并 #39:.4 的固定 ZWJ/keycap 原子格/选择/绘制和单列 resize。766 个规范用例只验证分段 helper;64 标量超限按明确无损分片,不宣称完整字素布局或完整 Unicode/IME。 | | 真实备用屏切换(#27 限定切片) | [五阶段原生快照与候选验证](../experiments/shell/results/2026-10-26-alternate-screen/validation.json)、[候选接口](../experiments/core/CANDIDATE.md) | 真实 1049h/1049l 五阶段的源 VT、CHAR_INFO、样式/光标恢复及候选历史隔离;origin/wrap、margin/resize 交叉与 #27 整体验收另做。 | | 局部滚动区域(#27 限定切片) | [原生阶段快照、独立 transport 重放与候选验证](../experiments/shell/results/2026-10-26-scroll-region/validation.json)、[候选接口](../experiments/core/CANDIDATE.md) | 真实 DECSTBM 局部滚动的源、原生快照与 transport 独立重放;区域外保持/历史隔离通过,不要求控制字节原样回显。 | -| ConPTY resize 重放、历史与 Find/选区(#29) | [限定合同](adr/0005-physical-row-resize.md)、[重放与历史验证](../experiments/core/results/2026-10-26-resize-replay/validation.json)、[文字列位置回归](../experiments/core/results/2026-10-27-replay-coordinates/README.md)、[候选接口](../experiments/core/CANDIDATE.md) | 固定 861B 重放的行身份、连续同步、历史合并及回放后清空;独立列位置回归检查移位后的选区失效和 Find 精确列。冻结快照尺寸、回放期间清空、完整 resize/reflow 及 WindTerm 运行时仍未验收。 | +| ConPTY resize 重放、历史与 Find/选区(#29) | [限定合同](adr/0005-physical-row-resize.md)、[重放与历史验证](../experiments/core/results/2026-10-26-resize-replay/validation.json)、[文字列位置回归](../experiments/core/results/2026-10-27-replay-coordinates/README.md)、[快照/清空修复](../experiments/core/results/2026-10-27-resize-snapshot/README.md)、[候选接口](../experiments/core/CANDIDATE.md) | 固定 861B 重放和独立列位置回归保留;`.5` 验证 resize 后 pending 可见网格一致及主屏 ED3/RIS 清空不复活。跨宽度历史、缓存/备用屏、完整 resize/reflow 与真实 child/呈现仍未完成。 | +| WindTerm resize 文本保留与回带(#29) | [方法与边界](research/windterm-resize-text.md)、[两次事实/哈希](../experiments/core/results/2026-10-27-resize-snapshot/windterm-observations.json) | 原版 2.7.0 的隔离 Telnet 五阶段观察:42 条已知文本保留,恢复宽/高后采集投影恢复;不把文本索引当物理格,不验证候选冻结/清空、ConPTY child、实际呈现或性能。 | | R2 复杂标量序列输入(#30 限定切片) | [真实键盘接收验证](../experiments/shell/results/2026-10-26-unicode-keyboard/validation.json)、[场景与复现](../experiments/shell/README.md) | 固定 ZWJ/keycap 通过 WM_CHAR/ConPTY/ReadConsoleW 精确接收;只证明输入保真,不证明绘制、字素宽度或完整应用键盘模式。 | | 自有窗口绘制正确性 | [呈现探针](../experiments/presentation/README.md)、[共享 renderer](../experiments/presentation/render.hpp)、[度量测试](../experiments/presentation/renderer_metrics_test.cpp) | 自有 DIB 的 retained/full RGB、字体/光标/缺字负例与覆盖;BitBlt/GdiFlush 提交不等于实际上屏,presented_done=null。 | | 进程生命周期和平台边界 | [ConPTY 探针](../experiments/conpty/README.md)、[跨环境记录](research/conpty-platform-check-2026-10-22.md)、[旧平台探针](../.github/workflows/conpty-compat.yml) | 受控后代、取消/超时和短批次回收;Windows 11/Server 2025 有固定记录,Server 2022 稳态句柄门槛仍失败。长期无泄漏未知。 | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index ab3e1648..2a548465 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -13,7 +13,7 @@ | 配置 | 当前定位 | 可以引用的证据与限制 | |---|---|---| | 默认 main:自研核心 + 系统 ConPTY | 默认构建与已发布 Alpha 的基础路线,不需要 Rust | 已有 R1 限定验收;VT/Unicode 能力仍为有限子集,不能引用候选核心的字素或 TUI 结果 | -| 显式候选 main:`termforge.4` + 系统 ConPTY | `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON`;按 [ADR-0003](adr/0003-core-reference-and-reuse.md)保留为可评估实验,正式核心选型待用户确认 | [固定来源](../experiments/candidates/alacritty/PROVENANCE.md)、[适配边界](../experiments/core/CANDIDATE.md)与 [#39 字素合同](research/streaming-graphemes.md);候选正确性通过不关闭系统 ConPTY 或 R2 父项 | +| 显式候选 main:`termforge.5` + 系统 ConPTY | `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON`;按 [ADR-0003](adr/0003-core-reference-and-reuse.md)保留为可评估实验,正式核心选型待用户确认 | [固定来源](../experiments/candidates/alacritty/PROVENANCE.md)、[适配边界](../experiments/core/CANDIDATE.md)与 [#39 字素合同](research/streaming-graphemes.md);候选正确性通过不关闭系统 ConPTY 或 R2 父项 | | 实验分支:`termforge.4` + 固定官方 ConPTY | [PR #50(Draft)](https://github.com/JavaZeroo/termforge/pull/50),联合源码 `3e5b96d` 的[原基线](research/joint-conpty-baseline.md)保留;后续限定修复已整合,尚未合入 main 或通过产品整体验收 | [#47](https://github.com/JavaZeroo/termforge/issues/47) 跟踪后端整合;终端 margin 与原生状态分列。[resize #51](https://github.com/JavaZeroo/termforge/issues/51)、[GUI Vim #52](https://github.com/JavaZeroo/termforge/issues/52)、[工作区 #63](https://github.com/JavaZeroo/termforge/issues/63) 仍开放。[#53](https://github.com/JavaZeroo/termforge/issues/53) 的原 3MB/768B/16KiB 未完成粘贴取消/关闭、负例及八会话 data-only 两轮已限定验证并整合到实验父分支;同次真实 CF/IME 与完整官方矩阵未通过。fence 默认关闭,不由实验结果选择产品后端 | 以下能力默认指已合并实现;某个配置的通过结果不能转用于另一个配置。R1 的真实系统 CF/拼音验收属于固定自研核心配置,不外推到候选 `.4` 或实验后端联合配置。 @@ -31,9 +31,9 @@ | 查找与定位 | **限定验收**:有界增量字面量搜索、原生查找框、方向/循环/高亮、中文、追加与淘汰;持续输出时可显式使用有效局部命中,运行中查询替换/取消/关闭均有[托管两轮独立证据](../experiments/shell/results/2026-10-24-r1-find-accepted/validation.json) | 局部结果不表示全局末端/循环;动作与受控 DPI 交叉见统一文字动作,组合与资源见 #18 的独立验收。正则、跨会话、完整 Unicode caseless 和大历史规模归 R4 | R1 / R4 | | 统一文字动作 | **限定验收**:共用目标/可用状态,实际终端右键/快捷键、原生 Find/rename 编辑、busy/closing/失效选区反馈及受控 DPI/zoom 保持;[最终系统审计](../experiments/shell/results/2026-10-24-r1-actions-accepted/validation.json)与[逐项对照](research/r1-action-comparison.md)覆盖 Find 实际 Ctrl+C/V、中文重选、默认 Return 和会话隔离 | 复杂 busy/closing 参考行为仍未知,采用明确项目策略;完整流程与八会话有限资源已由 #18 另验收,真实跨显示器及完整 WindTerm 等价未验收 | R1 | | 中文 IME 与键盘 | **限定验收**:基础按键/修饰键/UTF-16 与实际接收字节;[固定 ZWJ/keycap 输入](../experiments/shell/results/2026-10-26-unicode-keyboard/validation.json)在默认/候选各两轮精确接收。固定 Microsoft 拼音[产品场景](../research/ime/results/2026-10-24-product-ime-contexts/validation.json)覆盖两区域组合/提交/取消、候选、输出/resize/zoom/受控 DPI、隐藏/关闭、历史阅读和原生控件 | 限定 Windows Server 2025 固定拼音及已有场景;同次组合由八会话工作流另验收。固定字素绘制/选择已由 #39 限定完成,但所有输入法、应用键盘模式、完整 IME 和真实不同 DPI 跨屏仍未完成 | R1 / R2 | -| VT 核心 | **实现中,未验收**:默认自研支持基础光标/擦除(含 ECH)、CR/LF/BS/TAB、有限 SGR 和有界恢复。显式候选 `.4` 接入 `Term/Processor`、C++ 拥有值适配、稳定行元数据、每缓冲区 margin 与流式字素;[共同轨迹](../experiments/differential/results/2026-10-26-candidate-common-v6/summary.json)及[源 margin 合同](../experiments/shell/results/2026-10-26-margin-state/validation.json)为各自固定版本结果 | [#27](https://github.com/JavaZeroo/termforge/issues/27) 的 origin/wrap、插删、模式/resize 交叉未闭合;[#41](https://github.com/JavaZeroo/termforge/issues/41) 系统 ConPTY 已丢失的 margin 不能由末端补回。ECH pending-wrap 差异见[限定合同](research/more-capture-ech.md);#36/#38、完整呈现和平台支持独立验收 | R2 / R4 | -| Unicode 与文字显示 | **实现中,未验收**:默认自研保留 Unicode 15。显式候选 `.4` 的 [#39 Unicode 17 流式字素合同](research/streaming-graphemes.md)已限定验收:固定 ZWJ/keycap 的单 lead/两列、光标、SGR/查询、原子选择复制、单列 resize 与有界无损分片;实际 ConPTY/DWrite 核对固定样本的单 glyph、字体和网格 | [#30](https://github.com/JavaZeroo/termforge/issues/30) 仍需完整输入/IME 交叉;所有脚本/整形、通用 fallback、所有原生重放、真彩色/更多属性、Powerline 和真实跨屏未验收。分段 helper 的规范数据通过不代表完整终端 Unicode 正确性;[#54](https://github.com/JavaZeroo/termforge/issues/54) 保留已观察的性能成本 | R2 / R4 | -| resize 与 reflow | **限定验收**:默认按 ADR-0005 物理行裁剪;候选在[固定 ConPTY 重放](../experiments/core/results/2026-10-26-resize-replay/validation.json)与连续同步中保持行身份、Find/选区和有界历史,覆盖限定淘汰及回放完成后的清空;[列位置回归](../experiments/core/results/2026-10-27-replay-coordinates/README.md)核对文字移位后选择失效和 Find 重定位;候选 `.4` 另覆盖主/备用屏缩到单列 | [#29](https://github.com/JavaZeroo/termforge/issues/29) 的冻结快照尺寸、回放期间清空和完整 resize 交叉未完成;固定事务不等于完整 reflow 或 WindTerm 等价。系统/实验 ConPTY、备用屏/滚动区、真实 DPI、呈现、性能及 #36/#38 分别验收;默认缩窄不可恢复 | R2 | +| VT 核心 | **实现中,未验收**:默认自研支持基础光标/擦除(含 ECH)、CR/LF/BS/TAB、有限 SGR 和有界恢复。显式候选 `.5` 接入 `Term/Processor`、C++ 拥有值适配、稳定行元数据、每缓冲区 margin 与流式字素;[共同轨迹](../experiments/differential/results/2026-10-26-candidate-common-v6/summary.json)及[源 margin 合同](../experiments/shell/results/2026-10-26-margin-state/validation.json)为各自固定版本结果 | [#27](https://github.com/JavaZeroo/termforge/issues/27) 的 origin/wrap、插删、模式/resize 交叉未闭合;[#41](https://github.com/JavaZeroo/termforge/issues/41) 系统 ConPTY 已丢失的 margin 不能由末端补回。ECH pending-wrap 差异见[限定合同](research/more-capture-ech.md);#36/#38、完整呈现和平台支持独立验收 | R2 / R4 | +| Unicode 与文字显示 | **实现中,未验收**:默认自研保留 Unicode 15。显式候选 `.5` 保留 [#39 Unicode 17 流式字素合同](research/streaming-graphemes.md)已限定验收:固定 ZWJ/keycap 的单 lead/两列、光标、SGR/查询、原子选择复制、单列 resize 与有界无损分片;实际 ConPTY/DWrite 核对固定样本的单 glyph、字体和网格 | [#30](https://github.com/JavaZeroo/termforge/issues/30) 仍需完整输入/IME 交叉;所有脚本/整形、通用 fallback、所有原生重放、真彩色/更多属性、Powerline 和真实跨屏未验收。分段 helper 的规范数据通过不代表完整终端 Unicode 正确性;[#54](https://github.com/JavaZeroo/termforge/issues/54) 保留已观察的性能成本 | R2 / R4 | +| resize 与 reflow | **限定验收**:默认按 ADR-0005 物理行裁剪;候选保留[固定 861B 重放](../experiments/core/results/2026-10-26-resize-replay/validation.json)的身份/历史与[列位置回归](../experiments/core/results/2026-10-27-replay-coordinates/README.md)。`.5` 的[快照与清空修复](../experiments/core/results/2026-10-27-resize-snapshot/README.md)验证四种可见尺寸的 pending 快照,以及主屏 ED3/RIS 清空期间不复活旧历史、选择/Find 失效 | [#29](https://github.com/JavaZeroo/termforge/issues/29) 的跨宽度缓存显示、零原生历史启发式、备用屏与完整 resize 交叉未完成;[原版两次文本观察](research/windterm-resize-text.md)仅证明固定输入保留及恢复尺寸后文本投影恢复。完整 reflow、child ack、实际呈现、系统/实验后端联合配置及性能分别验收;默认缩窄不可恢复 | R2 | | 本地 shell 选择 | **实现中,未验收**:`ShellLaunch` 分离可执行文件、参数、工作目录及 shell 命令差异;cmd、Windows PowerShell 5.1、PowerShell 7、显式 Ubuntu WSL 各有[两轮真实启动/目录/尺寸/正常退出证据](../experiments/shell/results/2026-10-24-r2-shell-lifecycle/validation.json),无效程序启动错误可见且可关闭后恢复 cmd。四 shell 测试已补 [WSL 原生尺寸程序返回后的新提示符同步](research/wsl-size-return.md),正常退出证据只在实际全部回收后写入;默认/system 的事件反例、原两轮及完整回归分别留证 | 仅声明上述固定入口和隔离目录;#80 原 hosted stage6 是否同因仍未知,#81 hosted 准备超时另行定位;任意 profile/参数、通用分屏/排序、全部运行时错误提示及管理员 shell 未完成 | R2;权限场景 R4 | | 常用 TUI | **实现中,未验收**:显式候选 + 系统 ConPTY 有固定 Vim 进入、编辑、60×12 resize、退出写盘、主屏恢复及光标回复的[产品证据](../experiments/core/CANDIDATE.md);More 尾部 ECH 与捕获端点有[限定修复](research/more-capture-ech.md) | [#28](https://github.com/JavaZeroo/termforge/issues/28) 的更多应用及完整输入/输出模式未完成;PR #50 实验后端的 GUI Vim [#52](https://github.com/JavaZeroo/termforge/issues/52) 仍有失败;[native 捕获目录复用](research/vim-fixture-isolation.md)已独立定位并修正,不能代替 GUI 或完整 TUI 验收。headless 比较与自有 DIB 不等于真实呈现,`presented_done=null` | R2 | diff --git a/docs/PROVENANCE.md b/docs/PROVENANCE.md index c99fff4f..a1634315 100644 --- a/docs/PROVENANCE.md +++ b/docs/PROVENANCE.md @@ -24,7 +24,7 @@ - `experiments/core/unicode_width.hpp`:通过原创生成器从 Python 3.12.10 `unicodedata` 所带 Unicode 15.0.0 属性数据生成,含项目自己的宽度/拒绝策略。未复制 CPython 实现源码。Unicode 数据保留[官方许可](../experiments/core/UNICODE-LICENSE.txt)与归属,来源为 [Unicode](https://www.unicode.org/);正常构建使用固定生成文件。此数据不归项目 MIT 许可覆盖。 -- 外部核心候选当前使用 `alacritty_terminal 0.26.1-dev+termforge.4`(Apache-2.0),固定上游 commit `542147067dd016fc5f49120ef0ba605a98a4e3a7` 的 manifest/changelog/许可证与 `src/`,并保留 ED、稳定行身份/淘汰、安全 swap、每缓冲区滚动区域和流式字素补丁;精确传递依赖及校验值由 `Cargo.lock` 固定。未复制上游 46 MiB 行为录制,候选不进入当前产品包;默认 CMake 不依赖 Rust,显式适配器构建使用 staticlib。原始 0.26.0 身份、当前来源、许可证和补丁清单见[候选来源台账](../experiments/candidates/alacritty/PROVENANCE.md)。 +- 外部核心候选当前使用 `alacritty_terminal 0.26.1-dev+termforge.5`(Apache-2.0),固定上游 commit `542147067dd016fc5f49120ef0ba605a98a4e3a7` 的 manifest/changelog/许可证与 `src/`,并保留 ED、稳定行身份/淘汰、安全 swap、每缓冲区滚动区域、流式字素和显式历史清空代次补丁;精确传递依赖及校验值由 `Cargo.lock` 固定。未复制上游 46 MiB 行为录制,候选不进入当前产品包;默认 CMake 不依赖 Rust,显式适配器构建使用 staticlib。原始 0.26.0 身份、当前来源、许可证和补丁清单见[候选来源台账](../experiments/candidates/alacritty/PROVENANCE.md)。 - 候选新增精确锁定 `unicode-segmentation 1.13.3`(Unicode 17,MIT OR Apache-2.0),官方来源为 [unicode-rs](https://github.com/unicode-rs/unicode-segmentation) 与 [crates.io 固定版本](https://crates.io/crates/unicode-segmentation/1.13.3)。保留原依赖许可证;与既有 `unicode-width 0.2.2` 同使用 Unicode 17,默认自研核心仍使用 Unicode 15。版本、包哈希、MSRV、依赖成本和修改边界见[候选来源台账](../experiments/candidates/alacritty/PROVENANCE.md)。 diff --git a/docs/adr/0005-physical-row-resize.md b/docs/adr/0005-physical-row-resize.md index 1867d5ff..39376f54 100644 --- a/docs/adr/0005-physical-row-resize.md +++ b/docs/adr/0005-physical-row-resize.md @@ -39,14 +39,14 @@ API 依据是 [Microsoft ResizePseudoConsole](https://learn.microsoft.com/en-us/ 上述物理行合同继续适用于默认自研回退,包括缩窄不可恢复及 resize 改变行版本的规则。显式 `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON` 的候选采用以下独立合同,不把上游 `Term::resize` 的结果直接称为默认物理行策略或完整 reflow。 -- 候选在 resize 时保存有界屏幕快照,识别可跨 feed 的 `CSI ?25l` / `CSI ?25h` 重放。等待期间提供一致快照;等待超过 8192 字节或 `finish` 时解除冻结并同步实际状态。只有 resize 后的该事务才进入此路径,普通应用隐藏光标不等于已验证的 resize 重放。 +- 候选在 resize 时保存有界屏幕快照,识别可跨 feed 的 `CSI ?25l` / `CSI ?25h` 重放。等待期间保留最近一份完整的 resize 后快照,不能把旧尺寸的行和光标放回新尺寸;旧快照仅用于完成时身份匹配。等待超过 8192 字节或 `finish` 时解除冻结并同步实际状态。只有 resize 后的该事务才进入此路径,普通应用隐藏光标不等于已验证的 resize 重放。 - 行键包含换行、wrap、裁剪状态、文字、宽度、样式、组合标记、written 元数据及首个非 padding 单元格的列号。首部未写 padding 的数量决定文字位置,不能因文字字节相同而忽略;只有末尾真正未写 padding 不影响匹配。缓存身份映射也执行相同列位置检查。只有旧、新集合中都唯一且顺序一致的键才能保留 ID/version;重复文本不盲配。最终所有保留行必须具有非零、唯一、严格递增的 ID,冲突行取得新身份和版本,不能让选区或 Find 错配另一段文字。 - 上游增高可先把历史拉入可见区,随后 ConPTY 重放覆盖这些行。候选在完整重放后保留 TermForge 的有界历史,后续同步按 ID 和完整行键合并历史及可见行;同 ID 不同内容不视为同一行。历史容量和淘汰计数仍有界,真实淘汰不恢复,显式清空历史不复活旧行。无完整重放、无法确定语义的 reflow 和任意空白行身份均不在此保证内。 - Find 和选区同时检查 ID、位置、版本和实际投影文本。非软换行的物理列宽或后继 ID 变化不单独使命中失效;从文末新增后继会引入分隔符,仍需重扫。真正的软换行继续要求完整元数据和后继关系。半开选区排除的第 0 列末端空行可以在仍为空白时替换身份;其它行变化、非空端点及实际淘汰仍使选区原子失效。 - 候选合并不表示通用逻辑行 reflow、备用屏/滚动区全语义、所有应用、真实 DPI、呈现完成或性能验收通过。测试辅助读取使用共享文本投影,宽字符 spacer 不算额外空格,显式样式尾空格仍逐字校验。 -固定 WindTerm 2.7.0 的限定静态复核中,`0x140238f90` 仅在缩短时进入 `0x140233a30` 的保存路径;增高走 `0x1402ee110`,分配并初始化新屏幕行,不消耗 scrollback 来回填视口。该路径支持独立保留旧历史,与上述保守合同一致,但不要求照抄上游或宣称所有 WindTerm 配置均如此。本地 `local/analysis/windterm-resize-history-20261026/` 的 44 个定向函数均完成反编译并核对关键汇编,目标 SHA-256 为 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`。原始输出只留本地;这是静态、限定路径证据,不是运行时全量行为比较,也不覆盖 Document/Scintilla 的所有动态连接。 +固定 WindTerm 2.7.0 的限定静态复核中,`0x140238f90` 仅在缩短时进入 `0x140233a30` 的保存路径;增高走 `0x1402ee110` 分配并初始化屏幕行。它只说明该局部分支,不能据此断言整个文本视图增高时不回带历史。本地 `local/analysis/windterm-resize-history-20261026/` 的 44 个定向函数均完成反编译并核对关键汇编,目标 SHA-256 为 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`;原始输出留本地。[两次原版运行观察](../research/windterm-resize-text.md)显示固定 Telnet 合成输入的文本完整保留,恢复高度后较早历史重新进入文本坐标视图。屏幕分配与 Document/Scintilla 动态连接、物理格及实际呈现仍未知。 命令、哈希、默认/候选完整回归及未满足项见[重放与历史验证记录](../../experiments/core/results/2026-10-26-resize-replay/validation.json)。 -相同文字左右移位后的选区失效和 Find 重新定位另见[列位置回归](../../experiments/core/results/2026-10-27-replay-coordinates/README.md)。这是现有身份/版本合同的修复;它不裁决 WindTerm 的重排、历史回填或选择保留策略,也不补足冻结快照尺寸和回放期间清空历史的验证。 +相同文字左右移位后的选区失效和 Find 重新定位另见[列位置回归](../../experiments/core/results/2026-10-27-replay-coordinates/README.md)。`.5` 在主屏 ED3/RIS 实际执行时递增清空代次,经 C ABI 立即释放旧回放和历史缓存;原生历史为空也报告,ED2 不清历史,清空不冒充淘汰。[快照与清空回归](../../experiments/core/results/2026-10-27-resize-snapshot/README.md)覆盖固定尺寸、清空前/中/后/同 feed、选择/Find 失效与后续不复活。跨宽度历史的显示、零历史缓存启发式、备用屏和完整交叉仍未闭合;这些修复不裁决 WindTerm 的完整重排、清空或选择保留策略。 diff --git a/docs/research/windterm-resize-text.md b/docs/research/windterm-resize-text.md new file mode 100644 index 00000000..6266db40 --- /dev/null +++ b/docs/research/windterm-resize-text.md @@ -0,0 +1,31 @@ +# WindTerm 2.7.0 resize:文本保留与视图回带 + +这是 #29 的原版运行观察,与候选修复及产品验收分开。固定官方 Windows x64 EXE SHA-256 为 `7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0`;Windows 11 Pro 10.0.26200,自有非输入窗口站/desktop、全新合成 profile、loopback Telnet peer。按事先冻结计划运行两次,每次 REA 观察期限 45 秒,两次均完成后停止,没有修改探针后反复取绿。 + +## 观察 + +输入为 1091 字节、42 条已知逻辑文本,包含唯一编号历史、185 字符软长行、两条各 77 字符的 CRLF 硬行、中文/组合字符/ZWJ 和尾部标记。[合成输入](../../experiments/core/results/2026-10-27-resize-snapshot/windterm-input.json)记录精确逻辑文本;[事实与哈希](../../experiments/core/results/2026-10-27-resize-snapshot/windterm-observations.json)记录两次结果及私有原件身份。 + +| 阶段 | 请求外框 | peer 实收 NAWS | scrollTop | screenLineCapacity | visibleLineCount | H0019 起点 y | +|---|---|---|---|---|---|---| +| 稳定基线 | 1230×887 | 80×26 | 19 | 26 | 44 | 0 | +| 缩宽 | 1010×887 | 62×26 | 20 | 26 | 46 | -18 | +| 恢复宽度 | 1230×887 | 80×26 | 19 | 26 | 44 | 0 | +| 缩高 | 1230×679 | 80×18 | 26 | 18 | 44 | -126 | +| 恢复高度 | 1230×887 | 80×26 | 19 | 26 | 44 | 0 | + +两次的五阶段摘要一致。最初 NAWS 是 80×25,载荷按该尺寸生成,稳定后才变为 80×26;不把外框请求视为实际网格。在每阶段,`activeText.lineText(0..41)` 均能读到全部已知输入,`lineStart/lineEnd` 不变。恢复宽度、恢复高度后,采集到的整个文本投影精确回到基线。 + +软长行保持一个 `lineText` 条目,CRLF 硬行保持各自条目。增高后 `scrollTop` 从 26 回到 19,H0019 起点 y 从 -126 回到 0,支持**该文本视图接口重新纳入较早历史**的观察。不能再用单一静态分支推断“原版增高绝不回带历史”;原有屏幕分配路径与 Document/文本视图之间的动态关系仍待调查。 + +## 证据边界 + +- `visibleLineCount` 的 44/46 与已知输入的 42 个 `lineText` 条目粒度不同;额外索引返回空串并别名到末尾位置,不能据此计算物理格或额外空行。 +- `topLine` 为 undefined;selection caret/anchor 的文档位置不是物理光标行列。没有验证选区保留、复制或 Find 的用户操作。 +- 只证明固定输入与五个尺寸的文本保留/投影恢复;完整 reflow、物理格、Unicode 宽度、清空历史、所有配置和序列仍未知。 +- NAWS 来自独立 Telnet peer,不是 ConPTY child ack。实际呈现、本地 shell、性能与产品验收均未执行;250 ms 稳定采样间隔不构成性能门槛。 +- 两次核对 4047 文件分发 manifest、EXE/脚本/载荷 SHA;Job 最终 active=0,desktop/station 句柄已关闭,仅有一个合成会话。没有操作个人剪贴板、DPI 或输入桌面。完整原件、app 和 profile 留在本地,公开事实不含专有实现或个人数据。 + +## 对实现的约束 + +现有默认物理裁剪及候选缓存只描述本项目实现。原版上述文本保留事实要求继续调查当前跨宽度历史的表示和显示,不能用永久裁掉缓存文本来消除行宽不一致。修正候选 pending 快照与显式清空属于[独立不变量修复](../../experiments/core/results/2026-10-27-resize-snapshot/README.md),不等于本次原版观察已经验证这些候选行为。 diff --git a/experiments/candidates/alacritty/README.md b/experiments/candidates/alacritty/README.md index c6c711a9..bdbaf120 100644 --- a/experiments/candidates/alacritty/README.md +++ b/experiments/candidates/alacritty/README.md @@ -6,7 +6,7 @@ | 项目 | 当前固定值 | 来源 | |---|---|---| -| `alacritty_terminal` | `0.26.1-dev+termforge.4`,Apache-2.0 | [上游 commit `5421470`](https://github.com/alacritty/alacritty/commit/542147067dd016fc5f49120ef0ba605a98a4e3a7)与[来源/补丁台账](PROVENANCE.md) | +| `alacritty_terminal` | `0.26.1-dev+termforge.5`,Apache-2.0 | [上游 commit `5421470`](https://github.com/alacritty/alacritty/commit/542147067dd016fc5f49120ef0ba605a98a4e3a7)与[来源/补丁台账](PROVENANCE.md) | | 本地补丁 | ED1/ED2、稳定 Row ID/换行元数据/淘汰、安全完整 Row swap、每缓冲区滚动区域、流式字素与明确剪裁 | `vendor/alacritty_terminal/src/`;所有文件哈希进入结果 manifest | | 工具链 | Rust/cargo 1.92.0 | 本机与候选 CI 显式选择;当前锁定图最高声明 Rust 1.85 | | 传递依赖 | 59 个注册表包 + 根适配器与固定 vendor 两个 path crate,共 61 个包 | `Cargo.lock` 与 `cargo metadata --locked --format-version 1` | diff --git a/experiments/core/CANDIDATE.md b/experiments/core/CANDIDATE.md index 750a82f1..726fc697 100644 --- a/experiments/core/CANDIDATE.md +++ b/experiments/core/CANDIDATE.md @@ -2,7 +2,7 @@ 按 [ADR-0006](../../docs/adr/0006-confirmed-project-direction.md),用户确认保留本候选代码继续评估,尚未确认最终采用。下述实现、来源与回归结果继续保留;与 WindTerm 2.7.0 的重要差异、性能容差及正式选型分别提交用户确认。 -`CandidateTerminal` 是 #27 的拥有值适配层,不是自研核心的替代品声明。它把固定 `0.26.1-dev+termforge.4` 的 `Term/Processor` 接到现有 `Screen`/`Line`;默认 CMake 保留自研核心且不需要 Rust,候选产品由 `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON` 显式启用。 +`CandidateTerminal` 是 #27 的拥有值适配层,不是自研核心的替代品声明。它把固定 `0.26.1-dev+termforge.5` 的 `Term/Processor` 接到现有 `Screen`/`Line`;默认 CMake 保留自研核心且不需要 Rust,候选产品由 `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON` 显式启用。 ## 构建与验证 @@ -26,7 +26,9 @@ Linux 使用相应 CMake 配置。候选 workflow 在 Windows/Ubuntu 构建 `can - 不走 JSON。Rust/C++ 文本由调用方拥有,未做增量 FFI 或性能验收;候选 damage 的产品化仍属后续。 - `screen.resize` 与候选 `Term::resize` 同步尺寸;默认自研回退继续采用 ADR-0005 的物理行裁剪,候选不把它称为完整 reflow。真实 ConPTY 重放可能先由上游把历史拉入可见区再覆盖,候选在完整重放后合并有界历史,按 ID 和完整行键去重,保留 Find/选区所需的稳定身份,且不让冲突产生重复 ID。连续同步缓存原始 FFI ID 与最终身份的映射,仅在需要重放的未知旧 ID 时回退到完整键匹配,不重复扫描全部历史。真实淘汰不恢复,显式清空不复活;普通空白行、未决 reflow、备用屏/滚动区全语义不在此保证内。合同与验证见 [resize/历史记录](results/2026-10-26-resize-replay/validation.json)。 -回放行键与后续缓存匹配同时保留文字的起始列,只忽略末尾未写 padding。相同 `ABC` 从第 0 列移到第 2 列(或反向)不能沿用旧版本供选择/Find 使用;原选区失效,Find 重扫后命中实际列。独立[列位置回归](results/2026-10-27-replay-coordinates/README.md)覆盖回放中/回放后、整段/逐字节和原位对照。这不补足冻结快照尺寸、回放期间清空或原版行为的验证;已有历史清空证据只覆盖回放完成后的场景。 +回放行键与后续缓存匹配同时保留文字的起始列,只忽略末尾未写 padding。相同 `ABC` 从第 0 列移到第 2 列(或反向)不能沿用旧版本供选择/Find 使用;原选区失效,Find 重扫后命中实际列。独立[列位置回归](results/2026-10-27-replay-coordinates/README.md)覆盖回放中/回放后、整段/逐字节和原位对照。 + +等待回放时冻结最近一份完整的 resize 后状态,旧尺寸快照只用于完成时匹配身份。解析器实际执行主屏 ED3(含 `03J`)或 RIS 后,`.5` 的清空代次经 C ABI 释放旧回放/缓存,不等 `?25h` 才显示清空;ED2 不清历史,清空不计为淘汰。[快照与清空回归](results/2026-10-27-resize-snapshot/README.md)记录固定可见网格和清空边界。跨宽度历史行的显示、零原生历史缓存启发式及备用屏缓存仍未完整验证;不能把这些局部结果称为完整历史合同。[原版文本接口观察](../../docs/research/windterm-resize-text.md)仅证明固定序列的文本保留和投影恢复,不验证本项目清空或冻结策略。 ## 真实产品场景 diff --git a/experiments/core/results/2026-10-27-resize-snapshot/README.md b/experiments/core/results/2026-10-27-resize-snapshot/README.md new file mode 100644 index 00000000..c1dc2b18 --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/README.md @@ -0,0 +1,23 @@ +# #29:resize 等待快照与显式清空 + +本切片修复候选两个公开接口反例,不关闭 #29。候选为 `0.26.1-dev+termforge.5`,系统 ConPTY;默认核心和 PR #50 实验官方后端未修改。基点为 PR #108 的 `678a7f41df8323e550b2804f664f2a3d7510d0a4`。 + +## 原因与行为 + +- resize 已更新尺寸后,pending replay 曾把旧宽度的行和旧光标放回公开 Screen;新回归实际报 `pending replay row width differs`。现在冻结最近一次完整的 resize 后状态,旧快照仅用于完成回放时的身份匹配。四个目标尺寸 2×16、2×4、1×1、3×5 按每个拆包检查尺寸、光标和宽字配对,完成时发布真实写入。 +- `?25l → CSI 03J → ?25h` 曾将已清空历史从旧缓存补回,实际报 `clear during replay resurrected old history`。现在由 Rust 解析器报告主屏 ED3/RIS 清空代次,经 C ABI 传给适配器,在 pending 判断前释放旧回放和历史缓存。原生历史为空也会通知;ED2 不清历史,显式清空不计为淘汰。 +- 清空回归包含 ED3/03J/RIS × 回放开始前/等待中/完成后/同一 feed 完整事务 × 整段/逐字节,共 24 组合;另有两种分块下的“原生历史空而缓存非空”和 ED2 对照。检查选择/Find 失效及后续输出不复活;旧 861B whole/byte、列位置和超限释放回归保留。 + +## 复验与来源 + +Windows 候选/system 完整 CTest **49/49**;Rust 工作区 **157 上游单元 + 6 适配/程序单元 + 1 doc test** 全通过;重建 trace 后,候选报告测试 **6/6** 通过。核心与 CTest 对应本地 `14db868`,报告版本修复对应 `eedb577`,两者 C++/Rust 源码完全相同;公开代码提交为 `3a7510a8f6df95309b62d0531d150cd592aea825`,与 `eedb577` 的 tree 一致。 + +实测源码、二进制、原始日志哈希、各次结果及限制见 [validation.json](validation.json)。Rust workspace、公开候选检查和完整 Windows 候选 CTest 分列;构建配置保持 `TERMFORGE_BUILD_CANDIDATE_ADAPTER=ON`、Release、系统 ConPTY。TUI 测试进程 PATH 包含已有 Git for Windows Vim,不更改系统 PATH。首次配置未完成导致的 cache 错误,以及首次补丁编译错误均与语义反例分开留存,不用它们代替 red。 + +新增核心逻辑是按既有一致快照/显式清空合同独立编写的 C++ 适配与 Alacritty 补丁。固定上游 commit、Apache-2.0 和注册表依赖不变;来源台账及当前 runner 版本同步到 `.5`。没有引入 WindTerm 源码、反编译输出或资源。 + +[原版两次 resize 观察](../../../../docs/research/windterm-resize-text.md)另列:42 条合成逻辑文本在五阶段保留,恢复尺寸后文本投影回到基线。它支持继续保留文本,不证明本修复与原版清空/冻结行为一致。 + +## 未完成 + +完整历史 reflow、跨宽度缓存行的显示、备用屏缓存、全部滚动区域交叉、连续 resize 与真实 child ack、最终官方后端、真实 DPI、实际呈现和性能仍需独立验收。现有零原生历史的缓存启发式仍有未闭合边界;本补丁不宣称解决全部历史保留。不能将局部测试或原版文本接口观察拼成 R2 完成或性能等价。 diff --git a/experiments/core/results/2026-10-27-resize-snapshot/candidate-runner-tests.txt b/experiments/core/results/2026-10-27-resize-snapshot/candidate-runner-tests.txt new file mode 100644 index 00000000..c46eaf45 --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/candidate-runner-tests.txt @@ -0,0 +1,11 @@ +test_fixed_streaming_grapheme_samples (experiments.candidates.alacritty.test_candidate.AlacrittyCandidateTests.test_fixed_streaming_grapheme_samples) ... ok +test_identity_and_limitations_are_recorded (experiments.candidates.alacritty.test_candidate.AlacrittyCandidateTests.test_identity_and_limitations_are_recorded) ... ok +test_invalid_chunk_plan_is_an_execution_failure (experiments.candidates.alacritty.test_candidate.AlacrittyCandidateTests.test_invalid_chunk_plan_is_an_execution_failure) ... ok +test_missing_binary_is_not_a_pass (experiments.candidates.alacritty.test_candidate.AlacrittyCandidateTests.test_missing_binary_is_not_a_pass) ... ok +test_real_candidate_checks_pass (experiments.candidates.alacritty.test_candidate.AlacrittyCandidateTests.test_real_candidate_checks_pass) ... ok +test_summary_rejects_a_changed_real_result (experiments.candidates.alacritty.test_candidate.AlacrittyCandidateTests.test_summary_rejects_a_changed_real_result) ... ok + +---------------------------------------------------------------------- +Ran 6 tests in 1.062s + +OK diff --git a/experiments/core/results/2026-10-27-resize-snapshot/clear-green.txt b/experiments/core/results/2026-10-27-resize-snapshot/clear-green.txt new file mode 100644 index 00000000..10c2db47 --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/clear-green.txt @@ -0,0 +1 @@ +candidate terminal adapter checks passed diff --git a/experiments/core/results/2026-10-27-resize-snapshot/clear-matrix.txt b/experiments/core/results/2026-10-27-resize-snapshot/clear-matrix.txt new file mode 100644 index 00000000..10c2db47 --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/clear-matrix.txt @@ -0,0 +1 @@ +candidate terminal adapter checks passed diff --git a/experiments/core/results/2026-10-27-resize-snapshot/clear-red.txt b/experiments/core/results/2026-10-27-resize-snapshot/clear-red.txt new file mode 100644 index 00000000..2c1f7b90 --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/clear-red.txt @@ -0,0 +1 @@ +clear during replay resurrected old history diff --git a/experiments/core/results/2026-10-27-resize-snapshot/ctest-full.txt b/experiments/core/results/2026-10-27-resize-snapshot/ctest-full.txt new file mode 100644 index 00000000..e1d37113 --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/ctest-full.txt @@ -0,0 +1,104 @@ +Internal ctest changing into directory: /build/candidate-adapter +Test project /build/candidate-adapter + Start 1: shell_launch + 1/49 Test #1: shell_launch ..................... Passed 0.02 sec + Start 2: candidate_title + 2/49 Test #2: candidate_title .................. Passed 1.31 sec + Start 3: runtime_observation + 3/49 Test #3: runtime_observation .............. Passed 3.65 sec + Start 4: ime_input_expectation + 4/49 Test #4: ime_input_expectation ............ Passed 0.06 sec + Start 5: sent_message_wait + 5/49 Test #5: sent_message_wait ................ Passed 0.38 sec + Start 6: find_bar + 6/49 Test #6: find_bar ......................... Passed 1.19 sec + Start 7: renderer_metrics + 7/49 Test #7: renderer_metrics ................. Passed 1.30 sec + Start 8: ime_context + 8/49 Test #8: ime_context ...................... Passed 1.35 sec + Start 9: layout_state + 9/49 Test #9: layout_state ..................... Passed 0.18 sec + Start 10: workspace_state +10/49 Test #10: workspace_state .................. Passed 0.46 sec + Start 11: transport_completion +11/49 Test #11: transport_completion ............. Passed 0.02 sec + Start 12: scenario_tick_gate +12/49 Test #12: scenario_tick_gate ............... Passed 0.02 sec + Start 13: candidate_core +13/49 Test #13: candidate_core ................... Passed 1.68 sec + Start 14: resize_core +14/49 Test #14: resize_core ...................... Passed 0.03 sec + Start 15: history_core +15/49 Test #15: history_core ..................... Passed 0.02 sec + Start 16: selection_core +16/49 Test #16: selection_core ................... Passed 0.10 sec + Start 17: search_core +17/49 Test #17: search_core ...................... Passed 0.13 sec + Start 18: paste +18/49 Test #18: paste ............................ Passed 0.02 sec + Start 19: clipboard_policy +19/49 Test #19: clipboard_policy ................. Passed 0.02 sec + Start 20: keyboard +20/49 Test #20: keyboard ......................... Passed 0.02 sec + Start 21: text_action_policy +21/49 Test #21: text_action_policy ............... Passed 0.02 sec + Start 22: candidate_shell_tui +22/49 Test #22: candidate_shell_tui .............. Passed 15.28 sec + Start 23: core +23/49 Test #23: core ............................. Passed 6.08 sec + Start 24: recovery +24/49 Test #24: recovery ......................... Passed 10.64 sec + Start 25: measure +25/49 Test #25: measure .......................... Passed 4.59 sec + Start 26: binary_paths +26/49 Test #26: binary_paths ..................... Passed 0.25 sec + Start 27: conpty_margin_contract +27/49 Test #27: conpty_margin_contract ........... Passed 0.41 sec + Start 28: benchmark_tools +28/49 Test #28: benchmark_tools .................. Passed 5.65 sec + Start 29: differential +29/49 Test #29: differential ..................... Passed 20.29 sec + Start 30: ime_inventory +30/49 Test #30: ime_inventory .................... Passed 57.39 sec + Start 31: text_actions +31/49 Test #31: text_actions ..................... Passed 7.87 sec + Start 32: workflow_fixture +32/49 Test #32: workflow_fixture ................. Passed 23.38 sec + Start 33: workflow_sessions +33/49 Test #33: workflow_sessions ................ Passed 45.73 sec + Start 34: workflow_resources +34/49 Test #34: workflow_resources ............... Passed 0.61 sec + Start 35: workflow_resource_phases +35/49 Test #35: workflow_resource_phases ......... Passed 4.59 sec + Start 36: workflow_system_content +36/49 Test #36: workflow_system_content .......... Passed 0.22 sec + Start 37: workflow_system_runner +37/49 Test #37: workflow_system_runner ........... Passed 0.72 sec + Start 38: copy_error_frame +38/49 Test #38: copy_error_frame ................. Passed 0.17 sec + Start 39: research_guards +39/49 Test #39: research_guards .................. Passed 5.43 sec + Start 40: receive +40/49 Test #40: receive .......................... Passed 0.25 sec + Start 41: conpty +41/49 Test #41: conpty ........................... Passed 17.36 sec + Start 42: conpty_ctrl_c +42/49 Test #42: conpty_ctrl_c .................... Passed 4.46 sec + Start 43: conpty_margin +43/49 Test #43: conpty_margin .................... Passed 0.46 sec + Start 44: presentation +44/49 Test #44: presentation ..................... Passed 4.45 sec + Start 45: shell +45/49 Test #45: shell ............................ Passed 124.99 sec + Start 46: selection_bytes +46/49 Test #46: selection_bytes .................. Passed 4.66 sec + Start 47: selection_context +47/49 Test #47: selection_context ................ Passed 4.58 sec + Start 48: raw_console_receiver +48/49 Test #48: raw_console_receiver ............. Passed 0.40 sec + Start 49: paste_system +49/49 Test #49: paste_system ..................... Passed 15.62 sec + +100% tests passed, 0 tests failed out of 49 + +Total Test time (real) = 398.60 sec diff --git a/experiments/core/results/2026-10-27-resize-snapshot/pending-green.txt b/experiments/core/results/2026-10-27-resize-snapshot/pending-green.txt new file mode 100644 index 00000000..10c2db47 --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/pending-green.txt @@ -0,0 +1 @@ +candidate terminal adapter checks passed diff --git a/experiments/core/results/2026-10-27-resize-snapshot/pending-red.txt b/experiments/core/results/2026-10-27-resize-snapshot/pending-red.txt new file mode 100644 index 00000000..692c06bd --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/pending-red.txt @@ -0,0 +1 @@ +pending replay row width differs diff --git a/experiments/core/results/2026-10-27-resize-snapshot/validation.json b/experiments/core/results/2026-10-27-resize-snapshot/validation.json new file mode 100644 index 00000000..f071d46a --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/validation.json @@ -0,0 +1,246 @@ +{ + "scope": "R2 #29 pending visible snapshot and explicit primary-history clear regressions; not complete resize acceptance", + "source_commit": "3a7510a8f6df95309b62d0531d150cd592aea825", + "reviewed_local_code_commit": "eedb5770ffed441af36e8760f54c0d2a479a7980", + "identical_source_tree": "4d35b8e660de80023b21a1ba9d0506fbca33806b", + "base_commit": "678a7f41df8323e550b2804f664f2a3d7510d0a4", + "source_files": [ + { + "path": "experiments/candidates/alacritty/Cargo.lock", + "git_blob": "7a002fd05e0a4efcdff68284d5901da72c7fd4df", + "worktree_sha256": "f7b8906cc63b6a5d66943f15c50a78c51f773face60229fcddf24b2d02911736" + }, + { + "path": "experiments/candidates/alacritty/PROVENANCE.md", + "git_blob": "0a2ca5d2d63e67b917cba7ee150b315ca62f5699", + "worktree_sha256": "6421adc4ffa583c5c1b962784452e50886471c877d00d88774c7db75e2019581" + }, + { + "path": "experiments/candidates/alacritty/include/termforge_candidate.h", + "git_blob": "21fffc272f0cb871c048cecb07dc3982bfe6997e", + "worktree_sha256": "aeec8b32439f9b774406054c4f2bb963bb75f5b253b8de1b2f1886dcf25b1be9" + }, + { + "path": "experiments/candidates/alacritty/run.py", + "git_blob": "25bf3584c170dbda30799988db81570ee38fed43", + "worktree_sha256": "2d83240506bf03b4da92f7b2dc85adf40446aff25cd0f0dbe08132b611c2627e" + }, + { + "path": "experiments/candidates/alacritty/src/ffi.rs", + "git_blob": "5a70d6e19a2e0e385b92e22585d91db1d03a5288", + "worktree_sha256": "21fce51795e9ba2e2aa59a5b199a7dca53c521997f7b4760208ef497b20a8fbb" + }, + { + "path": "experiments/candidates/alacritty/test_candidate.py", + "git_blob": "8e3f56a92cec7c5b19dcd419c7a40d30905117ad", + "worktree_sha256": "80b19e6fcd9416d4a34c43a5e50525857e6504f1a485827045f209d4dae4f106" + }, + { + "path": "experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml", + "git_blob": "0755589b25d651443e1258c46f0d7c0725725be9", + "worktree_sha256": "2b5fc3bab139b7f141fbe39ae32364617cf526c4d6a5ec2ce12efc97379e42b7" + }, + { + "path": "experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs", + "git_blob": "618fbb2c2572cb287dbac7c7679056d5bf8312df", + "worktree_sha256": "86dc351a502c0b75f8c027da81025a2e81648ede3b25ffb5bb25098d50035750" + }, + { + "path": "experiments/core/candidate_terminal.cpp", + "git_blob": "d90332ea613b21087cd4c36b917001abcdb91a89", + "worktree_sha256": "386158ec9aff3a4f4a9a508eae2a8905ab27690a8815f2c993532a7b11e81c60" + }, + { + "path": "experiments/core/candidate_terminal.hpp", + "git_blob": "3cfe92cdcfe78edbcf9e219ec17a189f7cb21e86", + "worktree_sha256": "0cc344ca834f1532319e3fb2a2ca68e118568a747c1a52da13232f06f959752b" + }, + { + "path": "experiments/core/candidate_terminal_test.cpp", + "git_blob": "787c5ddd02bcb16629af1b54d03de34dee47ede7", + "worktree_sha256": "b8639d4380e571594c81db00296f0d39d124f9c23855e64bf9761abc94a427c1" + } + ], + "configuration": { + "os": "Windows 11 Pro 10.0.26200 x64", + "candidate": "0.26.1-dev+termforge.5", + "adapter": true, + "backend": "system ConPTY", + "build_type": "Release", + "rust": "1.92.0", + "python": "3.12.10", + "msvc": "19.42.34433.0", + "windows_sdk": "10.0.26100.0", + "vim": { + "version": "9.1 patches 1-532", + "sha256": "2e0fda63a89171adfc46c9417d5105cc39ed778cc69a890b83a4eabc34e7646a", + "path_policy": "Existing Git for Windows usr/bin prepended only in the test process environment" + } + }, + "binaries": [ + { + "path": "build/candidate-adapter/bin/Release/candidate_core_test.exe", + "sha256": "db1f04d77bd93d387720c4dc0ff1bf31c2e332d4271e8a8de413e79eb87d6191" + }, + { + "path": "build/candidate-adapter/bin/Release/termforge_shell.exe", + "sha256": "152299f6e8a2085fbf0e44688cc36652f71e609dc4d9d2333f8862f47d60348e" + }, + { + "path": "experiments/candidates/alacritty/target/release/termforge_alacritty_core.lib", + "sha256": "8805dd45b97726329058ee7beedbb8068d4544c9321f9cad65637846529bfcb3" + }, + { + "path": "experiments/candidates/alacritty/target/release/termforge-alacritty-trace.exe", + "sha256": "a60aa224a1005c1d620360a43d490e833c9adebc493381dcb05bfd1fe8f4c035" + } + ], + "negative_controls": [ + { + "name": "Original pending publication restores old rows under new dimensions", + "source_base": "678a7f41df8323e550b2804f664f2a3d7510d0a4", + "test": "pending_replay_keeps_resized_grid", + "exit_code": 1, + "output": "pending-red.txt", + "complete_binary_identity_retained": false + }, + { + "name": "After pending fix, primary history clear during coalesced replay resurrects old rows", + "input": "ESC[?25l ESC[03J ESC[?25h", + "exit_code": 1, + "output": "clear-red.txt", + "complete_binary_identity_retained": false + } + ], + "public_interface_cases": { + "pending_dimensions_rows_columns": [ + [ + 2, + 16 + ], + [ + 2, + 4 + ], + [ + 1, + 1 + ], + [ + 3, + 5 + ] + ], + "clear_combinations": 24, + "clear_sequences": [ + "ED3", + "CSI 03J", + "RIS" + ], + "clear_phases": [ + "before hide", + "pending", + "after show", + "coalesced transaction" + ], + "feed_shapes": [ + "whole", + "byte" + ], + "empty_native_history_cases": 2, + "ED2_controls": 2, + "existing_861B_coordinate_and_bounded_replay_cases_retained": true + }, + "validation": { + "cpp_and_rust_source_commit": "14db86856af5653bf769b9f844878465783d39a4", + "source_note": "Full CTest and Rust workspace ran with the core/test code at 14db868. eedb577 changes only the independent Python report version and its test; that runner was rebuilt/tested separately. All C++/Rust sources are identical between these commits.", + "ctest": { + "command": "ctest --test-dir build/candidate-adapter -C Release --output-on-failure --output-junit local-full.xml", + "exit_code": 0, + "passed": 49, + "failed": 0, + "skipped_ctest": 0, + "junit_raw_local_sha256": "470304aa558daa918cf45d5dc3f19ef76fcdb14b947ceeb27fcca28ff56696c3", + "output": "ctest-full.txt" + }, + "rust_workspace": { + "command": "cargo +1.92.0 test --manifest-path experiments/candidates/alacritty/Cargo.toml --locked --workspace", + "exit_code": 0, + "upstream_unit_passed": 157, + "adapter_and_bin_unit_passed": 6, + "doc_passed": 1, + "failed": 0, + "raw_local_sha256": "46abe49391d56cc743ba133533cb134a0dd91adc333a09b6be140efb1e95f017" + }, + "candidate_report_runner": { + "source_commit": "eedb5770ffed441af36e8760f54c0d2a479a7980", + "command": "python -m unittest experiments.candidates.alacritty.test_candidate -v", + "exit_code": 0, + "passed": 6, + "output": "candidate-runner-tests.txt" + }, + "reviews": { + "standards": "version report mismatch found and fixed in eedb577; re-review 0 findings", + "spec": "0 findings for the bounded code slice; full issue remains open" + } + }, + "logs": [ + { + "public_file": "pending-red.txt", + "raw_local_sha256": "37dd7338924d27a3e3d6fd6edc394a6de162c034a62cddfea7f9686e9f4f2fe7", + "public_lf_sha256": "44e320ba64a1febb60f444fe80707144788e9ed31fdc2aa65f2eaba15ebcf80d" + }, + { + "public_file": "clear-red.txt", + "raw_local_sha256": "dada20af1f28966710730374f3c4448ff6426604871cc8046562a31065035400", + "public_lf_sha256": "ba3ba26417544aa077a44f7c171309cf6bdd48d53a38c60adfa3b39d6343c7f4" + }, + { + "public_file": "pending-green.txt", + "raw_local_sha256": "f249f16080e7539f36f1dc3103a9b03e5366fbdc5ef7166c21bea21f1a334a71", + "public_lf_sha256": "caa3a5ad900c2d7cc0db5bcef2dfc69f45c6b326703df025f09701c4ebb04c05" + }, + { + "public_file": "clear-green.txt", + "raw_local_sha256": "f249f16080e7539f36f1dc3103a9b03e5366fbdc5ef7166c21bea21f1a334a71", + "public_lf_sha256": "caa3a5ad900c2d7cc0db5bcef2dfc69f45c6b326703df025f09701c4ebb04c05" + }, + { + "public_file": "clear-matrix.txt", + "raw_local_sha256": "f249f16080e7539f36f1dc3103a9b03e5366fbdc5ef7166c21bea21f1a334a71", + "public_lf_sha256": "caa3a5ad900c2d7cc0db5bcef2dfc69f45c6b326703df025f09701c4ebb04c05" + }, + { + "public_file": "ctest-full.txt", + "raw_local_sha256": "979cc2f93873dc542c395dbb9357b8f27a753ba0f3513b4e735a6e1d749bf696", + "public_lf_sha256": "13076cf06550edde98feae4ab833c07a0bff34a0c37ac1ca3e05b079421e1472" + }, + { + "public_file": "candidate-runner-tests.txt", + "raw_local_sha256": "0a0b6664859cbba4c83d4fd82adb2e968395cdf54dbed34191d527b1413e6095", + "public_lf_sha256": "f7a72e44bb80ac1985fb5d9d564caf17f4cc7dfb3abe940aaeb110ad751e605d" + } + ], + "setup_failures_not_semantic_red": [ + { + "file": "build-pending-red.txt", + "reason": "CMake configuration had not completed; cache unavailable", + "sha256": "12e30ef2597665c5a3ee0c6218381d618df1c59d9316af83ebcfbd90c63219f0" + }, + { + "file": "build-clear-compile-failed.txt", + "reason": "ClearMode has no PartialEq; corrected to matches! before semantic validation", + "sha256": "31b8bab99eb872668500419a051bcd6cb5c4fd348dc6da5bcd7e48d48d89be32" + } + ], + "windterm_runtime_evidence": "windterm-observations.json", + "limitations": [ + "Not complete history reflow or rectangular retained history", + "Mixed-width cached history and zero-native-history heuristic remain unresolved", + "Alternate-screen cache and full margin/resize cross-product not accepted", + "No new ConPTY child ack, actual presentation, DPI or performance equivalence claim", + "Default and experimental official backend configurations not validated by this candidate/system run", + "WindTerm Telnet text observations do not validate this candidate clear or freeze policy", + "Hosted CI tracked in the PR and issue; local checks do not imply its result" + ] +} diff --git a/experiments/core/results/2026-10-27-resize-snapshot/windterm-input.json b/experiments/core/results/2026-10-27-resize-snapshot/windterm-input.json new file mode 100644 index 00000000..83936221 --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/windterm-input.json @@ -0,0 +1,50 @@ +{ + "columns_at_payload_generation": 80, + "rows_at_payload_generation": 25, + "payload_sha256": "9474531ee34ea08ddd5c3c6b9a19c446852c2aff784dea484c54a91138724b3e", + "logical_input_lines": [ + "R29-H0000|history", + "R29-H0001|history", + "R29-H0002|history", + "R29-H0003|history", + "R29-H0004|history", + "R29-H0005|history", + "R29-H0006|history", + "R29-H0007|history", + "R29-H0008|history", + "R29-H0009|history", + "R29-H0010|history", + "R29-H0011|history", + "R29-H0012|history", + "R29-H0013|history", + "R29-H0014|history", + "R29-H0015|history", + "R29-H0016|history", + "R29-H0017|history", + "R29-H0018|history", + "R29-H0019|history", + "R29-H0020|history", + "R29-H0021|history", + "R29-H0022|history", + "R29-H0023|history", + "R29-H0024|history", + "R29-H0025|history", + "R29-H0026|history", + "R29-H0027|history", + "R29-H0028|history", + "R29-H0029|history", + "R29-H0030|history", + "R29-SOFT|sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss|SOFT-END", + "R29-HARD-A|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|A-END", + "R29-HARD-B|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|B-END", + "R29-U|中文 é 👩‍💻|U-END", + "R29-TAIL00|tail", + "R29-TAIL01|tail", + "R29-TAIL02|tail", + "R29-TAIL03|tail", + "R29-TAIL04|tail", + "R29-TAIL05|tail", + "R29-END" + ], + "note": "Known synthetic logical inputs; lineText and visibleLineCount use different granularities." +} diff --git a/experiments/core/results/2026-10-27-resize-snapshot/windterm-observations.json b/experiments/core/results/2026-10-27-resize-snapshot/windterm-observations.json new file mode 100644 index 00000000..1be8bcc2 --- /dev/null +++ b/experiments/core/results/2026-10-27-resize-snapshot/windterm-observations.json @@ -0,0 +1,376 @@ +{ + "scope": "default synthetic loopback Telnet session, instrumented non-input window station, five fixed geometries", + "observed_version": "WindTerm 2.7.0 Windows x64", + "os": "Windows 11 Pro 10.0.26200 x64", + "planned_runs": 2, + "actual_runs": 2, + "instrumentation_limit_seconds_per_run": 45, + "target_sha256": "7b10531dccc7cd614ae559f36f1ef518e0c96335637431b4646354823b2804d0", + "distribution_manifest_sha256": "e459adf2f12adb6a1b7f55e66a9da1c22794185e66e848057d21e0c60d669203", + "distribution_files": 4047, + "payload_sha256": "9474531ee34ea08ddd5c3c6b9a19c446852c2aff784dea484c54a91138724b3e", + "probe_template_sha256": "838a193715199003ce12298d345c572b0076beeab95917735948141b5797bd6e", + "peer_script_sha256": "6d4411dfaff8990f0248d5b5604aafc1624596a86dd0ef6e99f9ebe4ffc45cf6", + "analysis_script_sha256": "c11b4fe6cce995c5818fc45581f8bd2dfafe126e4d6b85f819c25839b1a584cf", + "runs": [ + { + "run": "run-1", + "gates": { + "rea_ok": true, + "no_observation_errors": true, + "completed_five_phases": true, + "exe_identity": true, + "distribution_identity": true, + "script_identity": true, + "payload_identity": true, + "peer_closed_without_error": true, + "job_drained": true, + "surfaces_closed": true, + "profile_only_synthetic": true + }, + "evidence_id": "ev_5c506d0c81a5fbe5cca264c9f96b96c4196db7f646cb5cfbf5163ad8bdccc777", + "probe_sha256": "6698e9d4f1d1da2405d041b5390495345bd8ef1c1610ff0c1185e472332b1251", + "naws_history": [ + { + "sequence": 0, + "columns": 80, + "rows": 25 + }, + { + "sequence": 1, + "columns": 80, + "rows": 26 + }, + { + "sequence": 2, + "columns": 62, + "rows": 26 + }, + { + "sequence": 3, + "columns": 80, + "rows": 26 + }, + { + "sequence": 4, + "columns": 80, + "rows": 18 + }, + { + "sequence": 5, + "columns": 80, + "rows": 26 + } + ], + "cleanup": { + "active": 0, + "total": 3, + "terminated": 0 + }, + "surface_cleanup": { + "desktop_handle_closed": true, + "station_handle_closed": true, + "original_station_restored": true + }, + "observations": [ + { + "phase": "baseline", + "naws": [ + 80, + 26 + ], + "requested_outer": [ + 1230, + 887 + ], + "scroll_top": 19, + "visibleLineCount_api": 44, + "screenLineCapacity_api": 26, + "input_logical_line_count": 42, + "all_input_texts_exact_in_lineText": true, + "input_line_start_end_equal_baseline": true, + "text_projection_exact_baseline": true, + "history_row_19_start_rect_y": 0, + "topLine_serialized": false + }, + { + "phase": "narrow-width", + "naws": [ + 62, + 26 + ], + "requested_outer": [ + 1010, + 887 + ], + "scroll_top": 20, + "visibleLineCount_api": 46, + "screenLineCapacity_api": 26, + "input_logical_line_count": 42, + "all_input_texts_exact_in_lineText": true, + "input_line_start_end_equal_baseline": true, + "text_projection_exact_baseline": false, + "history_row_19_start_rect_y": -18, + "topLine_serialized": false + }, + { + "phase": "restore-width", + "naws": [ + 80, + 26 + ], + "requested_outer": [ + 1230, + 887 + ], + "scroll_top": 19, + "visibleLineCount_api": 44, + "screenLineCapacity_api": 26, + "input_logical_line_count": 42, + "all_input_texts_exact_in_lineText": true, + "input_line_start_end_equal_baseline": true, + "text_projection_exact_baseline": true, + "history_row_19_start_rect_y": 0, + "topLine_serialized": false + }, + { + "phase": "short-height", + "naws": [ + 80, + 18 + ], + "requested_outer": [ + 1230, + 679 + ], + "scroll_top": 26, + "visibleLineCount_api": 44, + "screenLineCapacity_api": 18, + "input_logical_line_count": 42, + "all_input_texts_exact_in_lineText": true, + "input_line_start_end_equal_baseline": true, + "text_projection_exact_baseline": false, + "history_row_19_start_rect_y": -126, + "topLine_serialized": false + }, + { + "phase": "restore-height", + "naws": [ + 80, + 26 + ], + "requested_outer": [ + 1230, + 887 + ], + "scroll_top": 19, + "visibleLineCount_api": 44, + "screenLineCapacity_api": 26, + "input_logical_line_count": 42, + "all_input_texts_exact_in_lineText": true, + "input_line_start_end_equal_baseline": true, + "text_projection_exact_baseline": true, + "history_row_19_start_rect_y": 0, + "topLine_serialized": false + } + ], + "private_artifact_sha256": { + "evidence.json": "e712ebc98c3b53d64a2ace339d8983083aeea2cf0e4c2370dee6bf08a31e0a1c", + "run.json": "08ce50ea41d4df0e047b610502c30e97a706b1ed73281d7115cbf1bcdc4e8377", + "files.json": "a25c87cba935294d5bfd3bbc76c650d31a8473809564d798ad5a889e4aea00e8", + "payload.bin": "9474531ee34ea08ddd5c3c6b9a19c446852c2aff784dea484c54a91138724b3e", + "payload.json": "0fbbdcc406c283c8e8ded0b04f5022700437269b764992214a5d9178c0e3da2e", + "peer.json": "446c3875b99d4bdc5aee6b4a9c502be4c497f9ed7e3cbb331c8aac4550b53dea", + "probe.js": "6698e9d4f1d1da2405d041b5390495345bd8ef1c1610ff0c1185e472332b1251", + "cleanup.json": "8400f93b6a4a4a50cde35b5d474660590ed215732f55772a9d8afac138da4f1d", + "surface-cleanup.json": "2947878016d582f50a3241f5dfaf0c923671e6ddee62cd57e00a78a988ae7954", + "profile-check.json": "5e522a64e98197722ccd084639b1ce50258b238e03be2f016e81c8af023b5ee1" + } + }, + { + "run": "run-2", + "gates": { + "rea_ok": true, + "no_observation_errors": true, + "completed_five_phases": true, + "exe_identity": true, + "distribution_identity": true, + "script_identity": true, + "payload_identity": true, + "peer_closed_without_error": true, + "job_drained": true, + "surfaces_closed": true, + "profile_only_synthetic": true + }, + "evidence_id": "ev_be2b43633c5b58e8f9b6e2e2b25a47aafd9cbfac5d7fb98e00a8a6d60d0d1588", + "probe_sha256": "b2e9d4361514f97c6f1975dac0457c7f89b946f1fdeac3b60f98bb5b3154f8e9", + "naws_history": [ + { + "sequence": 0, + "columns": 80, + "rows": 25 + }, + { + "sequence": 1, + "columns": 80, + "rows": 26 + }, + { + "sequence": 2, + "columns": 62, + "rows": 26 + }, + { + "sequence": 3, + "columns": 80, + "rows": 26 + }, + { + "sequence": 4, + "columns": 80, + "rows": 18 + }, + { + "sequence": 5, + "columns": 80, + "rows": 26 + } + ], + "cleanup": { + "active": 0, + "total": 3, + "terminated": 0 + }, + "surface_cleanup": { + "desktop_handle_closed": true, + "station_handle_closed": true, + "original_station_restored": true + }, + "observations": [ + { + "phase": "baseline", + "naws": [ + 80, + 26 + ], + "requested_outer": [ + 1230, + 887 + ], + "scroll_top": 19, + "visibleLineCount_api": 44, + "screenLineCapacity_api": 26, + "input_logical_line_count": 42, + "all_input_texts_exact_in_lineText": true, + "input_line_start_end_equal_baseline": true, + "text_projection_exact_baseline": true, + "history_row_19_start_rect_y": 0, + "topLine_serialized": false + }, + { + "phase": "narrow-width", + "naws": [ + 62, + 26 + ], + "requested_outer": [ + 1010, + 887 + ], + "scroll_top": 20, + "visibleLineCount_api": 46, + "screenLineCapacity_api": 26, + "input_logical_line_count": 42, + "all_input_texts_exact_in_lineText": true, + "input_line_start_end_equal_baseline": true, + "text_projection_exact_baseline": false, + "history_row_19_start_rect_y": -18, + "topLine_serialized": false + }, + { + "phase": "restore-width", + "naws": [ + 80, + 26 + ], + "requested_outer": [ + 1230, + 887 + ], + "scroll_top": 19, + "visibleLineCount_api": 44, + "screenLineCapacity_api": 26, + "input_logical_line_count": 42, + "all_input_texts_exact_in_lineText": true, + "input_line_start_end_equal_baseline": true, + "text_projection_exact_baseline": true, + "history_row_19_start_rect_y": 0, + "topLine_serialized": false + }, + { + "phase": "short-height", + "naws": [ + 80, + 18 + ], + "requested_outer": [ + 1230, + 679 + ], + "scroll_top": 26, + "visibleLineCount_api": 44, + "screenLineCapacity_api": 18, + "input_logical_line_count": 42, + "all_input_texts_exact_in_lineText": true, + "input_line_start_end_equal_baseline": true, + "text_projection_exact_baseline": false, + "history_row_19_start_rect_y": -126, + "topLine_serialized": false + }, + { + "phase": "restore-height", + "naws": [ + 80, + 26 + ], + "requested_outer": [ + 1230, + 887 + ], + "scroll_top": 19, + "visibleLineCount_api": 44, + "screenLineCapacity_api": 26, + "input_logical_line_count": 42, + "all_input_texts_exact_in_lineText": true, + "input_line_start_end_equal_baseline": true, + "text_projection_exact_baseline": true, + "history_row_19_start_rect_y": 0, + "topLine_serialized": false + } + ], + "private_artifact_sha256": { + "evidence.json": "f1472f336e93d2221dc0b00645cab63becdc158e2c9ceac99562da5658c5b37c", + "run.json": "6569a9f8bc4bc9eda1f5b2e54ce012967832c78c85127699be2272b1412c46e5", + "files.json": "a25c87cba935294d5bfd3bbc76c650d31a8473809564d798ad5a889e4aea00e8", + "payload.bin": "9474531ee34ea08ddd5c3c6b9a19c446852c2aff784dea484c54a91138724b3e", + "payload.json": "0fbbdcc406c283c8e8ded0b04f5022700437269b764992214a5d9178c0e3da2e", + "peer.json": "446c3875b99d4bdc5aee6b4a9c502be4c497f9ed7e3cbb331c8aac4550b53dea", + "probe.js": "b2e9d4361514f97c6f1975dac0457c7f89b946f1fdeac3b60f98bb5b3154f8e9", + "cleanup.json": "8400f93b6a4a4a50cde35b5d474660590ed215732f55772a9d8afac138da4f1d", + "surface-cleanup.json": "2947878016d582f50a3241f5dfaf0c923671e6ddee62cd57e00a78a988ae7954", + "profile-check.json": "5e522a64e98197722ccd084639b1ce50258b238e03be2f016e81c8af023b5ee1" + } + } + ], + "unknown": [ + "topLine was undefined and omitted by JSON serialization", + "lineText index and visibleLineCount have different granularity; extra indices alias end position", + "physical-cell and cursor-coordinate equivalence", + "complete reflow and Unicode cell-width behavior", + "actual presented frames", + "local-shell ConPTY child acknowledgement", + "all configuration and sequence behavior", + "performance and product acceptance" + ], + "publication": "Reviewed synthetic facts only; app, profiles, complete raw evidence and scripts remain local." +}