diff options
| author | user <user@clank> | 2026-08-19 14:30:28 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-19 14:30:28 +0200 |
| commit | 20e1e104f2254c914b464a147aa0368491cdeb0c (patch) | |
| tree | ea3d1d0618426f7cdad703ad854b4ce07e06e9b3 /tests | |
| parent | slosh: the splash colours glyphs, never backgrounds (diff) | |
| download | slosh-20e1e104f2254c914b464a147aa0368491cdeb0c.tar.gz slosh-20e1e104f2254c914b464a147aa0368491cdeb0c.tar.xz slosh-20e1e104f2254c914b464a147aa0368491cdeb0c.zip | |
slosh: the logo flies into place -- a particle engine, statelessly
Every non-space glyph of the logo is a particle that flies to its cell
over the first two fifths of splash_ms, then *is* the logo for the
rest. There is deliberately no particle state anywhere: a position is
a pure function of (target, elapsed, seed), recomputed every frame
like the layout and the pane states and for the same reason -- physics
that is stored can drift, physics that is derived cannot. The seed is
the splash timestamp, so every attach flies differently and one splash
is coherent frame to frame; a per-particle hash staggers departures,
and the delay rescaling lands every straggler exactly at the end,
because an almost-assembled logo reads as a bug.
Five motions, picked by the clock and pinned by {"cmd":"splash",
"motion":N}: rain (fall in, column by column), scatter (converge from
everywhere), slide (rows enter from alternating sides), vortex (spiral
in around the centre, orbit shrinking and unwinding), shuffle (the
glyphs trade places, then sort themselves). The colour chains keep
playing over the box throughout, so particles catch their hue as they
cross into it.
expr_sin() is the expression language's own sine exported for C-side
animation: a motion sketched in the repl and one compiled into the
binary cannot disagree about what sin(90) is.
The tests learned two things the flights taught: the logo's opening
run appears three times per line and assembly is staggered, so a
middle occurrence can land first -- "assembled" is the row whose first
block glyph is where the match begins -- and windows need margins that
survive a parallel suite on a busy box.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_splash.py | 103 |
1 files changed, 72 insertions, 31 deletions
diff --git a/tests/test_splash.py b/tests/test_splash.py index 4e01f15..efbd7ec 100644 --- a/tests/test_splash.py +++ b/tests/test_splash.py @@ -3,8 +3,10 @@ A scripted session never attaches, so these drive it through the control API's `splash` verb -- the same call the server makes on MSG_HELLO. What is -asserted is the contract: centered, temporary, ended early by input, and -absent when splash_ms says never. +asserted is the contract: the glyphs fly in and land exactly, centered, +temporary, colourful, ended early by input, and absent when splash_ms says +never. Assembly takes the first two fifths of splash_ms, which is why each +test picks a duration to suit what it is watching. """ import os @@ -15,6 +17,11 @@ from harness import Session, check, report SH = ["/bin/sh", "-c", 'printf "\\033]2;p\\007"; stty raw -echo; cat'] GLYPHS = "\u2584\u2588\u2588\u2588\u2588\u2588\u2588\u2584" # ▄██████▄, line one +BLOCKS = ("\u2584", "\u2588", "\u2580") # any glyph a particle could be + + +def flying(snap): + return any(b in snap.screen() for b in BLOCKS) def cfg(text): @@ -24,43 +31,59 @@ def cfg(text): return f.name -def test_the_splash_shows_centered_and_goes_away(): - conf = cfg("splash_ms 400\n") +def test_the_splash_assembles_centered_and_goes_away(): + conf = cfg("splash_ms 1600\n") # assembly done by 640ms, margin under load with Session(SH, cols=80, rows=24, config=conf) as s: s.settle(30) - check("no greeting until asked", GLYPHS not in s.snapshot().screen(), "") + check("no greeting until asked", not flying(s.snapshot()), "") s.api("splash") - snap = s.snapshot() - check("the logo is up", GLYPHS in snap.screen(), snap.screen()[:200]) + + # The logo's first line contains its opening run three times over, and + # assembly is staggered, so a middle occurrence can land before the + # leftmost: "assembled" is the row whose *first* block glyph is where + # the match begins. + def landed_row(sn): + for i, line in enumerate(sn.text): + if GLYPHS in line and line.index(GLYPHS) == line.index("\u2584"): + return i + return None + + snap = s.until(lambda sn: landed_row(sn) is not None, timeout_ms=6000) + y = landed_row(snap) + check("the glyphs land as the logo", y is not None, snap.screen()[:200]) # Centered: the logo is 44 columns in an 80-column screen, so its # first glyph lands at (80 - 44) / 2 give or take the box margin. - rows = [i for i, line in enumerate(snap.text) if GLYPHS in line] - line = snap.text[rows[0]] - x = line.index("\u2584") + x = snap.text[y].index(GLYPHS) if y is not None else -1 check("h-centered", 14 <= x <= 22, f"x={x}") - check("v-centered", 6 <= rows[0] <= 12, f"y={rows[0]}") + check("v-centered", y is not None and 6 <= y <= 12, f"y={y}") - snap = s.until(lambda sn: GLYPHS not in sn.screen()) - check("and it goes away by itself", GLYPHS not in snap.screen(), "") + snap = s.until(lambda sn: not flying(sn)) + check("and it goes away by itself", not flying(snap), "") os.unlink(conf) -def test_any_key_ends_it_and_still_lands(): - conf = cfg("splash_ms 10000\n") # long enough that expiry cannot pass this +def test_every_motion_lands_exactly(): + """Five ways to fly in, one place to land: whatever the motion, assembly + ends with every particle on its cell -- an almost-assembled logo reads as + a bug, which is what the per-particle delay rescaling exists to prevent.""" + conf = cfg("splash_ms 1600\n") # landed window: 960ms, wide enough under load with Session(SH, cols=80, rows=24, config=conf) as s: s.settle(30) - s.api("splash") - check("up", GLYPHS in s.snapshot().screen(), "") - s.send("marker") # plain keys: dismiss the greeting, reach the shell - snap = s.until(lambda sn: GLYPHS not in sn.screen()) - check("a keystroke puts it away", GLYPHS not in snap.screen(), "") - snap = s.until_text("marker") - check( - "...and still reaches the pane it was aimed at", - "marker" in snap.screen(), - snap.screen()[:200], - ) + for motion in range(5): + s.api("splash", fx=0, motion=motion) + # Landed means the line exists *and* nothing is still passing to + # its left: the first block glyph on that row is the logo's own. + landed = lambda sn: any( + GLYPHS in line and line.index("\u2584") == 18 for line in sn.text + ) + snap = s.until(landed, timeout_ms=6000) + check( + f"motion {motion} lands the logo, exactly placed", + landed(snap), + snap.screen()[:200], + ) + s.until(lambda sn: not flying(sn), timeout_ms=6000) # expire between rounds os.unlink(conf) @@ -70,12 +93,11 @@ def test_the_splash_is_colourful_not_a_tinted_logo(): the logo must show genuinely different *hues* -- counted as dominant- channel families, because two hundred shades of the same blue is exactly the failure this guards against.""" - conf = cfg("splash_ms 10000\n") + conf = cfg("splash_ms 2500\n") # assembled by 1000ms, coloured until 2500 with Session(SH, cols=80, rows=24, config=conf) as s: s.settle(30) s.api("splash", fx=0) # spectrum: deterministic, and the loudest - s.settle(60) - snap = s.snapshot() + snap = s.until(lambda sn: GLYPHS in sn.screen(), timeout_ms=6000) rows = [i for i, line in enumerate(snap.text) if GLYPHS in line] families = set() for y in rows: @@ -94,13 +116,32 @@ def test_the_splash_is_colourful_not_a_tinted_logo(): os.unlink(conf) +def test_any_key_ends_it_mid_flight_and_still_lands(): + conf = cfg("splash_ms 10000\n") # long enough that expiry cannot pass this + with Session(SH, cols=80, rows=24, config=conf) as s: + s.settle(30) + s.api("splash") + snap = s.until(flying) + check("particles are in the air", flying(snap), "") + s.send("marker") # plain keys: dismiss the greeting, reach the shell + snap = s.until(lambda sn: not flying(sn)) + check("a keystroke puts the whole flight away", not flying(snap), "") + snap = s.until_text("marker") + check( + "...and still reaches the pane it was aimed at", + "marker" in snap.screen(), + snap.screen()[:200], + ) + os.unlink(conf) + + def test_splash_ms_zero_means_never(): conf = cfg("splash_ms 0\n") with Session(SH, cols=80, rows=24, config=conf) as s: s.settle(30) s.api("splash") s.settle(30) - check("nothing appears", GLYPHS not in s.snapshot().screen(), "") + check("nothing appears", not flying(s.snapshot()), "") os.unlink(conf) @@ -111,7 +152,7 @@ def test_a_screen_too_small_gets_no_greeting(): s.settle(30) s.api("splash") s.settle(30) - check("no logo on 40 columns", GLYPHS not in s.snapshot().screen(), "") + check("no logo on 40 columns", not flying(s.snapshot()), "") os.unlink(conf) |
