Latency: predictive local echo in the terminal (D128) #50

Open
opened 2026-10-07 06:36:14 +00:00 by kayg · 41 comments
Owner

Predictive local echo (D128, N6/N9): show typed characters immediately in a subtle style and confirm them when the host echoes back, like mosh. Client-only. It turns off automatically for full-screen programs and when echo is off (password prompts). Measure the felt latency on the India↔Germany path.

Predictive local echo (D128, N6/N9): show typed characters immediately in a subtle style and confirm them when the host echoes back, like mosh. Client-only. It turns off automatically for full-screen programs and when echo is off (password prompts). Measure the felt latency on the India↔Germany path.
Author
Owner

Starting predictive echo on branch predictive-echo at base SHA ac03b1913b. Issue #50 has no prior comments; parent #9 and high-latency gate issue #55 comments are read. The checked-in DECISIONS.md ends at 112; the task and issue text supply D128, D130, D162, D167 and N6/N9 requirements.

Starting predictive echo on branch predictive-echo at base SHA ac03b1913bfb6dd9b978477fc23ee5c88e276f67. Issue #50 has no prior comments; parent #9 and high-latency gate issue #55 comments are read. The checked-in DECISIONS.md ends at 112; the task and issue text supply D128, D130, D162, D167 and N6/N9 requirements.
Author
Owner

Finding: Chromium with Ghostty at 300 ms RTT showed a prompt cursor at x=2,y=0, visible=1, but typing a left prediction empty with reason=awaiting-output. Reproduced in apps/tracer/test/predictive-echo.spec.ts; initial resize left the safety wait active without later wire output. I’m correcting layout-change handling to clear stale overlays and re-check the current cursor.

Finding: Chromium with Ghostty at 300 ms RTT showed a prompt cursor at x=2,y=0, visible=1, but typing a left prediction empty with reason=awaiting-output. Reproduced in apps/tracer/test/predictive-echo.spec.ts; initial resize left the safety wait active without later wire output. I’m correcting layout-change handling to clear stale overlays and re-check the current cursor.
Author
Owner

Resolution: terminal resize had left the output-wait flag active after the initial viewport fit. Layout changes now clear stale geometry while preserving waits caused by unconfirmed input; prediction safety also checks the viewport. passes against Ghostty at measured RTT 358.4 ms, cursor x=2/y=0 visible=true, with after the first key.

Resolution: terminal resize had left the output-wait flag active after the initial viewport fit. Layout changes now clear stale geometry while preserving waits caused by unconfirmed input; prediction safety also checks the viewport. passes against Ghostty at measured RTT 358.4 ms, cursor x=2/y=0 visible=true, with after the first key.
Author
Owner

Resolution: terminal resize had left the output-wait flag active after the initial viewport fit. Layout changes now clear stale geometry while preserving waits caused by unconfirmed input; prediction safety also checks the viewport. apps/tracer/test/predictive-echo.spec.ts passes against Ghostty at measured RTT 358.4 ms, cursor x=2/y=0 visible=true, with pendingText=a after the first key.

Resolution: terminal resize had left the output-wait flag active after the initial viewport fit. Layout changes now clear stale geometry while preserving waits caused by unconfirmed input; prediction safety also checks the viewport. apps/tracer/test/predictive-echo.spec.ts passes against Ghostty at measured RTT 358.4 ms, cursor x=2/y=0 visible=true, with pendingText=a after the first key.
Author
Owner

Finding: a terminal Output can be submitted to Ghostty while attached, then its render callback can run after a WebSocket disconnect. The callback previously treated that as an unsafe screen and kept prediction waiting forever; the resumed no-echo test exposed it. Painting now validates only alternate-screen and scrollback state, while new input still requires an attached transport. The real-renderer resume regression passes: dropped Ack, deduplicated b, then pending p at 322.8 ms RTT.

Finding: a terminal Output can be submitted to Ghostty while attached, then its render callback can run after a WebSocket disconnect. The callback previously treated that as an unsafe screen and kept prediction waiting forever; the resumed no-echo test exposed it. Painting now validates only alternate-screen and scrollback state, while new input still requires an attached transport. The real-renderer resume regression passes: dropped Ack, deduplicated b, then pending p at 322.8 ms RTT.
Author
Owner

Resolution: the output renderer callback no longer treats a later socket disconnect as a screen change. It reconciles already-painted output against the pending queue; new keystrokes still require an attached connection. Focused Ghostty test passes the dropped-Ack resume, one-key no-echo mute, proof-based resume, and bounded 2-byte paste cases at 297–323 ms RTT.

Resolution: the output renderer callback no longer treats a later socket disconnect as a screen change. It reconciles already-painted output against the pending queue; new keystrokes still require an attached connection. Focused Ghostty test passes the dropped-Ack resume, one-key no-echo mute, proof-based resume, and bounded 2-byte paste cases at 297–323 ms RTT.
Author
Owner

Finding: with 300 ms configured RTT and ±30 ms burst jitter, a wrong-echo case measured RTT=279.3 ms but its pending glyph timed out before the host Output paint callback; renderer sequence advanced to 23 while misprediction count stayed 0. The no-echo deadline needs to include the profile jitter allowance as well as the renderer frame budget.

Finding: with 300 ms configured RTT and ±30 ms burst jitter, a wrong-echo case measured RTT=279.3 ms but its pending glyph timed out before the host Output paint callback; renderer sequence advanced to 23 while misprediction count stayed 0. The no-echo deadline needs to include the profile jitter allowance as well as the renderer frame budget.
Author
Owner

