Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions docs/performance/motion-background/environment.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
2026-09-11T00:25:01.830518+00:00
Linux anubis 7.1.5-76070105-generic #202607241434~1787955023~24.04~38a419f SMP PREEMPT_DYNAMIC Fri A x86_64 x86_64 x86_64 GNU/Linux
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 46 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) Ultra 5 325
CPU family: 6
Model: 204
Thread(s) per core: 1
Core(s) per socket: 8
Socket(s): 1
Stepping: 3
CPU(s) scaling MHz: 98%
CPU max MHz: 2100.0000
CPU min MHz: 400.0000
BogoMIPS: 6220.80
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a rdseed adx smap clflushopt clwb intel_pt sha_ni xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk avx_vnni lass fred lam wbnoinvd dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req hfi vnmi umip pku ospke waitpkg gfni vaes vpclmulqdq rdpid bus_lock_detect movdiri movdir64b fsrm md_clear serialize arch_lbr ibt flush_l1d arch_capabilities
Virtualization: VT-x
L1d cache: 320 KiB (8 instances)
L1i cache: 512 KiB (8 instances)
L2 cache: 16 MiB (5 instances)
L3 cache: 12 MiB (1 instance)
NUMA node(s): 1
NUMA node0 CPU(s): 0-7
Vulnerability Gather data sampling: Not affected
Vulnerability Ghostwrite: Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit: Not affected
Vulnerability L1tf: Not affected
Vulnerability Mds: Not affected
Vulnerability Meltdown: Not affected
Vulnerability Mmio stale data: Not affected
Vulnerability Old microcode: Not affected
Vulnerability Reg file data sampling: Not affected
Vulnerability Retbleed: Not affected
Vulnerability Spec rstack overflow: Not affected
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2: Mitigation; Enhanced / Automatic IBRS; IBPB conditional; PBRSB-eIBRS Not affected; BHI BHI_DIS_S
Vulnerability Srbds: Not affected
Vulnerability Tsa: Not affected
Vulnerability Tsx async abort: Not affected
Vulnerability Vmscape: Not affected

Rust 1.90.0; edition/workspace configuration unchanged.
Build: CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=cc RUSTFLAGS=""
Release overrides: CARGO_PROFILE_RELEASE_DEBUG=line-tables-only CARGO_PROFILE_RELEASE_STRIP=none
Release still uses opt-level=3, fat LTO, codegen-units=1.
Toolchain and dependencies installed under /tmp; clang/mold unavailable on this host.
Run environment: NO_COLOR unset, TERM=xterm-256color, COLORTERM=truecolor.
Shared host; no affinity or priority changes.
SHA256 310e2455b6abb6e44976d4ad2aa7b5f82b982ae46382d96e16094d8b6e6f572d target/debug/examples/motion_storybook
SHA256 415b36966afa1a414256d3d6f6d24033d7bb9bc20e76d6ac0f2ba319877b6a74 target/release/examples/motion_storybook
SHA256 f65b0b0b45142d0cda1a786264dca9547adf88a3a7740437a0f78f3722877540 tui-components/src/motion/fields.rs
SHA256 b23c9fef745d6469e193e830aa208643f295b841f15cf45a2536304d7a89ff56 tui-components/src/motion/mod.rs
SHA256 87405be278af80b04eee1700e20304c67e71beef8bcb365de4c0a792a0309ede tui-components/src/motion/tests.rs
SHA256 26c8c855e5fa18585da6640239393b2de2a84af7cfbcc69636042103a83fa74e tui-components/examples/motion_storybook/e2e.rs
SHA256 e83e867038912687a281e5221feacdc399bf9e98c443e3024f6763929751792c tui-components/examples/motion_storybook/profile.rs
SHA256 0aba99815d6bdc8f4e04a2cdc6236299bd9b3572edfd34daecaf1412a02e2698 tui-components/examples/motion_storybook/story.rs
SHA256 bed6a3f94f7812e7416bff9e57371c8803dde6412560f7281c99c0c70d42cae6 tui-components/examples/motion_storybook.rs
87 changes: 87 additions & 0 deletions docs/performance/motion-background/methodology.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Motion profiling methodology

This is a baseline investigation of the stripped `motion_storybook`, before
optimizing field geometry, sampling, colors, or terminal output. The example
runs only Dots ↔ Formations (three seconds each way), on the whole screen.
Two policies pin the destination to Platoon or Muster; the default public
component still uses the ambient cycle. No former login/card UI is rendered.

## Measurements

