Files
room_glitch/docs/pacman_rfid_first_setup.md
Jeff Luke 41ef12f954 Add Pac-Man power-on flow and document Odin integration
- Add a four-second arcade-style boot sequence followed by the
  Character / Nickname introduction.
- Separate cabinet power-on from RFID credit and player Start.
- Add operator Power on control and cabinet power-state reporting.
- Prevent repeated power commands from restarting boot or gameplay.
- Ignore RFID scans received before the cabinet is ready.
- Add power-state, RFID, controller, and GPIO feedback regression tests.

- Expand the master walkthrough with export, packaging, file transfer,
  fresh-Pi setup, RFID compilation, and end-to-end testing instructions.
- Correct tester hardware documentation: BCM23/J20 drives an LED;
  physical buttons connect through a separate I2C1 input board.
- Document operator-triggered power-on as the current test workflow
  and retain the temporary 2.4 configuration compatibility notes.
- Record successful physical testing of power-on, RFID credit,
  and player Start.

- Add an Odin integration README describing the proposed Rust/NATS
  bridge, shared messages, Flutter GM interface, autopilot sequencing,
  hardware ownership, and Starfish SSH-management role.
- Define migration milestones and information needed from the
  framework owner; link the integration guide from the root README.

Validation: 69 controller tests, 10 GPIO tests, Pac-Man power/RFID
checks, managed-cabinet lifecycle checks, and visual screen inspection.
2026-10-17 18:47:26 -07:00

36 KiB
Raw Permalink Blame History

Cabinet 1: Pac-Man RFID credit — clean setup and physical test

This guide sets up Glitch: the Dark Machine with a master Pi hosting the operator webpage and Cabinet 1 running Pac-Man with the custom GPIO PCB. Follow the steps in order. Commands marked PC, master, or cabinet run on that machine. Replace YOUR_USER with the actual Pi login.

The supplied scenario is Cabinet 1: Pac-Man RFID credit (pacman_rfid_credit_test). It prepares Pac-Man in Waiting for power. Click the operator Power on button to run a four-second cosmetic ROM-check sequence followed by the Character / Nickname screen with CREDIT 0. A new accepted scan on J1 / cab1_rfid1 plays games/pacman/audio/credit.wav and changes the display to CREDIT 1. The player presses their mapped Start button, consuming the credit. It wins after one cleared board. It has no automatic win timer and no relay/motor rewards.

The first test accepts any readable card. Credit is capped at one; scans during play do not bank credits. After a win/loss, reset and prepare the scenario again. The ordinary free-start Pac-Man assignment remains available separately.

1. Machines, cards, and wiring

Use Raspberry Pi OS 64-bit Desktop on Cabinet 1. The master may use Desktop or Lite. In Raspberry Pi Imager, give them different hostnames (for example glitch-master and glitch-cabinet1), set their login details, and enable SSH. Enable desktop autologin on the cabinet so its game has a graphical session. Godot itself is not installed on either Pi.

For this baseline use the master as both the network boundary and operator server:

Venue LAN / staff computers and tablets
                  |
          master Wi-Fi (venue IP)
          Master Pi: operator server on TCP 8766
          master Ethernet: 192.168.77.1
                  |
          Private Ethernet switch
                  |
          Cabinet 1: 192.168.77.11
          GPIO PCB + RFID readers on J1 / J2

The private switch has no direct connection to the venue router. Cabinet Wi-Fi is disabled after downloading dependencies. The master does not route or bridge traffic between the networks. Browsers communicate with the master, and cabinets communicate with that same master on its private address. This setup uses static addresses, so no DHCP server is required on the private network.

If you keep a separate existing network/router Pi, do not apply these network instructions on top of its unknown configuration. It needs its own subnet/firewall plan. The clean two-Pi arrangement above replaces that role for this test and leaves the original Odin installation untouched.

Power off before moving the PCB ribbon or reader connections. Use your documented connector orientation. J1 is selector slot 0, J2 slot 1. RFID owns BCM17/27/22 (selection), BCM20 (reset), and SPI0 BCM8/9/10/11. Remove the old BCM17 LED test from this setup; do not run its output configuration. This test enables no general output pins.

