Files
pokemon_pinball_wiki/native-playable-table.md

9.7 KiB

Native macOS Playable Table

Status

The first native playable-cabinet implementation lives under emulation/. It combines two existing, independently useful runtimes:

  • QEMU 11 with HVF runs the generated ARM64 guest and genuine Pokémon Pro game code. Its Cocoa window and CoreAudio output are the backbox, game display, music, speech, and game effects.
  • Visual Pinball X 10.8.1 owns the physical balls, collision geometry, switches, trough inventory, and moving playfield mechanisms.

The supported first release is deliberately two-window. It does not use VNC, noVNC, browser capture, JPEG screendumps, or the dashboard for gameplay. The dashboard remains an optional diagnostic monitor. This is an evidence-calibrated development recreation; it is not yet a playable release and is not a measurement-validated replica of ramp heights, coil forces, or rebound curves.

The one-command Finder entry point is emulation/run-playable.command; the terminal entry point is emulation/scripts/run-playable-macos.sh.

Current stop points (2026-07-14)

The latest native launch is blocked first by a VPX script error:

Runtime error: Variable is undefined: 'Service1Key'

The table uses the unsupported legacy names Service1Key through Service4Key. The pinned VPX host exposes those actions as VPXActionKey(25) through VPXActionKey(28). Since the references are inside the common key handler, an input event can abort before even non-service input is forwarded. This must be repaired and exercised in the native host before the cabinet path can be called playable.

The next confirmed blocker is the power-distribution input model. Start and Action physically enter the power-distribution board and are returned to the game by command 0x15, active-low in bits 1 and 0. Bit 2 is the active-low cabinet-door input. The current generic 0xff response therefore leaves both buttons released and the door open, which is consistent with the observed factory/pre-play setup screen remaining stuck. Correct idle/Start/Action/both responses are 0x03, 0x01, 0x02, and 0x00 respectively.

Separately, the game's CPU-SPI receive alignment is 00 || rx[0..6]. 0-SW-23 must therefore be raw byte 1 bit 7 low, making the correct idle raw SPI record ff7fffffffffffff. The complete ordered plan and acceptance checklist are in emulation/VPX_EMULATOR_STATUS.md in the parent emulator repository.

Runtime ownership

Concern Authority
Rules, scoring, player/ball progression Genuine game process in QEMU
Display, music, speech, game effects QEMU Cocoa/CoreAudio
Ball physics and collision geometry Native VPX at a 1,000 Hz physics step
Physical switch state and pulses VPX table script and controller plugin
Lamps and mechanism driver commands Genuine game through netbridge
Mechanical ball/rubber/flipper/gate sounds VPX only
Window placement and focus Native macOS helper

emulation/vpx/layout.json is the single mapping source. It records 45 playfield switches plus 11 sensors, 13 drivers, 113 logical lights expanded to 150 exact SPIKE channels, six serveable trough balls, and a separate captive ball. The plugin mapping header is generated from this manifest by emulation/vpx-plugin/generate-layout-mapping.py; it is not a second handwritten address map.

Playfield protocol and safe recovery

analysis/netbridge-emulator/netbridge_emu.py provides a persistent, loopback-only JSONL endpoint at 127.0.0.1:8091 using spike3-playfield/v1. VPX sends hello, switch.set, switch.pulse, cabinet.set, release_transient, and heartbeat. The bridge sends complete snapshot records, coalescible lamp.delta records, lossless ordered driver.pulse records, and status records.

Every server record has a process-unique bridge_session, a monotonic launcher bridge_generation, sequence, model revision, and monotonic timestamp. A new connection is not considered ready after TCP connect or hello. The bridge first sends a disconnected snapshot and synchronizing; the plugin releases transients, restores stable switch/cabinet state, and sends a heartbeat as an ordering barrier. The bridge reports ready and sends QEMU monitor cont only after that barrier and after the guest UART is connected.

With physics_authority=vpx, the netbridge synthetic shooter, trough, outlane, VUK, flipper-EOS, and ball-return shortcuts are disabled from process start. On a VPX disconnect, transient contacts open, VPX mechanisms de-energize, stable trough/held-ball contacts remain, and QEMU receives monitor stop. A supervised bridge restart removes the old readiness record, restores only stable contacts plus lamp/driver snapshot baselines, assigns a new session and generation, and never replays cumulative driver counts as new pulses.