Finding: The cursor-position test deliberately delayed echo 500 ms while RTT was ~306 ms; the expected deadline is RTT + 64 ms, so the state machine correctly muted after timeout. The broader test then expected prediction without host proof. Added a normal echo proof before continuing.

Finding: The cursor-position test deliberately delayed echo 500 ms while RTT was ~306 ms; the expected deadline is RTT + 64 ms, so the state machine correctly muted after timeout. The broader test then expected prediction without host proof. Added a normal echo proof before continuing.
Author
Owner

Performance finding (300 ms RTT ±30, CPUWeight=10000): measured RTT 336.30 ms; 20 keys produced 18 overlay-visible predictions (p50 13.20 ms), so glyph-visible p99 including host-paint fallbacks was 381.90 ms. Echo confirmation p99 was 381.90 ms vs 370.30 ms RTT+34 budget; reconnect p99 1044.20 ms vs 900 ms. These are misses to address or report, not passes.

Performance finding (300 ms RTT ±30, CPUWeight=10000): measured RTT 336.30 ms; 20 keys produced 18 overlay-visible predictions (p50 13.20 ms), so glyph-visible p99 including host-paint fallbacks was 381.90 ms. Echo confirmation p99 was 381.90 ms vs 370.30 ms RTT+34 budget; reconnect p99 1044.20 ms vs 900 ms. These are misses to address or report, not passes.
Author
Owner

Finding: After the forced dropped-Ack reconnect, the test typed the no-echo probe before the resumed session had a new RTT sample; prediction was correctly off as rtt-unmeasured. Added a post-reconnect RTT readiness wait before testing no-echo muting.

Finding: After the forced dropped-Ack reconnect, the test typed the no-echo probe before the resumed session had a new RTT sample; prediction was correctly off as `rtt-unmeasured`. Added a post-reconnect RTT readiness wait before testing no-echo muting.
Author
Owner

Full verify latency finding: host infraternal-dev, x86_64, 8 cores; tracer-browser load 27.49/26.35/20.75. Network self-tests and auth browser passed; Playwright finished 33/37. The 50 MB check timed out at 5 s after painting 5,242,880 bytes, so its poll now allows 5 min. Native build failed because .cargo/musl-cc.sh hardcodes worktree/target/auth-tools/zig/zig despite CARGO_TARGET_DIR; I will rerun with a temporary symlink to the scratch target. Terminal on-mode predicted-only p99 was 13.60 ms (18/20 overlay coverage); two frames fell back at p99 358.60 ms. Sign-in p99 5019.20/5000 ms and reconnect 988.40/900 ms exceeded existing budgets.

Full `verify latency` finding: host infraternal-dev, x86_64, 8 cores; tracer-browser load 27.49/26.35/20.75. Network self-tests and auth browser passed; Playwright finished 33/37. The 50 MB check timed out at 5 s after painting 5,242,880 bytes, so its poll now allows 5 min. Native build failed because `.cargo/musl-cc.sh` hardcodes `worktree/target/auth-tools/zig/zig` despite CARGO_TARGET_DIR; I will rerun with a temporary symlink to the scratch target. Terminal on-mode predicted-only p99 was 13.60 ms (18/20 overlay coverage); two frames fell back at p99 358.60 ms. Sign-in p99 5019.20/5000 ms and reconnect 988.40/900 ms exceeded existing budgets.
Author
Owner

Bulk-throughput finding: on infraternal-dev (x86_64, 8 cores), load 23.97/26.72/25.82, CPUWeight=10000, alternating paired 50 MB samples measured off 1.72/1.73/1.96 MB/s and on 1.90/1.97/1.97 MB/s. Medians 1.73 vs 1.97 MB/s (+13.93%); no regression against the −5% budget. The isolated Playwright throughput test passed (3.4 min).

Bulk-throughput finding: on infraternal-dev (x86_64, 8 cores), load 23.97/26.72/25.82, CPUWeight=10000, alternating paired 50 MB samples measured off 1.72/1.73/1.96 MB/s and on 1.90/1.97/1.97 MB/s. Medians 1.73 vs 1.97 MB/s (+13.93%); no regression against the −5% budget. The isolated Playwright throughput test passed (3.4 min).
Author
Owner

Full latency rerun completed (infraternal-dev, x86_64, 8 cores; tracer-browser load 21.34/22.68/24.72; finish 16.80/24.45/26.17). Native release, network self-tests, auth-browser, and TypeScript passed; browser suite 33/37. Throughput median 1.38 MB/s off vs 1.35 on (−2.08%, within −5%). Terminal probe: RTT sample 362.90 ms; echo p50/p99 on 346.00/388.90 ms, off 345.40/385.20 ms; overlay coverage 19/20, DOM-marked p99 8.40 ms for the 19 shown, browser CPU on 0.50/7.80 ms/key and off 0.20/4.20. Existing sign-in p99 5034.40/5000 and reconnect 1012.10/900 failed. Three additional browser failures were motion audit, onboarding reconnect, and access-screen budget. Real-agent baseline p99 passed at 324.40 ms, but flood same-session p99 1566.60 ms and isolation growth 1242.18/16.67 failed under load. I am correcting the prediction visibility mark to use a frame boundary before final probe results.