- **Headless:** real widget → Ratatui lazy buffer diff → Crossterm encoding into
a byte-counting sink → buffer reset. No test backend, simplified field model,
mocked encoder, OS output, sleeping, or terminal parser. A separate diff-only
scan is a warm-cache diagnostic, excluded from pipeline total. It must not be
treated as an exact additive split of the fused diff/encoder pass.
- **Live:** actual `Terminal<CrosstermBackend<Stdout>>::draw` through a PTY into
isolated tmux, recording widget time, total draw time, the remainder outside
the widget, and start-to-start intervals. The remainder includes terminal
size queries, buffer diff, ANSI encoding, write/flush, cursor handling, and
buffer reset. Timing is accumulated in memory and saved only after alternate
screen restoration. No per-cell timing is added to the renderer.
- **Planned CPU sampling (interrupted; no retained profile here):** optimized build with release optimization/LTO unchanged,
`debug=line-tables-only`, `strip=none`, using Linux perf userspace cycles and
DWARF call stacks. Instruction samples identify hot functions/source lines;
they are separate runs, not mixed into the wall-clock timing data.

A completed write is not proof that a graphical terminal displayed that frame.
The live results measure production throughput and tmux/PTY backpressure, not
monitor refresh, compositor presentation, or the user's terminal's displayed FPS.
The headless sink's Write-call count is not a system-call count.

## Sampling protocol

All cases run serially after builds/tests finish, on the same shared host, with
`NO_COLOR` unset, `TERM=xterm-256color`, `COLORTERM=truecolor`. No affinity,
priority, power policy, terminal setting, or system security setting is changed.

The headless matrix has debug/release × 80×24/120×40/200×60/320×100 ×
Platoon/Muster × three repetitions. Each run warms a full six-second loop, then
records 300 deterministic frames at a simulated 25 FPS (two loops). Case order
is shuffled with seed 926. The first measured full repaint is excluded from
summaries, leaving 897 frames per configuration. The clock resets only between
runs, not at zoom reversals.

The live matrix records 300 frames per case at 25 FPS for debug/release ×
120×40/200×60/320×100 × both variants. Additional release 200×60 cases request
60 FPS. Geometry advances by actual elapsed time, preserving the original
start-to-start scheduler and its behavior under overload. As a consequence,
heavy phases produce fewer frames when late; headless and live percentile
populations are not identical. First live frames are excluded from summaries.

The report uses nearest-rank percentiles. `work_over_budget_pct` compares work
time with 1/FPS. `intervals_over_1_5_budget_pct` counts conspicuously long
start-to-start gaps, allowing small scheduler jitter. Neither is claimed to be
a measured count of display-dropped frames. `produced_fps` is the reciprocal of
mean start-to-start interval. Phase bands are early [0,.45), middle [.45,.75),
and late [.75,1].

## Reproduce

From `cli/nori-rs`, build both example profiles first, then run without Cargo in
the timed region:

```sh
cargo build -p nori-tui-components --example motion_storybook
CARGO_PROFILE_RELEASE_DEBUG=line-tables-only CARGO_PROFILE_RELEASE_STRIP=none \
cargo build --release -p nori-tui-components --example motion_storybook

env -u NO_COLOR TERM=xterm-256color COLORTERM=truecolor \
target/release/examples/motion_storybook --bench --size 200x60 \
--frames 300 --profile /tmp/platoon.csv

env -u NO_COLOR TERM=xterm-256color COLORTERM=truecolor \
target/release/examples/motion_storybook --bench --size 200x60 \
--muster --frames 300 --profile /tmp/muster.csv

# In the terminal you want to evaluate, sized as desired:
env -u NO_COLOR TERM=xterm-256color COLORTERM=truecolor \
target/release/examples/motion_storybook --muster --frames 300 \
--profile /tmp/muster-live.csv

python3 tui-components/scripts/summarize-motion-profile.py \
/tmp/platoon.csv /tmp/muster.csv /tmp/muster-live.csv --output /tmp/summary
```

The summary tool also accepts `.csv.gz` inputs. See [measurement status](report.md)
for which artifacts survived the interruption.
27 changes: 27 additions & 0 deletions docs/performance/motion-background/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Motion measurement status

A new durable baseline and completed optimization pass are available in the
[September 11 report](../motion-low-hanging/README.md). The notes below describe
the earlier interrupted investigation.

The initial investigation was interrupted. Its broad headless/live runs reported
frame-budget overruns at larger terminal sizes, but their raw traces were stored
in temporary files and are no longer available. The planned CPU sampling for
this checkout did not produce a retained, verified profile. The methodology is
a record of that protocol, not a claim that every listed artifact is present.

A subsequent small comparison against `codex/animated-onboarding` is retained in
[the comparison directory](../motion-comparison/README.md), including raw frame
CSVs, exact commands, binary hashes, and interpretation limits. That comparison
uses existing release binaries with different visual/sampling policies and no
terminal I/O; it is not a measurement of displayed FPS.

The comparison describes the implementations before the API cleanup. Its data
files are a historical baseline; rerunning its script replaces those files with
measurements of the binaries currently at the recorded paths. Preserve that
baseline and use a separate output directory for future experiments.

The cleanup separates the stateless rendering API, optional controller, palette,
and geometry modules. It retains the existing sampling schedule and scene
formulas. No post-cleanup performance improvement is claimed. See the
[integration guide](../../reference/motion-background.md) for the current API.
99 changes: 99 additions & 0 deletions docs/performance/motion-comparison/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Comparison of the two uncommitted motion implementations