Cards do not need a game written onto them: this implementation reads their identifier. The MFRC522 supports ISO/IEC 14443-A / MIFARE / NTAG cards; an unspecified RFID card is not automatically compatible (for example 125 kHz cards will not work). Start by reading one. The helper currently follows Odin's 32-bit identifier representation; it is not full-length UID support for every card, a balance stored on the card, or an anti-cloning/payment system.

2. Install prerequisites while Internet access is available

Master:

sudo apt update
sudo apt install -y python3 openssh-client nftables

Cabinet:

sudo apt update
sudo apt install -y python3 python3-gpiozero python3-lgpio build-essential curl ca-certificates
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
cargo --version
sudo usermod -aG spi,gpio "$USER"
sudo raspi-config

Choose the default Rust installation. In raspi-config enable Interface Options > SPI, and desktop autologin if not already enabled. Reboot the cabinet and log into its desktop. Check:

ls -l /dev/spidev0.0
groups

The SPI device must exist and your user should have spi and gpio membership. Download/build the reader helper in step 5 before removing cabinet Internet access.

3. Export, package, and copy one complete release

This is the release-building part of the master walkthrough. Build everything from the same saved project before copying anything to a Pi. For the first RFID installation, use the manual configuration in steps 5–9 below; the fleet deployment wizard does not yet preserve all RFID settings (see 3.7).

3.1 Save and check the source project — Windows PC

Open D:\Projects\escape-gallery\room_glitch in the project's Godot version (currently 4.7.2). Save scenes and resources before running command-line exports. Check:

  • Cabinet 1 has ID cabinet_01, and its PCB identifies J1 as cab1_rfid1 and J2 as cab1_rfid2.
  • data/project_database.tres includes Cabinet 1: Pac-Man RFID credit. Its assignment uses Pac-Man, rfid_credit_device = cab1_rfid1, and power_on_device = cab1_btn_ylw. The power setting and corresponding saved PCB entry are legacy metadata from the earlier button assumption; retain them temporarily for compatibility with the current 2.4 scenario validation. They do not describe the actual wiring. Do not include that legacy button in the bridge's devices.json. See the software-power notes at the end of this guide.
  • The assignment has no output mappings for this initial reader test. rfid_allowed_uids = [] accepts any readable card.
  • The Linux preset is ARM64, with Embed PCK disabled. The existing Windows Desktop preset is x86_64.
  • Export templates matching the editor version are installed (Editor > Manage Export Templates). An editor executable alone is not sufficient to export.

Use a release folder outside the Godot project. Do not export back into the project or mix old setup/pi exports with current source. In each preset's Resources tab, exclude the existing setup/* folder from the game export: it contains old exported artifacts and copied tools that do not belong inside a game PCK. Keep the actual core, data, games, ui, and runtime resources included. Do not add INIs, private keys, or SQLite files to the export's non-resource include filter.

The runtime for this scenario must advertise arcade-cabinet-2.4 and both rfid_credit_v1 and pacman_power_v1. Python files or a fresh catalog cannot add that feature to an old game PCK.

3.2 Create a fresh release directory — Windows PowerShell

The example release name is glitch-rfid-001. Pick a new name for every build you intend to deploy. These commands are examples to run, not artifacts already built by this walkthrough.

Set-Location 'D:\Projects\escape-gallery\room_glitch'
$releaseName = 'glitch-rfid-001'
$releaseDir = "D:\ArcadeReleases\$releaseName"
if (Test-Path -LiteralPath $releaseDir) { throw 'Choose a new release name; this directory already exists.' }
New-Item -ItemType Directory -Path "$releaseDir\pi", "$releaseDir\windows" | Out-Null

Use this same PowerShell session for the following commands so the variables remain available. If you return later, set them again to the intended existing release; do not rerun the directory-creation check.

3.3 Export the catalog and game — Windows PowerShell

First export the controller catalog from the saved resources:

.\Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://tools/room_controller/export_catalog.gd
if ($LASTEXITCODE -ne 0) { throw 'Catalog export failed.' }

Then export the actual Pi game, using the preset name exactly as shown:

.\Godot_v4.7.2-stable_win64_console.exe --headless --path . --export-release 'Linux' "$releaseDir\pi\ArcadeEscape.arm64"
if ($LASTEXITCODE -ne 0) { throw 'Pi export failed.' }

Check the export log for errors, not just whether an old file exists. The folder must contain both ArcadeEscape.arm64 and ArcadeEscape.pck. Godot may also create ArcadeEscape.sh; the Python launcher runs the ARM64 executable directly, so that shell wrapper is not required by our deployment package.

