Performance verification
ckVision treats the machine-independent half of its performance charter as a
correctness property. scene_budget_gate runs cvision_bench --gate in CTest and
fails if a warmed compositor touches a cell on an unchanged frame, more than
one cell after a single-cell content change, or a complete retained Window
move (retained_window_move) exceeds its 696-cell / 6,272-byte caps. It
fails as well if a resize-drag step (retained_window_resize) repaints any
Window other than the resized one, repaints that one other than exactly once,
or exceeds 532 cells / 4,096 bytes. That benchmark drives the drag as a reader
does: a press on the resize grip and pointer motion injected through
HeadlessTerminal, alternating a 30x10 window with its 36x13 size beside a
peer window. 532 cells is the 38x14 rectangle around the larger size and its
shadow; a measured step touches 528 cells and emits at most 3,737 bytes. The
instrumented
warmed_*allocate_nothing* tests replace allocation only inside the test
executable and verify, after warm-up, that compositor, raster-layer movement,
Presenter, focused and pointer dispatch, traversal, due-timer delivery, and
posted-work draining perform no ordinary heap allocation. This instrumentation
is test-only; it does not introduce a library global or alter client allocation
behavior.
--gate runs each benchmark only four times: every iteration checks its
budgets, so four visits every state an alternating benchmark cycles through,
and the gate stays fast under Debug and sanitizer builds. The timed run, with
each benchmark's full iteration count, is cmake --build build --target bench_smoke_run.
Application::last_compose_cells_touched(),
Application::last_bytes_emitted(), Desktop::last_content_repaints(), and
Window::content_repaint_count() are the deterministic per-frame counters
used by interaction tests and benchmarks. A move must repaint no Window
content and compose only the old/new window rectangles and their shadows; a
resize may repaint only the resized Window's local backing. The pointer paths
are gated the same way in test_application_render.cpp:
dragging_a_window_recomposes_without_repainting_its_retained_content for a
title-bar drag, and grip_dragging_a_window_repaints_only_its_retained_subtree
for a grip drag. In the grip drag, every press, motion and release step
repaints the resized window once and its peer never, within a cells-touched
bound. The same retained
rule applies to popups: a same-size move is layer composition only, whereas
their own content invalidation and a resize repaint that popup's backing.
The same CTest budget gate includes the editor's deterministic local-relex
oracle: on a 4,000-line YAML source, changing a state-independent first line
may invoke highlighting for at most that line and the one unchanged line that
proves the cache fixed point. The cap is two lines; it is independent of host
speed and causes scene_budget_gate to fail if a local edit starts scanning an
unbounded suffix again.
Window, drop-down-menu and popup-list layers use the same binary shadow compositor rule;
their shadows are included in the move damage bound and never compound.
Pinned-host p99 procedure
The wall-clock gate runs separately from shared CI on the named reference host
macos-27.0.1-arm64-m1-max-32g (Apple M1 Max, 32 GiB, model Mac13,1,
macOS 27.0.1 build 26A434; D-136). Run it from a clean Release build, with
the host otherwise idle and a
real PTY endpoint. For each of 10,000 scripted operations—keystroke echo,
focus movement, menu navigation, window move, and resize—timestamp immediately
before the harness writes the input bytes and when it observes the final byte
of that operation's one Presenter write. Record the sorted samples' 99th
percentile, terminal capability profile, build compiler/version, and commit.
The acceptance limits are those in the architecture §8: input event to presented bytes p99 below 2 ms and a full theme-switch recompose/diff below 5 ms. The first accepted runs establish checked-in baselines for both measurements; later runs fail if either absolute limit is exceeded or either p99 regresses by more than 5% from its baseline. Multi-host publication is owned by WP-32; this document fixes the procedure and comparison rule so that later infrastructure cannot silently redefine the gate.
The PTY harness
cvision_pty_latency (benchmarks/pty_latency.cpp, POSIX only) implements the
event-to-bytes half of the procedure. It is a tool target and deliberately not
a CTest: shared CI never judges wall-clock time.
- The session. The harness opens a pseudo-terminal sized to the §8
reference configuration, 200×60, and forks a child that runs a real
Applicationon the slave side throughPosixTerminal. The child's scene is ordinary chrome over aDesktop: a menu bar, a status line, an Editor window with threeInputLines, and a Notes window behind it. Its capability profile is stated rather than probed: theModernVtbaseline (legacy keyboard, SGR mouse, focus reports, bracketed paste) with true colour and synchronized output, probes off. The harness is the terminal and answers no probes. - The frame boundary. With synchronized output in the profile, the
Presenter writes every non-empty frame as one
write()bracketed byCSI ? 2026 h…CSI ? 2026 l. The final byte of a frame is therefore thelof the closing bracket. Nothing else the session writes, such as cursor blink or pointer shapes, carries that bracket. - The operations. One 25-operation cycle holds the documented mix, and 400
cycles make the 10,000 operations. The cycle is 10 echo keys (five characters
typed into the focused field, then five Backspaces), 4 focus moves (Tab, Tab,
Shift+Tab, Shift+Tab), and 5 menu operations: Alt+F opens File; Down, Down
and Up walk it; Enter chooses an item that does nothing, which closes the
menu and returns the focus. The cycle ends with 3 title-bar drag steps and 3
resize-grip drag steps as SGR mouse reports, each drag returning to where it
began. Each operation is one
write()of the bytes a host sends for it. A lone Esc is not scripted. A legacy keyboard delivers it only after the decoder's 50 ms quiet deadline, which comes from the protocol's Esc-versus-Alt ambiguity, not from event-to-bytes work. - The rehearsal. Before any timing, the harness plays one cycle headlessly on the same scene and profile. Every operation must present exactly one frame, and the cycle must leave the composed frame and the focus exactly as it found them. The rehearsal proves that each timed operation measures the work it names. During the timed run, a frame that no operation asked for fails the run.
- The measurement. For each operation, the harness reads the steady clock
immediately before the
write()to the PTY master. It reads the clock again when theread()that delivers the closing bracket returns. Four warm-up cycles run first and are discarded. Percentiles are nearest-rank over the sorted samples. The harness prints p50, p90, p99 and maximum for each operation class (echo,focus,menu_open,menu_navigate,menu_close,window_move,window_resize) and overall, with the mean frame size in bytes. It writes the same figures as JSON, together with the build type, compiler, ckVision version, the commit it was given,uname, the capability profile and the host's load average before and after the run. It exits 0 when overall p99 is below 2 ms and within the optional baseline, 1 when either check fails, and 3 when the run itself could not be completed.
Run it on the reference host from a Release build:
# Set TMPDIR to a unique task directory on the host's required temporary volume
# first; for the project Mac, that volume is /Volumes/PRO-BLADE/tmp.
build_dir="$TMPDIR/build-release"
cmake -S . -B "$build_dir" -DCMAKE_BUILD_TYPE=Release -DCKVISION_WARNINGS_AS_ERRORS=ON
cmake --build "$build_dir" --target cvision_pty_latency
uptime # record the load; the host should be otherwise idle
"$build_dir/benchmarks/cvision_pty_latency" \
--commit "$(git rev-parse --short HEAD)" --output "$TMPDIR/pty_latency.json"
# later runs: add --baseline-p99-ns <the baseline's overall p99_ns>
--operations N and --warmup-cycles N change the counts. The procedure uses
the defaults, 10,000 and 4. The pty_latency_run target runs the harness with
defaults and writes pty_latency.json into the build tree. Each run is
recorded with the host identity (sw_vers, sysctl hw.model machdep.cpu.brand_string), commit, build type, load average and date; the
first diagnostic run was on the former macos-27.0-arm64-m1-max-32g pin.
It did not establish an accepted baseline; the first accepted run must use
the D-136 pin above.
The theme-switch harness
cvision_theme_switch_latency (benchmarks/theme_switch_latency.cpp, POSIX
only) measures the other §8 wall-clock limit on the same idle reference host.
Its 200×60 scene has two overlapping retained windows, a desktop and a status
line. Classic and Dark alternate. Timing starts after set_theme() has
invalidated the tree and ends after Application::step() has repainted,
composed, diffed and emitted the frame. The output sink counts writes and bytes
without PTY I/O or virtual-display parsing, so the timed span includes the
requested recompose and diff without host display work. Every transition must
touch the full grid and emit exactly one nonempty diff; otherwise the run is
invalid. CTest theme_switch_work_gate checks that deterministic workload but
does not gate wall-clock time on shared machines.
On the idle reference host, use the same build_dir and TMPDIR from above:
cmake --build "$build_dir" --target cvision_theme_switch_latency
"$build_dir/benchmarks/cvision_theme_switch_latency" \
--commit "$(git rev-parse --short HEAD)" \
--output "$TMPDIR/theme_switch_latency.json"
# later runs: add --baseline-p99-ns <the accepted theme-switch p99_ns>
The default is 40 discarded warm-up transitions followed by 1,000 measured
transitions. The JSON records the host, compiler, version, configuration, load,
grid, p50/p90/p99/maximum, mean frame bytes and pass/fail results. An accepted
run requires every measured transition below 5 ms and p99 no more than 5%
above the checked-in theme-switch baseline. --verify-only exercises four
alternating transitions without judging time; --iterations and --warmup
change counts for diagnostic runs. Record the exact source revision, build
inputs and host-idle evidence alongside the two JSON results before accepting
either baseline.
Progress displays
cvision_bench includes prepared painting of 20 visible rows from 101 tasks
and weighted aggregation over 100 children. The allocation gate separately
requires zero steady allocations for all four meter presentations, both task
row layouts and activity phases. Controller tests require one timer, no timer
for hidden/static/completed displays, and one future deadline after a late tick.
Mailbox tests cover bounded coalescing rather than unbounded posted callbacks.