Present only what changed: a frame identical to the screen reaches no device
What
An unchanged frame now produces zero device writes. FrameTerminal suppresses cursor commands and the empty-diff attribute reset only while its tracked cursor still describes the device.
The restoration journal counts outside output operations. Direct writes, mode setup, suspend/resume, restoration, resize and failed frames invalidate tracking. Input has a narrow read-only path that cannot expose the writer or invalidate output. Windows reads the count under the existing owner lock, without submitting driver work or starting a worker; an unavailable guard returns unknown.
Structural class: redundant redraws and cursor suppression against stale device state. Observation validation and presentation share one lifecycle boundary, including empty frames. If rendering races with resume, the unsubmitted diff is retained and the cursor is restated before committing. Accepted counter values are returned from that boundary; no post-presentation observation can hide an intervening outside write.
Proof
- A shrinking property draws random scenes twice and compares the emulated screen/cursor with a separately initialized per-command baseline. Repeats send zero bytes; synchronized frames have exact brackets. At N=8/16, unchanged draws have zero writes/bytes. Changed-frame allocations equal the old path; repeats remove exactly one allocation each.
- GraphCoder's real renderer verifies screen equality, exact changed-frame writes and allocations at 150/300 transcript lines. Three/six page keys plus their observed layout changes cost six/twelve frames and exactly three/six writes.
- Failed cursor-only changes retry the required position/visibility commands; the next successful repeat writes nothing. Dropping recovery preserves the wrong cursor as a negative control.
- Suspend/resume, resize and outside cursor moves have emulator regressions and negative controls.
- Production-lock Loom models cover presentation versus suspend/resume, forced resume after the render snapshot, and the Windows owner guard handoff. Negative controls detect empty-frame acknowledgement without the lifecycle lock and trusting a borrowed guard. Emulator regressions cover both visible/hidden cursors and empty/changed frames.
- Real Unix PTY reads at N=8/16, each followed by an identical redraw, send exactly zero bytes. The former generic-writer read path is the negative control and sends redundant output. These cases run within the existing process-owned lifecycle fixture.
Per OS
Current head 9021eefd1: Linux terminal library 14 passed (0.23 s), frames 11 passed / 1 ignored (0.04 s), faults 3 passed (0.01 s); build 6.26 s. Three lifecycle presentation Loom models pass (<0.01 s), plus the owner handoff model and negative control (0.01 s); model build 7.97 s. Windows GNU harness/GraphCoder/TUI library/test proof passes in 2m48s.
Model command: RUSTFLAGS="--cfg acyclic_harness_tui_loom" cargo test --locked -p acyclic-harness-tui --features tui --lib term::frame::frame_model.
Earlier integrated head 734f166d7: Linux terminal library 14 passed (0.12 s), frames 9 passed / 1 ignored (0.04 s), faults 3 passed (0.01 s); build 9.30 s. Counter loom model and negative control: 2 passed (0.01 s; build 10.98 s). Real-PTY lifecycle/read regression: passed (0.01 s; build/test 7.34 s). Current Windows GNU harness/GraphCoder/TUI lib/tests proof: passed in 5m09s.
Earlier real Windows frame tests passed. ConPTY PageUp x3 measured 2 writes / 106–118 ms versus earlier main's 6 writes / 552 ms; a typed key cost one write. The test-only alacritty_terminal dependency was already in the lockfile.
Current attribute-recovery head 9bcd88d8a: default SGR validity now shares the cursor invalidation boundary. Unknown attributes are reset before cells/clears, including empty diffs; a rejected checked presentation prepends the reset before the retained diff. Emulator coverage compares all cell attributes against an independently initialized per-command terminal for outside output before/during rendering and empty/changed frames; removing only the recovery reset fails the same assertion. Real-lock Loom models cover attribute recovery and its negative control. Linux library 14 / 0.10 s, frames 13 / 0.05 s, faults 3 / 0.01 s; build 3.92 s. Five lifecycle models / 0.006 s and two owner models / 0.02 s, model build 3.86 s. Windows GNU Harness/GraphCoder/TUI lib/tests proof 40.80 s. macOS at 9bcd88d8a: library 14 / 0.30 s, frames 13 / 0.02 s, faults 3 / 0.01 s; build 7.30 s in the same single lane target.
Decisions
Preserve pushed history by merging main, through e4e6cbbe4. The test conflict retains both main's fallible helpers/checked cursor conversion and the cursor-visibility/repeated-frame oracle. Counter observation uses the existing lock and returns unknown during ownership handoff. Input gets an operation-specific read method, rather than an untracked mutable writer.
Deletion count against the merge base: 57 lines (974 added across 12 files).
Live steps
macOS at 4547739c3: terminal library 14 / GraphCoder 48 / frames 9 / faults 3 pass (0.34 / 0.17 / 0.02 / 0.01 s; build 2m50s). Paired debug PTY frames at 120×40: 5200.6 → 653.4 µs/frame; 240×70: 9604.2 → 1290.9 µs/frame. One submission/frame, 12.7/19.8 physical writes due to the PTY queue. Paired GraphCoder baseline passes (6.06 s); unchanged frames write zero bytes. Detailed numbers and exact binary rerun command are in the macOS follow-up comment.
Current macOS head 734f166d7: terminal library 14 / frames 9 / faults 3 pass (0.14 / 0.03 / 0.01 s; build 1.54 s). Paired debug PTY: 3001.8 → 258.8 µs at 120×40 and 5236.0 → 533.7 µs at 240×70, one frame submission. Earlier full GraphCoder and paired measurements above remain labelled with their head.
macOS at current 9021eefd1: terminal library 14 / frames 11 / faults 3 pass (0.24 / 0.03 / 0.01 s), focused build 7.80 s. Reused ~/mac-target/tui-perf and the stable source directory; removed only this lane’s old incremental cache after disk exhaustion. Earlier paired performance numbers remain labelled with their measured heads.