Optionally export Windows for PC testing:

.\Godot_v4.7.2-stable_win64_console.exe --headless --path . --export-release 'Windows Desktop' "$releaseDir\windows\ArcadeEscape.exe"
if ($LASTEXITCODE -ne 0) { throw 'Windows export failed.' }

Keep each platform's executable and accompanying PCK together. Do not swap the Windows and Pi PCKs between release folders. The Windows export can test games and controls with GPIO disabled; it cannot validate the physical SPI reader. A browser-only master does not need either Godot game export or the Godot editor.

If you prefer the Godot UI, use Project > Export > the corresponding preset > Export Project, disable Export With Debug, and use the same filenames and separate release folders above. Still run the catalog export before packaging.

3.4 Package the master software and cabinet bundle — Windows PowerShell

From the project root, run:

py -3 tools/deploy/deploy.py master-package --output "$releaseDir\master"
if ($LASTEXITCODE -ne 0) { throw 'Master packaging failed.' }
py -3 tools/deploy/deploy.py bundle --export "$releaseDir\pi" --output "$releaseDir\$releaseName.tar.gz" --release $releaseName
if ($LASTEXITCODE -ne 0) { throw 'Cabinet packaging failed.' }
Get-FileHash -Algorithm SHA256 -LiteralPath "$releaseDir\$releaseName.tar.gz" | Format-List

The master directory and archive must not already exist. Do not edit project code, game resources, or the catalog between exporting and packaging. If something changes, build a new release from the beginning.

The packager uses a file allowlist. It checks the ARM64 executable header and PCK header and writes SHA-256 hashes into the cabinet manifest. It does not prove that the PCK was built from the current source; keeping the build steps together is essential.

Your deliverables are now:

Item Destination / purpose
master/ Master Pi: controller, browser operator, matching catalog, launch/deployment scripts.
glitch-rfid-001.tar.gz Cabinet software: ARM64 game + PCK, bridge, helper source, launch scripts, catalog and manifest.
windows/ Optional Windows game test. Not needed on the master Pi.
Release notes and archive SHA-256 Keep with your release records for verification and rollback.

The packages intentionally omit live tokens/INIs, SSH keys, databases, Godot saved controls, and Odin. They also omit the compiled RFID executable and each cabinet's devices.json. Build/create those on the cabinet in step 5. Custom cabinet artwork is embedded in the packaged operator HTML; you do not need to copy the source PNG directory separately for this page.

Record the release name, editor version, source commit (if committed), any uncommitted source changes included, export date, and tests performed in a small release-notes file. Keep the original archive unchanged. Retain Cargo.lock produced by the successful Pi helper build with your records as well.

3.5 Copy to clean installations

Use a USB drive or SFTP/SCP. The target folder is ~/arcade under the actual Pi user, not /root/arcade. This subsection is for fresh installs; do not extract over a running or previously configured installation. Updates are covered in 3.8.

Master: create ~/arcade and copy the contents of the release's master folder into it. The result must be:

~/arcade/tools/room_controller/server.py
~/arcade/tools/room_controller/operator.html
~/arcade/tools/room_controller/catalog.json
~/arcade/tools/launch/launch.py
~/arcade/tools/deploy/deploy.py

Other allowlisted files will also be present. Do not accidentally end up with ~/arcade/master/tools/...; the remaining walkthrough assumes ~/arcade/tools/....

Cabinet: copy the archive into the cabinet user's home. Before extraction, compare its hash with the Windows result (letter case does not matter):

sha256sum ~/glitch-rfid-001.tar.gz
mkdir -p ~/arcade
tar -tzf ~/glitch-rfid-001.tar.gz

Confirm ~/arcade is empty on this fresh installation, and that this is the archive you just built. Extract:

tar -xzf ~/glitch-rfid-001.tar.gz -C ~/arcade
chmod +x ~/arcade/ArcadeEscape.arm64

The resulting cabinet layout includes:

~/arcade/manifest.json
~/arcade/ArcadeEscape.arm64
~/arcade/ArcadeEscape.pck
~/arcade/tools/launch/launch.py
~/arcade/tools/launch/pi_service.py
~/arcade/tools/pi_gpio/pi_gpio_bridge.py
~/arcade/tools/pi_gpio/device_feedback.py
~/arcade/tools/pi_gpio/rfid_helper/Cargo.toml
~/arcade/tools/pi_gpio/rfid_helper/src/main.rs

