# Main Game Behavior Notes

## Scope

Target:

- `/games/pokemon_pro/game`

Derived analysis artifacts:

- `analysis/static-triage/game.strings`: `strings -a -t x /games/pokemon_pro/game`.
- `analysis/static-triage/game.objdump-p.txt`: `objdump -p /games/pokemon_pro/game`.

Ghidra project:

- Project `stern`, program `/games/pokemon_pro/game`.

Static caveat: this page is based on rootfs files, strings with offsets, and targeted Ghidra decompilation. It does not include live machine or hardware validation.

## Main Game Binary

Confirmed type:

- `/games/pokemon_pro/game` is a stripped AArch64 PIE executable, 52,829,400 bytes.
- SHA-256: `1e5becb85840d0cc7a1d334b7cc43bdb83e51c7c7f8243bead8db679e9ffb22d`.

Dynamic dependencies from `objdump -p` include:

- `libcrypto.so.3`
- `libcurl.so.4`
- `libusb-1.0.so.0`
- `libgpiod.so.2`
- `libEGL.so.1`, `libGLESv2.so.2`, `libdrm.so.2`, `libgbm.so.1`
- `libgstapp-1.0.so.0`, `libgstvideo-1.0.so.0`, `libgstreamer-1.0.so.0`
- `libavcodec.so.59`, `libavformat.so.59`, `libavfilter.so.8`, `libavutil.so.57`

## Topper Connection and Authentication

The Pokemon topper is represented as an optional Magikarp node-board/mechanism subsystem, not as a USB or network accessory in the evidence reviewed here.

Evidence from `/games/pokemon_pro/game` strings:

- `0x258bf30`: `AD_MAGIKARP_TOPPER_SERIAL_NUMBER_KEY`
- `0x258f808`: `MAGIKARP TOPPER SERIAL NUMBER KEY`
- `0x2594a70`: `Adjustments related to Magikarp Topper.`
- `0x2594a98`: `Magikarp Topper`
- `0x25a03c8`: `Press Select for Magikarp Topper Calibration`
- `0x25a03f8`: `Magikarp Topper Node Board Not Signed`
- `0x25a0420`: `Magikarp Topper Mech Needs Calibration`
- `0x25a0448`: `Magikarp Topper Mech is Disabled`
- `0x25b8388`: `Topper Servos (Optional)`
- `0x25b83a8`: `Topper LEDs (Optional)`
- `0x25ce291`: `Cannot detect Magikarp Topper.`
- `0x25ce2b9`: `Check Interlock switch and cable connections`
- `0x25d2f20`: `Magikarp Node Board Device:`
- `0x25d2f3c`: `Calibrated %d %d %d %d %d`
- `0x25d2f74`: `Not Calibrated`
- `0x25f3100`: `31MagikarpTopperNodeBoardSettings`

Inference: the topper connects over the SPIKE node-board bus as one or more node devices, with servos and LED channels exposed to the game. Use is gated by node detection, a node-board signature check, and calibration state. The `AD_MAGIKARP_TOPPER_SERIAL_NUMBER_KEY` string maps to a serial-derived node-board key path; see `topper-serial-key-emulator.md` for the recovered `0xfc` command and XOR key algorithm.

### Topper Node Registration Communication

The Magikarp topper registration/authentication path appears to reuse the generic SPIKE node-board registration path. Static evidence supports this sequence:

1. The game powers and scans the node bus during startup. In Ghidra, `FUN_0090bb00` toggles node-bus power/control helpers, calls a node locator (`FUN_0090aff0`), and updates the expected-node table with `FUN_007cbee0(..., 0x20, 2)`.
2. `FUN_0090aff0` clears a 32-byte discovery buffer, polls node addresses through `FUN_00a23450`, stores each discovered address, acknowledges it with `FUN_00a26690(address)`, and marks it through `FUN_00a26700(address, 1)`.
3. Discovered node records live in a global 32-entry table beginning at `DAT_033c0258`. `FUN_007cbd10(start_address)` returns the next present board record. `FUN_008e7090` formats the diagnostic inventory from this table as:

   ```text
    %2d: ver %d.%d.%d, part %s%c, serial %d-%d-%d
   ```

   The formatted fields come from the board record: node address at offset `0x00`, firmware version bytes at `0x24..0x26`, part string at `0x38`, part suffix at `0x37`, and serial fields at `0x58`, `0x5a`, and `0x5c`.