Full latency rerun completed (infraternal-dev, x86_64, 8 cores; tracer-browser load 21.34/22.68/24.72; finish 16.80/24.45/26.17). Native release, network self-tests, auth-browser, and TypeScript passed; browser suite 33/37. Throughput median 1.38 MB/s off vs 1.35 on (−2.08%, within −5%). Terminal probe: RTT sample 362.90 ms; echo p50/p99 on 346.00/388.90 ms, off 345.40/385.20 ms; overlay coverage 19/20, DOM-marked p99 8.40 ms for the 19 shown, browser CPU on 0.50/7.80 ms/key and off 0.20/4.20. Existing sign-in p99 5034.40/5000 and reconnect 1012.10/900 failed. Three additional browser failures were motion audit, onboarding reconnect, and access-screen budget. Real-agent baseline p99 passed at 324.40 ms, but flood same-session p99 1566.60 ms and isolation growth 1242.18/16.67 failed under load. I am correcting the prediction visibility mark to use a frame boundary before final probe results.
Author
Owner

Corrected frame-boundary probe (systemd CPUWeight=10000; host load at launch 13.71/21.71/25.06, 8 cores): 20/20 overlay frames, prediction-on glyph-visible p50/p99 6.40/12.20 ms (17 ms target); off p50/p99 354.20/381.20 ms. Browser CPU per key on 0.50/6.70 ms vs off 0.20/5.00 ms. Echo confirmation p99 on 379.30 ms/off 381.20 ms; measured RTT 347.70 ms. Sign-in p99 passed at 3944.60 ms; reconnect p99 1041.60/900 ms failed. The focused Playwright case reached all metrics and failed only its existing reconnect budget.

Corrected frame-boundary probe (systemd CPUWeight=10000; host load at launch 13.71/21.71/25.06, 8 cores): 20/20 overlay frames, prediction-on glyph-visible p50/p99 6.40/12.20 ms (17 ms target); off p50/p99 354.20/381.20 ms. Browser CPU per key on 0.50/6.70 ms vs off 0.20/5.00 ms. Echo confirmation p99 on 379.30 ms/off 381.20 ms; measured RTT 347.70 ms. Sign-in p99 passed at 3944.60 ms; reconnect p99 1041.60/900 ms failed. The focused Playwright case reached all metrics and failed only its existing reconnect budget.
Author
Owner

Step complete: bounded prediction state machine committed as 291c7ee. Tracer TypeScript check passed; production Ghostty Playwright case passed 1/1. Fast gate passed on the full current worktree (including pending integration and test edits): network RTT p50/p99 300.16/308.85 ms; Rust gates and both static targets passed; TypeScript stream differential covered 134 streams and 11,439 schedules with zero divergences; JavaScript transfer measured 246,683 B gzip and 202,745 B brotli.

Step complete: bounded prediction state machine committed as 291c7ee. Tracer TypeScript check passed; production Ghostty Playwright case passed 1/1. Fast gate passed on the full current worktree (including pending integration and test edits): network RTT p50/p99 300.16/308.85 ms; Rust gates and both static targets passed; TypeScript stream differential covered 134 streams and 11,439 schedules with zero divergences; JavaScript transfer measured 246,683 B gzip and 202,745 B brotli.
Author
Owner

Step complete: tracer wiring committed as fd45064. Production stays off unless ?predict=1; RTT, Ghostty screen safety, output paint, and teardown now control the shared overlay. Fast gate passed on the current worktree with Rust tests, Clippy, cargo-deny, checksums and both static targets passing; RTT self-test p50/p99 was 299.46/316.48 ms. Tracer transfer: 812,735 B identity, 246,683 B gzip, 202,745 B brotli. Protocol differential: 134 streams, 11,439 schedules, zero divergences.

Step complete: tracer wiring committed as fd45064. Production stays off unless ?predict=1; RTT, Ghostty screen safety, output paint, and teardown now control the shared overlay. Fast gate passed on the current worktree with Rust tests, Clippy, cargo-deny, checksums and both static targets passing; RTT self-test p50/p99 was 299.46/316.48 ms. Tracer transfer: 812,735 B identity, 246,683 B gzip, 202,745 B brotli. Protocol differential: 134 streams, 11,439 schedules, zero divergences.
Author
Owner

Step complete: production Ghostty prediction tests committed as 25eda77. Focused Playwright case passed 1/1 and emitted all eight pending/confirmed capture files at 393×852 and 1440×900 in ink and paper themes. Fast gate passed (RTT self-test p50/p99 294.80/317.17 ms; Rust suites and both static targets passed; tracer transfer 246,683 B gzip/202,745 B brotli; 134 streams and 11,439 schedules with zero protocol divergences).

Step complete: production Ghostty prediction tests committed as 25eda77. Focused Playwright case passed 1/1 and emitted all eight pending/confirmed capture files at 393×852 and 1440×900 in ink and paper themes. Fast gate passed (RTT self-test p50/p99 294.80/317.17 ms; Rust suites and both static targets passed; tracer transfer 246,683 B gzip/202,745 B brotli; 134 streams and 11,439 schedules with zero protocol divergences).
Author
Owner

Finding: the final capture scenario failed before reaching the prediction PNGs because passkey login did not connect within 5 s. Under host load, the separate access-screen gate also measured setup/sign-in render p99 1842.30/2011.90 ms against 1000 ms; first-contentful-paint p99 was 1637.60/1934.00 ms. I’m moving the eight captures into a standalone mock-terminal test so unrelated access-screen performance cannot prevent review artifacts.

Finding: the final capture scenario failed before reaching the prediction PNGs because passkey login did not connect within 5 s. Under host load, the separate access-screen gate also measured setup/sign-in render p99 1842.30/2011.90 ms against 1000 ms; first-contentful-paint p99 was 1637.60/1934.00 ms. I’m moving the eight captures into a standalone mock-terminal test so unrelated access-screen performance cannot prevent review artifacts.
Author
Owner

