summaryrefslogtreecommitdiff
path: root/test/deck/replay.zig
diff options
context:
space:
mode:
Diffstat (limited to 'test/deck/replay.zig')
-rw-r--r--test/deck/replay.zig116
1 files changed, 116 insertions, 0 deletions
diff --git a/test/deck/replay.zig b/test/deck/replay.zig
new file mode 100644
index 00000000..90f46486
--- /dev/null
+++ b/test/deck/replay.zig
@@ -0,0 +1,116 @@
+//! Replays slices of real Steam Deck recordings (deckcap .zon captures)
+//! through src/deck.zig and checks the aggregate actions, so the deck
+//! control mapping is exercised without the device.
+
+const std = @import("std");
+const deck = @import("deck");
+const schema = @import("schema.zig");
+
+const Tally = struct {
+ moves: u32 = 0,
+ dx: f32 = 0,
+ dy: f32 = 0,
+ max_step: f32 = 0, // largest single-motion |dx| or |dy|
+ click_down: [3]u32 = @splat(0), // indexed by deck.Click
+ click_up: [3]u32 = @splat(0),
+ wheel: i32 = 0,
+ keys: [4]u32 = @splat(0), // indexed by deck.Keytap
+ rumbles: u32 = 0,
+};
+
+fn replay(src: [:0]const u8) !Tally {
+ const gpa = std.testing.allocator;
+ const rec = try schema.parse(gpa, src);
+ defer schema.free(gpa, rec);
+ var d: deck.Deck = .{};
+ var tally: Tally = .{};
+ for (rec.events) |ev| {
+ const in: deck.Input = switch (ev.ev) {
+ .button => |b| .{ .button = .{ .idx = b.idx, .down = b.down } },
+ .axis => |a| .{ .axis = .{ .idx = a.idx, .value = a.value } },
+ .touch => |t| .{ .touch = .{ .pad = t.touchpad, .phase = switch (t.phase) {
+ .down => .down,
+ .motion => .motion,
+ .up => .up,
+ }, .x = t.x, .y = t.y } },
+ else => continue,
+ };
+ var acts: [2]deck.Action = undefined;
+ for (acts[0..d.feed(in, &acts)]) |act| switch (act) {
+ .move => |mv| {
+ tally.moves += 1;
+ tally.dx += mv.dx;
+ tally.dy += mv.dy;
+ tally.max_step = @max(tally.max_step, @max(@abs(mv.dx), @abs(mv.dy)));
+ },
+ .click => |ck| if (ck.down) {
+ tally.click_down[@intFromEnum(ck.which)] += 1;
+ } else {
+ tally.click_up[@intFromEnum(ck.which)] += 1;
+ },
+ .wheel => |ticks| tally.wheel += ticks,
+ .key => |k| tally.keys[@intFromEnum(k)] += 1,
+ .rumble => tally.rumbles += 1,
+ };
+ }
+ return tally;
+}
+
+test "right-pad drag moves the pointer, fresh touches don't jump" {
+ const t = try replay(@embedFile("rpad-drag.zon"));
+ // 4 strokes whose within-stroke finger travel sums to (-0.0209, -0.1853)
+ // in SDL pad coords; the pointer must move exactly that, scaled (and
+ // down-screen for the falling y, via PAD_Y_SIGN). The touch-to-touch
+ // gaps (~ +0.17 in x) must NOT leak in: a missing fresh-touch re-anchor
+ // would drag dx positive by ~0.17 * POINTER_SCALE.
+ try std.testing.expect(t.moves > 100);
+ try std.testing.expectApproxEqAbs(-0.020874 * deck.POINTER_SCALE, t.dx, 1.0);
+ try std.testing.expectApproxEqAbs(-0.185333 * deck.PAD_Y_SIGN * deck.POINTER_SCALE, t.dy, 1.0);
+ // and no single motion teleports the cursor
+ try std.testing.expect(t.max_step < 0.05 * deck.POINTER_SCALE);
+ try std.testing.expectEqual(0, t.wheel);
+ try std.testing.expectEqual(0, t.rumbles);
+}
+
+test "trigger pull+release clicks exactly once each" {
+ const t = try replay(@embedFile("triggers.zon"));
+ // one R2 pull (select) and one L2 pull (execute), each an analog ramp
+ // through dozens of intermediate values, interleaved with ~500 stick
+ // axis events: hysteresis must yield exactly one click apiece, only
+ // the execute pull rumbles, and the sticks produce nothing
+ try std.testing.expectEqual(1, t.click_down[@intFromEnum(deck.Click.select)]);
+ try std.testing.expectEqual(1, t.click_up[@intFromEnum(deck.Click.select)]);
+ try std.testing.expectEqual(1, t.click_down[@intFromEnum(deck.Click.execute)]);
+ try std.testing.expectEqual(1, t.click_up[@intFromEnum(deck.Click.execute)]);
+ try std.testing.expectEqual(0, t.click_down[@intFromEnum(deck.Click.look)]);
+ try std.testing.expectEqual(1, t.rumbles);
+ try std.testing.expectEqual(0, t.moves);
+}
+
+test "left-pad swipe scrolls" {
+ const t = try replay(@embedFile("lpad-swipe.zon"));
+ // one swipe: SDL y falls 0.2189 within the stroke -> floor(0.2189 *
+ // SCROLL_SCALE) = 5 wheel-down ticks. The slice opens with orphan
+ // motions and an orphan up from an earlier touch (mid-touch startup),
+ // which must be ignored — and the left pad must never move the pointer.
+ try std.testing.expectEqual(5, t.wheel);
+ try std.testing.expectEqual(0, t.moves);
+ try std.testing.expectEqual(0, t.rumbles);
+}
+
+test "buttons: dpad n/N, A Enter, Y Tab, L1 look + rumble" {
+ const t = try replay(@embedFile("buttons.zon"));
+ // every button press of a real all-buttons sweep: dpad-down x1,
+ // dpad-up x3, A x2, Y x5, L1 x3; the other buttons map to nothing
+ try std.testing.expectEqual(1, t.keys[@intFromEnum(deck.Keytap.n)]);
+ try std.testing.expectEqual(3, t.keys[@intFromEnum(deck.Keytap.cap_n)]);
+ try std.testing.expectEqual(2, t.keys[@intFromEnum(deck.Keytap.enter)]);
+ try std.testing.expectEqual(5, t.keys[@intFromEnum(deck.Keytap.tab)]);
+ try std.testing.expectEqual(3, t.click_down[@intFromEnum(deck.Click.look)]);
+ try std.testing.expectEqual(3, t.click_up[@intFromEnum(deck.Click.look)]);
+ try std.testing.expectEqual(3, t.rumbles);
+ try std.testing.expectEqual(0, t.click_down[@intFromEnum(deck.Click.select)]);
+ try std.testing.expectEqual(0, t.click_down[@intFromEnum(deck.Click.execute)]);
+ try std.testing.expectEqual(0, t.moves);
+ try std.testing.expectEqual(0, t.wheel);
+}