diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 17:56:10 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | b4781b334a06d16aedb0b6a3d604cb249eb619de (patch) | |
| tree | 6c95db55cbd6bf7dde4bc89d1e76e6c1556223b1 /src/draw.zig | |
| parent | deeb8ac54f71d4322a778336db9a7aec411c23ce (diff) | |
| download | pardes-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.zig | 38 |
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: |