The extra controller files inside the cabinet bundle are harmless; do not start a second controller there. Continue with network setup in step 4, RFID build/devices.json in step 5, and the separate master/cabinet INIs in step 6. The .sh file, source repository, and Godot editor are not required on the cabinet.

Optional exact copy commands from Windows

While both fresh Pis are still on venue Wi-Fi, record their Wi-Fi addresses with hostname -I on each Pi. Use those actual addresses here, not the later private Ethernet addresses. Keep the PowerShell release variables from section 3.2. Substitute your Pi usernames and current Wi-Fi IPs:

$masterLogin = 'YOUR_MASTER_USER'
$masterHost = 'MASTER_WIFI_IP'
$cabinetLogin = 'YOUR_CABINET_USER'
$cabinetHost = 'CABINET_WIFI_IP'
tar -czf "$releaseDir\master.tar.gz" -C "$releaseDir\master" .
if ($LASTEXITCODE -ne 0) { throw 'Master archive failed.' }
scp "$releaseDir\master.tar.gz" "${masterLogin}@${masterHost}:master.tar.gz"
if ($LASTEXITCODE -ne 0) { throw 'Master copy failed.' }
scp "$releaseDir\$releaseName.tar.gz" "${cabinetLogin}@${cabinetHost}:$releaseName.tar.gz"
if ($LASTEXITCODE -ne 0) { throw 'Cabinet copy failed.' }

These SSH copies can use the Pi login passwords set in Imager; SSH key enrollment is not required for the first manual test. On first connection, verify the host fingerprint against the intended Pi before accepting it. Do not copy the operator token into an SSH password prompt.

On the fresh master, extract the master archive:

mkdir -p ~/arcade
tar -xzf ~/master.tar.gz -C ~/arcade
ls ~/arcade/tools/room_controller/server.py

On the fresh cabinet, use section 3.5's cabinet extraction commands. Compile and test the RFID helper in section 5 before section 4 disables cabinet Wi-Fi. After that, laptop access is to the master only; use the master to SSH to 192.168.77.11 if needed.

3.6 Preflight before calling the release ready

After completing steps 4–6, validate each machine without starting it:

Master:

cd ~/arcade
python3 tools/launch/launch.py controller --config controller.ini --check

Cabinet:

cd ~/arcade
python3 tools/launch/launch.py cabinet --config cabinet.ini --check

These checks validate configuration/dependencies/port availability. They do not prove card communication, display rendering, audio, or successful gameplay. Stop any existing stack first if the check reports its ports are occupied.

Use step 7's physical test as the acceptance gate: correct version, fresh J1 readings, Waiting for power -> operator Power on -> four-second boot -> CREDIT 0 -> scan/sound -> CREDIT 1 -> physical Start -> CREDIT 0, and no unintended outputs. Verify again after the reboot/startup test in step 9. Only then label the release ready for the room.

3.7 Optional fleet deployment: current RFID limitation

For later multi-cabinet rollout, the generated master package includes tools/deploy/deploy.py; the fleet deployment guide explains setup, enrollment, preflight, stage, activate, and rollback. Staging verifies each manifest hash before accepting a release. It does not start a game by itself.

The current fleet configuration generator is not yet a complete RFID installer. It rejects real-GPIO cabinets with an empty output pin list and does not write devices or rfid_helper. Do not add a fake output pin to bypass that check, and do not run fleet activation over the working RFID installation expecting those settings to be preserved. For this RFID-only first test, follow the manual ~/arcade installation and service steps in this walkthrough. The bundle remains useful for consistent, checked packaging even when copied manually.

The deployment wizard also uses its own site credentials/configuration directory. Do not mix its generated controller.ini with the manual one below or install two competing master services. Choose one configuration owner. Until RFID provisioning is supported by the generator, use this walkthrough's manual path for Cabinet 1.

3.8 Updating and rolling back the manual installation