Finished: HEAD 81e25883ca. The independent production capture test passed 1/1 and produced all eight pending/confirmed PNGs at 393×852 and 1440×900 for ink and paper themes. D167 probe: RTT 347.70 ms; predicted glyph visibility p50/p99 6.40/12.20 ms (20/20), versus 354.20/381.20 ms off; echo confirmation p99 379.30 ms on and 381.20 ms off; browser CPU/key 0.50/6.70 ms on and 0.20/5.00 ms off. Paired 50 MB throughput median was 1.38 MB/s off and 1.35 MB/s on (−2.08%, within the −5% gate). The reconnect p99 1041.60/900 ms and broader concurrent-load latency failures remain recorded above.

scripts/verify fast exited 0. Verbatim final summary output:

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| clippy                    | PASS         |
| test                      | PASS         |
| cargo-deny                | PASS         |
| static builds             | PASS         |
+---------------------------+--------------+

==> Vendored Ghostty artifact checksums
dist/ghostty-web.js: OK
dist/ghostty-vt.wasm: OK
dist/index.d.ts: OK

==> TypeScript protocol compiler

==> Tracer compiler, precompression and bundle transfer budget
bun install v1.4.2 (744846f84)

Checked 38 installs across 69 packages (no changes) [3.00ms]
$ bun x tsc --project tsconfig.json
bun test v1.4.2 (744846f84)

browser-build.test.ts:
(pass) the browser renderer has no unresolved Vite Node-file shim request [16.22ms]

precompress.test.ts:
(pass) text sidecars round-trip deterministically; compressed formats remain identity [18.41ms]

 2 pass
 0 fail
 15 expect() calls
Ran 2 tests across 2 files. [50.00ms]
$ bun run build.ts
built main.js, chunk-pxh9419f.js, chunk-exmwxgk3.js, backdrop.worker.js, faulty-terminal.worker.js, chunk-8gaywydk.js, chunk-bm38atwe.js, chunk-axbkkqp1.js, chunk-3ht9z9kw.js
chunk-bm38atwe.js: identity=24954 gzip=8997 brotli=7912 bytes
faulty-terminal.worker.js: identity=1021 gzip=535 brotli=460 bytes
backdrop.worker.js: identity=241 gzip=211 brotli=160 bytes
chunk-3ht9z9kw.js: identity=695768 gzip=201656 brotli=163322 bytes
chunk-axbkkqp1.js: identity=90 gzip=90 brotli=78 bytes
chunk-8gaywydk.js: identity=185 gzip=149 brotli=130 bytes
chunk-exmwxgk3.js: identity=6464 gzip=3112 brotli=2823 bytes
main.js: identity=76213 gzip=28604 brotli=24892 bytes
chunk-pxh9419f.js: identity=7799 gzip=3329 brotli=2968 bytes
JavaScript transfer: identity=812735 gzip=246683/307200 brotli=202745/256000 bytes

==> Rust stream oracle (for the differential test)

==> TypeScript protocol tests
bun test v1.4.2 (744846f84)

src/codec.test.ts:
(pass) wire protocol golden vectors > encodes and decodes each frame exactly as the shared manifest describes [5.53ms]
(pass) wire protocol golden vectors > returns borrowed payload views and writes into reusable buffers [0.16ms]
(pass) wire protocol golden vectors > decodes muxed messages split across WebSocket messages [0.75ms]
(pass) wire protocol golden vectors > rejects oversized, non-canonical, unknown, and malformed frame bytes [0.15ms]
(pass) split mux header preserves trailing Close [0.16ms]

src/stream.test.ts:
stream differential: 134 streams, 11439 schedules, zero divergences
(pass) Rust and TypeScript preserve every frame at all boundaries and random multi-splits [2066.95ms]
(pass) maximum frame size and fragmented oversized prefix obey the allocation boundary [6.55ms]

 7 pass
 0 fail
 34390 expect() calls