4. For each discovered board whose status is `2`, `FUN_0090bb00` calls `FUN_00a2b410(address, out_status)`. `FUN_00a2b410` sends a node command frame with:

   ```text
   byte 0: address | 0x80
   byte 1: 0x01
   byte 2: 0xff
   byte 3: 0x08
   ```

   The response is treated as two 32-bit status words. `FUN_0090bb00` stores an inverted view of bit `0x80000` from the second word into `DAT_03431830[address]`.
5. If board byte `0x9e` is not already set, `FUN_0090bb00` calls `FUN_0090a9c0(board_record)`. This helper performs additional board provisioning/configuration checks. It sends several node commands through the shared frame sender `FUN_00a22aa0`, including commands `0x14`, `0x40`, `0x44`, `0x46`, `0x48`, `0x72`, `0xf2`, and `0xf8` depending on board state. It also reads 64 bytes of board data via `FUN_00a2aad0`, which issues repeated command `0xf2` reads in 8-byte chunks:

   ```text
   byte 0: address | 0x80
   byte 1: 0x02
   byte 2: 0xf2
   byte 3: offset
   byte 4: 0x08
   ```

6. `FUN_0090a9c0` sets board-record flag bit `0x4000` when its probe/provisioning checks find the board in the unsigned/problem state. The node-board test UI separately enumerates these boards: `FUN_00853f80` prints `Unsigned Board #%d` when the same flag bit is set.
7. After the per-board checks, `FUN_0090bb00` calls runtime/configuration helpers including `FUN_00a25290`, `FUN_007c7b40`, `FUN_006b4b40`, `FUN_008cc100`, `FUN_008eb840`, and `FUN_007c0710`. `FUN_006b4b40` is the recurring node-board service loop; it sends command frames for runtime/config/device operations such as `0x40`, `0x41`, `0x42`, `0x43`, `0x45`, `0x4d`, and `0xf4`.

For the topper specifically:

- The topper is exposed to the game as `MagikarpTopperNodeBoardSettings`, `TopperMech`, `Topper Servos (Optional)`, `Topper LEDs (Optional)`, and named servo/LED devices such as `SERVO HEAD`, `SERVO TAIL`, and `ETOPPER ... LED ...`.
- The topper test/calibration UI uses the generic node-board state. It can display `Cannot detect Magikarp Topper.`, `Magikarp Topper Node Board Not Signed`, `Magikarp Topper Mech Needs Calibration`, or `Magikarp Topper Mech is Disabled`.
- The calibration details callback at `FUN_006a9380` reports `Magikarp Node Board Device: Calibrated %d %d %d %d %d` using adjustment IDs `0xa6` through `0xaa`, or `Magikarp Node Board Device: Not Calibrated` if the calibration predicate fails.

Interpretation: the game communicates with the topper control board as a SPIKE node-board at a discovered node address. Registration is not a cloud registration exchange and not a USB/network exchange; it is a local node-bus handshake. The game discovers the board, reads identity/status/version/part/serial fields, sends a status command (`0xff`), reads additional 64-byte board data with command `0xf2`, and then marks the board as signed/usable or unsigned/problem via board-record flags. The recovered Magikarp-specific serial key path reads 16 bytes with command `0xfc` and computes `(word0 ^ word1 ^ word2 ^ word3) & 0x7fffffff`; details and a MicroPython emulator are documented in `topper-serial-key-emulator.md`.

## Stuck-Ball and Lost-Ball Handling

The game has explicit ball-search, lost-ball, and trough replacement behavior.

Evidence from `/games/pokemon_pro/game` strings:

- `0x258b878`: `AD_LOST_BALL_RECOVERY`
- `0x258eaa0`: `Serve a new ball in the event of a lost/stuck ball.`
- `0x2595ea0`: `AUD_TOTAL_BALL_SEARCH_STARTS`
- `0x2595ec0`: `AUD_TOTAL_LOST_BALL_FEEDS`
- `0x2595ee0`: `AUD_TOTAL_LOST_BALL_GAME_STARTS`
- `0x25981c0`: `Total count times the game attempted to find a stuck ball via ball search.`
- `0x2598220`: `Total count times the game determined a stuck ball was lost, and was replaced with a ball from the trough.`
- `0x25982a8`: `Total count times the game determined a stuck ball was lost while starting a game.`
- `0x25aba50`: `SELECT: TROUGH TEST`
- `0x25abaa8`: `%d : Ball%P1//s/% Missing`
- `0x25ada30`: `BALL MISSING`
- `0x25ada90`: `LOCATING BALLS`
- `0x25beaf0`: `TROUGH JAM`
- `0x25beb18`: `SHOOTER LANE`