Do updates between room sessions. Preserve the previous complete release and configuration, not just the executable.

  1. Build a new versioned master package and cabinet archive on the PC using 3.1–3.4. Do not overwrite/reuse a release archive.
  2. Back up the master's controller.ini, controller database/state directory, and any SSH/site credentials. Back up the cabinet's cabinet.ini, devices.json, working RFID binary/lockfile, and Godot control mappings. Keep the GPIO request ledger at ~/.local/state/arcade_gpio/requests.sqlite3; do not erase it to retry an uncertain physical action.
  3. Extract/copy the new software into a new sibling directory, such as ~/arcade-next, on each machine. Build the helper there if its source/dependencies changed. Copy your existing site-specific INIs/devices.json into that new installation and inspect their paths. The guide's relative paths remain valid inside the new root; any custom absolute paths need adjustment.
  4. Stop the old services with systemctl --user stop arcade-master.service on the master and systemctl --user stop arcade-cabinet.service on the cabinet, or Ctrl+C their manual launchers. Copy the stopped master's database into the new installation's matching state location before switching. Avoid opening the live database with two controllers.
  5. Keep the old directory as ~/arcade-previous-RELEASE_NAME, and move the new directory into ~/arcade. Use previously unused backup names; do not merge two release directories. Because the service paths still point at ~/arcade, the manual services in step 9 continue to use the intended location. Recheck config paths, helper permissions, and the same operator/cabinet credentials.
  6. Start the master and cabinet and repeat the preflight/physical acceptance test. Re-prepare the scenario for the new session. Do not resume an uncertain old room state automatically.
  7. If the release fails, stop both services, keep the failed installation separately for diagnosis, restore the previous full software/configuration/database set at ~/arcade, and start again. Keep the GPIO deduplication ledger intact. Future database format changes may require release-specific migration/rollback steps; never point an older controller at an unreviewed newer database.

Do not overwrite saved input mappings or re-image the SD card just to update game code. The manual path does not provide atomic activation; the fleet tool's staged activation can replace this workflow once its RFID configuration support is completed.

4. Configure the private network

Do this from each Pi's local terminal, not an SSH connection that you are about to disconnect. These commands assume a clean Raspberry Pi OS installation with NetworkManager and eth0 as the wired interface. Check first:

nmcli device status
nmcli connection show

Use the actual Ethernet connection name in place of Wired connection 1 if different. Verify that 192.168.77.0/24 is not the venue subnet; if it overlaps, choose another private subnet and change every corresponding address in this guide.

Master:

sudo nmcli connection modify "Wired connection 1" ipv4.method manual ipv4.addresses 192.168.77.1/24 ipv4.gateway "" ipv4.dns "" ipv4.never-default yes ipv6.method disabled
sudo nmcli connection up "Wired connection 1"

Keep the master's Wi-Fi on the venue LAN, with its normal Internet route. Reserve that Wi-Fi address in the venue router so the operator bookmark stays stable. Do not choose NetworkManager's shared mode.

Cabinet (after step 5 has downloaded the Rust dependencies):

sudo nmcli connection modify "Wired connection 1" ipv4.method manual ipv4.addresses 192.168.77.11/24 ipv4.gateway "" ipv4.dns "" ipv4.never-default yes ipv6.method disabled
sudo nmcli connection up "Wired connection 1"
sudo nmcli radio wifi off

Cabinet 2 can later use .12, Cabinet 3 .13, etc. Leave gateway/DNS blank on all private wired connections. Verify from the master:

ping -c 3 192.168.77.11
ssh YOUR_CABINET_USER@192.168.77.11

Enforce no forwarding on the clean master

These steps are for the fresh master described above, not an existing managed router/firewall. They do not restrict connections to the master, including SSH and the operator webpage; they block forwarding through it.

Create /etc/sysctl.d/90-arcade-private.conf with sudo nano:

net.ipv4.ip_forward=0
net.ipv6.conf.all.forwarding=0

Apply it:

sudo sysctl --system

On the fresh master, inspect /etc/nftables.conf. In its default table inet filter, change the forward chain's policy from accept to drop. Leave the input/output policies alone for this first test. If the file contains an existing custom policy, stop here and have it reviewed rather than replacing it. Validate and enable the policy:

sudo nft -c -f /etc/nftables.conf
sudo systemctl enable --now nftables
sudo systemctl restart nftables
sudo nft list ruleset
sysctl net.ipv4.ip_forward net.ipv6.conf.all.forwarding