Ran 7 tests across 2 files. [2.08s]
Finished: HEAD 81e25883ca66c72e10bca9530f963044f6dd0c2a. The independent production capture test passed 1/1 and produced all eight pending/confirmed PNGs at 393×852 and 1440×900 for ink and paper themes. D167 probe: RTT 347.70 ms; predicted glyph visibility p50/p99 6.40/12.20 ms (20/20), versus 354.20/381.20 ms off; echo confirmation p99 379.30 ms on and 381.20 ms off; browser CPU/key 0.50/6.70 ms on and 0.20/5.00 ms off. Paired 50 MB throughput median was 1.38 MB/s off and 1.35 MB/s on (−2.08%, within the −5% gate). The reconnect p99 1041.60/900 ms and broader concurrent-load latency failures remain recorded above. `scripts/verify fast` exited 0. Verbatim final summary output: ```text +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | clippy | PASS | | test | PASS | | cargo-deny | PASS | | static builds | PASS | +---------------------------+--------------+ ==> Vendored Ghostty artifact checksums dist/ghostty-web.js: OK dist/ghostty-vt.wasm: OK dist/index.d.ts: OK ==> TypeScript protocol compiler ==> Tracer compiler, precompression and bundle transfer budget bun install v1.4.2 (744846f84) Checked 38 installs across 69 packages (no changes) [3.00ms] $ bun x tsc --project tsconfig.json bun test v1.4.2 (744846f84) browser-build.test.ts: (pass) the browser renderer has no unresolved Vite Node-file shim request [16.22ms] precompress.test.ts: (pass) text sidecars round-trip deterministically; compressed formats remain identity [18.41ms] 2 pass 0 fail 15 expect() calls Ran 2 tests across 2 files. [50.00ms] $ bun run build.ts built main.js, chunk-pxh9419f.js, chunk-exmwxgk3.js, backdrop.worker.js, faulty-terminal.worker.js, chunk-8gaywydk.js, chunk-bm38atwe.js, chunk-axbkkqp1.js, chunk-3ht9z9kw.js chunk-bm38atwe.js: identity=24954 gzip=8997 brotli=7912 bytes faulty-terminal.worker.js: identity=1021 gzip=535 brotli=460 bytes backdrop.worker.js: identity=241 gzip=211 brotli=160 bytes chunk-3ht9z9kw.js: identity=695768 gzip=201656 brotli=163322 bytes chunk-axbkkqp1.js: identity=90 gzip=90 brotli=78 bytes chunk-8gaywydk.js: identity=185 gzip=149 brotli=130 bytes chunk-exmwxgk3.js: identity=6464 gzip=3112 brotli=2823 bytes main.js: identity=76213 gzip=28604 brotli=24892 bytes chunk-pxh9419f.js: identity=7799 gzip=3329 brotli=2968 bytes JavaScript transfer: identity=812735 gzip=246683/307200 brotli=202745/256000 bytes ==> Rust stream oracle (for the differential test) ==> TypeScript protocol tests bun test v1.4.2 (744846f84) src/codec.test.ts: (pass) wire protocol golden vectors > encodes and decodes each frame exactly as the shared manifest describes [5.53ms] (pass) wire protocol golden vectors > returns borrowed payload views and writes into reusable buffers [0.16ms] (pass) wire protocol golden vectors > decodes muxed messages split across WebSocket messages [0.75ms] (pass) wire protocol golden vectors > rejects oversized, non-canonical, unknown, and malformed frame bytes [0.15ms] (pass) split mux header preserves trailing Close [0.16ms] src/stream.test.ts: stream differential: 134 streams, 11439 schedules, zero divergences (pass) Rust and TypeScript preserve every frame at all boundaries and random multi-splits [2066.95ms] (pass) maximum frame size and fragmented oversized prefix obey the allocation boundary [6.55ms] 7 pass 0 fail 34390 expect() calls Ran 7 tests across 2 files. [2.08s] ```
Author
Owner

Orchestrator review of 81e2588: the latency win is confirmed by the numbers (predicted glyph 6.4/12.2 ms p50/p99 vs 354/381 ms off). Visual finding: in the pending state the cursor stays on the predicted glyph (cursor block over the x) instead of advancing one cell; the cursor must be predicted too. The overlay positions glyphs from cell metrics, which #84 is reworking (a production drift regression from run-based drawing). Run 2 waits for #84: rebase onto it, predict the cursor advance, add pixel tests that predicted glyphs and the cursor land on exactly the cells the confirmed echo later occupies (multi-character, long line, dpr 1/2/3), and add multi-character captures.

Orchestrator review of 81e2588: the latency win is confirmed by the numbers (predicted glyph 6.4/12.2 ms p50/p99 vs 354/381 ms off). Visual finding: in the pending state the cursor stays on the predicted glyph (cursor block over the x) instead of advancing one cell; the cursor must be predicted too. The overlay positions glyphs from cell metrics, which #84 is reworking (a production drift regression from run-based drawing). Run 2 waits for #84: rebase onto it, predict the cursor advance, add pixel tests that predicted glyphs and the cursor land on exactly the cells the confirmed echo later occupies (multi-character, long line, dpr 1/2/3), and add multi-character captures.
Author
Owner

Run 2 starting on branch predictive-echo, based on dev at ac03b1913b (current HEAD 81e25883ca). I read all #50, parent #9, and latency-gate #55 comments. Next: merge dev's #75/#84 renderer changes, align prediction and cursor to shared renderer metrics, then add DPR/capture evidence and rerun latency plus fast gates.

Run 2 starting on branch predictive-echo, based on dev at ac03b1913bfb6dd9b978477fc23ee5c88e276f67 (current HEAD 81e25883ca66c72e10bca9530f963044f6dd0c2a). I read all #50, parent #9, and latency-gate #55 comments. Next: merge dev's #75/#84 renderer changes, align prediction and cursor to shared renderer metrics, then add DPR/capture evidence and rerun latency plus fast gates.
Author
Owner

Finding: after merging #84 cell metrics, bun x tsc failed at apps/tracer/src/terminal.ts:1019 because GhosttyActivityControls.getMetrics omitted the renderer's baseline field. I’m updating the boundary type to match the pinned renderer and rerunning the check.

Finding: after merging #84 cell metrics, bun x tsc failed at apps/tracer/src/terminal.ts:1019 because GhosttyActivityControls.getMetrics omitted the renderer's baseline field. I’m updating the boundary type to match the pinned renderer and rerunning the check.
Author
Owner

Finding: fast gate's agent_review had 5/6 tests pass; initial_attach_requires_explicit_credit_before_replay failed because scripts/verify-agent-attach-credit.py:17 creates target/ without creating target when CARGO_TARGET_DIR is external. This is a harness path assumption, not a product assertion. I’ll rerun with a temporary target symlink to the scratch build directory and remove it afterward.

Finding: fast gate's agent_review had 5/6 tests pass; initial_attach_requires_explicit_credit_before_replay failed because scripts/verify-agent-attach-credit.py:17 creates target/<id> without creating target when CARGO_TARGET_DIR is external. This is a harness path assumption, not a product assertion. I’ll rerun with a temporary target symlink to the scratch build directory and remove it afterward.
Author
Owner

The merge fast gate exited 1: Rust fmt, VT fmt, clippy and cargo-deny passed; tests failed only on the missing literal target/ parent, and both static builds stopped because .cargo/musl-cc.sh resolves target/auth-tools/zig/zig relative to the worktree. That Zig binary exists under the scratch target. I’m rerunning with a temporary worktree target symlink to the scratch target, preserving all build output there.