Targeted Ghidra decompilation:

- String `0x026cdee0`, `%s: MD_EVENT_STABLE - balls_on_playfield = %d, endball_count = %d\n`, is referenced by `FUN_005e4f20`.
- `FUN_005e4f20` handles `trough_event_handler` stable events. It computes `balls_on_playfield` from the total balls in game minus the stable count of balls in devices, logs the number of balls in the trough and `endball_count`, then pushes `balls_on_playfield` into game state through virtual calls.
- For `MD_EVENT_KICK`, the same function fires the trough eject coil using a strength adjustment.
- For `MD_EVENT_ENDBALL`, it logs whether endball processing will be ignored or processed.

Behavior summary: when the game cannot find a ball, it starts ball search and records the event. If it decides the ball is lost/stuck and lost-ball recovery is enabled, it serves a replacement ball from the trough and records a lost-ball feed. If all balls dispensed onto the playfield are physically stuck and no ball returns to a device/trough, static evidence says the game will continue treating those balls as on the playfield, then eventually show missing/locating-ball or device-malfunction paths. It cannot create more replacement balls once the trough is depleted; operator intervention is required to recover the physical balls.

## Unexpected Extra Balls

Confirmed behavior:

- The multiball-device subsystem validates impossible device states such as `Too many balls locked`, `Too many balls in device`, `Bad lock count after lost ball`, and `Multiball device system is not stable` at offsets `0x259eba0`, `0x259ebe8`, `0x259ebc8`, and `0x259ed38`.
- The trough event handler calculates and propagates a live `balls_on_playfield` count on stable events.
- Game diagnostic text exposes `FG_VALID_PLAYFIELD_ACHIEVED_THIS_BALL` at `0x259fa88` and `0x25a4120`.

Inference: the game logic does track physical ball accounting and detects inconsistent ball counts in devices. I did not find a distinct string or decompiled branch named "unexpected extra ball on playfield." A loose extra ball on the playfield would be reflected in `balls_on_playfield` once the trough/device counts stabilize, but the static evidence found here does not prove a special penalty, tilt, or operator alert solely for an unexpected extra playfield ball.

## Hidden Buttons and Debug Controls

Accessible without opening the cabinet:

- `0x258d460`: Home Team can be used by holding the left flipper after setup.
- `0x258e138`: restart behavior can be allowed by holding the start button.
- `0x258e190`: end current game by holding Start plus Left Flipper.
- `0x2597e98`: audit text counts games ended by holding left flipper and start.
- `0x25a33b0`: `HOLD BOTH FLIPPER BUTTONS FOR MORE GAME MODES`
- `0x25a3868`: `HOLD BOTH FLIPPER BUTTONS FOR MENU`
- `0x25a5bb8`: `PRESS BOTH FLIPPER BUTTONS TO SELECT PLAYER`
- `0x25a5c38`: `HOLD BOTH FLIPPER BUTTONS TO MOVE PLAYER BACK TO ROSTER`
- `0x25a5e50`: `HOLD BOTH FLIPPER BUTTONS TO REMOVE GUEST FROM HOME TEAM&trade;`
- `0x25a6140`: `Scan QR to log in or hold left flipper to activate Home Team&trade;`

Debug/test controls found in the binary:

- `0x258c768`: `AD_DEBUG_MENU_SHOW`
- `0x2590af0`: `DEBUG MENU SHOW`
- `0x2590bd8`: `Test Only: Award Magikarp Letters`
- `0x2590c20`: `Test Only: Start Pikachu vs. Magikarp`
- `0x2590c90`: `GODOT ASSET DEMO`
- `0x2590ce0`: `POKEMON PREVIEW`
- `0x2590d40`: `GODOT PROGRESSION TEST`
- `0x259fa40`: `FG_TEST_MENU_SHOW_DEBUG_ADJUSTMENTS`
- `0x259fa68`: `FG_TEST_MENU_SHOW_DEBUG_AUDITS`

