# 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™` - `0x25a6140`: `Scan QR to log in or hold left flipper to activate Home Team™` 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.