1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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);
}
|