Latency gate: D375 120 Hz target table in PROCESS §6 and verify latency (J407) #447
Open
opened 2026-11-09 06:31:26 +00:00 by kayg
·
20 comments
No Branch/Tag specified
main
j470b-server-mesh-rejoin
mesh-compat-382
j444b-d381-key-wins-flood
j461b-macos-utun-reader-thread
j475b-remove-host-motion
fix-reconnect-new-link
j481-d380
dev
j479-mesh-soak
fix/j476-host-facts
j475-motion-pill-split
j470-mac-node-join
root-upgrade-d376
j474-review-merge
latency-targets-120hz
nightly-suite-shards
gotatun-pin-j469
macos-utun-reader-thread
fix-flood-echo-stall
wip/j416
motion-fast-ease-out
mesh-addhost-j259
takeover-close-terminals-d368
kbd-instant-followups
fix-trust-key-double-click
nightly-suite-partial-report
appwin-icons-j207
rusqlite-slice1
swipe-content-first-d374
mesh-mtu-381
addhost-instant-open
glass-contrast-d365-mock
glass-contrast-d365
signout-followups-442
fix-startup-toast-red
research-gotatun-matrix-j317
themes-sync-j304
fix-load-flakes
fix-nix-zig-patch
fix-compute01-browser-baseline
ratchet-size-baseline-419
predict-auto-d130
revoke-coverage-d367
nightly-suite-queue-timeout
nightly-browser-suite
fix-rustsec-2026-0459
fonts-ioskeley-obel
log-retention-d375
fix-kbd-no-anim-427
fix-dev-red-specs-2
takeover-proof-305-j205
wip/j410-server-pump
ui-consistency-d372
fix-osc52-clipboard
libghostty-vt-1.0
forget-retired-host-d369
j404-merge-426
fix-setup-lock-race
fix-touch-select-426
revoke-access-report-ui
fix-predictive-echo-specs
fix-selinux-agent-exec
wip/j395-checkpoint2
wip/j395
j391-review-scratch
ui-consistency-j195-deferred-rebased
fix-rustc-paths-421
decisions-wording-341
fix-install-suite-red
fix-dev-rtt-specs
fix-pending-removal-align
fix-privileged-skip
fix-install-suite-404
review-j368
mesh-dns-j254
revoke-access-report-d356b
revoke-privilege-round5
fix-removal-confirm
review-j360
gotatun-rotation-367
deps-refresh-340
review-j354
review-J351
review-j350
addhost-takeover-d359
revoke-report-items-347
revoke-end-sessions-d362
review-j348
size-mesh-ssh-346
size-trim-337
revoke-access-diff-d356
j259-pre-rebase-b06c5a6
j305-ended-at-start-spec
addhost-sudo-consent
fix-layout-real-specs-411
fix-shell-open-411
fix-console-hosts-373
fix-shell-exit
fuzz-host-name-353
fix-host-details-410
macos-filter-j197
research-gotatun-tricks
revoke-p3-j216l
stale-state-pill
revoke-followups-j216k
revoke-progress-j216e
backup/macos01-20261106/lume/j197
backup/macos01-20261106/lume/appwin-icons-j207-stash0
backup/macos01-20261106/claude/stash-0
backup/macos01-20261106/claude/stash-1
backup/macos01-20261106/claude/J80-macos-s1
backup/macos01-20261106/claude/J80
backup/macos01-20261106/claude/J80-slice1-20261102
backup/macos01-20261106/claude/J16b-appwin-on-dev
backup/macos01-20261106/claude/appwin-launch-idle-146
backup/macos01-20261106/claude/J80-s1-run
wip/j259
trust-285-p3c
runtime-lock-j216f
mesh-ssh-p3-j175
tun-proof-p3b
trust-285-p3
terminal-polish-j271
tun-proof-p3
ssh-install-flakes-j216i
fuzz-fixes-j264
trust-285-a
trust-285-b
restart-oversized-fixture-j198
backup/macos01-20261106/lume/appwin-icons-j207
restart-bound-j216h
up-preflight-p3-j198
backup/macos01-20261106/lume/j251
up-preflight-j198
wip/up-preflight-j198-j245-p2
wip/up-preflight-j198-j245-p1
agent-fsync-offloop-j196
wip/up-preflight-j198-j245
settings-offscreen-j231
wip/up-preflight-j198-j226-inspection
wip/j103f
themes-j206
wip/up-preflight-j198-j226
flakes-j216c
backup/macos01-20261106/claude/j197
flakes-j216b
backup/macos01-20261106/claude/j197-red
wip/up-preflight-j198-review
flakes-j216
backup/macos01-20261106/claude/appwin-icons-j207
engineering-j208b
appwin-mock-j209
toast-stack-j165
engineering-j208
decisions-d351
decisions-d350
decisions-d349
appearance-presets-mock-j129
decisions-d348
docs-audit-spec
doc-coverage-baseline-raise
decisions-d347
decisions-d346b
decisions-d346
j88h-profile
ui-consistency-j195-deferred
ui-consistency-j195
engineering-practices-j194
preferred-login-ui-j166
signin-pools-263-j174
macos-node-265-s1
decisions-d345
decisions-d344
proxy-cgroup-j164
verify-quick-fuzz-329
proxy-start-io-j168
timing-flakes-j163
up-263-s1
mcp-events-j147
verify-quick-fuzz-j158
proxy-p3-j159
agent-resource-j157-detect
wip/j103d-final
remote-cancel-hang-j155
proxy-traefik-281-j62d
j149-verify-quick
proxy-traefik-281-j62b
j150-drop-unnamed
wip/j79d-red-r4
wip/j103d-d332
rootless-toast-j142
wip/j103d-head
wip/j103d
j125c-mcp
macos-build-j136
backup/macos01-20261106/claude/j136
wip/j79d-red-cli
wip/j79d-red-server
j121-link-liveness
j118c-tracer-failures
wip/j79c-head
release-desktop-j131
j124c-signed-macos
wip/j79c-red-p3
wip/j79c-red-p23
wip/j79c-red-p22
merge-j128-check
perf-deferred-j128
sidebar-toggle-260
j127-shortcut-changed-dot
j125-mcp
j118-tracer-failures
j126-account-summary
settings-sections-mock-j92
j120-pinch-lock
sidebar-292
backup/macos01-20261106/claude/j120
taste-2026-11-03
row-actions-242-215
backup/macos01-20261106/claude/stash-0-untracked
backup/macos01-20261106/claude/J80-265-s1-run
backup/macos01-20261106/claude/J80-macos-s1-20261102
j112-canvas-commands
ui-no-overshoot-scroll
fix-recovery-restart
backup/macos01-20261106/claude/stash-1-untracked
taste-2026-11-02
size-baseline-ratchet
machine-cards-structure-j108
j109-boot-copy-mockups
fix-dev-red
wip/up-263-s1
j95b-settings-rows
proxy-traefik-281
j95-option-rows
docs-human-j91
docs-keys-palette
fix-flood-1013-270-j38d
mesh-ssh-s1
mesh-ssh-s0
addhost-d291-tunnel
test-stability-load
mesh-relay-220
repro-296
shim-thp-off-24
mesh-pins-258-v2
mesh-dns
signin-d311-merged
docs-pages
ui-size-283
fix-ui-batch-specs
wip/j12b-edge-update
gate-on-arm
fix-rename-sync-293
trust-285-j60b
fix-focus-ring-clip
integ-ui-1101
edge-update-v2
signin-d311
taste-2026-11-01
layout-per-tab-250
ui-approved-splitopt-224
ui-approved-splitopt-224-on-dev
icon-text-centre-282
ui-approved-189-212-216
research-reverse-proxy
transfer-budget-320
review-mesh-pins-258-r2
sidebar-pill-259
review-mesh-relay-220
wip/j12-edge-update-v2
nix-package
status-crash-loop-237
fix-dev-test-failures
fix-fixture-socket-path
wip/mesh-relay-220-pre-dev-advance-rebase
fix-canvas-side-clip
research-head-to-head
frosted-real-256
review-mesh-pins-258
wip/mesh-relay-220-pre-final-rebase
review-layout-per-tab-250-r2
broad-suite-triage
nix-devshell
review-status-crash-loop-237-r3
wip/nix-devshell-pre-p3
taste-frosted-real
wip/code-serve-demo-2026-10-30
wip/code-serve-glass-2026-10-30
wip/code-serve-clusters-2026-10-30
wip/code-merge-release94-dev-2026-10-30
wip/rproxy-delta-scratch-2026-10-30
wip/stale-note-257-staged-2026-10-30
wip/d241-size-staged-2026-10-30
review-broad-suite-207
wip/mesh-pins-258-pre-split
wip/mesh-relay-220-pre-rebase
mesh-measure-220
plan-211-update
review-edge-autoupdate-r3
perf24-reconnect
review-stale-note-r2
release-prod-live
d241-shell-marker
review-mesh-pins-server-258
review-revocation-r3
review-setup-token-r2
review-sync-journal-r3
wip/review-mesh-pins-258-prune-review-1995fee46
frosted-scroll-256
sync-journal-r3
revocation-per-account-wip
review-tokio-asyncfd-0386
review-sync-p3s
wip/stash-8by64-stash0-2026-10-30
mesh-drag-r2
setup-token-data-wip
release-9-4
auth-busy-1013
mesh-view-overlap
sync-p3s
review-auth-busy-wake
review-readd-r8-wip
mesh-drag-bounds
audit-auth-93
app-windows-stream-start
review-authority-cap-r3
app-windows-r7-measure
readd-minimal-r6
readd-minimal-r5
app-windows-r7-soak
readd-minimal-r4
app-windows-r7
app-windows-r6
readd-minimal-r3
readd-minimal-r2
readd-minimal
merge-release8-dev
review-busy-page-liveness-r2
review-busy-page-liveness
mesh-view-trim
review-authority-read-retry-r2
review-remove-uninstall-r2
release-retry-reattach
release-retry
review-remove-uninstall
review-authority-read-retry
app-windows-ship
mesh-autoswitch-p2
mesh-ssh
mesh-relay
mesh-macos-node
screen-and-control
session-labels
review-real-logins-r5
review-real-logins-r4
review-real-logins-r3
shim-memory-r2
review-real-logins-r2
shim-memory
shadow-addhost-instant
shadow-real-logins
shadow-same-session-flood
shadow-prod-perf-fix
hosts-from-server
app-windows-real
app-windows-transport
release-prod-6w
mesh-policy-verify
quiet-close-toast
dev-merge-r4
review-mesh-agent-transport
review-app-windows-transport
settings-roundtrip
review-release-pipeline-r4
mesh-agent-transport
ended-strip-fix
review-audit-ca-update-d244
settings-rows
phone-width
signout-clear
fix-restart-tests
dev-merge-rp
signer-hardening
review-audit-ca-update
terminal-fidelity
review-audit-ca-update-wip
mesh-integrate
review-mesh-agent-transport-515d3712
review-mesh-dataplane-feec1988
mesh-dataplane
signout
research-canvas-node
wip/review-renderer-cost
wip/review-terminal-fidelity
padlock-glyph
review-release-pipeline-2e95b689
phone-polish
sidebar-polish
inbox-generic
review-mesh-dataplane-378d4055
prod-batch
ui-inbox
ui-ca-unlock
ui-machine-sections
review-mesh-dataplane-a5f744fb
tls-session-prod
session-bounds-prod
fix-dead-session
session-bounds
tls-session
palette-polish
shortcuts-d222
research-appmeta
phone-gutter
ui-centre-button
wip/ui-centre-button
wip/tls-session
review-addhost-breakit
ui-platform
renderer-cost
remote-agents
mesh-api
ssh-bootstrap
prod-hotfix2
prod-fixes
review-remote-agents
review-mesh-dataplane
session-life
research-browser
review-mesh-api
research-capture
review-mesh-api-round3
review-mesh-api-round1
gate-stability
review-gate-stability
review-relay-direct
review-mesh-api-findings
proto-sessions
font-picker
proto-addhost
proto-windows
mesh-core
latency-gate
research-bsd
research-discovery
keytray-mods
mock-core
ui-tweaks
fix-motion-sel
fix-motion
integrate2
fix-robust
t-web
t-server
vt-ghostty
fix-snap-echo
t-web-auth
review-vt2
t-agent
lane-protocol
lane-shim
lane-auth
s1-base
s1-contracts
s1-workspace
No results found.
No labels
launch-qa
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
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#447
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
D375 (owner, 2026-11-09) rewrites D41's budgets as a 120 Hz target table (one frame = 8.3 ms). This job (J407, branch
latency-targets-120hz, base80a09ad02) brings PROCESS.md §6 and the latency gate in line with it.Scope:
scripts/verify latencymeasures and reports every D375 row on every run, gated exactly as D375 says (own overhead gates at today's ~25 ms baseline until 8.3 ms is reached).No merge, no deploy.
Start: branch
latency-targets-120hzfrom origin/dev80a09ad02.Plan (about 3 h):
METRIC name=d375_<row> value_ms= target_ms= gate_ms= result=) andscripts/latency-report.py, which prints the 10-row table at the end ofverify latencyand fails on a missing or failed row (30 min).scripts/verify-latency.py(real agent + PTY): real echo (RTT + jitter bound + 16.7), flood (+8.3), own overhead at zero network, resumed-link reattach (1 RTT + 50), idle agent RSS (45 min).Finding 1: the 60 Hz inventory (code at
9850758c3).60 Hz budgets moved to 120 Hz (in this branch):
scripts/verify-latency.pyflood growth1000/60(16.67 ms)scripts/verify-latency.pyecho componentsapps/tracer/test/terminal-scenario.tspredicted glyphapps/tracer/perf/terminal.spec.tspredicted paint assertapps/tracer/perf/synchronized-output-input.spec.tsheld-output input sendapps/tracer/test/terminal-theme.spec.tstheme-switch JS p99Frame pacing (left unchanged, as the task says):
The renderer does not cap at 60 Hz. Every paint path is
requestAnimationFrame: Ghostty-web's render loop (vendor/ghostty-web/dist/ghostty-web.js), predictive echo's overlay (predictive-echo.ts:701,745), synchronized-output release (synchronized-output.ts:293), touch momentum (terminal-touch.ts:91) and the canvas springs. rAF runs at the display's rate, so a 120 Hz display already gets 120 Hz pacing. Nothing has to change for pacing. These are the places that assume 60 Hz, and what changing them would cost:src/canvas/springs.tsrestAtdelta * 60("a quarter pixel per frame at 60 Hz")delta * 120costs one more spring frame per flight. This is visual only, not latency. Leave it.src/terminal-touch.tsmomentum0.95^(dt/16)src/predictive-echo.tsCONFIRMATION_FRAME_BUDGET_MS = 34src/perf-hud.on-demand.ts:440test/motion-audit.spec.ts:121real/switch.spec.tsmedians (34/50, 70/100 ms)verify latency.TASTE.md/canvas/camera,mesh/graph"60 fps rule"The 120 fps cost (#260-style): the budget per frame halves, from 16.7 to 8.3 ms. The rendering row measures it directly: 1000 / p99 animation-frame interval with the frame-rate limit off while the terminal takes 10 MB/s. It is in the run table, posted next. If p99 frame work is above 8.3 ms, the cost is engine work (parse and raster per frame), not pacing. Unverified platform note: iOS Safari has historically capped rAF at 60 Hz behind a user setting. If it still does, a phone PWA cannot show 120 fps whatever the code does. That needs a ProMotion device to confirm.
Finding 2: a native trial run on compute01, before the full profile. This is evidence only, the profile run comes later.
Binary: a static build from
j404-merged(dev, 06:46 UTC), with this branch'sscripts/verify-latency.pyat9850758c3, nice 10, on a shared, loaded VM. Log:claude@infraternal-dev-01-compute01:~/.cache/codex-scratch/j407/native-trial.log.yesfloods the same PTY you type in, echo p99 grows by 148 ms. This fails the old budgets too (472 ms against the old RTT + 34 = 334 ms), so the failure is not caused by the 120 Hz change. A flood in a different session costs nothing (−22 ms, which is noise).0e7b61962the row is now the agent's p99 plus the browser's own spans p99 (keydown to send, plus echo arrival to paint), an upper bound. D296's ~25 ms was a different workload: the pooled shell echo on the perf VM. So the gate stays at the 25 ms baseline until a perf-VM run of this composed row reaches 8.3 ms.Progress: the profile run (run 1) at
0e7b61962, compute01, load 28–42 on 16 cores.verify quick: PASS (QUICK_EXIT=0).compute01:~/.cache/codex-scratch/j407/evidence/verify-latency-20261109T073942Z.log):own_overhead_agentp99 5.45 ms (p50 0.36);verify-auth-browserfailed on dev's own code with "initial public status". Since #252,/auth/statusalso returnssetup_token_command, andscripts/browser-auth/check.mjscompared the whole body with{setup_required:true}. This branch has no Rust changes, so the failure is dev's. Fixed in92233f3a2: the check now verifiessetup_requiredand that no keys beyond the two public ones appear.verify fast.verify latencyrun only the latency-producing specs instead of the full functional suite? The suite already runs elsewhere. The safe default is unchanged: this branch leaves the profile's step list as it was.Run 2 (head
92233f3a2, compute01, load 31–40). Log:compute01:~/.cache/codex-scratch/j407/evidence/verify-latency-browser-rows-20261109T105844Z.log.67cea945b:first-contentful-paintentry was found. It now uses a buffered observer, with the visible-after-two-frames upper bound as a fallback, and counts the fallbacks.verify-auth-browserstopped at its own relay self-test (configured 300 ms RTT did not arrive: p50=332.53 ms p99=371.59, load 37). That is host load, so the #252 fix has not been exercised yet.verify fastafter a minute, because it was gating a head I was about to change. Run 3, queued at position 10, runs every row on the final head in one log, thenverify fast.Run 3 (head
67cea945b, compute01, load 32–39 on 16 cores). Log:compute01:~/.cache/codex-scratch/j407/evidence/verify-latency-rows-*.log(13:25 UTC).configured 300 ms RTT did not arrive: p50=321.95 p99=425.45). Every round-trip row from this run is inflated by host load: baseline PTY echo p99 was 350 ms against 324–334 in the quieter runs.tscrefusedbrowserType(not part of this Playwright test API). Fixed in94882075fwith theplaywright.chromiumlauncher, and the tracertscnow exits 0 on compute01. I stopped run 3'sverify fastagain for the same reason as before.94882075f: all rows +verify fast) is queued at position 16.Finding 5: the browser rows on a quiet venue. dev-02 (aarch64, 10 cores, load 2.7–3.4), head
94882075f. Log:infraternal-dev-02-std:~/.cache/codex-scratch/j407/evidence/verify-latency-rows-aarch64-*.log. The relay self-test PASSED here: p50 303.6, p99 319.3 ms.first-contentful-paintentry for any of the 20 phone pages. So the row uses the "visible after two frames" upper bound: 210 ms p99 at 4× CPU slowdown.auth browser: PASS (4 isolated scenarios; Chromium 158.0.8127.18). The recovery check after it needs the tracer'snode_modules. This fresh worktree had none yet because the profile installs them later. Rerunning.Finding 6: echo under a same-session flood stalls on aarch64 (dev-02). The stall predates this branch.
yes & catruns in the PTY you type into. After the 64 KiB warm-up, the agent stops sending output on that stream.yesandcatare alive but blocked, the relay threads sit idle, and the key's echo never arrives. Before174127529the probe waited forever (15 min in a slot). Periodic stream-0 frames kept resetting the socket timeout. Snapshot:dev-02:~/.cache/codex-scratch/j407/evidence/same-session-stall-aarch64.txt.174127529) it reproduces asTimeoutError: PTY latency fixture did not reach the Ack and echo of a key on stream 3 within 30 s, withleftover_agent_processes=0. Log:dev-02:~/.cache/codex-scratch/j407/evidence/verify-latency-native2-aarch64-*.log.scripts/verify-latency.py(c442500c2, no re-attach phase), on the same binary, undertimeout 420. It completed baseline and other-session flood (growth 0.07 ms), then hung in the same-session phase:DEV_SCRIPT_EXIT=124. Log:dev-02:~/.cache/codex-scratch/j407/devcheck.log. So the stall is in dev's agent or fixture flow control on aarch64, not in this branch.Finish: branch
latency-targets-120hz, head174127529e786eb6f90d8b799706dc691e45cb71(git ls-remote origin latency-targets-120hz→174127529e786eb6f90d8b799706dc691e45cb71 refs/heads/latency-targets-120hz). Base: origin/dev80a09ad02. Not merged.D375 rows measured against targets (dev, this branch)
The main venue is dev-02 (aarch64, 10 cores, load 2.7–3.4; relay self-test PASS, p50 303.6 ms). The x86 column is compute01, measured at load 32–50, where the relay itself missed its RTT, so read it as contention.
What the misses mean:
Evidence:
dev-02:~/.cache/codex-scratch/j407/evidence/verify-latency-rows-aarch64-20261109T130134Z.log(browser rows),…/verify-latency-native-aarch64-20261109T131159Z.logand…/verify-latency-native2-aarch64-*.log(native),…/verify-latency-aarch64-combined.log(report);compute01:~/.cache/codex-scratch/j407/evidence/verify-latency-20261109T073942Z.log,…/verify-latency-rows-20261109T143255Z.log.What changed (11 commits)
scripts/latency_targets.py: the D375 table,METRIC name=d375_<row>lines andreport <log>, which exits 1 on any missing or failed row. Own overhead gates atOWN_OVERHEAD_BASELINE_MS = 25until reached.apps/tracer/test/latency-targets.ts: the TypeScript copy, checked against the Python one byscripts/test-latency-report.py(a new fast-gate row).scripts/verify-latency.py: native rows (agent overhead at RTT 0, idle RSS, resumed-link re-attach, flood effect). Budgets moved to 120 Hz. Every wait now has a deadline, and SIGTERM still cleans up.latency-profile.spec.tsadds the rendering (frame limit off) and phone first-paint rows.scripts/verify latency: tees to an evidence log and ends with the D375 table.browser-auth/check.mjs: accepts #252'ssetup_token_command(fixes a dev failure).Gate
scripts/verify quick(compute01,0e7b61962):QUICK_EXIT=0, all rows PASS, including tracer typecheck and doc comments on the diff.scripts/verify fast(compute01, head174127529), verbatim:Both failures are dev's, not this branch's.
git diff origin/dev...HEAD -- crates Cargo.lock deny.tomlis empty.RUSTSEC-2026-0459(libsqlite3-sys, C SQLite CVE-2026-119375), a new advisory. The same failure is inj428/deny-dev.log(dev),j410,j416andj417.logins::tests::a_linger_request_landing_after_disable_never_survives_revocationfailed on a systemctl call count (left 3, right 2) during the parallel workspace run at load 34–50. Rerun alone 3 times at load 50:test result: ok. 1 passed× 3 (code:~/.cache/codex-scratch/j407/linger.txt). This is a timing flake.For the orchestrator / owner
verify latencystop running the whole 1283-test functional suite, which takes about 5 h in one slot, and run only the row-producing specs? This branch keeps the existing step list.verify-auth-browser's recovery check fails on dev withAuthError: The request could not be accepted(dev-02). Under load on compute01 it fails with "resident handle resolved wrong user". This branch doesn't touch either path.Cleanup: target dirs deleted on compute01 (50G) and dev-02 (4.5G), and the leftover agent processes from the stalled run were killed by PID. VM worktrees
~/worktrees/j407and the evidence are kept.Round 2 start. Branch
latency-targets-120hz, rebased on origin/dev5eb968744; head234fb403c.Plan, following the orchestrator's decisions:
474a838da: the profile runs onlytest/terminal.spec.tsandtest/latency-profile.spec.ts. The full suite stays inverify fastand the nightly.474a838da: the row specs run twice, unthrottled (--disable-gpu-vsync --disable-frame-rate-limit, which gates) and at 60 Hz (reported beside it). Aframe_pacing idle_fpscheck fails the run if the unthrottled run did not get above 70 fps.234fb403cbuilds the agent for the host's target, so the profile also runs on dev-02. Next: PROCESS §6 text and a "how to measure on your device" note.Then
verify quick, one profile run, and push.Round 2 findings (dev-02 aarch64, load 3–7 during the measured phases, head
0b5bd0f01). Log:dev-02:~/.cache/codex-scratch/j407/evidence/verify-latency-20261109T162528Z.log. This is the realscripts/verify latency, end to end.Unthrottled mode works:
frame_pacingidle 357.0 fps unthrottled against 60.0 fps paced.D375 REPORT result=FAIL, which is expected: the flood row is MISSING and the network-change reconnect misses its target.Reading it: once the 60 Hz wait is gone, our own work per key is under 1 ms at p50. The frame-bound rows at 60 Hz (15.4 / 12.5 / 15.3 ms) are almost all waiting for the display's frame.
Own-overhead gate proposal (point 4): lower the gate from 25 ms to 12 ms, and switch it to the 8.3 ms target after two consecutive perf-VM runs at or under 8.3. Evidence: the unthrottled own overhead is 8.56 ms. The agent's share is 0.56 ms (0.67 and 0.90 in earlier dev-02 runs). The browser's share has p99 8.00 ms but p50 0.90. With n=20, the p99 is simply the slowest of 20 keys, so one slow key sets it. 12 ms is the measured 8.56 plus about 40% headroom for that single-sample p99 on a shared VM. It is still half of today's 25 ms gate. I'd also raise the probe from 20 to 100 keys in the latency profile, so the p99 means something. That's a one-line change I have not made; it's the orchestrator's call. Until it's decided,
OWN_OVERHEAD_BASELINE_MSstays at 25.Auth recovery check (point 5): the check, not the product. Fixed on this branch in
d224a9109and the commit before it. A diagnostic run logging cookie names (never values):Replacing a session correctly answers with two
Set-Cookieheaders. The checks' Playwright proxy rebuilt the headers withObject.fromEntries(pairs), which kept only the last one (the expiry), so the browser lost its session. The same loss explainscheck.mjs's "resident handle resolved wrong user". The newfulfillHeaders()joinsSet-Cookievalues with\n, which Playwright splits back into separate headers, and all 7 fixture proxies use it. Green ond224a9109:Red, on
0b5bd0f01:DIAG /auth/wraps 401 {"error":"authorization failed"}→RECOVERY_EXIT=1. A fresh checkout also needed the tracer's dependencies before the recovery bundle;verify-auth-browsernow installs them first.One correction to the decisions:
verify fastdoes not run the functional browser suite. Its TypeScript part is unit tests, typecheck, build and the bundle budget, so the whole suite runs only in the nightly (D373). PROCESS §6 says that.Round 2 finish. Branch
latency-targets-120hz, head1da4832c7884b62c1eb3f6b087aede49a459e2bb(git ls-remotegives the same SHA). Rebased on origin/dev5eb968744. Not merged. No separatefix-auth-recovery-checkbranch, because the cause was the check, not the product.Round 2 commits on top of round 1:
474a838dalatency profile: row specs only, unthrottled gate, 60 Hz reported beside it,frame_pacingguard234fb403cverify latency: build the agent for the host's own target0b5bd0f01PROCESS §6: row specs only, frame pacing, measuring on your own deviced224a9109^ browser-auth: keep every Set-Cookie (fulfillHeaders, 7 proxies, unit test)d224a9109verify-auth-browser: run the header test, install tracer dependencies first1da4832c7fixture-headers.mjs module doc commentscripts/verify quick(dev-02,1da4832c7; it compared against origin/devbd551e549, which dev had moved to by then):I ran
verify fastin round 1 only (on174127529); this round asked forquick. Linger flake evidence is on #475. dev-02 target dirs deleted after each run.Round 3 start. Head
e44666d08:87d41bb3blowers the own-overhead gate to 12 ms (OWN_OVERHEAD_GATE_MS) and puts the switch rule in PROCESS §6: after two consecutive perf-VM runs at or under 8.3 ms, the gate becomes 8.3 ms.e44666d08raises the probe to 100 keys in the latency profile and on the perf VM. The functional suite stays at 20 keys.Now on dev-02, since compute01 has 9 queued:
verify latency, thenverify fast.Round 3 progress, head
545bbe744. Two problems surfaced once the probe typed 100 keys, both fixed:96107b29a: the scenario read the probe results whenp99:appeared, but the panel re-renders after every painted key, so it read 49 of 100 echoed keys. It now waits forPainted echoes: N / N.545bbe744(product, the?probe=1panel): a run had a fixed deadline of max(15 s, 50 ms per key). Each key waits about one round trip, so at 300 ms a run stopped at 45 keys. Red on96107b29a:Expected substring: "Painted echoes: 100 / 100", received"Painted echoes: 45 / 100". The deadline is now an idle timeout: 15 s with no painted echo. The owner's on-device check (n=100 over a WAN) would have hit the same cut-off.I stopped the
verify fastruns on the two superseded heads, by my own process group. The final run (profile, then fast) is queued on dev-02, since compute01 has 9 queued.Round 3 finish. Branch
latency-targets-120hz, head545bbe744fb11161432a90ca71c1eca07d4039cb(confirmed withgit ls-remote). Not merged.Round 3 commits:
87d41bb3bown-overhead gate 12 ms (OWN_OVERHEAD_GATE_MS), plus the switch rule in PROCESS §6: after two consecutive perf-VM runs at or under 8.3 ms, the gate becomes 8.3 ms. Shared-VM runs don't count.e44666d08100 probe keys in the latency profile and on the perf VM. The functional suite keeps 20.96107b29athe scenario waits for the whole probe run. It had read the results after the first key.545bbe744the probe panel ends a run after 15 s with no echo, instead of at a fixed max(15 s, 50 ms per key) deadline. That deadline cut runs at 300 ms RTT off at 45 keys.Re-measured with 100 keys (dev-02 aarch64,
scripts/verify latency, logdev-02:~/.cache/codex-scratch/j407/evidence/verify-latency-20261109T180354Z.log; frame pacing: unthrottled 356.6 fps, 60 Hz 59.5):With 100 keys the browser's p99 dropped from 8.00 ms (the slowest of 20 keys) to 4.70 ms. Own overhead is now under the 8.3 ms target and near the 5 ms stretch. This is a shared-VM run, so it doesn't count toward the two perf-VM runs the switch rule needs. The rest of the table: flood MISSING (#477), network-change reconnect 1455 ms against 1370 (FAIL), every other row PASS. Overall
D375 REPORT result=FAIL, as expected from those two rows.scripts/verify fast(dev-02, head545bbe744; compute01 had 9 queued), verbatim:The only failure is
apps/tracer/src/canvas/sessions/startup-toast.test.ts› "a closing notice cannot hide its replacement":TypeError: element.getAttribute is not a functioninmarkMotion(packages/ui/src/input-source.ts:83, called fromtoast.tsundertoast-fake-dom.ts). It is dev's, not this branch's. Rerun alone it fails 2 of 2, andinput-source.ts,toast.ts,toast-fake-dom.tsand the test are byte-identical to origin/dev547a48885. This branch changes nothing underpackages/orapps/tracer/src/canvas; its only change inapps/tracer/srcisperf-probe.ts. It likely comes from #427'smarkMotionlanding without the toast fake DOM gaininggetAttribute. Every Rust row passes, includingtestandcargo-deny.Disk: dev-02's j407 target (34G) was deleted when the run finished, and only logs and evidence (2.8M) are kept.
df -h /on dev-02:/dev/vda3 158G 84G 68G 56% /.Review of
latency-targets-120hz@545bbe744fb11161432a90ca71c1eca07d4039cb(reviewer sub-agent, J459)Verdict: REQUEST CHANGES. One P2 blocks: the 12 ms own-overhead gate failed in my re-run, with a number five times the author's. The rest is approvable, and the P3s below don't block on their own.
Re-run of
scripts/verify latency(item 6)dev-02 aarch64, through build-slot inside
nix develop -c, head545bbe744, load 1.8–2.3 during the browser phases. Log:dev-02:~/.cache/codex-scratch/latency/evidence/verify-latency-20261109T192313Z.log(table indev-02:~/.cache/codex-scratch/review447/run2.log). Frame pacing: unthrottled 310.9 fps, 60 Hz 60.0.TimeoutError … Ack and echo of a key on stream 3 within 30 s)D375 REPORT result=FAIL. Every row agrees with the author except own overhead.Findings
P2. The own-overhead gate looks unstable (27.03 vs 5.54 ms). The agent share matches (0.73 vs 0.84 ms). The unthrottled browser p99 is 26.30 ms with p50 0.90 ms, so a couple of slow keys set it (nearest-rank p99 of n=100 is the 99th-smallest sample). In the same run the S1 per-hop p99s are all small (the worst,
term.write → write callback, is 2.8 ms, and the hops sum to well under 8 ms). So the browser own-overhead span captures something the per-hop table does not show, or one noisy stretch (the 60 Hz run'swrite callbackp99 was 18.1 ms). Before the 12 ms gate becomes a hard row, please find what the outlier keys are. Also show the browser's slowest samples, not only p50 and p99. The switch rule to 8.3 ms (two perf-VM runs) should wait until the gate is stable on a shared VM.P3. Items the owner listed, none blocking by itself:
FIRST_PAINT_MS,RENDER_FPS,RECONNECT_ALLOWANCE_MSor the30jitter literal in the TypeScript file, orFIRST_PAINT_MSin the Python file, each turnsscripts/test-latency-report.pyred, and that test is the "latency report (D375)" row inverify fast. Gaps:PHONE_CPU_SLOWDOWN(TypeScript only),OWN_OVERHEAD_GATE_MSandIDLE_AGENT_RSS_MB(Python only) are not compared. The30literal is not checked against the realLatencyConfigjitter. The comparison logic ofd375Row()is not checked againstrow_line().parse()keeps the last line per row and display, so a rerun inside one log hides an earlier FAIL. By design, but surface a "rerun" count in the report.The other checks
D375 REPORT result=FAIL; an empty log exits 1.verify_latencyruns the report after the steps and setsstatus=1on a non-zero exit, so a failure cannot be skipped. My real run showed the same: flood MISSING, reconnect FAIL, own overhead FAIL.armIdleTimeout, clearing before arming) and cleared indispose(),finishRun()andfailRun(). No echo means the run ends after 15 s with "Timed out". A closed panel leaves no timer. A run is capped at 5,000 keys.fulfillHeaders()joins repeatedSet-Cookiewith\n, which Playwright splits back into separate headers. The author's red/green runs show the fix, andverify-auth-browserpassed in my run (auth browser: PASS (4 isolated scenarios)). NoObject.fromEntrieson a response-header path remains inscripts/browser-auth. I did not audit everyroute.fulfillcall site elsewhere. Minor scope creep:check.mjsalso changes its "public status" assertion.startup-toast.test.ts(J453), and the flood stall on aarch64 (#477).I ran nothing beyond what's listed. Not merged, pushed or deployed.
Round 4: where the own-overhead outliers come from (J459 P2). Head
6458fdcb5.Shape: a tail, not a shifted median. Unthrottled browser share across 7 runs on dev-02: p99 1.6, 16.0, 1.3, 19.6, 1.2, 4.1, 1.8 ms. p50 stays 0.8–1.1 ms every time. J459's 26.3 ms is the same tail, a bit worse. Logs:
dev-02:~/.cache/codex-scratch/j407/diag-oldhead.log,diag-run1.log,diag-trace.log,alloc.log.Which keys: the new
own_overhead_browser_slowestline (30f224f2c) shows:termWrite > writeCallback(34.3, 17.2, 11.0 ms). That's the wait for the engine's next animation frame. Every other hop stays under 3 ms.What overlapped them (Chromium trace, lean categories):
MajorGCevents of 5.2–5.5 ms in the 30 s run, plus heavy background marking.Where the garbage comes from: a V8 sampling heap profile over the prediction-off run, including objects already collected:
TerminalCursorLayer.placeSet(vendored renderer, 2 per frame)render(closures, objects per frame)syncCursorLayerAllocation scales with frame rate. While output flows, the engine's render loop renders every animation frame, and our hook ran
syncCursorLayerafter every pass, rebuilding a placement object and a key string just to learn the cursor hadn't moved. That's real product cost on any display (2× at 120 Hz against 60 Hz). Uncapped, it's about 3× a 120 Hz screen, which is why it shows so strongly in this venue.Fixes:
44d01de9a): a render pass now skips the cursor-layer sync unless an input it uses changed. The inputs are parsed output, viewport, grid and canvas size, style or theme, and the layer's flags; a forced redraw always resyncs. The check is a few field reads with no allocation or layout read.place()compares fields instead of building a key string. No pacing change.new Set()×2 and closures (about 1 MB/s uncapped, about 0.35 MB/s at 120 Hz) need a patch invendor/ghostty-weband a pinned rebuild ofdist. That's engine frame work, outside this job. Recommended as its own job with this profile as the gate.30f224f2c): the last-screen probe now drops only an attached terminal. One run had failed with{"linkDown":false,…}because the click came before the previous resume's attachment was confirmed.P3s (
6458fdcb5):PHONE_CPU_SLOWDOWN,OWN_OVERHEAD_GATE_MS,IDLE_AGENT_RSS_MB, the jitter literal against the realLatencyConfig, andd375Row()againstrow_line()line for line under bun.scripts/test-latency-report.py: 19 tests OK.Now running on dev-02: the allocation profile after the fix, the cursor-layer regression specs,
verify quick, and the profile ×3.Round 4 finish. Branch
latency-targets-120hz, head6458fdcb5e3a8da83f3f6f75052c83998d486129(confirmed withgit ls-remote). Not merged.Three profile runs on dev-02 (aarch64, load 3–7, real
scripts/verify latency; logsdev-02:~/.cache/codex-scratch/j407/evidence/run{1,2,3}/and~/.cache/codex-scratch/j407/r4.log):The 12 ms gate held in all three runs, with a spread of 2.49–5.93 ms; every run was also under the 8.3 ms target. The slowest browser keys are now 1.7–2.8 ms, against 11–38 ms before the fix. The 60 Hz column (own overhead 7.6–19.5 ms) is still dominated by the wait for a 60 Hz frame. Each run's
D375 REPORT result=FAILcomes only from the two known rows.Allocation after the fix: 1703 KiB/s, from 2708 (−37%).
placeandsyncCursorLayerare gone from the top list. What remains is the vendored renderer:Set711 andrender381 KiB/s at 3280 frames/s, about 220 bytes per frame, about 26 KiB/s on a 120 Hz screen. That remainder is an artefact of the uncapped venue, not a 120 Hz cost. It is a candidate for its own vendor-patch job, not this one.Other checks at
6458fdcb5:verify quick:QUICK_EXIT=0.cursor-blink,keyboard-instant,keyboard-sweep,synchronized-output): 45 passed (22.1 m),CURSOR_SPECS_EXIT=0.scripts/test-latency-report.py: 19 tests OK, including the bun-rund375Rowagainstrow_linecomparison.Disk: the one dev-02 target was deleted at the end of the run;
df -h /shows/dev/vda3 158G 64G 88G 42% /.J468 re-review of
latency-targets-120hz@6458fdcb5— verdict: REQUEST CHANGES (one P2, three P3)Evidence is on
codein~/.cache/codex-scratch/j468/evidence/:verify-latency-20261110T045648Z.log,run.log,spec5.log,spec4.log,cursor-layer-skip.spec.ts,spec2.sh.1. Cursor-layer skip
I read every input of
syncCursorLayer. Cursor position and visibility come from parsed output (parsedOutputVersion). Scrollback offset (viewportY), cols/rows, canvas size, cursor style, theme identity and the sixcursorLayerWantedflags are compared. Font, theme and resize callrender(..., true), which always resyncs. Focus and blink don't feed the sync. Selection and IME are not inputs.I wrote
cursor-layer-skip.spec.ts. It checks the layer in the frame after each renderer pass, not only after the renderer parks. A parked-renderer check can't see a stale cursor, becausepauseTerminalalways resyncs after 700 ms. The spec covers wire output (hide/show/move, alternate screen), scrollback up and back, font size, resize, theme, typing with prediction, and selection.cursor-blink.spec.ts: 6 of 6 pass on the branch.P2 — local writes bypass the output version, so the layer stays stale (
apps/tracer/src/terminal.ts, theterminal.writewrapper near line 432).writeTextForTest,seedPromptForTestandbenchmarkOutputAt10MBpscallterminal.writedirectly.parsedOutputVersion, so every pass skips the sync.writeText("local one\r\nlocal two")the cursor was at (9,1). The layer stayed at the old cell for about 36 consecutive frames, until the renderer parked.render_10mbpsrow optimistic. The benchmark runs with the cursor-layer sync skipped on every frame, while a real 10 MB/s flood syncs every frame (getCursor()andcellMetrics()still allocate).parsedOutputVersioninside the existingterminal.writewrapper. That covers all three callers. Then add my spec, or a trimmed copy, to the suite.2. Worst of repeats
parse()keeps the worse line per row and display. A FAIL beats a PASS, a lower fps counts as worse, andrunsis shown. The unit tests cover this.P3 —
compose_own_overheadkeeps the last agent and browser line, not the worst. It's equivalent today, because each is printed once per display per run. Make it worst-wins too.3. Vacuous passes
resultdefaults to FAIL. n=0 or a non-finite value fails. The unthrottled floor fails when it wasn't measured.flood_effectis MISSING and the report FAILs.P3 —
test_typescript_rows_decide_like_python_rowscallsskipTestwhen bun is missing, so the TS-vs-Python check can pass without running.P3 — the report doesn't enforce a minimum n. A p99 from 5 samples is just the maximum.
P3 —
fcp_missing_n=20, sofirst_paint_phonealways uses the two-frame upper bound, never the FCP entry. That's honest, but the row says "first paint".P3 (not new) —
predicted_glyphp50 was −1.20 ms: the prediction stamp precedes the keydown stamp. The formula is unchanged from dev, but the row can under-report.4. Latency profile re-run on dev-02 (build-slot, aarch64, unthrottled gating)
Unthrottled, from
verify-latency-20261110T045648Z.log:Round 5 (J468's re-review), head
af543ecd6.P2, local writes bypass the output version: fixed in
066b9f0bc. Theterminal.writewrapper bumpsparsedOutputVersion, which coverswriteTextForTest,seedPromptForTestandbenchmarkOutputAt10MBps. The pump's own bump stays; every reader compares with!==. J468'scursor-layer-skip.spec.tsis in the branch;af543ecd6adds a theme-lookup guard so it passes the tracer typecheck.6458fdcb5(compute01):✘ a local write (mock helper, no wire sequence) moves the layer,Received + 562.af543ecd6(dev-02): 7 passed (1.9 m).render_10mbps, re-measured (dev-02, load 0.1–2.8, two runs per head):
6458fdcb5(sync skipped on local writes)af543ecd6(fixed)The difference is inside run-to-run noise. The benchmark writes 100 chunks a second, so the fix adds about 100 syncs a second, not one per frame. The optimism was real in principle and too small to measure. A compute01 run at load 19–27 gave 16 fps on the old head, so render numbers from that host aren't usable.
P3s, fixed (
066b9f0bc):compose_own_overheadkeeps the slower share per display;min_n(100 keys or frames or flood samples, 20 reconnects or pages, 2 memory readings) and fails as "FAIL (n X < min)" below it, which PROCESS §6 now states;scripts/test-latency-report.py: 21 OK.Follow-ups, noted and not fixed:
first_paint_phonenever uses the FCP entry. Every run showsfcp_missing_n=20, so the row is always the "visible after two frames" upper bound. Something about the CPU-throttled mobile context (or the route-fulfilled page) keeps Chromium from emittingfirst-contentful-paint, or the buffered observer can't see it. Needs its own look.predicted_glyphshows a negative p50 in some runs (J468). The overlay's paint stage can be stamped beforekeydown(likely the rAF timestamp, which is the frame's start time, againstperformance.now()at keydown), sopredicted - startedgoes below zero. The row should clamp at 0, or stamp the paint withperformance.now(). Not changed here.verify fastis now running on compute01; its output follows.