The merge fast gate exited 1: Rust fmt, VT fmt, clippy and cargo-deny passed; tests failed only on the missing literal target/ parent, and both static builds stopped because .cargo/musl-cc.sh resolves target/auth-tools/zig/zig relative to the worktree. That Zig binary exists under the scratch target. I’m rerunning with a temporary worktree target symlink to the scratch target, preserving all build output there.
Author
Owner

Step complete: merged dev's #75 theme patch and #84 renderer metrics in 0548a29 (two-parent integration commit). Terminal fit and prediction geometry now share cellMetrics(). Fast gate passed on the scratch target: RTT p50/p99 293.51/315.85 ms; x86_64 musl 16,161,072 bytes and aarch64 musl 13,570,408 bytes; tracer transfer 248,852 B gzip/204,548 B brotli; stream differential 134 streams, 11,439 schedules, zero divergences.

Step complete: merged dev's #75 theme patch and #84 renderer metrics in 0548a29 (two-parent integration commit). Terminal fit and prediction geometry now share cellMetrics(). Fast gate passed on the scratch target: RTT p50/p99 293.51/315.85 ms; x86_64 musl 16,161,072 bytes and aarch64 musl 13,570,408 bytes; tracer transfer 248,852 B gzip/204,548 B brotli; stream differential 134 streams, 11,439 schedules, zero divergences.
Author
Owner

Finding: the merged overlay still draws no predicted cursor; packages/ui/src/terminal-text-overlay.ts has only a text node. The prediction path also drops pending input at position.x + usedCells + cells >= columns in apps/tracer/src/predictive-echo.ts, so it cannot cover the requested >80-column line. I am adding failing renderer-backed coverage before changing either path.

Finding: the merged overlay still draws no predicted cursor; `packages/ui/src/terminal-text-overlay.ts` has only a text node. The prediction path also drops pending input at `position.x + usedCells + cells >= columns` in `apps/tracer/src/predictive-echo.ts`, so it cannot cover the requested >80-column line. I am adding failing renderer-backed coverage before changing either path.
Author
Owner

Finding: DPR 1 pixel probe after predicted backspace showed buffer cursor at the expected cell but canvas center pixel (96, 96, 98) instead of paper cursor #3d63dd. The renderer-paint callback had invalidated the hidden-cursor flag without restoring the host cursor. It now repaints that cursor before overlay reconciliation.

Finding: DPR 1 pixel probe after predicted backspace showed buffer cursor at the expected cell but canvas center pixel `(96, 96, 98)` instead of paper cursor `#3d63dd`. The renderer-paint callback had invalidated the hidden-cursor flag without restoring the host cursor. It now repaints that cursor before overlay reconciliation.
Author
Owner

Step complete: predicted cursor geometry, backspace movement, and wrapping now pass real-renderer pixel checks at DPR 1, 2, and 3. Each case checks a 24-cell run, a wrapped line over 80 cells, and backspace; confirmed cursor pixels match the renderer color at its measured cell. Browser test runs used port 4250.

Step complete: predicted cursor geometry, backspace movement, and wrapping now pass real-renderer pixel checks at DPR 1, 2, and 3. Each case checks a 24-cell run, a wrapped line over 80 cells, and backspace; confirmed cursor pixels match the renderer color at its measured cell. Browser test runs used port 4250.
Author
Owner

Finding: CPUWeight=10000 latency run at configured 300 ms ±30 ms passed baseline PTY echo (p99 323.23 ms) and other-session flood growth (0.89 ms), but same-session flood failed: confirmation p99 471.41 ms, PTY echo p99 567.53 ms, growth 244.31 ms versus 16.67 ms budget (100 samples). Continuing the full profile to capture browser metrics and determine whether this is reproducible.

Finding: CPUWeight=10000 latency run at configured 300 ms ±30 ms passed baseline PTY echo (p99 323.23 ms) and other-session flood growth (0.89 ms), but same-session flood failed: confirmation p99 471.41 ms, PTY echo p99 567.53 ms, growth 244.31 ms versus 16.67 ms budget (100 samples). Continuing the full profile to capture browser metrics and determine whether this is reproducible.
Author
Owner

Finding: full tracer browser run failed terminal cursor-move coverage at terminal.spec.ts:203: the pending run had no parent text x attribute (observed 0 vs renderer cell x=32), although its tspan had explicit geometry. Restoring the parent SVG text origin from renderer metrics; the test failure is the evidence.

Finding: full tracer browser run failed terminal cursor-move coverage at terminal.spec.ts:203: the pending run had no parent text x attribute (observed 0 vs renderer cell x=32), although its tspan had explicit geometry. Restoring the parent SVG text origin from renderer metrics; the test failure is the evidence.
Author
Owner

Latency profile findings: 300 ms ±30 ms full browser run reported access-screen render p99 1509.90 ms (setup) and 1859.40 ms (sign-in), both above the 1000 ms local render budget. Snapshot resume also timed out on exact viewport comparison: expected 66.62, observed 66.67 after theme reconnect. Both occurred in existing non-prediction paths; isolating the snapshot case next.

Latency profile findings: 300 ms ±30 ms full browser run reported access-screen render p99 1509.90 ms (setup) and 1859.40 ms (sign-in), both above the 1000 ms local render budget. Snapshot resume also timed out on exact viewport comparison: expected 66.62, observed 66.67 after theme reconnect. Both occurred in existing non-prediction paths; isolating the snapshot case next.
Author
Owner