Expected: forward policy drop and both forwarding values 0. Do not bridge the two interfaces or set up Internet sharing. Do not add router port forwarding. Staff browsers will use http://MASTER_VENUE_IP:8766; cabinets use http://192.168.77.1:8766. The GPIO bridge stays local to the cabinet at 127.0.0.1:8765.

5. Compile and test the RFID readers on the cabinet

Do this before disabling its Internet connection:

cd ~/arcade
chmod +x ArcadeEscape.arm64
cargo build --release --manifest-path tools/pi_gpio/rfid_helper/Cargo.toml

A successful build creates tools/pi_gpio/rfid_helper/target/release/arcade-rfid. The new helper has not yet been built or physically verified on our tester Pi; if compilation fails, retain the exact output for troubleshooting. Do not assume a successful Windows test proves hardware communication.

Stop the game service, GPIO bridge, and any Odin reader process before testing. On a fresh Pi there should be none. On an existing Arcade installation:

systemctl --user stop arcade-cabinet.service

Then:

cd ~/arcade
./tools/pi_gpio/rfid_helper/target/release/arcade-rfid 0,1

Present a card separately to J1 and J2. Success resembles:

{"slot":0,"uid":"12AB34CD","error":""}

The example UID is not a real enrolled card. Write down the exact value reported for each of your cards. No card-writing/programming step is required. A blank uid with No tag read (absent or unreadable) is not an accepted scan. Reader did not respond indicates reader communication trouble, not simply an unprogrammed card. Press Ctrl+C before starting the bridge.

Create ~/arcade/devices.json with this minimal RFID-only inventory:

[
  {"id":"cab1_rfid1","name":"Credit reader","port":"J1","kind":"rfid"},
  {"id":"cab1_rfid2","name":"Second reader","port":"J2","kind":"rfid"}
]

J2 is visible for diagnostics but grants no credit in this scenario. BCM23 / J20 is connected to an LED, not a button. Do not wire a button there or configure it as an input. This test does not drive that LED or any other output; leave the output pin allowlist empty.

The tester's physical buttons connect through a separate input board on I2C1. Its model, I2C address, channel mapping, and matching driver still need to be confirmed before those buttons can control the game. For now, use operator Power on. The mapped arcade Start control is separate from this unimplemented I2C input board.

If following an older version of this guide, stop the cabinet stack, remove only the cab1_btn_ylw / J20 input entry from devices.json, and restart the stack. Keep the two RFID entries and preserve any unrelated site-specific devices. Do not regenerate the live inventory blindly from the saved PCB catalog: its legacy J20 button entry has not yet been migrated to the corrected hardware model.

6. Generate credentials and create the two INIs

Master: generate two different 16-character tokens (first operator, second cabinet):

python3 -c 'import secrets; print("Operator:", secrets.token_urlsafe(12)); print("Cabinet 1:", secrets.token_urlsafe(12))'

Create ~/arcade/controller.ini on the master:

[controller]
bind = 0.0.0.0
port = 8766
catalog = tools/room_controller/catalog.json
database = state/arcade_room.sqlite3
operator_token = REPLACE_WITH_OPERATOR_TOKEN
cabinet_tokens = {"cabinet_01":"REPLACE_WITH_CABINET_TOKEN"}

Create ~/arcade/cabinet.ini on Cabinet 1:

[cabinet]
id = cabinet_01
url = http://192.168.77.1:8766
token = REPLACE_WITH_CABINET_TOKEN
executable = ArcadeEscape.arm64
gpio = true

[gpio]
controller = cabinet_01_gpio
pins =
devices = devices.json
rfid_helper = tools/pi_gpio/rfid_helper/target/release/arcade-rfid

Use matching cabinet tokens on both sides; the operator token belongs only on the master and in the browser login. INI token values have no surrounding quotes; the cabinet_tokens JSON string values do have JSON double quotes. All relative file paths above are relative to the containing INI at ~/arcade.

Leave pins = explicitly empty. Omitting it defaults to the old BCM17 output, which conflicts with RFID and will be rejected. The scenario has no output actions, so there is no reason to enable output pins for this test.

With this launcher approach, no hand-written arcade_network.cfg is needed: the launcher supplies the URL and token. Old environment variables can override INIs. In a reused terminal, clear them before launching:

unset ARCADE_ROOM_URL ARCADE_CABINET_TOKEN ARCADE_OPERATOR_TOKEN ARCADE_CABINET_TOKENS

