From b4781b334a06d16aedb0b6a3d604cb249eb619de Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 17:56:10 -0300 Subject: Lift moves at the display's rate: render-time animation Review (and the coordinator): Lift stepped with the core's 16 ms ticks, so a 144 Hz display held each shadow for 2-3 refreshes. The lift springs are now sampled at each frame's own time (p.now_ns), and Pardes.continuous() reports a render-time animation in motion: while it is, the pump draws every loop (a 1 ms wait takes only the input there is) and the present's vsync paces it at the display's rate. They left nextWake; the stepped, frame-indexed animations (transitions, message fades, the theme fade) and every golden are unchanged. With PARDES_TEST_CLOCK, each loop of a moving render-time animation moves the virtual clock by a display frame at PARDES_TEST_HZ (default 60), so a 144 Hz series is deterministic: a focus switch is 51 distinct frames at 144 Hz. Test: renders 6.9 ms apart give rising lifts; settled, no frames. The tty never lifts (GUI capability). Shared files touched: pardes.zig (continuous, pump), gui.zig. --- src/pardes.zig | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'src/pardes.zig') diff --git a/src/pardes.zig b/src/pardes.zig index 449ef798..04ef4b36 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -5040,7 +5040,9 @@ pub const Pardes = struct { if (v.wait_input) |f| { // Asleep until input or until core animation next needs a // frame; 0 is no timeout at all. - const timeout: u32 = if (p.nextWake()) |due| wake: { + // A render-time animation draws every loop: the wait only takes + // what input is there. + const timeout: u32 = if (p.continuous()) 1 else if (p.nextWake()) |due| wake: { const now = if (v.now) |clock| clock(h.ctx) else p.now_ns; const ms = ((due -| now) + std.time.ns_per_ms - 1) / std.time.ns_per_ms; break :wake @intCast(std.math.clamp(ms, 1, std.math.maxInt(u32))); @@ -5059,7 +5061,7 @@ pub const Pardes = struct { // rendering and nothing else. // A shell with a clock draws what `advance` stepped; one without // steps its own `.tick`s and draws while anything animates. - if (!p.needs_frame and (v.now != null or !p.animationActive())) return; + if (!p.needs_frame and !p.continuous() and (v.now != null or !p.animationActive())) return; _ = p.frame_arena.reset(.retain_capacity); const surface = try p.render(p.frame_arena.allocator()); if (v.present) |f| f(h.ctx, surface); @@ -6217,7 +6219,6 @@ pub const Pardes = struct { pub fn nextWake(p: *const Pardes) ?u64 { const next = p.stepped_ns + animation.frame_ns; if (p.chrome_animation.isActive() or p.presentation.animating()) return next; - for (p.lifts) |spring| if (!spring.settled) return next; var frames: ?u64 = null; if (p.look_hover_wait) |waiting| { const delay = config.look_preview_delay_frames orelse return next; @@ -6230,10 +6231,19 @@ pub const Pardes = struct { return if (frames) |n| p.stepped_ns + n * animation.frame_ns else null; } + /// A render-time animation is moving (a pane's lift): it is sampled at + /// each frame's own `now`, so while it moves the shell draws every loop + /// and its present (vsync) paces it, at the display's rate. `nextWake` + /// is for the stepped animations, which count frames of `frame_ns`. + pub fn continuous(p: *const Pardes) bool { + for (p.lifts) |spring| if (!spring.settled) return true; + return false; + } + /// For shells that only need a yes or no: macOS and the board, which /// keep their own frame banks. pub fn animationActive(p: *const Pardes) bool { - return p.nextWake() != null; + return p.nextWake() != null or p.continuous(); } /// Bring core animation up to `now_ns`, the shell's monotonic clock: one -- cgit v1.3