mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 04:36:00 -10:00
Addresses the four review lenses on #449. No change to the wiring tables, to STATE_VERSION, or to EXPECT_DIGEST_FULL / EXPECT_DIGEST_PORT1 (both re-measured bit-identical). BLOCKING A retro_set_controller_port_device forced INPUTDEV_PAD on any JOYPAD or NONE assignment, with a comment claiming "the core option takes over again on the next check_variables()". Nothing schedules one -- check_variables() runs from retro_load_game and from retro_run only behind GET_VARIABLE_UPDATE -- and RetroArch issues a per-port controller init right after load, so the mouse the core option had just attached was detached for the whole session. This is why the feature was never seen working in a frontend. The option resolution is now p2_device_from_option() and is re-run immediately when the frontend releases its claim. Called that early TitleDBOverride returns NULL, so the "auto -> per-title DB" leg resolves at the retro_load_game check_variables(), not on the early call. A2 update_input suppressed port 2's RetroPad unconditionally on the configured device type, before reading any mouse state, so a frontend that does not route mouse state to the port lost BOTH devices. Rule adopted and commented: selecting a mouse must never leave the port with no working input. Mouse state is read first; the pad is released only once the mouse is live (non-zero delta or button), and stays released until the device type changes. B joymatrix_identity's third assertion compared mouse_port1 only -- mouse_full was formatted into the detail string and never checked. Adds EXPECT_DIGEST_FULL_WITH_MOUSE (0x5E1858EA, measured on this branch after D and E) as a fourth assertion, moves the InputDev* sweep ahead of the --print early return so the constant can be re-measured at all, and puts all four results in the exit status (the old code folded the mouse result into ok_port1; the new rows would have printed FAIL and still exited 0). The stale "draining throughout the sweep" comment is corrected: the feed clamps to QUAD_MAX_BACKLOG, so the encoder drains over the first 64 qualifying polls of ~131,072 and then holds -- which is what catches the polarity class. C Missing InputDev* symbols now exit 77 after harness_shutdown instead of printing a note and returning 0. make invokes the tool bare, so 77 fails the build: loud, never a silent pass. STRONG D QuadReset parked at phase 0 = (A=0,B=0); the overlay is active low, so an idle mouse asserted all four direction lines -- row 0 reading U+D+L+R held, rows 1-3 four keypad digits -- after every JERRYReset, device change and pre-v12 state load. Parks at QUAD_REST_PHASE = 2 = (1,1), the only rest position where an idle mouse is indistinguishable from an idle pad. No source names a rest phase; the comment says so. E The emission clock fired on any $F14000 offset-0 read. Now gated on port 2's row select (offset1 != 0xFF -- any port-2 row, not row 0), per mapping doc section 5: a title polling port 1 rapidly was advancing the port-2 encoder between the port-2 poller's own samples, which loses motion outright rather than merely lagging. The overlay is untouched and still asserts in every row. F InputDevSetType reset the encoders and button latches but not inputdev_armed, so mouse -> pad -> mouse left a stale arm worth one unrequested advance. Cleared on the type-change path only. G InputDevStateLoad sanitized phase but loaded backlog and frac raw; QuadFeed clamps only after `backlog += states`, so a corrupt state could overflow first. Both clamped on load. H headroom_logged was a function-local static in retro_serialize and so unreachable from retro_deinit. Hoisted to file scope and added to the reset block (iOS cannot dlclose a core). I The mouse sensitivity percent is clamped to 1600 (InputDevSetScale's 4096 ceiling as a percent) before the *256 multiply, so a hand-edited config value cannot overflow a signed int. J The joymatrix_identity make rule now lists src/jerry/inputdev.h as a prerequisite -- the tool includes it for InputDevType, and an enum renumber was leaving a stale binary testing wrong constants. TESTS test_quadrature asserts the rest phase and that it drives both lines high. mouse_decode_test gains test_port1_poll_does_not_clock: 200 port-1 row polls must not advance the port-2 encoder and must not lose the queued motion. Both assertions fail with E reverted.
252 lines
6.4 KiB
C
252 lines
6.4 KiB
C
/*
|
|
* test/test_quadrature.c -- unit test for the Gray-code quadrature encoder.
|
|
*
|
|
* Standalone: compiles src/jerry/quadrature.c directly, no core, no
|
|
* dlopen. Asserts the properties the mouse (#429) and the rotary (#436)
|
|
* both depend on:
|
|
*
|
|
* 1. the Gray sequence, forwards and backwards, against the table in
|
|
* quadrature.h (which is docs/jaguar-mouse-adapter-mapping.md
|
|
* section 5 and the Technical Reference V10's rotary sequence -- the
|
|
* two agree bit-for-bit);
|
|
* 2. QuadAdvance() moves AT MOST one state, ever. This is the whole
|
|
* rate policy: a two-state jump indexes the "undetermined" entry of
|
|
* a real decoder's table and is silently discarded, so emitting one
|
|
* is worse than emitting nothing;
|
|
* 3. the backlog clamp;
|
|
* 4. the Q8 remainder carry, so a <1.0 sensitivity decimates smoothly
|
|
* instead of quantising every small movement to zero.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "../src/jerry/quadrature.h"
|
|
|
|
static int failures;
|
|
|
|
static void check(int cond, const char *what)
|
|
{
|
|
if (cond)
|
|
printf(" ok %s\n", what);
|
|
else
|
|
{
|
|
printf(" FAIL %s\n", what);
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
/* Reference phase table, written out independently of the implementation
|
|
* so a change to either side has to be deliberate. */
|
|
static const int ref_a[4] = { 0, 1, 1, 0 };
|
|
static const int ref_b[4] = { 0, 0, 1, 1 };
|
|
|
|
static void test_sequence_forward(void)
|
|
{
|
|
quad_axis q;
|
|
int i;
|
|
int ok = 1;
|
|
|
|
printf("Gray sequence, positive direction (A leads B)\n");
|
|
|
|
QuadReset(&q);
|
|
QuadFeed(&q, 8, 256);
|
|
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
int a, b;
|
|
int expect = (QUAD_REST_PHASE + i + 1) & 3;
|
|
|
|
if (!QuadAdvance(&q))
|
|
{
|
|
ok = 0;
|
|
break;
|
|
}
|
|
QuadLevels(&q, &a, &b);
|
|
if (a != ref_a[expect] || b != ref_b[expect])
|
|
ok = 0;
|
|
}
|
|
|
|
check(ok, "8 advances walk the Gray table upward from the rest phase");
|
|
check(q.backlog == 0, "backlog fully drained after 8 advances");
|
|
check(QuadAdvance(&q) == 0, "an empty backlog does not advance");
|
|
}
|
|
|
|
static void test_sequence_reverse(void)
|
|
{
|
|
quad_axis q;
|
|
int i;
|
|
int ok = 1;
|
|
|
|
printf("Gray sequence, negative direction (B leads A)\n");
|
|
|
|
QuadReset(&q);
|
|
QuadFeed(&q, -8, 256);
|
|
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
int a, b;
|
|
int expect = (QUAD_REST_PHASE - (i + 1)) & 3; /* downward */
|
|
|
|
if (!QuadAdvance(&q))
|
|
{
|
|
ok = 0;
|
|
break;
|
|
}
|
|
QuadLevels(&q, &a, &b);
|
|
if (a != ref_a[expect] || b != ref_b[expect])
|
|
ok = 0;
|
|
}
|
|
|
|
check(ok, "8 advances walk the Gray table downward from the rest phase");
|
|
check(q.backlog == 0, "backlog fully drained after 8 advances");
|
|
}
|
|
|
|
static void test_one_state_per_advance(void)
|
|
{
|
|
quad_axis q;
|
|
int i;
|
|
int ok = 1;
|
|
|
|
printf("one state per advance (the rate policy)\n");
|
|
|
|
/* Motion arriving faster than it can drain: 100 units fed per poll,
|
|
* one poll per iteration. Every advance must still move exactly one
|
|
* state -- this is the case where a naive implementation would "catch
|
|
* up" by jumping, which a real decoder discards outright. */
|
|
QuadReset(&q);
|
|
|
|
for (i = 0; i < 200; i++)
|
|
{
|
|
uint8_t before;
|
|
int step;
|
|
|
|
QuadFeed(&q, 100, 256);
|
|
before = q.phase;
|
|
QuadAdvance(&q);
|
|
step = ((int)q.phase - (int)before + 4) & 3;
|
|
if (step != 1)
|
|
ok = 0;
|
|
}
|
|
|
|
check(ok, "200 saturated polls each move exactly 1 state (never a jump)");
|
|
|
|
/* And when the backlog IS empty, an advance is a no-op rather than a
|
|
* wrap: a decoder reads "no movement", which is harmless. */
|
|
ok = 1;
|
|
QuadReset(&q);
|
|
QuadFeed(&q, 3, 256);
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
uint8_t before = q.phase;
|
|
int step;
|
|
|
|
QuadAdvance(&q);
|
|
step = ((int)q.phase - (int)before + 4) & 3;
|
|
if (step > 1)
|
|
ok = 0;
|
|
if (i >= 3 && step != 0)
|
|
ok = 0;
|
|
}
|
|
|
|
check(ok, "advances past an empty backlog hold the phase steady");
|
|
}
|
|
|
|
static void test_backlog_clamp(void)
|
|
{
|
|
quad_axis q;
|
|
|
|
printf("backlog clamp\n");
|
|
|
|
QuadReset(&q);
|
|
QuadFeed(&q, 100000, 256);
|
|
check(q.backlog == QUAD_MAX_BACKLOG, "positive backlog clamps at +64");
|
|
|
|
QuadReset(&q);
|
|
QuadFeed(&q, -100000, 256);
|
|
check(q.backlog == -QUAD_MAX_BACKLOG, "negative backlog clamps at -64");
|
|
}
|
|
|
|
static void test_q8_carry(void)
|
|
{
|
|
quad_axis q;
|
|
int i;
|
|
int advanced = 0;
|
|
|
|
printf("Q8 sensitivity carry\n");
|
|
|
|
/* 25% sensitivity: four one-unit feeds must produce exactly one state,
|
|
* not zero (which is what dropping the remainder would give). */
|
|
QuadReset(&q);
|
|
for (i = 0; i < 4; i++)
|
|
QuadFeed(&q, 1, 64);
|
|
check(q.backlog == 1, "4 x (1 unit @ 25%) accumulates exactly 1 state");
|
|
|
|
QuadReset(&q);
|
|
for (i = 0; i < 4; i++)
|
|
QuadFeed(&q, -1, 64);
|
|
check(q.backlog == -1, "4 x (-1 unit @ 25%) accumulates exactly -1 state");
|
|
|
|
/* 200% doubles. */
|
|
QuadReset(&q);
|
|
QuadFeed(&q, 3, 512);
|
|
check(q.backlog == 6, "3 units @ 200% is 6 states");
|
|
|
|
/* A single sub-unit feed emits nothing yet but is not lost. */
|
|
QuadReset(&q);
|
|
QuadFeed(&q, 1, 64);
|
|
check(q.backlog == 0, "1 unit @ 25% emits nothing yet");
|
|
for (i = 0; i < 3; i++)
|
|
QuadFeed(&q, 1, 64);
|
|
while (QuadAdvance(&q))
|
|
advanced++;
|
|
check(advanced == 1, "...and the carried remainder completes the state");
|
|
}
|
|
|
|
static void test_reset(void)
|
|
{
|
|
quad_axis q;
|
|
|
|
printf("reset\n");
|
|
|
|
QuadReset(&q);
|
|
QuadFeed(&q, 7, 256);
|
|
QuadAdvance(&q);
|
|
QuadFeed(&q, 1, 64);
|
|
QuadReset(&q);
|
|
check(q.backlog == 0 && q.frac == 0 && q.phase == QUAD_REST_PHASE,
|
|
"QuadReset zeroes backlog and carry and parks at the rest phase");
|
|
|
|
/* The rest phase must be the (1,1) state: the mouse overlay is active
|
|
* low, so any other parking position leaves an IDLE mouse asserting
|
|
* direction lines at $F14000 -- see QUAD_REST_PHASE in quadrature.h. */
|
|
{
|
|
int a = -1, b = -1;
|
|
QuadLevels(&q, &a, &b);
|
|
check(a == 1 && b == 1,
|
|
"the rest phase drives both lines high (idle asserts nothing)");
|
|
}
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
printf("=== quadrature encoder ===\n");
|
|
|
|
test_sequence_forward();
|
|
test_sequence_reverse();
|
|
test_one_state_per_advance();
|
|
test_backlog_clamp();
|
|
test_q8_carry();
|
|
test_reset();
|
|
|
|
if (failures)
|
|
{
|
|
printf("FAILED: %d check(s)\n", failures);
|
|
return 1;
|
|
}
|
|
|
|
printf("All quadrature checks passed\n");
|
|
return 0;
|
|
}
|