Measured September 10, 2026. Both checkouts have base commit
`fd92abe23e6c94c66ea6ef7afa94678895ecc8f7`; their motion implementations are
uncommitted additions. No component code was changed for this comparison.

Current: `/home/clifford/Documents/source/nori/cli`.
Other: `/home/clifford/.codex/worktrees/b08b/nori/cli/.worktrees/animated-onboarding`,
branch `codex/animated-onboarding`.

## Measurements

Run `python3 run.py` from any directory to reproduce against the existing release
binaries. It runs sequentially, shuffling 16 cases with seed 910: two sizes,
two variants, two implementations, two repetitions. Each process records 150
frames at simulated 25 FPS, excluding its first measured frame from summaries.
There is no live terminal, CPU sampling, rebuild, or concurrent benchmark.
Raw CSVs, the summary, exact commands, and binary SHA256 hashes are saved here.

| Size / variant | Current median / p95 ms | Worktree median / p95 ms |
|---|---:|---:|
| 120x40 default | 0.86 / 5.83 | 3.42 / 6.86 |
| 120x40 muster | 0.91 / 6.38 | 3.97 / 8.16 |
| 240x80 default | 3.92 / 24.67 | 14.01 / 29.59 |
| 240x80 muster | 3.04 / 23.31 | 15.38 / 28.21 |

These are headless widget + buffer diff + ANSI encoding + buffer reset timings,
not displayed FPS. No retained frame exceeded 40 ms in this small run. This does
not supersede the earlier live results or prove either version is smooth.
Absolute timings differ substantially from the earlier runs; compare the two
implementations within this run rather than treating the difference from old
numbers as a code improvement. There were no new rendering optimizations.

This is a comparison of the existing examples, not identical visual work:

- Current pins Platoon or Muster at ambient time 12s. Other starts at time 0s
(quiet field) or 94s (muster), retaining its ambient cycle.
- Their noise/hash, muster shape, palettes, and sampling schedules differ.
- Current warms a full loop. Other warms ten copies of its initial frame.
- Current streams the actual lazy diff to Crossterm. Other collects the diff
into a new Vec to time diff and encoding independently, adding allocation
and separating operations that are fused in live rendering. Widget-only
p95 is also lower in current, so the harness difference is not the whole gap.
- Build provenance was not normalized by rebuilding; these are existing release
binaries, with hashes recorded. Small sample count and shared-host scheduling
limit precision.

Current generated approximately 13–18% more ANSI bytes per frame in these cases.
Its finer palette and different pictures make that unsurprising, but the exact
cause was not isolated. A real terminal may favor the other implementation
more than these headless timings suggest.

## Design assessment

Both correctly leave terminal ownership, events, authentication, and scheduling
outside the reusable widget, and both have determinism/clipping/snapshot tests.
Neither creates a scene graph of heap-allocated objects per field sample.

The worktree has the cleaner minimal animation interface: phase, explicit time,
and transition progress. Callers can select duration, easing, and seek directly.
It also separates zoom/formation geometry from tile/creature geometry. Its
four precomputed styles are simpler and cheaper than current's per-cell RGB
mixing. At the fully formed endpoint it skips the now-invisible island field;
current still calculates that field before blending with weight one.

Current is more complete for integration: interruptible caller-held state,
custom theme-derived palette, an optional quiet region that avoids sampling
under a panel, ASCII fallback, and an explicit fixed formation selector. Its
Field object already computes scale and shape once per frame, where the other
source expresses zoom invariants inside the per-cell function. Current's
noise interpolation weights are outside the eight-corner loop. Compiler
optimization can affect the realized benefit of these source differences.

Current's mandatory MotionState also hardcodes three seconds per adjacent phase
and exposes no direct seek/easing control. That is less flexible as the lowest
level component API. It is better treated as an optional controller over a
renderer accepting explicit time and transition position.

The largest obvious workload difference is braille selection: at zoom 0.5,
current takes one sample per cell while the other takes eight. At zoom 0.75,
current averages 4.94 versus eight (over the Bayer pattern). Consequently,
current's much lower median is partly a different sampling/visual policy,
not a demonstration of an equivalently detailed renderer being four times faster.

The worktree has stronger retained profiling evidence: compressed frame traces,
CPU profiles for quiet/muster/live runs, and syscall counts. Its separate
geometry mode compiles the actual geometry source. Current has more viewport
and FPS controls and a closer-to-live lazy-diff benchmark, but its earlier
raw traces were in temporary storage and are unavailable after the interruption.
These new comparison traces are persisted in the repository directory.

## Recommendation

Use current's frame context and integration capabilities, adopt the other
version's explicit-time/progress rendering API, and retain state transitions as
an optional helper. Preserve the geometry module split and endpoint early-out.
Choose one desired visual/sampling schedule before comparing optimizations.
Then measure frame/tile invariant reuse and output batching independently.
Neither existing branch resolves the large-terminal smoothness problem yet.
Loading
Loading