Expand Glitch operations hub, cabinet authoring, deployment, and RFID support

Operator hub
- Introduce Escape Gallery branding and Glitch: the Dark Machine room views
- Add cabinet illustrations, Pac-Man marquee/gameplay artwork, and waiting states
- Add device feedback panels with RFID readings, input/output states, and stale status
- Add logged operator hardware interventions with request deduplication

Room and cabinet authoring
- Add a visual custom-PCB connection editor with per-cabinet device inventory
- Support device IDs, bindings, wiring notes, input polarity, and validation
- Add physical-room label/dimension toggles and editable doorway annotations
- Preserve doorway changes through undo, save, and revert
- Expand cabinet profiles, room layout, and Glitch scenario resources

Scenarios and gameplay
- Add per-assignment game configuration and runtime feature validation
- Add timed test wins and Frogger individual-home objectives
- Add the three-cabinet timed/score/Frogger test scenario
- Add Cabinet 1 Pac-Man RFID credits, credit sound, and CREDIT 0/1 display
- Require physical Start after an accepted scan and prevent duplicate credits

Pi hardware and deployment
- Add GPIO feedback sampling and a separate multiplexed RFID helper
- Validate reader pin ownership and input/output conflicts
- Add SSH cabinet startup and Pi desktop-session service support
- Add master packaging, site setup, SSH enrollment, and staged cabinet releases
- Verify deployment manifests and support explicit activation/status/log commands

Documentation and verification
- Consolidate the root README around the current architecture and setup workflow
- Add the complete master/Pi/RFID export and deployment walkthrough
- Document network isolation, credentials, card identification, and rollback
- Add PCB, room-editor, feedback, credit, deployment, and controller tests
- Isolate protocol tests from designer-edited cabinet configurations

Known limitations
- RFID helper compilation and physical Pi commissioning remain unverified
- Fleet provisioning does not yet configure RFID-only installations completely
- Broader Pac-Man tests retain a ghost-capture assertion failure
- Staged exports must be rebuilt and checked before deploying runtime 2.3
This commit is contained in:
Jeff Luke committed 2026-10-17 14:53:54 -07:00
1 parent 705ec31e0b
commit c1b2195834
90 files changed
+4969 -456

No files matched your search

