# shellcheck shell=bash # Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. # See LICENSE.md for license information. # SPDX-License-Identifier: MIT # # Shared by the joystick gates that read examples/joy's screen # (scripts/verify-*-joy.sh): the steps, the check of each screenshot # (scripts/lib/joyScreen.py) and the tally. Source after joeyEnv.sh. The gate # defines three functions first: # # joyShot PNG save a screenshot of JOY as PNG; non-zero if none # joyPress CONTROL... hold the controls (below) # joyRelease CONTROL... let them go # # A control is a stick (A or B) and a line: A-left A-right A-up A-down # A-button0 A-button1, and the same for B. A screenshot reads as # "A= B=", each state five characters: connected (1/0), X and # Y (- 0 +), button 0 and button 1 (1/0). # # joyCheck NAME WANT a screenshot, which must read WANT ("A=... B=..."); # an unreadable one ('?': JOY caught between # erasing a dot and drawing it again) is taken # once more before it counts # joyRunSteps STEPS... for each "name|controls|A's state|B's state": # hold the controls, check, let go, check at rest; # REST in A's state stands for JOY_REST, in B's # for JOY_REST_B (JOY_REST, or JOY_ABSENT where the # machine has one stick) # joyFail WHAT note a failure (after JOY_PASS, if the gate set # it); the gate goes on to report the rest # joyResult LABEL WHAT print LABEL's PASS (with WHAT) or FAIL line; # exit 1 on a failure # # The gate sets JOY_LABEL (its name, for the lines it prints) and JOY_WORK # (its scratch folder). JOY_SCALE (joyScreen.py --scale) is the stage's # scale in the screenshot, empty to let joyScreen.py find it; # JOY_SCREEN_ARGS are more joyScreen.py options (--st); JOY_SHOTS, if # set, keeps every screenshot there; JOY_PRESS_SECONDS is how long a change # takes to show; JOY_SHOT_TRIES is 1 for a gate whose screenshots were all # taken before the checks (they cannot be taken again). JOY_REST="10000" # a stick connected, in the middle, buttons up JOY_ABSENT="00000" # no stick JOY_REST_B="$JOY_REST" # stick B when nothing holds it JOY_SCALE="${JOY_SCALE:-}" JOY_SCREEN_ARGS=() JOY_PRESS_SECONDS="${JOY_PRESS_SECONDS:-1}" JOY_SHOT_TRIES="${JOY_SHOT_TRIES:-2}" JOY_RETRY_SECONDS=0.5 JOY_FAILS="" JOY_CHECKED=0 JOY_PASS="" # a gate that runs its steps more than once names each run # Both sticks, every direction, two diagonals each, each button alone and # together, and a step holding different lines on both sticks at once, so # crosstalk between them shows up. JOY_STEPS_TWO_STICKS=( "A left|A-left|1-000|REST" "A right|A-right|1+000|REST" "A up|A-up|10-00|REST" "A down|A-down|10+00|REST" "A up-left|A-left A-up|1--00|REST" "A down-right|A-right A-down|1++00|REST" "A button 0|A-button0|10010|REST" "A button 1|A-button1|10001|REST" "A both buttons|A-button0 A-button1|10011|REST" "B left|B-left|REST|1-000" "B right|B-right|REST|1+000" "B up|B-up|REST|10-00" "B down|B-down|REST|10+00" "B up-right|B-right B-up|REST|1+-00" "B down-left|B-left B-down|REST|1-+00" "B button 0|B-button0|REST|10010" "B button 1|B-button1|REST|10001" "B both buttons|B-button0 B-button1|REST|10011" "A left+button 1, B right+button 0|A-left A-button1 B-right B-button0|1-001|1+010" ) # The same for sticks with one button (the Atari's fire button). Held for the # screenshot, the button is button 1: JOY_PRESS_SECONDS is past the half # second that makes a press a hold. A tap is button 0 for one poll only, # which no screenshot catches; tests/host/inputHost.c checks it. JOY_STEPS_ONE_BUTTON=( "A left|A-left|1-000|REST" "A right|A-right|1+000|REST" "A up|A-up|10-00|REST" "A down|A-down|10+00|REST" "A up-left|A-left A-up|1--00|REST" "A down-right|A-right A-down|1++00|REST" "A button held: button 1|A-button0|10001|REST" "B left|B-left|REST|1-000" "B right|B-right|REST|1+000" "B up|B-up|REST|10-00" "B down|B-down|REST|10+00" "B up-right|B-right B-up|REST|1+-00" "B down-left|B-left B-down|REST|1-+00" "B button held: button 1|B-button0|REST|10001" "A left+button held, B right+button held|A-left A-button0 B-right B-button0|1-001|1+001" ) joyCheck() { local png="$JOY_WORK/shot.png" local got local try local args=("${JOY_SCREEN_ARGS[@]}") JOY_CHECKED=$((JOY_CHECKED + 1)) [ -z "$JOY_SCALE" ] || args+=(--scale "$JOY_SCALE") for ((try = 1; try <= JOY_SHOT_TRIES; try++)); do [ "$try" = 1 ] || sleep "$JOY_RETRY_SECONDS" rm -f "$png" if ! joyShot "$png" || [ ! -s "$png" ]; then joyFail "$1: no screenshot" return fi got=$(python3 "$JOEY_REPO/scripts/lib/joyScreen.py" "$png" "${args[@]}") [[ "${got% scale=*}" == *"?"* ]] || break done if [ -n "${JOY_SHOTS:-}" ]; then mkdir -p "$JOY_SHOTS" cp "$png" "$JOY_SHOTS/$(printf '%02d' "$JOY_CHECKED") $1.png" fi echo "$JOY_LABEL: $1: $got" [ "${got% scale=*}" = "$2" ] || joyFail "$1: read ${got% scale=*}, want $2" } joyFail() { JOY_FAILS="${JOY_FAILS:+$JOY_FAILS; }${JOY_PASS:+$JOY_PASS: }$1" } joyResult() { if [ -n "$JOY_FAILS" ]; then echo "$1: FAIL - $JOY_FAILS" exit 1 fi echo "$1: PASS ($JOY_CHECKED screenshots: $2)" } joyRunSteps() { local step local name local held local wantA local wantB local controls for step in "$@"; do IFS='|' read -r name held wantA wantB <<< "$step" read -r -a controls <<< "$held" joyPress "${controls[@]}" sleep "$JOY_PRESS_SECONDS" joyCheck "$name" "A=${wantA/REST/$JOY_REST} B=${wantB/REST/$JOY_REST_B}" joyRelease "${controls[@]}" sleep "$JOY_PRESS_SECONDS" joyCheck "$name released" "A=$JOY_REST B=$JOY_REST_B" done }