OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / tests / test_joystick.c
8.0 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// The SNES controller ports: the latch/clock shift register, and the virtual
2
// joystick that lets automation drive a port with no gamepad attached.
3
//
4
// The properties worth pinning are the ones a script driving the machine over
5
// DAP depends on: that a button it holds actually reaches the data line, on the
6
// right bit for the right port; that the shift register clocks out LSB-first
7
// with the upper nibble forced high the way the hardware does; and -- the one
8
// that is easy to break -- that a port nothing is driving still reads as
9
// released, forever, rather than draining a shift register to zero and
10
// reporting every button pressed.
11
//
12
// joystick.c reaches SDL's headers and its controller-enumeration functions, so
13
// this links SDL, but never calls joystick_init(): the virtual path deliberately
14
// needs no SDL controller, and the test is here to prove that.
15
16
#include "support/harness.h"
17
18
#include "../src/joystick.h"
19
20
#include <string.h>
21
22
// Reading a port means latching, then clocking bits out one at a time. The
23
// latch emits bit 0 itself, so the loop starts at 1. Joystick_data is cleared
24
// first because the latch path ORs into it without clearing, exactly as it does
25
// when the VIA drives the line.
26
static uint16_t
27
read_slot(int slot, int bits)
28
{
29
	const uint8_t slot_bit = 0x80 >> slot;
30
	uint16_t      out      = 0;
31
32
	Joystick_data = 0;
33
	joystick_set_latch(true);
34
	if (Joystick_data & slot_bit) {
35
		out |= 1;
36
	}
37
	joystick_set_latch(false);
38
39
	for (int i = 1; i < bits; ++i) {
40
		joystick_set_clock(false);
41
		joystick_set_clock(true);
42
		if (Joystick_data & slot_bit) {
43
			out |= (uint16_t)(1u << i);
44
		}
45
	}
46
47
	return out;
48
}
49
50
static void
51
reset_joysticks(void)
52
{
53
	for (int i = 0; i < NUM_JOYSTICKS; ++i) {
54
		joystick_clear_virtual(i);
55
		Joystick_slots_enabled[i] = false;
56
	}
57
}
58
59
static void
60
test_virtual_button_reaches_the_data_line(void)
61
{
62
	reset_joysticks();
63
64
	// Active low: clearing a bit presses the button.
65
	joystick_set_virtual(0, (uint16_t)~JOY_BIT_A);
66
	check_eq(read_slot(0, 16), (uint16_t)(~JOY_BIT_A | 0xF000), "a pressed A clocks out on port 1");
67
68
	joystick_set_virtual(0, (uint16_t)~(JOY_BIT_DPAD_LEFT | JOY_BIT_B));
69
	check_eq(read_slot(0, 16), (uint16_t)(~(JOY_BIT_DPAD_LEFT | JOY_BIT_B) | 0xF000),
70
	         "two buttons held at once both clock out");
71
}
72
73
static void
74
test_upper_nibble_is_forced_high(void)
75
{
76
	reset_joysticks();
77
78
	// Every defined button pressed. Bits 12-15 have no button behind them and
79
	// the latch forces them high, so the port cannot report them as pressed.
80
	joystick_set_virtual(1, 0x0000);
81
	check_eq(read_slot(1, 16), 0xF000, "bits 12-15 read high even with every button held");
82
}
83
84
static void
85
test_each_port_is_independent(void)
86
{
87
	reset_joysticks();
88
89
	joystick_set_virtual(0, (uint16_t)~JOY_BIT_A);
90
	joystick_set_virtual(1, (uint16_t)~JOY_BIT_B);
91
	joystick_set_virtual(2, (uint16_t)~JOY_BIT_START);
92
	joystick_set_virtual(3, (uint16_t)~JOY_BIT_DPAD_UP);
93
94
	check_eq(read_slot(0, 16), (uint16_t)(~JOY_BIT_A | 0xF000), "port 1 reports only its own buttons");
95
	check_eq(read_slot(1, 16), (uint16_t)(~JOY_BIT_B | 0xF000), "port 2 reports only its own buttons");
96
	check_eq(read_slot(2, 16), (uint16_t)(~JOY_BIT_START | 0xF000), "port 3 reports only its own buttons");
97
	check_eq(read_slot(3, 16), (uint16_t)(~JOY_BIT_DPAD_UP | 0xF000), "port 4 reports only its own buttons");
98
}
99
100
static void
101
test_a_port_with_no_source_reads_released(void)
102
{
103
	reset_joysticks();
104
105
	// The compatibility guard, and the reason presence detection works at all.
106
	// A port nothing drives must keep reporting 1s past the sixteenth clock,
107
	// because that is how the KERNAL tells an empty port from an occupied one:
108
	// a real controller's shift register runs out and reads 0, while a floating
109
	// port stays high. Let this fall through to the shift register and every
110
	// port would report a controller plugged into it.
111
	check_eq(read_slot(0, 16), 0xFFFF, "an undriven port reads as nothing pressed");
112
113
	Joystick_data = 0;
114
	joystick_set_latch(true);
115
	joystick_set_latch(false);
116
	for (int i = 0; i < 40; ++i) {
117
		joystick_set_clock(false);
118
		joystick_set_clock(true);
119
		if (!(Joystick_data & 0x80)) {
120
			check(false, "an undriven port stays high past the sixteenth clock");
121
			return;
122
		}
123
	}
124
	check(true, "an undriven port stays high past the sixteenth clock");
125
}
126
127
static void
128
test_a_driven_port_runs_out(void)
129
{
130
	reset_joysticks();
131
132
	// The other half of that contract: a port something *is* driving has a
133
	// sixteen-bit shift register and nothing behind it, so the seventeenth
134
	// clock onwards reads 0. That difference is the presence signal, so a
135
	// virtual joystick has to reproduce it or nothing that checks whether a
136
	// controller is plugged in would see one.
137
	joystick_set_virtual(0, 0xFFFF);
138
139
	Joystick_data = 0;
140
	joystick_set_latch(true);
141
	joystick_set_latch(false);
142
	for (int i = 1; i < 16; ++i) {
143
		joystick_set_clock(false);
144
		joystick_set_clock(true);
145
	}
146
147
	joystick_set_clock(false);
148
	joystick_set_clock(true);
149
	check(!(Joystick_data & 0x80), "a driven port reads 0 once its sixteen bits run out");
150
}
151
152
static void
153
test_releasing_hands_the_port_back(void)
154
{
155
	reset_joysticks();
156
157
	joystick_set_virtual(2, (uint16_t)~JOY_BIT_Y);
158
	check_eq(read_slot(2, 16), (uint16_t)(~JOY_BIT_Y | 0xF000), "the held button reads back");
159
160
	joystick_clear_virtual(2);
161
	check(!joystick_virtual_active(2), "clearing deactivates the virtual joystick");
162
	check_eq(read_slot(2, 16), 0xFFFF, "a released port reads as nothing pressed again");
163
}
164
165
static void
166
test_holding_persists_across_reads(void)
167
{
168
	reset_joysticks();
169
170
	// A held direction has to survive being read every frame, or nothing that
171
	// polls the port more than once could be driven at all.
172
	joystick_set_virtual(0, (uint16_t)~JOY_BIT_DPAD_RIGHT);
173
	for (int frame = 0; frame < 3; ++frame) {
174
		check_eq(read_slot(0, 16), (uint16_t)(~JOY_BIT_DPAD_RIGHT | 0xF000),
175
		         "a held direction survives repeated reads");
176
	}
177
}
178
179
static void
180
test_driving_a_port_enables_it(void)
181
{
182
	reset_joysticks();
183
184
	check(!Joystick_slots_enabled[3], "the port starts disabled");
185
	joystick_set_virtual(3, (uint16_t)~JOY_BIT_SELECT);
186
	check(Joystick_slots_enabled[3], "driving a port enables it, so no -joy4 flag is needed");
187
188
	// Releasing leaves the port enabled: that bit is the user's configuration,
189
	// and a physical pad may bind to it now the virtual one has let go.
190
	joystick_clear_virtual(3);
191
	check(Joystick_slots_enabled[3], "releasing leaves the port enabled");
192
}
193
194
static void
195
test_debug_snapshot_reports_the_virtual_state(void)
196
{
197
	reset_joysticks();
198
	joystick_set_virtual(1, (uint16_t)~JOY_BIT_X);
199
200
	joystick_debug_state_t js;
201
	memset(&js, 0, sizeof(js));
202
	joystick_debug_get_state(&js);
203
204
	check(js.slots[1].virtual_active, "the snapshot reports the virtual joystick");
205
	check(!js.slots[1].controller_bound, "no controller is bound to a virtual port");
206
	check_eq(js.slots[1].button_mask, (uint16_t)~JOY_BIT_X, "the snapshot reports the held buttons");
207
	check(!js.slots[0].virtual_active, "an undriven port is not reported as virtual");
208
	check_eq(js.slots[0].button_mask, 0xFFFF, "an undriven port reports nothing pressed");
209
	check_eq(js.slots[0].shift_mask, 0xFFFF, "an undriven port reports the 1s it actually clocks out");
210
}
211
212
static void
213
test_an_out_of_range_port_is_ignored(void)
214
{
215
	reset_joysticks();
216
217
	joystick_set_virtual(-1, 0x0000);
218
	joystick_set_virtual(NUM_JOYSTICKS, 0x0000);
219
	joystick_clear_virtual(-1);
220
	joystick_clear_virtual(NUM_JOYSTICKS);
221
222
	check(!joystick_virtual_active(-1), "a negative port is not active");
223
	check(!joystick_virtual_active(NUM_JOYSTICKS), "a port past the last is not active");
224
	check_eq(read_slot(0, 16), 0xFFFF, "an out-of-range write does not spill onto a real port");
225
}
226
227
int
228
main(void)
229
{
230
	test_virtual_button_reaches_the_data_line();
231
	test_upper_nibble_is_forced_high();
232
	test_each_port_is_independent();
233
	test_a_port_with_no_source_reads_released();
234
	test_a_driven_port_runs_out();
235
	test_releasing_hands_the_port_back();
236
	test_holding_persists_across_reads();
237
	test_driving_a_port_enables_it();
238
	test_debug_snapshot_reports_the_virtual_state();
239
	test_an_out_of_range_port_is_ignored();
240
241
	return x16_test_summary("joystick");
242
}