diff --git a/mainline.py b/mainline.py index d7f6456..7d34886 100755 --- a/mainline.py +++ b/mainline.py @@ -491,6 +491,7 @@ def _save_cache(items): # ─── STREAM ─────────────────────────────────────────────── _SCROLL_DUR = 3.75 # seconds per headline +_FRAME_DT = 0.05 # 50ms base frame rate (20 FPS) FIREHOSE_H = 12 # firehose zone height (terminal rows) _mic_db = -99.0 # current mic level, written by background thread _mic_stream = None @@ -766,13 +767,14 @@ def stream(items): fh = FIREHOSE_H if FIREHOSE else 0 sh = h - fh # scroll zone height GAP = 3 # blank rows between headlines - dt = _SCROLL_DUR / (sh + 15) * (4 if FIREHOSE else 2) + scroll_interval = _SCROLL_DUR / (sh + 15) * 2 # active blocks: (content_rows, color, canvas_y, meta_idx) active = [] cam = 0 # viewport top in virtual canvas coords next_y = sh # canvas-y where next block starts (off-screen bottom) noise_cache = {} + scroll_accum = 0.0 def _noise_at(cy): if cy not in noise_cache: @@ -780,17 +782,31 @@ def stream(items): return noise_cache[cy] while queued < HEADLINE_LIMIT or active: + t0 = time.monotonic() w, h = tw(), th() fh = FIREHOSE_H if FIREHOSE else 0 sh = h - fh - # Enqueue new headlines when room at the bottom - while next_y < cam + sh + 10 and queued < HEADLINE_LIMIT: - t, src, ts = _next_headline(pool, items, seen) - content, hc, midx = _make_block(t, src, ts, w) - active.append((content, hc, next_y, midx)) - next_y += len(content) + GAP - queued += 1 + # Advance scroll on schedule + scroll_accum += _FRAME_DT + while scroll_accum >= scroll_interval: + scroll_accum -= scroll_interval + cam += 1 + + # Enqueue new headlines when room at the bottom + while next_y < cam + sh + 10 and queued < HEADLINE_LIMIT: + t, src, ts = _next_headline(pool, items, seen) + content, hc, midx = _make_block(t, src, ts, w) + active.append((content, hc, next_y, midx)) + next_y += len(content) + GAP + queued += 1 + + # Prune off-screen blocks and stale noise + active = [(c, hc, by, mi) for c, hc, by, mi in active + if by + len(c) > cam] + for k in list(noise_cache): + if k < cam: + del noise_cache[k] # Draw scroll zone top_zone = max(1, int(sh * 0.25)) @@ -841,19 +857,12 @@ def stream(items): gi = random.randint(0, g_limit - 1) buf[gi] = f"\033[{gi+1};1H{glitch_bar(w)}" - sys.stdout.write("".join(buf)) + sys.stdout.buffer.write("".join(buf).encode()) sys.stdout.flush() - time.sleep(dt) - # Advance viewport - cam += 1 - - # Prune off-screen blocks and stale noise - active = [(c, hc, by, mi) for c, hc, by, mi in active - if by + len(c) > cam] - for k in list(noise_cache): - if k < cam: - del noise_cache[k] + # Precise frame timing + elapsed = time.monotonic() - t0 + time.sleep(max(0, _FRAME_DT - elapsed)) sys.stdout.write(CLR) sys.stdout.flush()