Measurement under systemd-run --user --scope -p CPUWeight=10000 (scripts/verify latency, 300 ms ±30 ms): infraternal-dev, x86_64, i9-9900K, 8 logical CPUs, 31.3 GiB RAM; tracer-browser-start load 2.82/5.67/8.82. Relay n=31 p50/p99 298.76/321.05 ms; tracer RTT 337.70 ms. Predicted glyph only n=16 p50/p99 10.60/13.90 ms (PASS, <17 ms); 4/20 samples fell back, making all-input p99 364.10 ms. Echo confirmation on 346.40/372.90 ms vs off 348.10/381.30 ms. Browser CPU on 1.400/9.900 ms/key vs off 0.100/0.400. 50 MB bulk median 3.03 MB/s on and off, -0.04% (PASS).

Measurement under systemd-run --user --scope -p CPUWeight=10000 (scripts/verify latency, 300 ms ±30 ms): infraternal-dev, x86_64, i9-9900K, 8 logical CPUs, 31.3 GiB RAM; tracer-browser-start load 2.82/5.67/8.82. Relay n=31 p50/p99 298.76/321.05 ms; tracer RTT 337.70 ms. Predicted glyph only n=16 p50/p99 10.60/13.90 ms (PASS, <17 ms); 4/20 samples fell back, making all-input p99 364.10 ms. Echo confirmation on 346.40/372.90 ms vs off 348.10/381.30 ms. Browser CPU on 1.400/9.900 ms/key vs off 0.100/0.400. 50 MB bulk median 3.03 MB/s on and off, -0.04% (PASS).
Author
Owner

Repeat check: snapshot-resume.spec.ts passed in isolation after the full latency profile's 66.62 vs 66.67 viewport comparison timeout (9.6 s, TRACER_TEST_PORT=4250). The full run's three outstanding failures are the same-session PTY flood budget, access-screen local render p99, and terminal timing budgets; see measurements above and earlier finding comments.

Repeat check: snapshot-resume.spec.ts passed in isolation after the full latency profile's 66.62 vs 66.67 viewport comparison timeout (9.6 s, TRACER_TEST_PORT=4250). The full run's three outstanding failures are the same-session PTY flood budget, access-screen local render p99, and terminal timing budgets; see measurements above and earlier finding comments.
Author
Owner

Finding: full predictive spec again hit socket hang up at test/predictive-echo.spec.ts:468; the same DPR 1 pixel test passed in isolation. Switching this reset request to page fetch, as terminal.spec.ts already does, to avoid the isolated API request connection path.

Finding: full predictive spec again hit socket hang up at test/predictive-echo.spec.ts:468; the same DPR 1 pixel test passed in isolation. Switching this reset request to page fetch, as terminal.spec.ts already does, to avoid the isolated API request connection path.
Author
Owner

Finding: full predictive spec again hit apiRequestContext.post screen-reset socket hang up at test/predictive-echo.spec.ts:468; the same DPR 1 pixel test passed in isolation. Switching this reset request to page fetch, as terminal.spec.ts already does, to avoid the isolated API request connection path.

Finding: full predictive spec again hit apiRequestContext.post screen-reset socket hang up at test/predictive-echo.spec.ts:468; the same DPR 1 pixel test passed in isolation. Switching this reset request to page fetch, as terminal.spec.ts already does, to avoid the isolated API request connection path.
Author
Owner

Step: shared overlay cursor/run support committed as dfb33aa. The legacy string API remains available; tsc passed after the change.

Step: shared overlay cursor/run support committed as dfb33aa. The legacy string API remains available; tsc passed after the change.
Author
Owner

Step: renderer integration committed as c636196. Current predictive spec passed 5/5 under CPUWeight=10000; pixel alignment passed separately at DPR 1, 2, and 3, including the wrapped line and backspace cursor.

Step: renderer integration committed as c636196. Current predictive spec passed 5/5 under CPUWeight=10000; pixel alignment passed separately at DPR 1, 2, and 3, including the wrapped line and backspace cursor.
Author
Owner

Step: predictive cursor, differential, DPR, and capture coverage committed as fd311c7. Full predictive spec passed 5/5 in the CPU-weighted scope; pixel tests passed at DPR 1/2/3. Eight current-build captures are in apps/tracer/test-results/predictive-echo-predictive-5c604-uction-viewports-and-themes/ (393×852 and 1440×900; ink and paper; pending and confirmed).

Step: predictive cursor, differential, DPR, and capture coverage committed as fd311c7. Full predictive spec passed 5/5 in the CPU-weighted scope; pixel tests passed at DPR 1/2/3. Eight current-build captures are in apps/tracer/test-results/predictive-echo-predictive-5c604-uction-viewports-and-themes/ (393×852 and 1440×900; ink and paper; pending and confirmed).
Author
Owner

Finish: head fd311c73d2 (predictive-echo). The final scripts/verify fast run exited 0. Output excerpts, verbatim:
METRIC name=network_rtt_selftest configured_rtt_ms=300 jitter_ms=30 loss=unsupported transport=tcp-stream n=3 p50_ms=296.90 p99_ms=300.30 result=PASS
| fmt | PASS |
| VT native fmt | PASS |
| clippy | PASS |
| test | PASS |
| cargo-deny | PASS |
| static builds | PASS |
dist/ghostty-web.js: OK
dist/ghostty-vt.wasm: OK
dist/index.d.ts: OK
JavaScript transfer: identity=824445 gzip=250138/307200 brotli=205909/256000 bytes
stream differential: 134 streams, 11439 schedules, zero divergences
7 pass
0 fail
34390 expect() calls