Conclusion: I found player-accessible hold/press combinations for login, player selection, game-mode UI, ball launch, restart, and end-game. I did not find evidence of a cabinet-closed secret sequence that directly opens operator diagnostics or debug commands. Debug/test items exist, but the names indicate they are gated by service/test-menu feature flags or adjustments rather than exposed as a hidden public button sequence.

## QR Scanner and QR Input Handling

The QR scanner appears as a SPIKE node-board peripheral, not as host-side camera decode logic.

Evidence:

- `0x25b8418`: `QR Scanner`
- `0x25b8a40`: `QR SCANNER BACKLIGHT`
- `0x25b8a58`: `QR SCANNER STATUS READY`
- `0x25b8a70`: `QR SCANNER STATUS STRING`
- `0x25b8a90`: `QR SCANNER STATUS ENCODED DATA`
- `0x25b8ab0`: `QR SCANNER STATUS MISSED STRING`
- `0x25b8ad0`: `QR SCANNER STATUS OVERRUN`
- `0x25b8af0`: `QR SCANNER STATUS UNSUPPORTED_AP`
- `0x25b8b18`: `QR SCANNER STATUS FIFO OVERRUN`
- `0x25b8c00`: `QR SCANNER STATUS NO READER`
- `0x25dd3e8`: `Check QR scanner RJ45 nodebus cable`
- `0x25dd410`: `Check QR scanner ribbon cable`
- `0x25e26e8`: `/connectivity/qr_code_storage/`
- `0x25e2708`: `wifi_qr_code`

Targeted Ghidra decompilation:

- `FUN_00913720` builds QR scanner status/diagnostic display text. It explicitly chooses `Check QR scanner RJ45 nodebus cable` or `Check QR scanner ribbon cable` depending on scanner/node state.
- No host-side QR decode library strings such as `zbar`, `quirc`, `qrencode`, or `libcamera` were found in the game strings. `libusb-1.0.so.0` is linked, but the QR scanner strings point to nodebus status and cabling.

Insider Connected scan flow evidence:

- `0x25dd568`: `ScannedUserAccountUUID`
- `0x25dedd0`: `QET_SCANNED_USER_ACCOUNT`
- `0x25de1a0`: `Authenticating player with back-end server.`
- `0x25de1d0`: `Failed to begin construction of PLAYER_AUTHENTICATE message.`
- `0x25ecf18`: `PLAYER_AUTHENTICATE`
- `0x25d3b10`: `user_uuid`
- `0x25df370`: `{ "machine_uuid": "%s", "user_uuids": [`
- `0x25dfa20`: `/api/v2/game/player_properties`
- `0x25e0cc8`: `Server response to retrieve_player_properties has no body; returned error status %s.`
- `0x25e0d20`: `Server response to retrieve_player_properties has invalid JSON; returned error status %s.`
- `0x25e0d80`: `Server response to retrieve_player_properties has invalid user %s.`

Targeted Ghidra decompilation:

- `FUN_00914520` maps netgame action `0x10` to `ScannedUserAccountUUID`.
- `FUN_00919250` maps queue entry type `7` to `QET_SCANNED_USER_ACCOUNT` and `0x1f` to `QET_RETRIEVE_PLAYER_PROPERTIES`.
- `FUN_0092d270` handles the retrieve-player-properties response. It parses JSON, extracts `user_uuid`, converts it with `uuid_copy` / `uuid_unparse`, checks whether the UUID maps to a known player object, and logs `invalid JSON`, `no body`, and `invalid user` failures.

Security assessment: the game treats scanned login data as a UUID/account identifier and sends it through the connectivity agent to Stern back-end authentication and player-property APIs. The recovered response-handling path validates JSON shape and user identity against the local player/account model before applying properties. I did not find a static evidence path where arbitrary QR text is directly executed as a command, file path, SQL statement, or shell input. The scanner also reports encoded-data, overrun, unsupported-AP, FIFO-overrun, and no-reader status states. Remaining uncertainty: this pass did not recover the exact QR payload parser or maximum input length checks, so QR input should be considered partially assessed rather than proven memory-safe.
