mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
## Summary - ship **Buzz Term** end to end: the terminal engine/runtime, mounted desktop substrate, and user-visible naming - add Quinn's tape-deck-inspired banner: a beveled chassis filled by the `buzz term` wordmark, surrounded by a complete-hex field - derive the wordmark's three-stop sweep from each theme's terminal palette so primary, secondary, and accent roles remain visibly distinct across all 62 shipped themes, including light themes - paint the banner once on its own pointer-transparent canvas; PTY rendering beneath it remains unchanged ## Banner behavior - uses the renderer's shared `8.4 × 17` cell metrics and production aspect ratio `2.0238` - regenerates only for viewport/theme changes; palette switches repaint correctly while the banner is visible - dismisses on non-empty output from the active terminal session; empty output and inactive sessions do not dismiss it - fails closed below **70 columns** rather than squeezing or clipping the wordmark - adds **8 lines** to `terminalRenderer.ts` for shared cell metrics and **zero lines inside `paint()`** ## Screenshots | Buzz (light) | Buzz Dark | |---|---| |  |  | | Kanagawa Lotus (light) | Red | |---|---| |  |  | Additional production-aspect finals: [Vesper](https://buzz.block.builderlab.xyz/media/9ca6514b63f8cfb2107a85ca46f16a940c0883848e6fbc718e411af94aa13100.png), [Min Dark](https://buzz.block.builderlab.xyz/media/f67bd2970e5d64ffb07b1ae78ab58c847e6ebc23e7e7a48e067eb024dba64ec8.png), and [Dark Plus](https://buzz.block.builderlab.xyz/media/290fee08924f37d064abc687ecf3e9526ab05b87e8e56d610f23048949793dbe.png). The screenshot harness was checked against the shipped painter at this exact head: all **2,541 draw calls** matched on color, glyph, x, and y; four deliberate divergence controls fired. ## Verification at `98ebc8f9048bd5f0ceb7e843b67874d642f0b7fd` - desktop tests: **3,946 / 3,946** - TypeScript: clean - checks: pass (two pre-existing informational `useTemplate` notices only) - integration/e2e: PASS (independent exact-SHA lane; artifacts recorded in the originating Buzz thread) - artifact/dead-path sweep: clean - redteam G1–G7: PASS - all six named banner emitter-deletion mutants die - independent handwritten five-row full-wordmark fixture kills Quinn's seven-mutant battery, including a one-pixel glyph change - real `112 × 46` canvas-rect dismissal tests separately cover active non-empty, active empty, and inactive non-empty output - layer-drop and zero-draw painter mutants die; z-order and pointer-events verified - CI's `tsc && vite build` includes all three banner modules - performance at DPR 2 (worst-case measured envelope): - one-time content paint: **~0.7–0.8 ms**, paid only when the banner is built or its palette changes - busy compositor, CSS `1277 × 697`, backing `2554 × 1394`: **470–497 µs/frame** for the full banner (**2.82–2.98%** of a 60 Hz frame) - busy compositor, CSS `1920 × 1080`, backing `3840 × 2160`: **1,139–1,212 µs/frame** (**6.83–7.27%**) - empty, one-glyph, and full-banner controls converge: compositor cost follows backing-layer area and DPR rather than painted-cell count - in the actual idle welcome state, cost is below both vsync-clamped rigs' resolution; it is not claimed as zero - **Pane cross-rig spread: resolved at matched loop rate.** Two independent rigs initially differed 2.3× (58–68 vs 136 µs/Mpx of backing store; pane, CSS 1277×697 / backing 2554×1394, DPR 2). The cause of *that* spread is rAF loop rate: the higher figure came from a free-running loop at ~1600fps. Throttled to ~200–236fps, both rigs read 58–68 µs/Mpx (1.25–1.44% of a 60Hz frame). The busy-composite figures quoted above remain the **unthrottled worst case** and are conservative by ~2.3× at the pane. Not established: the mechanism and sign of free-running distortion (one rig under-charges ~15%, the other over-charges 2.3×), and the 1080p figure has not been re-measured throttled. - the layer paints only on generation/theme/resize and dismisses on first non-empty active-session output, so the measurable busy cost is a short-lived worst case rather than a persistent PTY paint-path tax ## Follow-ups in this PR These are intentionally subsequent commits after the certified static-banner head, not claims about `98ebc8f90`: 1. close the compositor metrology: remeasure the 1080p point throttled and characterize the opposite-sign free-running rAF distortion, with each measurement regime stated 2. add Tyler's animated honeycomb color waves, gated by `prefers-reduced-motion`, a full 62-theme phase-sweep contrast check, and DPR-2 per-tick performance certification 3. land the already-proven mounted theme-switch regression probe from `RESEARCH/BUZZ_TERM_G3A_PROBE/` 4. bound the slow/hang-shaped G1-c mutant `waitFor` 5. optionally trim the generator to its ink bounding box, reducing the minimum viewport from 70 to 62 columns --------- Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Signed-off-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Co-authored-by: npub1cc3ha7z055mu0rwwu7806t2wt8mj3pvu0uv5mfp2c50dahaqhczshdalg6 <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
170 lines
6.0 KiB
Rust
170 lines
6.0 KiB
Rust
//! Mutation-sensitive byte fixtures for the two parser fences.
|
|
//!
|
|
//! These use the shipping `Terminal::feed` path. Arms that could be masked by
|
|
//! the other fence disable it explicitly; the switches are runtime values, not
|
|
//! cargo features, so the default test binary always contains every arm.
|
|
|
|
use alacritty_terminal::grid::Dimensions;
|
|
use alacritty_terminal::index::{Column, Line, Point};
|
|
use buzz_terminal::fences::{Fences, OSC_BUDGET, SYNC_CAP};
|
|
use buzz_terminal::{Size, Terminal};
|
|
|
|
const CHUNK: usize = 8192;
|
|
const G1_BYTES: usize = 2 << 20;
|
|
const G2_FRAMES: usize = 40;
|
|
const G2_FRAME_BYTES: usize = 1_900 * 1024;
|
|
|
|
fn size() -> Size {
|
|
Size {
|
|
columns: 120,
|
|
screen_lines: 40,
|
|
scrollback: 2000,
|
|
}
|
|
}
|
|
|
|
fn feed_synchronized(term: &mut Terminal, payload: &[u8], close: bool) {
|
|
term.feed_fully(b"\x1b[?2026h");
|
|
for chunk in payload.chunks(CHUNK) {
|
|
term.feed_fully(chunk);
|
|
}
|
|
if close {
|
|
term.feed_fully(b"\x1b[?2026l");
|
|
}
|
|
}
|
|
|
|
fn repeated(pattern: &[u8], bytes: usize) -> Vec<u8> {
|
|
pattern.iter().copied().cycle().take(bytes).collect()
|
|
}
|
|
|
|
fn count_markers(term: &Terminal, markers: usize) -> usize {
|
|
let grid = term.term().grid();
|
|
let mut text = String::new();
|
|
let top = -(grid.history_size() as i32);
|
|
for line in top..term.size().screen_lines as i32 {
|
|
for column in 0..term.size().columns {
|
|
text.push(grid[Point::new(Line(line), Column(column))].c);
|
|
}
|
|
text.push('\n');
|
|
}
|
|
(0..markers)
|
|
.filter(|m| text.contains(&format!("MK{m:03}")))
|
|
.count()
|
|
}
|
|
|
|
fn legitimate_frame(markers: usize, bytes: usize) -> Vec<u8> {
|
|
let mut payload = Vec::with_capacity(bytes);
|
|
for marker in 0..markers {
|
|
payload.extend_from_slice(format!("MK{marker:03}\r\n").as_bytes());
|
|
let target = bytes * (marker + 1) / markers;
|
|
while payload.len() < target {
|
|
payload.extend_from_slice(b"\x1b[1;32mx\x1b[0m");
|
|
}
|
|
payload.extend_from_slice(b"\r\n");
|
|
}
|
|
payload.truncate(bytes);
|
|
payload
|
|
}
|
|
|
|
/// G1: every hostile content shape must remain below the deterministic byte
|
|
/// bound, and the same shape with F1 deleted must cross it. Keeping both arms
|
|
/// adjacent prevents a simplified fixture from becoming vacuously cheap.
|
|
#[test]
|
|
fn g1_sync_abort_bounds_all_hostile_shapes() {
|
|
let shapes: [(&str, &[u8]); 5] = [
|
|
("sgr", b"\x1b[1;32mbuzz\x1b[0m\r\n"),
|
|
("ascii", b"buzz substrate output\r\n"),
|
|
("emoji", "🐝🚀✨\r\n".as_bytes()),
|
|
("zalgo", "z\u{0301}\u{0302}\u{0303}\u{0304}\r\n".as_bytes()),
|
|
("truecolor", b"\x1b[38;2;255;0;128mRGB\x1b[0m\r\n"),
|
|
];
|
|
|
|
for (name, pattern) in shapes {
|
|
let payload = repeated(pattern, G1_BYTES);
|
|
let (mut fenced, _) = Terminal::new(size(), Fences::ALL);
|
|
feed_synchronized(&mut fenced, &payload, false);
|
|
let fenced_stats = fenced.stats();
|
|
assert!(fenced_stats.sync_aborts > 0, "{name}: F1 never fired");
|
|
assert!(
|
|
fenced_stats.max_release <= 2 * SYNC_CAP,
|
|
"{name}: fenced release {} exceeds 128 KiB",
|
|
fenced_stats.max_release
|
|
);
|
|
|
|
let (mut unfenced, _) = Terminal::new(size(), Fences::NONE);
|
|
feed_synchronized(&mut unfenced, &payload, false);
|
|
let unfenced_stats = unfenced.stats();
|
|
assert_eq!(unfenced_stats.sync_aborts, 0, "{name}: control enabled F1");
|
|
assert!(
|
|
unfenced_stats.max_release > 2 * SYNC_CAP,
|
|
"{name}: unfenced release {} stayed inside the gate; fixture is vacuous",
|
|
unfenced_stats.max_release
|
|
);
|
|
}
|
|
}
|
|
|
|
/// G2 arm 1: deletion oracle. F1 remains enabled because this arm proves F2
|
|
/// deletion under the combined production configuration.
|
|
#[test]
|
|
fn g2_hostile_unsynchronized_osc_resets_parser() {
|
|
let (mut term, _) = Terminal::new(size(), Fences::ALL);
|
|
term.feed_fully(b"\x1b]0;");
|
|
for chunk in repeated(b"A", OSC_BUDGET * 4).chunks(CHUNK) {
|
|
term.feed_fully(chunk);
|
|
}
|
|
assert!(term.stats().osc_resets > 0, "F2 never rebuilt the parser");
|
|
}
|
|
|
|
/// G2 arm 2: every synchronized release is attributed. F1 is disabled so its
|
|
/// small abort releases cannot mask an implementation that omits ESU flushes.
|
|
#[test]
|
|
fn g2_each_synchronized_flush_is_attributed() {
|
|
let payload = repeated(b"A", G2_FRAME_BYTES);
|
|
let (mut term, _) = Terminal::new(size(), Fences::OSC_ONLY);
|
|
for _ in 0..G2_FRAMES {
|
|
feed_synchronized(&mut term, &payload, true);
|
|
}
|
|
let stats = term.stats();
|
|
assert_eq!(stats.sync_aborts, 0, "F1 must be disabled in this arm");
|
|
assert_eq!(
|
|
stats.osc_resets, G2_FRAMES as u64,
|
|
"expected one reset for each atomic synchronized release"
|
|
);
|
|
assert!(
|
|
stats.charged_bytes >= (G2_FRAMES * G2_FRAME_BYTES) as u64,
|
|
"flush bytes were omitted from attribution: {} charged",
|
|
stats.charged_bytes
|
|
);
|
|
}
|
|
|
|
/// G2 arm 3: parser-visible attribution preserves a legitimate 1.5 MiB frame.
|
|
/// F1 is disabled; raw-input counting would reset mid-frame and lose markers.
|
|
#[test]
|
|
fn g2_legitimate_large_frame_preserves_all_markers() {
|
|
let markers = 200;
|
|
let payload = legitimate_frame(markers, 1_500 * 1024);
|
|
let (mut term, _) = Terminal::new(size(), Fences::OSC_ONLY);
|
|
feed_synchronized(&mut term, &payload, true);
|
|
assert_eq!(
|
|
count_markers(&term, markers),
|
|
markers,
|
|
"legitimate frame lost markers"
|
|
);
|
|
}
|
|
|
|
/// Legitimacy control: neither fence alone nor the production combination may
|
|
/// corrupt a normal synchronized frame.
|
|
#[test]
|
|
fn g2_legitimate_frame_survives_each_fence_configuration() {
|
|
let markers = 200;
|
|
let payload = legitimate_frame(markers, 128 * 1024);
|
|
for fences in [Fences::SYNC_ONLY, Fences::OSC_ONLY, Fences::ALL] {
|
|
let (mut term, _) = Terminal::new(size(), fences);
|
|
feed_synchronized(&mut term, &payload, true);
|
|
assert_eq!(
|
|
count_markers(&term, markers),
|
|
markers,
|
|
"{fences:?} lost markers"
|
|
);
|
|
}
|
|
}
|