The latency profile remains non-green on this host: same-session flood p99 567.53 ms (+244.31 ms vs 16.67 ms), access-screen render p99 1509.90/1859.40 ms vs 1000 ms, and the CPU-weighted terminal probe exceeded returning-sign-in and reconnect p99 budgets (5027.20/5000 ms and 1012.10/900 ms). Prediction-only glyph p99 was 13.90 ms; 50 MB throughput delta was -0.04%.

Finish: head fd311c73d2e2f9490377262937a0d2f4f58ee599 (predictive-echo). The final scripts/verify fast run exited 0. Output excerpts, verbatim: METRIC name=network_rtt_selftest configured_rtt_ms=300 jitter_ms=30 loss=unsupported transport=tcp-stream n=3 p50_ms=296.90 p99_ms=300.30 result=PASS | fmt | PASS | | VT native fmt | PASS | | clippy | PASS | | test | PASS | | cargo-deny | PASS | | static builds | PASS | dist/ghostty-web.js: OK dist/ghostty-vt.wasm: OK dist/index.d.ts: OK JavaScript transfer: identity=824445 gzip=250138/307200 brotli=205909/256000 bytes stream differential: 134 streams, 11439 schedules, zero divergences 7 pass 0 fail 34390 expect() calls The latency profile remains non-green on this host: same-session flood p99 567.53 ms (+244.31 ms vs 16.67 ms), access-screen render p99 1509.90/1859.40 ms vs 1000 ms, and the CPU-weighted terminal probe exceeded returning-sign-in and reconnect p99 budgets (5027.20/5000 ms and 1012.10/900 ms). Prediction-only glyph p99 was 13.90 ms; 50 MB throughput delta was -0.04%.
Author
Owner

Orchestrator (Claude) finished the prediction rendering on predictive-echo at 2edced6 (commits 0eef684, 2edced6):

  • Predicted text now uses a canvas overlay that copies the renderer's drawing path (same backing size, DPR transform, font, baseline, per-cell x). It is pixel-identical to confirmed text except for alpha 0.74. Before, it was SVG text that looked smeared and doubled.
  • Exactly one cursor: the renderer's renderCursor is guarded while a prediction is pending.
  • Input is sent before predicting. The mute deadline is 2×RTT plus guards, and a late echo counts as proof.

Latency profile, INFRATERNAL_TEST_RTT=300, CPUWeight=10000, i9-9900K 8 vCPU, load 3.7–9.8:

  • 20/20 keys predicted (0 fallbacks; before: 4/20).
  • Glyph visible p50/p99: 13.2/14.0 ms.
  • Prediction cost per key: 0.6/0.9 ms p50/p99 (budget 2 ms).
  • Input-to-send: 0.1/0.3 ms, the same as with prediction off (before: 1.4/9.9 ms).
  • The reconnect budget in that probe still fails, as before (1010 ms vs 900 ms); unrelated to prediction.

Tests: predictive spec 5/5; pixel alignment at DPR 1, 2 and 3.

scripts/verify fast: exit 0.

| fmt                       | PASS         |
| VT native fmt             | PASS         |
| clippy                    | PASS         |
| test                      | PASS         |
| cargo-deny                | PASS         |
| static builds             | PASS         |
 2 pass / 0 fail; stream differential: 134 streams, 11439 schedules, zero divergences; 7 pass / 0 fail
METRIC network_rtt_selftest p50 298.18 p99 319.94 PASS; sizes 16160816 / 13571944 within budget
Orchestrator (Claude) finished the prediction rendering on predictive-echo at 2edced6 (commits 0eef684, 2edced6): - Predicted text now uses a canvas overlay that copies the renderer's drawing path (same backing size, DPR transform, font, baseline, per-cell x). It is pixel-identical to confirmed text except for alpha 0.74. Before, it was SVG text that looked smeared and doubled. - Exactly one cursor: the renderer's renderCursor is guarded while a prediction is pending. - Input is sent before predicting. The mute deadline is 2×RTT plus guards, and a late echo counts as proof. Latency profile, INFRATERNAL_TEST_RTT=300, CPUWeight=10000, i9-9900K 8 vCPU, load 3.7–9.8: - 20/20 keys predicted (0 fallbacks; before: 4/20). - Glyph visible p50/p99: 13.2/14.0 ms. - Prediction cost per key: 0.6/0.9 ms p50/p99 (budget 2 ms). - Input-to-send: 0.1/0.3 ms, the same as with prediction off (before: 1.4/9.9 ms). - The reconnect budget in that probe still fails, as before (1010 ms vs 900 ms); unrelated to prediction. Tests: predictive spec 5/5; pixel alignment at DPR 1, 2 and 3. `scripts/verify fast`: exit 0. ``` | fmt | PASS | | VT native fmt | PASS | | clippy | PASS | | test | PASS | | cargo-deny | PASS | | static builds | PASS | 2 pass / 0 fail; stream differential: 134 streams, 11439 schedules, zero divergences; 7 pass / 0 fail METRIC network_rtt_selftest p50 298.18 p99 319.94 PASS; sizes 16160816 / 13571944 within budget ```
Author
Owner

Deployed dev e19c4eb: predictive local echo behind ?predict=1 (#50). Before deploy the orchestrator checked the cursor on the production build with and without prediction at DPR 1/2 (cell 8, cursorX 86 on an 86-char line). Gate exit 0.

Deployed dev e19c4eb: predictive local echo behind ?predict=1 (#50). Before deploy the orchestrator checked the cursor on the production build with and without prediction at DPR 1/2 (cell 8, cursorX 86 on an 86-char line). Gate exit 0.
Sign in to join this conversation.
No labels
launch-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal#50
No description provided.