The implementation and synchronization tests are in analysis/netbridge-emulator/test_netbridge_emu.py. The native supervisor is emulation/scripts/run-macos.sh; the outer cabinet lifecycle and identity checks are in emulation/scripts/run-playable-macos.sh.

VPX plugin and table modes

emulation/vpx-plugin/ is rebased to the installed VPX commit ca80d1604e6a525db2a3f6d1ec704fc651d573fa. Its builder checks the exact Git revision, host binary SHA-256, ARM64 architecture, and bundle version before writing emulation/work/vpx-plugin/Spike3Controller/vpx-host-attestation.txt. The playable launcher copies the attested app to a private workspace runtime, installs the matching plugin there, and ad-hoc signs that private copy. It does not modify the user's source VPX app.

Network work runs on a background thread. Bounded queues keep socket I/O away from VPX's render/physics thread; lamp state is coalesced, while driver events retain order and cumulative counters. Reconnect snapshots establish a driver counter baseline and cannot become stale physical events.

The table has explicit modes:

  • standalone: preserves the self-contained greybox rules and mechanisms.
  • spike3: QEMU is authoritative for lamps and every driver-driven mechanism.
  • qa-spike3: integrated handshake/input/output acceptance without falling back to standalone behavior.
  • Other qa-* modes are deterministic native geometry, shot, sling-authority, plunger, and inventory checks documented in emulation/vpx/README.md.

In integrated mode, a missing controller is fail-safe: the table remains in SPIKE mode, disables standalone serving/GI, de-energizes mechanisms, and logs the error. It does not silently animate local flippers. Physical flippers, slings, pops, trough eject, auto-launch, posts, gate, Meowth motor, and ejects move only from mapped game driver pulses.

Physics and private visual assets

The versioned pokemon-pro-vpx-physics/v1 record in emulation/vpx/layout.json fixes the inspected service-manual values at a 6.5-degree pitch, 1-1/16-inch steel ball, and 50 Shore A rubber. Every physics or dimension value is tagged manual, photo_estimate, comparable_part, or playability_tuned.

The registered overhead photograph is the provisional playfield texture; the manual locator remains a hidden registration/QA layer. Reconstructed post and pop meshes sit over simple collision bodies. Extracted Pokémon images are used only as local toy decals or temporary plastics, and unmeasured toys/wireforms remain labeled proxy geometry. The table, extracted artwork, reference photos, manual, and game audio are private-use inputs and are excluded from any redistributable package.

Controls and native windows

The launcher writes a private VPX input profile without overwriting existing service bindings. Defaults are:

Input Function
Left/Right Shift or LB/RB Flippers
Return, right trigger, or right stick Plunger/launch
1 or Menu Start
5 or View Coin
Return, Left Option, or A Action
7/8/9/0 or D-pad Service back/down/up/enter

VPX receives focus. On two displays the AppKit/Accessibility helper attempts a fullscreen VPX playfield plus fullscreen QEMU backbox. On one display it uses approximately 65% for VPX and 35% for a 16:9 backbox. If Accessibility access is declined or a window does not expose fullscreen through AX, both processes remain running and the launcher prints the saved placement log and manual layout.

Verification boundary

The reproducible checks are split by what they prove:

  • python3 -m unittest discover -s analysis/netbridge-emulator -v verifies recovered serial behavior, protocol ordering, output identity, safe pause, snapshot restore, synchronization, and lossless driver delivery.
  • emulation/vpx-plugin/tests/run-tests.sh verifies the generated mapping, background client, ordered input edges, output records, driver baseline, and reconnect behavior against a loopback server.
  • emulation/vpx/build.sh plus emulation/vpx/qa.sh verifies the assembled VPX, source round-trip, exact mapping counts, physics tags, ball inventory, and collision topology.
  • The native emulation/vpx/qa-*.sh launchers exercise VPX itself rather than only inspecting generated JSON.
  • A complete cabinet run additionally has to prove the real QEMU game accepts inputs/physical switches and drives VPX mechanisms through the live bridge.

Do not infer a normal three-ball game, multiball, measured frame-rate percentile, or 60-minute invariant soak from the static/unit suites. Those acceptance rows must remain explicitly open until their corresponding native run logs exist.

Optional composed backbox

QEMU D-Bus shared-memory scanout is a follow-up only. It must demonstrate correct page flips, sustained 60 fps for ten minutes, and no more than one added frame before being connected to a VPX Backglass/ScoreView texture. The working two-window Cocoa path remains the fallback; VNC/browser/screendump capture is not an accepted gameplay fallback.