Protect the INIs on their respective machines:

chmod 600 ~/arcade/controller.ini

On the cabinet use chmod 600 ~/arcade/cabinet.ini instead.

7. Start and run the test

Master:

cd ~/arcade
python3 tools/launch/launch.py controller --config controller.ini

Cabinet, in a terminal on its desktop:

cd ~/arcade
python3 tools/launch/launch.py cabinet --config cabinet.ini

The cabinet launcher starts both the local GPIO bridge/reader helper and the game. Do not also start the standalone reader or another bridge. In a staff browser open http://MASTER_VENUE_IP:8766 and log in with the operator token.

  1. Confirm Cabinet 1 is online, reports arcade-cabinet-2.4, and GPIO is real.
  2. In Room control select Cabinet 1: Pac-Man RFID credit and prepare it. Reset an already active cabinet first if requested.
  3. Wait for Waiting for power and fresh device readings. In Cabinets, select Cabinet 1 and click Power on. The operator should change to Booting, then Ready after four seconds; the cabinet shows the Character / Nickname introduction and SCAN CARD / CREDIT 0. Keep the card away until Ready; historical scans and scans during boot are discarded.
  4. Press the cabinet Start button without a card: nothing should start.
  5. Scan J1: hear the credit sound and see CREDIT 1 / PRESS START. Hold/re-scan the card: it must stay at one credit and not repeatedly sound for the same accepted credit.
  6. Press the mapped Start button: Pac-Man starts and shows CREDIT 0. The operator changes to Playing. Operator Start is disabled for this scenario because the player must start it.
  7. Scan during gameplay: no credit is banked. Clear one board to win. No physical reward is configured.
  8. Operator Power on should be disabled during boot and play. Reset/re-prepare to return to Waiting for power and test again. Scanning J2 must never grant credit. Disconnecting the reader/bridge must not create credits; feedback becomes stale/error.

Use Prepare assigned cabinets for the named scenario, not the separate manual Prepare game action: ordinary Pac-Man boots automatically and does not wait for operator power-on.

The game uses the arcade_start input mapping. If the physical button is wrong, use the launcher Controls setup to map it and save, then return to managed mode. Keyboard/controller mapping is independent of the RFID reader.

The operator's Pac-Man image is an illustration, not a streamed screen; CREDIT 0/1 is rendered on the actual cabinet display. The Hardware view shows reader feedback, not a claim that a credit was granted. The physical test above verifies both.

8. Optionally restrict accepted cards

After recording the helper's reported identifier, edit the scenario assignment's game_config in Godot:

rfid_credit_device: cab1_rfid1
rfid_allowed_uids: ["12AB34CD", "5678ABCD"]
boards_to_win: 1
score_to_win: 0

Replace the examples with your actual uppercase eight-character values. An empty array accepts any readable card. Changing this list does not write to the card. Keep a separate list of card labels and identifiers for your staff.

Save, re-export the controller catalog, and copy the updated catalog to the master. Restart the controller between sessions and re-prepare the scenario. For changes only to this assignment's allowlist/objective, the compatible 2.4 cabinet runtime accepts the new configuration without another game export. Game code, cabinet profiles, and new assets still require exporting the game.

9. Enable startup after the manual test passes

Cabinet, on its local desktop:

cd ~/arcade
python3 tools/launch/pi_service.py install --config "$HOME/arcade/cabinet.ini"
systemctl --user start arcade-cabinet.service

Stop the manually launched cabinet stack with Ctrl+C before installing. The installer captures the current desktop environment. The service intentionally has no boot enable target because the display must be ready first. For automatic start after desktop login, first run mkdir -p ~/.config/autostart, then create ~/arcade/start-cabinet.sh:

#!/bin/sh
/usr/bin/python3 "$HOME/arcade/tools/launch/pi_service.py" refresh-session || exit 1
exec systemctl --user start arcade-cabinet.service

Run chmod +x ~/arcade/start-cabinet.sh. Create ~/.config/autostart/glitch-cabinet-start.desktop:

[Desktop Entry]
Type=Application
Name=Start Glitch Cabinet
Exec=/home/YOUR_USER/arcade/start-cabinet.sh
Terminal=false

Replace YOUR_USER in the Exec path. This explicitly refreshes the display environment before starting the game. Desktop autologin must remain enabled. Do not use sudo for the game service.

Master: stop the manually launched controller, then create ~/.config/systemd/user/arcade-master.service (create the directory first):

[Unit]
Description=Glitch room operator controller

[Service]
Type=simple
WorkingDirectory=%h/arcade
ExecStart=/usr/bin/python3 %h/arcade/tools/launch/launch.py controller --config %h/arcade/controller.ini
UnsetEnvironment=ARCADE_ROOM_URL ARCADE_CABINET_TOKEN ARCADE_OPERATOR_TOKEN ARCADE_CABINET_TOKENS
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target

Then:

systemctl --user daemon-reload
systemctl --user enable --now arcade-master.service
sudo loginctl enable-linger "$USER"

After a reboot verify both services and the web page. Preparing a scenario remains an operator action; a cabinet reboot invalidates the old session and requires preparation again.

Logs:

journalctl --user -u arcade-master.service -n 80 --no-pager
journalctl --user -u arcade-cabinet.service -n 80 --no-pager

Run each command on the corresponding Pi. If startup is already managed by the deployment tools, use their service/config instead of installing a second master service.

Troubleshooting and acceptance

Symptom Check
Build fails Keep the first Rust compiler error; confirm Cargo installed and Internet available.
SPI device missing Enable SPI, reboot, check /dev/spidev0.0.
GPIO/SPI permission denied spi/gpio groups; log out/back in after adding membership.
Reader unresponsive Ribbon orientation, reader wiring/power, correct J1/J2 slots, no competing Odin process.
Blank/unprogrammed cards not reading Check card technology; no card data-writing is required by this test.
Pin conflict on startup Explicit blank output pins; RFID reserves BCM17/27/22/20 and SPI0.
Operator Power on is greyed out Prepare the named RFID scenario using Prepare assigned cabinets; Cabinet 1 must be online, Waiting for power, and have no pending command. Check version 2.4 and preparation errors.
I2C buttons do nothing The separate I2C input board is not integrated yet; use operator Power on and the mapped arcade Start control.
Scenario rejected Updated master catalog, cabinet 2.4 export, real GPIO, matching cab1_rfid1 inventory.
Live UID but no credit Prepare this scenario, remove/re-present card after initial feedback, use J1, verify allowed UID.
Credit but no start Verify arcade_start mapping and physical button.
Unauthorized Operator token in browser; matching Cabinet 1 token on master/cabinet; check inherited environment.
Cabinet offline Cabinet URL uses private master IP and http://...:8766; 8765 is only local GPIO.
Staff page inaccessible Master venue address, venue client isolation/firewall, server bound to 0.0.0.0.

Software regression tests cover credit gating, fresh versus historical/stale feedback, UID/reader filtering, duplicates, credit consumption, and non-RFID behavior. The operator power-on, RFID credit, and player Start flow has been physically confirmed by the user on the tester. Repeat the acceptance test for each fresh installation; the I2C input board and BCM23 LED control remain unverified and outside this test. No Pi, GPIO, network, or live deployment was changed while preparing this guide.

References: Raspberry Pi networking/configuration, Rust installation, NXP MFRC522 supported card families, project GPIO guide, fleet deployment guide.

Pac-Man software power and updates

This is a software state, not a mains relay. Operator Power on starts the cosmetic ROM-check sequence; it does not energize the BCM23 LED. Other games retain their existing startup behavior. The boot pattern is a visual recreation, not a checksum or hardware diagnostic.

Temporary 2.4 compatibility: keep power_on_device = cab1_btn_ylw in the scenario and its corresponding legacy input entry in the saved cabinet/catalog inventory for now. The current controller validates that reference, and the game uses its presence to remain in Waiting for power. Removing the setting makes Pac-Man boot immediately; removing only its catalog entry makes scenario validation fail. This is legacy configuration metadata, not permission to configure BCM23 as an input. The bridge's live devices.json must contain only the reader entries shown above for this test. A future change should introduce an explicit operator-only power mode and correct the saved PCB inventory together.

For a fresh release, export the Pi game, re-export the catalog, and package the controller and bridge from the same source version. A Windows game export is optional for PC testing. On an existing working 2.4 installation, removing the incorrect J20 entry from devices.json requires restarting the cabinet stack, not rebuilding the game or RFID helper. Preserve unrelated site-specific devices and settings. See section 3.8 for full software updates and rollback.