+165 -269
View File
@@ -1,14 +1,33 @@
# Arcade Escape
# Escape Gallery — Glitch: the Dark Machine
**An arcade-powered escape room framework built with Godot.**
**A Godot arcade escape-room framework with a browser-based operations hub.**
Arcade Escape brings seven classic arcade recreations together with room-design tools, configurable cabinet controls, scenario-driven hardware outputs, and a browser operator dashboard. Designers define the room and its scenarios; Raspberry Pi cabinets run the games; staff prepare and monitor sessions from a computer or tablet.
Design rooms, configure cabinets and their connected devices, assign arcade games to scenarios, and manage play from a staff computer or tablet. Raspberry Pi cabinets run exported games; a Python controller coordinates their sessions and serves the **Escape Gallery** operator webpage. The first room is **Glitch: the Dark Machine**.
The Arcade Escape controller is separate from Odin and does not require its code or services.
The Arcade software is separate from Odin. Its RFID helper follows the tester PCB's selector arrangement and Odin's reader-library approach without requiring Odin's room services.
## Start here
**For a fresh installation, follow the [master walkthrough: Cabinet 1 Pac-Man RFID test](docs/pacman_rfid_first_setup.md).** It covers the entire process: PC exports, matching release packages, private cabinet LAN, Pi prerequisites, reader compilation, credentials, configuration, startup, and physical acceptance testing.
| Task | Guide |
| --- | --- |
| Export and package a matching release | [Master walkthrough, section 3](docs/pacman_rfid_first_setup.md#3-export-package-and-copy-one-complete-release) |
| Run Cabinet 1 with RFID credits | [Full master walkthrough](docs/pacman_rfid_first_setup.md) |
| Configure cabinet controls | [Launcher and control setup](games/launcher/README.md) |
| Author the custom PCB wiring inventory | [Visual PCB setup](docs/cabinet_pcb_setup.md) |
| Configure GPIO outputs and device feedback | [Pi GPIO guide](tools/pi_gpio/README.md) |
| Run controller/cabinet launch scripts | [Launch tools](tools/launch/README.md) |
| Start Pi services remotely over SSH | [Remote cabinet startup](docs/remote_pi_start.md) |
| Stage and activate fleet releases | [Deployment tools and current limitations](tools/deploy/README.md) |
| Configure the operator/controller | [Room controller guide](tools/room_controller/README.md) |
| Test sequential cabinet objectives | [5 seconds → 500 points → 1 frog](docs/three_cabinet_test.md) |
The fleet wizard is not yet a complete RFID installer: it does not generate the device/helper paths and rejects an empty output allowlist with real GPIO enabled. Use the master walkthrough's manual installation for the RFID-only test. Do not add an arbitrary output pin to bypass this limitation.
## Included games
| | Game | Cabinet controls |
| | Game | Controls |
| --- | --- | --- |
| ![Pac-Man](games/launcher/icons/ico_pacman.png) | **Pac-Man** | Four-way joystick + Start |
| ![Galaga](games/launcher/icons/ico_galaga.png) | **Galaga** | Horizontal movement + Fire + Start |
@@ -16,314 +35,191 @@ The Arcade Escape controller is separate from Odin and does not require its code
| ![Missile Command](games/launcher/icons/ico_missilecommand.png) | **Missile Command** | Trackball + three battery buttons + Start |
| ![Frogger](games/launcher/icons/ico_frogger.png) | **Frogger** | Four-way joystick + Start |
| ![Space Invaders](games/launcher/icons/ico_spaceinvaders.png) | **Space Invaders** | Horizontal movement + Fire + Start |
| ![Robotron](games/launcher/icons/ico_robotron.png) | **Robotron 2084** | Two eight-way joysticks, or one joystick + four directional fire buttons, plus Start |
| ![Robotron](games/launcher/icons/ico_robotron.png) | **Robotron 2084** | Two eight-way joysticks, or movement joystick + four directional fire buttons, plus Start |
The games include attract/title screens, sound, and game-specific visual treatments. They are playable recreations under active development; arcade accuracy and individual mechanics continue to be refined.
These are playable recreations with title/attract screens, sound, and game-specific rendering. Arcade accuracy and mechanics continue to be refined. Pi 4 gameplay has been tested; verify controls, audio, performance, and hardware on each installation.
## Start here
- **Start the services with one command:** [Windows and Pi launch scripts](tools/launch/README.md)
- **Play or configure controls:** [Launcher and cabinet controls](games/launcher/README.md)
- **Deploy the room and operator:** [Controller deployment guide](tools/room_controller/README.md)
- **Connect real Pi outputs:** [GPIO setup and LED test](tools/pi_gpio/README.md)
## How it fits together
## System layout
```text
Godot development computer
Escape Room editor + game/cabinet/scenario resources
| |
| game export | exported catalog.json
v v
Raspberry Pi cabinets ------> Room controller on Mac mini / PC
games + saved controls Python + persistent SQLite state
| ^
v |
Local GPIO bridge Browser operator on Mac / PC / iPad
|
Physical outputs
Development PC: Godot editor + saved resources
| ARM64 game/PCK + Python tools | matching catalog + operator assets
v v
Pi cabinets <--------------------> Master controller
| Python + SQLite
| / \
Local GPIO bridge Private LAN Venue LAN
| |
PCB inputs / RFID / outputs Staff browsers
```
| Component | Purpose | Runs on |
The master can run on a Pi, PC, Mac mini, or another suitable Python host. For the isolated Pi arrangement, its Ethernet connects to the private cabinet switch and its Wi-Fi or second Ethernet interface connects to the venue LAN. It hosts the operator on both networks without forwarding cabinet traffic to the venue. Network isolation requires the configuration in the walkthrough; the application does not configure routing/firewalls automatically.
| Port | Service | Address |
| --- | --- | --- |
| Godot project and Escape Room editor | Build games, place cabinets, and author scenarios | Development computer |
| Arcade Launcher | Select games and configure physical controls | Desktop or Pi cabinet |
| Managed cabinet client | Load assigned games and report session events | Each Pi cabinet |
| Room controller | Validate assignments, track cabinet state, and queue commands | Mac mini, PC, or suitable server |
| Browser operator | View the room, prepare scenarios, and start/reset cabinets | Staff computer or iPad |
| GPIO bridge | Drive configured local Pi outputs | Each Pi that controls hardware |
| **8766** | Controller API and operator webpage | Master private IP for cabinets; master venue IP for staff |
| **8765** | GPIO bridge | Cabinet-local `127.0.0.1` only |
## 1. Run the project locally
## Develop and play locally
1. Clone this repository and import **project.godot** into Godot.
2. Use the project's configured **Godot 4.7** version and **Compatibility** renderer. Allow the initial asset import to finish.
3. Run the project with **F5**, or open [ArcadeLauncher.tscn](games/launcher/ArcadeLauncher.tscn) and use **F6**.
4. Complete **Control Setup** if prompted, then select a game and press Start.
1. Import [project.godot](project.godot) into the project's Godot version, currently **4.7.2**, and let asset import complete. The project uses the Compatibility renderer.
2. Run the main project or [ArcadeLauncher.tscn](games/launcher/ArcadeLauncher.tscn).
3. Complete **Control Setup** and select a game. Review saved editor run arguments if the project starts in a test/managed mode instead.
4. Install matching Godot export templates before making Windows or Linux builds.
Godot's matching export templates are needed to create builds. Python 3 is needed for the room controller and GPIO bridge, but not for standalone desktop gameplay.
Standalone games do not require Python. The controller/launcher/bridge do. Pi hardware uses GPIO Zero/lgpio; the separate RFID helper is built with Rust on the Pi.
The launcher is the configured main scene. The project also has saved editor run arguments for a test scenario/cabinet; review **Project Settings → Editor → Run → Main Run Args** before testing another assignment.
- **Start:** select or start; **Select:** return to the standalone launcher.
- **F1:** reopen Control Setup; **Escape:** quit the application.
- Map each machine independently: Windows and Pi USB button indices can differ.
- Saved mappings live in `user://arcade_controls.cfg` and survive game-export replacement.
### Controls
## Author rooms, cabinets, and scenarios
- **Joystick:** navigate the launcher and move in games.
- **Start:** select an entry or begin play.
- **Select:** return to the launcher in standalone mode.
- **F1:** reopen Control Setup from the launcher.
- **Escape:** quit the application entirely.
Open the **Escape Room** main-screen editor tab. Its plugin is enabled in the project; check Project Settings > Plugins if the tab is missing.
Configure each machine independently. USB encoders can report different button numbers on Windows and Raspberry Pi OS. The setup wizard saves local mappings, including separate Robotron firing controls; a trackball should report as a mouse.
Mappings live in `user://arcade_controls.cfg` and survive replacing the game export. See the [control guide](games/launcher/README.md) for keyboard defaults, multiple-controller behavior, and reconnect handling.
## 2. Author a room and its scenarios
The **Escape Room** editor plugin is enabled in this project. Open its main-screen tab in Godot; if it is missing, check **Project Settings → Plugins**.
The existing resources provide a starting point:
| Resource | What to configure |
| Resource | Purpose |
| --- | --- |
| [Project database](data/project_database.tres) | Register cabinets, games, and scenarios |
| [Room layout](data/rooms/arcade_room.tres) | Boundary, walls, cabinet placements, and room devices |
| [Cabinet profiles](data/cabinets/) | Unique cabinet IDs, available controls, and hardware configurations |
| [Game definitions](data/games/) | Game scenes, control requirements, and exposed events |
| [Scenarios](data/scenarios/) | Cabinet/game assignments and event-to-hardware mappings |
| [Project database](data/project_database.tres) | Register games, cabinets, scenarios |
| [Physical room](data/rooms/arcade_room.tres) | Boundary, walls, doorways, placements, devices |
| [Cabinet profiles](data/cabinets/) | Unique IDs, control capabilities, hardware actions, PCB connections |
| [Game definitions](data/games/) | Scenes, required controls, game events |
| [Scenarios](data/scenarios/) | Assignments, dependency gates, per-game objectives, event/output mappings |
A typical authoring workflow:
Give each cabinet a unique ID, describe its actual controls, register it, and place it in the room. Assign compatible games to a scenario and map only intended events to configured hardware actions. Save all resources before exporting. **Capabilities** determine game compatibility; **input mappings** determine which physical controls activate game actions.
1. Create or duplicate a cabinet profile and give it a unique `cabinet_id`.
2. Describe its actual controls in `capabilities`, and configure its hardware actions with the correct controller ID and BCM pins.
3. Register the cabinet in the project database and place it in the room layout.
4. Create a scenario with one assignment per participating cabinet. Choose a compatible game and the `standard` variant.
5. Map game events to the cabinet's hardware action IDs. For example, `game_won` → `coin_release`.
6. Register and save the scenario. Use the editor's simulation tools before deploying real outputs.
Assignments support `game_config` overrides, including score/board objectives, Frogger home counts, and a test-only timed win. These settings survive scenario working-copy/save operations. The controller validates settings and cabinet feature support before preparation. Older example scenarios may require updating action IDs and capabilities to match the current designer-authored cabinet resources.
Designer-facing physical dimensions and positioning use feet; internal room geometry remains in meters.
### Physical room editor
**Capabilities and input mappings are separate.** Capabilities determine which games a cabinet can support; Control Setup determines which physical inputs activate their actions. The sample cabinet profiles describe limited controls, so update them to match your hardware before expecting every game to be available.
The **Physical** view provides Labels and Dimensions toggles to reduce clutter. Hidden labels do not prevent selecting objects; these toggles are editor-session view settings.
### Export the operator catalog
Use **DRAW DOORWAY** and drag from hinge to opposite jamb. Align it with a wall to display an opening, threshold, leaf, and swing arc. Select the doorway to edit its endpoints, convert wall/doorway type, or reverse its swing; use DELETE WALL to remove it. Edits use the room undo/save/revert workflow. Designer dimensions are in feet; stored geometry is in meters.
From the project root, with your Godot executable available as `godot`:
Doorways are layout annotations. They do not change game collisions or act as live door sensors. Browser room-map doorway rendering remains future work.
### Visual PCB connections
Open a cabinet profile's **PCB Connections** editor and select a connector to assign a device type, name, unique ID, binding, and wiring notes. Digital inputs expose their pull-up setting. The editor validates duplicate assignments and relevant pin/action conflicts, supports Undo/Redo, and saves the configuration with the cabinet resource.
The board includes direct GPIO connectors, J16 I2C, and multiplexed J1–J8 reader connectors. See the [pin mapping guide](docs/cabinet_pcb_setup.md). Saving this inventory does not energize outputs or install drivers. Supported entries can be exported into the optional runtime feedback configuration; I2C and Other devices remain inventory-only.
## Operator hub and managed play
The browser hub provides an overview, **Glitch: the Dark Machine** room map, per-cabinet details, hardware controls, and activity history. It shows assigned games and reported cabinet phases. Pac-Man has a custom cabinet treatment with marquee and gameplay artwork; the screen says **Waiting for operator** before play. This artwork is an illustration, not live video.
Prepare a scenario after its participating cabinets report online. Independent assignments can start through the operator or physical Start; dependency-gated assignments activate after their predecessor wins. RFID-credit assignments require a scan and physical Start. Reset before reassigning an active cabinet.
Manual game assignments do not inherit scenario rewards. Operator hardware interventions require an online prepared cabinet, configured action, and a reason; requests are logged and deduplicated. An accepted output command is not proof of physical movement or delivery.
Optional device panels show RFID identifiers, digital input activity, electrical output state, errors, and stale feedback. Activity records device changes. Generic sensor-driven puzzle rules and detailed live puzzle-step diagrams are not yet implemented; the Pac-Man RFID credit flow is the first specific input-driven scenario.
### Pac-Man RFID credit scenario
Select **Cabinet 1: Pac-Man RFID credit** (`pacman_rfid_credit_test`):
1. Prepare Cabinet 1: Pac-Man displays **SCAN CARD / CREDIT 0**.
2. A new accepted scan on **J1 / cab1_rfid1** grants **CREDIT 1** and plays the credit sound.
3. The player presses mapped Start, consumes the credit, and begins play.
4. Clear one board to win; reset/re-prepare for another session.
Credit is capped at one. Historical/stale samples, duplicate events, scans on J2, and scans during gameplay do not bank extra credits. The initial scenario accepts any compatible readable card; `rfid_allowed_uids` optionally restricts accepted identifiers. No card-writing step is required. No motor/relay reward is configured for this test.
This needs **arcade-cabinet-2.3**, real GPIO feedback, the updated bridge, and the compiled RFID helper. The helper follows Odin's 32-bit identifier representation and is not full-length UID support for every card type. Compilation and physical reader testing on the tester Pi remain to be verified.
## Configuration and credentials
Prefer the launcher INIs shown in the master walkthrough. They start the required services and provide the managed game with its connection settings.
| Role | Settings |
| --- | --- |
| Master controller | `[controller]` URL binding, catalog/database paths, operator token, cabinet-token map |
| Pi cabinet | `[cabinet]` own ID/token, master private URL, executable, real GPIO option |
| Local hardware bridge | `[gpio]` controller ID, output pin allowlist, device file, RFID helper path |
| Optional direct Godot client | `user://arcade_network.cfg`, `[network]`, role-specific token and controller URL |
Browser staff use the **operator token**. Each cabinet uses its **own cabinet token**. These are application tokens, not SSH keys. Generate distinct values, for example:
```sh
godot --headless --path . --script tools/room_controller/export_catalog.gd
```
This generates [catalog.json](tools/room_controller/catalog.json) from the saved designer resources. The controller uses it for room positions, scenarios, and game compatibility. Regenerate it after authoring changes, restart the controller after copying it, and redeploy the game export when its packaged resources change.
## 3. Export and run a Pi cabinet
1. Install matching Godot export templates on the development computer.
2. Use the **Linux** export preset, configured for **ARM64**, for a 64-bit Raspberry Pi OS installation. The **Windows Desktop** preset is available for PC testing.
3. Copy the executable and any accompanying export files to the Pi.
4. Make the Linux executable runnable, then launch it normally to configure the controls:
```sh
chmod +x ArcadeEscape.arm64
./ArcadeEscape.arm64
```
Use your actual exported filename if different. The Pi runs the exported game; it does not need the Godot editor. The project has been tested on a Pi 4; verify the target hardware, display resolution, and controls for each installation.
For gameplay-only testing, no controller service or GPIO bridge is required. Real GPIO remains opt-in through `--gpio=real`.
## 4. Start the room controller and browser operator
Copy these three files together to the controller machine:
- [server.py](tools/room_controller/server.py)
- [operator.html](tools/room_controller/operator.html)
- [catalog.json](tools/room_controller/catalog.json)
The controller uses Python's standard library; it has no pip dependencies. Generate distinct random tokens of at least 16 characters for the operator and each cabinet. Replace the placeholders below with those values.
**macOS / Linux**, from that controller folder:
```sh
export ARCADE_OPERATOR_TOKEN='your-generated-operator-token'
export ARCADE_CABINET_TOKENS='{"cabinet_01":"your-generated-cabinet-one-token","cabinet_02":"your-generated-cabinet-two-token"}'
python3 server.py --bind 0.0.0.0 --database arcade_room.sqlite3
```
**Windows PowerShell**, from the repository root:
```powershell
$env:ARCADE_OPERATOR_TOKEN = 'your-generated-operator-token'
$env:ARCADE_CABINET_TOKENS = '{"cabinet_01":"your-generated-cabinet-one-token","cabinet_02":"your-generated-cabinet-two-token"}'
py -3 tools/room_controller/server.py --bind 0.0.0.0 --database arcade_room.sqlite3
```
Keep the database at a stable location and back it up. Allow TCP **8766** through the controller machine's firewall for the room LAN. The assigned internal tokens are documented in the [cabinet setup guide](docs/cabinet_setup.md), as requested for this installation. Keep deployment databases out of source control.
Open `http://CONTROLLER_IP:8766/` on the staff computer or iPad and enter the operator token. The iPad needs only a browser. Use a trusted isolated LAN; the default HTTP connection is not encrypted. See the [deployment guide](tools/room_controller/README.md) for network and credential details.
## 5. Connect cabinets to the controller
### PC versus Pi: which configuration file?
`arcade_network.cfg` configures a **Godot client**, not the Python controller. Its contents depend on the role that Godot runs in, not just the operating system.
| Machine / role | Where settings belong | Credentials |
| --- | --- | --- |
| PC running the Python controller + browser | `tools/launch/launcher.ini`, `[controller]` | `operator_token` and JSON `cabinet_tokens` for all cabinets |
| PC running the optional Godot `--operator` app | `user://arcade_network.cfg`, `[network]` | `url` and `operator_token` |
| Pi running a managed cabinet | `user://arcade_network.cfg`, `[network]` | `url`, its own `cabinet_id`, and its own `cabinet_token` |
| PC running a game as a test cabinet | Same cabinet fields as the Pi | That test cabinet's ID/token; do not duplicate an active Pi's ID |
**For the normal browser-operator setup, the PC does not need an `arcade_network.cfg`.** Enter the operator token in the browser; the server takes its credentials from the launcher/environment.
Example Pi Godot configuration:
```ini
[network]
url="http://WINDOWS_PC_IP:8766"
cabinet_id="cabinet_01"
cabinet_token="CABINET_01_TOKEN"
```
Optional PC Godot operator configuration (only for `--operator`, with the controller on that same PC):
```ini
[network]
url="http://127.0.0.1:8766"
operator_token="OPERATOR_TOKEN"
```
The Pi's URL must use the **PC's LAN IP**, never `127.0.0.1` unless the controller actually runs on that Pi. Both client roles use controller port **8766**. GPIO port **8765** is separate and stays local to the Pi.
Godot CFG strings require double quotes. Launcher INI scalar values are unquoted; the `cabinet_tokens` JSON object still uses JSON quotes.
When using the scripts, nonempty `[cabinet] url` and `token` values in `launcher.ini` override Godot's CFG. Existing `ARCADE_ROOM_URL` / `ARCADE_CABINET_TOKEN` environment values override those launcher values. Leave the INI URL/token blank to fall back to the CFG, and clear stale environment variables if needed. The scripts always pass their configured `[cabinet] id` (default `cabinet_01`) as `--cabinet`, overriding the CFG's ID: set the correct ID on every Pi even when using the CFG for its token.
### Generate a 16-character token
These are application authentication tokens, not SSH keys. Generate a separate token for each cabinet and for the operator:
```powershell
# Windows
py -3 -c "import secrets; print(secrets.token_urlsafe(12))"
```
```sh
# Pi / macOS / Linux
python3 -c "import secrets; print(secrets.token_urlsafe(12))"
```
Each command produces exactly 16 URL-safe characters. Preserve capitalization and any `-` or `_`, including a trailing hyphen. Restart the affected controller/client after changing credentials.
This produces a 16-character URL-safe token; use `py -3` on Windows if appropriate. For existing installations, preserve their assigned credentials instead of silently generating replacements. The older installation-specific examples remain in [cabinet setup](docs/cabinet_setup.md); the clean-install walkthrough uses fresh placeholders.
The supplied internal token assignments, complete controller configuration, and individual Cabinet 1–7 examples are in the [cabinet setup guide](docs/cabinet_setup.md). Use those assigned tokens for the current installation; generate new ones when provisioning additional identities or deliberately replacing a token.
INI scalar values are unquoted; JSON values such as `cabinet_tokens` retain JSON quotes. Godot CFG strings use double quotes. The CFG belongs in Godot's user-data directory, not beside the executable. On Linux that is normally `~/.local/share/godot/app_userdata/ArcadeEscape/`; on Windows, `%APPDATA%/Godot/app_userdata/ArcadeEscape/`.
On each cabinet, create **arcade_network.cfg** in the application's Godot user-data directory:
Nonempty launcher URL/token values override the CFG; inherited `ARCADE_ROOM_URL` and `ARCADE_CABINET_TOKEN` override those launcher values. The configured cabinet ID is passed as a command-line argument. Clear stale environment overrides when troubleshooting.
```ini
[network]
url="http://CONTROLLER_IP:8766"
cabinet_id="cabinet_01"
cabinet_token="your-generated-cabinet-one-token"
```
The cabinet controller URL is `http://MASTER_PRIVATE_IP:8766`, not the cabinet's own IP or GPIO port. The default operator connection is plain HTTP for the internal network; do not expose it through public router port forwarding.
Use that cabinet's own ID and token. CFG strings use **double quotes**.
## Exports, startup, and deployment
| Machine | Typical configuration directory |
| --- | --- |
| Raspberry Pi / Linux | `~/.local/share/godot/app_userdata/ArcadeEscape/` |
| Windows | `%APPDATA%/Godot/app_userdata/ArcadeEscape/` |
Use the [master walkthrough's release workflow](docs/pacman_rfid_first_setup.md#3-export-package-and-copy-one-complete-release) as the source of truth:
This file goes in the user-data directory, **not beside the executable**. `ARCADE_ROOM_URL` and `ARCADE_CABINET_TOKEN` environment variables override their file values; `--cabinet=...` overrides the cabinet ID.
1. Save resources and export `catalog.json` from the same project revision.
2. Build Linux ARM64 executable + PCK into a fresh release folder outside the repository; optionally build Windows separately.
3. Package the matching Python/controller assets with `master-package` and the cabinet files with `bundle`.
4. Configure site-specific files separately; compile the RFID helper on the cabinet.
5. Validate launcher settings, start the services, and run the physical acceptance test before enabling startup.
Start the managed client:
The existing `setup/pc` and `setup/pi` folders are manual staging areas, **not proof of a current build**. Their checked-in artifacts are not automatically rebuilt when source changes. Do not assume they contain the 2.3 RFID feature until explicitly exported and verified. Exclude staged exports from the game PCK.
```sh
./ArcadeEscape.arm64 -- --managed-cabinet --cabinet=cabinet_01
```
The deployment tools provide a terminal setup wizard, separate site credentials, SSH enrollment, per-cabinet preflight, versioned uploads with manifest hashes, explicit activation, and service/status/log commands. They do not install a private DHCP/firewall configuration or offer a browser provisioning wizard. RFID-only provisioning still needs the manual route described above.
This bypasses the game list and waits for the operator. It uses the controls previously saved through the launcher. Deploy the current export: the fleet controller requires protocol 2 (`arcade-cabinet-2`).
The Pi user-service helper captures the desktop environment and refreshes it on login. It does not automatically enable cabinet boot startup; follow the walkthrough for desktop autologin/startup, or use the master remote-start workflow. No Godot editor is needed on a deployed cabinet or browser-only master.
### Enable physical GPIO outputs
Follow the [GPIO guide](tools/pi_gpio/README.md) to install Pi dependencies and verify the wiring with an LED first. GPIO action pins use **BCM numbering**, not physical header positions.
For the Cabinet 01 GPIO17 example, run the helper and game in separate terminals:
```sh
# Terminal 1: local GPIO helper
GPIOZERO_PIN_FACTORY=lgpio python3 pi_gpio_bridge.py --controller cabinet_01_gpio --pins 17
```
```sh
# Terminal 2: managed game with real outputs enabled
./ArcadeEscape.arm64 -- --managed-cabinet --cabinet=cabinet_01 --gpio=real
```
Copy the bridge script from [tools/pi_gpio/](tools/pi_gpio/) to the Pi. Match its controller ID and allowed pins to that cabinet's hardware resources.
| Port | Service | Address used |
| --- | --- | --- |
| **8766** | Room controller and browser operator | Controller machine's LAN address |
| **8765** | GPIO bridge | `127.0.0.1` on the cabinet Pi |
Keep the room URL on **8766** even when the GPIO bridge reports **8765**. An accepted GPIO command confirms the software call; without sensor feedback it does not confirm physical coin delivery.
## 6. Run a room session
1. Confirm each required cabinet is **ONLINE** in the browser operator.
2. Select a scenario and choose **Prepare assigned cabinets**.
3. Wait for each cabinet to acknowledge **READY**, then start it remotely or with its physical Start button.
4. Monitor game state, event history, and hardware command acknowledgements.
5. Use **Reset cabinet…** to end its session before changing assignments.
Scenario preparation validates the complete assignment list before dispatch, including connectivity and game compatibility. Cabinets acknowledge independently. Manual game assignments intentionally run without inherited scenario rewards.
The sample [proof_of_concept](data/scenarios/proof_of_concept.tres) connects a Pac-Man win on Cabinet 01 to its `coin_release` action. The [two_cabinet_test](data/scenarios/two_cabinet_test.tres) demonstrates separate cabinet assignments and event mappings.
The older `-- --operator` Godot mode remains available for Cabinet 01 quick controls. Use the browser interface for multiple cabinets and the room map.
For a sequential two-Pi test, choose **Cabinet 1 -> Cabinet 2**: winning Pac-Man on the first cabinet loads and automatically starts Galaga on the second. See the [chain-test walkthrough](tools/room_controller/README.md#two-pi-chain-test-cabinet-1-wins---cabinet-2-starts).
## Troubleshooting
| Symptom | Check |
| --- | --- |
| Controls differ between Windows and Pi | Run Control Setup on the Pi; do not copy Windows mappings blindly. |
| Room connection token is not configured | Check the user-data CFG location, double quotes, role-specific token, and environment overrides. |
| Unauthorized | The service is reachable; check the token and cabinet ID against the controller configuration. |
| Cabinet stays offline | Check the controller IP, port 8766, firewall, running client, and export protocol version. |
| A game is missing from the compatible list | Update the cabinet's capabilities, regenerate the catalog, and redeploy the cabinet resources. |
| GPIO command fails or LED stays off | Check real mode, local helper, controller ID, pin allowlist, and the wiring test in the GPIO guide. |
| Delivery is unconfirmed | Software acknowledgement is available, but physical feedback is not; inspect the output. |
## Repository layout
```text
addons/escape_room/ Godot editor plugin
core/ Game contracts, resources, runtime, controls, networking
editor/ Room-authoring workspace and simulation tools
data/ Saved games, cabinets, rooms, scenarios, and mappings
games/ Seven games, art/audio, launcher, and control setup
operator/ Godot operator scenes
main/ Room runtime entry scene
tools/room_controller/ Python controller, browser dashboard, catalog exporter
tools/pi_gpio/ Local Raspberry Pi GPIO bridge
tests/ Godot and Python verification scripts
```
Back up site configuration, controller state, input mappings, and the GPIO deduplication ledger. Keep previous release bundles for rollback. Do not mix old PCKs, new catalogs, and unrelated Python releases.
## Development checks
Run commands from the repository root, replacing `godot` with your executable as needed:
Run from the project root; replace `godot` with the project's console executable and `python3` with `py -3` on Windows as needed:
```sh
python3 -m unittest discover -s tests/room_network -v
python3 -m unittest discover -s tests/gpio -v
godot --headless --path . --script tests/room_network/check_capabilities.gd
godot --headless --path . --script tests/launcher/check_launcher.gd
godot --headless --path . --script tests/launcher/check_controls.gd
python3 tests/room_network/check_integration.py /path/to/godot
godot --headless --path . --script res://tests/pcb/check_pcb.gd
godot --headless --path . --script res://tests/pcb/check_rfid_credit.gd
godot --headless --path . --script res://tests/room_editor/check_room_view.gd
godot --headless --path . --script res://tests/launcher/check_controls.gd
```
On Windows, `py -3` can replace `python3`. The network integration test launches two headless Godot clients and temporary services on ports 8765/8766, forces test-only wins, and checks a fake GPIO output. Stop any real services using those ports before running it. These tests do not replace physical Pi, wiring, or iPad checks.
Controller tests use a stable historical catalog fixture so designer changes do not alter protocol tests; the RFID scenario test also checks the actual exported catalog. Network/GPIO tests use temporary services and fake hardware, not physical reader/actuator validation.
## Current scope
Last recorded verification during this work: 66 controller tests and the RFID credit checks passed; the prior GPIO suite passed 10 tests. The broader `tests/test_pacman.gd` suite reported one ghost-capture assertion failure in unchanged model code. Godot test shutdown also reported resource-leak warnings. These are known validation limitations, not a claim that every project test passes. New Pi exports, RFID compilation, card compatibility, audio, and end-to-end physical operation still need installation testing.
The project supports standalone arcade play, room authoring and simulation, multiple managed cabinets, scenario assignments, a browser room map, and real GPIO outputs. External sensor inputs, detailed live puzzle-step diagrams, coordinated finale variants, and room timers/hints remain future work. Device locations on the map currently provide layout context rather than live physical sensor state.
## Troubleshooting
| Symptom | First checks |
| --- | --- |
| Controls differ on Pi | Run Control Setup locally; do not assume Windows button indices match. |
| Unauthorized | Correct role/token and cabinet ID; check inherited environment overrides. |
| Cabinet offline | Master URL/8766, network, running client, matching export version. |
| Game unavailable | Cabinet capabilities and matching exported catalog/resources. |
| RFID scenario rejected | Cabinet 2.3, real GPIO, matching reader ID in the exported PCB inventory. |
| RFID pin conflict | Explicit empty `pins =` for RFID-only testing. BCM17/27/22/20 and SPI0 are reserved for readers. |
| UID visible but no credit | Prepare the RFID scenario, then remove/re-present at J1; check the accepted-card list. |
| Output delivery unconfirmed | GPIO acknowledgement/electrical state does not confirm physical movement; use an appropriate sensor. |
| Reader stopped or stale | Helper process, SPI/GPIO permissions, wiring, and competing Odin/bridge processes. |
## Repository layout
```text
addons/escape_room/ Editor plugin and PCB inspector registration
core/ Runtime, networking, controls, room/cabinet/scenario resources
editor/ Authoring workspace, physical room and PCB editors
data/ Saved games, cabinets, room layouts and scenarios
games/ Seven games, assets, launcher and control setup
operator/ Optional Godot operator scenes
main/ Room runtime entry scene
tools/room_controller/ Python controller, browser hub, catalog exporter
tools/pi_gpio/ Local GPIO bridge, feedback sampler, Rust RFID helper source
tools/launch/ Supervised launchers, Pi service helper, remote SSH startup
tools/deploy/ Packaging, site setup, staged deployment and activation
setup/pc/, setup/pi/ Existing manually staged exports/configuration examples
docs/ Setup, deployment and designer walkthroughs
tests/ Godot/Python checks and isolated fixtures
```
## Remaining work
Physical RFID commissioning, complete RFID support in the fleet wizard, browser doorway rendering, additional cabinet artwork, general sensor-to-scenario rules, detailed puzzle-step views, coordinated finale variants, and room timers/hints remain future work. The hub can link to other services; it is not yet a full multi-room orchestration system.