summaryrefslogtreecommitdiff
path: root/src/draw.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 17:56:10 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commitb4781b334a06d16aedb0b6a3d604cb249eb619de (patch)
tree6c95db55cbd6bf7dde4bc89d1e76e6c1556223b1 /src/draw.zig
parentdeeb8ac54f71d4322a778336db9a7aec411c23ce (diff)
downloadpardes-b4781b334a06d16aedb0b6a3d604cb249eb619de.tar.gz
pardes-b4781b334a06d16aedb0b6a3d604cb249eb619de.zip
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.
Diffstat (limited to 'src/draw.zig')
-rw-r--r--src/draw.zig38
1 files changed, 35 insertions, 3 deletions
diff --git a/src/draw.zig b/src/draw.zig
index 0998db24..70b629a5 100644
--- a/src/draw.zig
+++ b/src/draw.zig
@@ -127,11 +127,12 @@ pub fn place(p: *Pardes, s: *Surface) void {
serial.* = pane.serial;
spring.* = .{};
}
- spring.retarget(if (p.settings.lift and id == p.active) 1 else 0, p.stepped_ns);
- _ = spring.step(p.stepped_ns);
+ // At this frame's own time, not the stepped animations' 16 ms grid.
+ spring.retarget(if (p.settings.lift and id == p.active) 1 else 0, p.now_ns);
+ _ = spring.step(p.now_ns);
}
for (s.regions[0..s.nregions]) |*region| switch (region.kind) {
- .grip, .tag, .body, .rail => region.lift = p.lifts[region.owner].value(p.stepped_ns),
+ .grip, .tag, .body, .rail => region.lift = p.lifts[region.owner].value(p.now_ns),
.notice => region.lift = if (p.settings.lift) 1 else 0,
else => {},
};
@@ -1122,6 +1123,37 @@ test "a notice wider than its band, as a shader's compile error is, draws its en
}
}
+test "a lift is sampled at each frame's own time, and asks for frames only while it moves" {
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 });
+ defer p.deinit();
+ _ = try p.setTestFile("text\n");
+ var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
+ defer arena.deinit();
+ const ms = std.time.ns_per_ms;
+ p.advance(1000 * ms);
+ _ = try p.render(arena.allocator());
+ try std.testing.expect(!p.continuous());
+ p.settings.lift = true;
+ var lifts: [3]f32 = undefined;
+ // 144 Hz: 6.9 ms apart, none of them on a 16 ms step of its own.
+ for (&lifts, 0..) |*lift, i| {
+ p.advance(1000 * ms + @as(u64, @intCast(i)) * 6_944_444);
+ _ = arena.reset(.retain_capacity);
+ const s = try p.render(arena.allocator());
+ lift.* = for (s.regionList()) |region| {
+ if (region.kind == .grip and region.owner == p.active) break region.lift;
+ } else return error.TestExpectedGrip;
+ try std.testing.expect(p.continuous());
+ }
+ try std.testing.expect(lifts[0] < lifts[1] and lifts[1] < lifts[2]);
+ // Settled, it is exactly up, and the core is idle again.
+ p.advance(2000 * ms);
+ _ = arena.reset(.retain_capacity);
+ _ = try p.render(arena.allocator());
+ try std.testing.expect(!p.continuous());
+ try std.testing.expectEqual(@as(?u64, null), p.nextWake());
+}
+
test "a monkey over notices, prompts, tags and tiny panes at a gui's metrics never panics" {
// The snapshot harness runs the tty, whose tagline pitch is the body's,
// so it never reaches what a narrower tagline does to a notice's band: