14 KiB
Native macOS Playable Table
Status
The native playable-cabinet implementation lives under emulation/. It
combines two 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 default VPX frontend is now the clean-room table assembled at
emulation/vpx-rebuild/dist/PokemonPro_Rebuild.vpx. 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 playable development recreation; it 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.
Clean rebuild workflow
With no table override, emulation/scripts/run-playable-macos.sh runs
emulation/vpx-rebuild/build.sh and selects
emulation/vpx-rebuild/dist/PokemonPro_Rebuild.vpx. The maintained sources are
design/geometry.json, design/lights.json, the VBS template, procedural
assets, and generator under emulation/vpx-rebuild/; generated source/ and
dist/ files must not be hand-edited. emulation/vpx-rebuild/run-standalone.sh
provides the short no-QEMU physics/render path.
SPIKE3_VPX_TABLE still permits an explicit custom table. If that table needs
a preparation step, set SPIKE3_VPX_BUILD; setting the custom table without a
build override prevents the clean rebuild script from modifying it. The
historical emulation/vpx/PokemonPro_Greybox.vpx and
emulation/vpx/manual/PokemonPro_Manual.vpx are deprecated historical
artifacts. The clean workflow does not inspect or write either file, and
neither is the active geometry source or default.
Current stop point (2026-07-20)
The bridge now ingests host events immediately and routes service controls over
active-low CPU-SPI. Start and Action use the installed power-distribution
command 0x15 path, including unsolicited input reports. The genuine display
has acknowledged service navigation, and protocol tests cover a Start change
without a preceding host poll. Those are transport results, not a completed
game.
The clean table and canonical hardware contract pass their static validators.
emulation/vpx-rebuild/qa/runtime-smoke.log records a bounded VPX 10.8.1
CaptureAttract pass on the exact current table SHA-256
ddce5d48033ee3c6e67248910a73897293e8b9c36eee29df164160a93572a30e,
with table/VBS load, script compile/start, Metal initialization, two rendered
frames, clean close, and exit code 0. In integrated modes,
the clean script publishes physical contacts, consumes exact-address lamp
components, preserves ordered driver pulses, establishes reconnect baselines,
and releases transients/mechanisms on disconnect. Cabinet edges move the
flippers locally while cabinet/EOS state is still sent to the genuine game.
The final clean table also completed the supervised native QEMU/VPX startup
gate on 2026-07-20. The accepted session reached Playable cabinet connected
with synchronized VPX physics, 392 lamp updates, 7 LED frames, 1 driver pulse,
stable captive-ball contacts, and QEMU-keyboard coin/Start press-release
records. The default hardware-output wait is 420 seconds because normal game
initialization can take about five minutes. A transient bridge blip is accepted
only after a later synchronized reconnect; unrecovered disconnects still fail
closed.
The remaining immediate cabinet gate is completing and preserving a genuine
normal game cycle. The generated QEMU disk is writable and persistent, but the
current conagent data_store model is memory-only. After that gate, the final clean table still needs a captured normal game,
multiball, full physical shot coverage, integrated RGB/driver/mechanism traces,
restart recovery, and a 60-minute inventory soak. Earlier greybox/manual
runtime logs remain useful historical bridge evidence, but they do not prove
the clean table's geometry or final trajectories.
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/playfield-contract/pokemon-pro-hardware.json is the canonical
geometry-free mapping. It records 45 playfield switches, 11 sensors, 9 cabinet
controls, 13 drivers, and 113 logical lamps expanded to 176 component bindings
at 150 exact SPIKE addresses. The plugin mapping header is generated from this
contract by emulation/vpx-plugin/generate-layout-mapping.py; it is not a
second handwritten address map. Ball inventory and placement live separately
in the clean geometry/script rather than becoming transport fields.
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: provides local serving, flipper/plunger control, and mechanism behavior for no-QEMU physics/render checks.spike3: QEMU is authoritative for lamps and every driver-driven mechanism.qa-spike3: integrated handshake/input/output acceptance without falling back to standalone behavior.
In integrated mode, a missing controller is fail-safe: the table remains in SPIKE mode, disables standalone serving/GI, de-energizes driver-controlled mechanisms, and logs the error. Flippers are the deliberate exception to game driver ownership: cabinet buttons actuate them locally and publish their cabinet/EOS states to the bridge. Slings, pops, trough eject, auto-launch, posts, gate, Meowth motor, and ejects move only from mapped game driver pulses.
Physics and private visual assets
emulation/vpx-rebuild/design/geometry.json is the active clean-room geometry
and physics source. It fixes a 952 by 2115 VPX plane, 6.5-degree pitch, and
standard 50-unit-diameter ball. XY references follow the full 451 by 998 Pro
locator registration; Z paths, clearances, restitution, friction, coil force,
and mechanism timing remain simulation values until measured on a physical Pro
machine. design/lights.json independently records lamp placement and
confidence rather than deriving visual coordinates from the transport map.
The deterministically registered official Pro photograph is the provisional playfield texture; it retains photographed hardware, shadows, and lighting under the separately rendered VPX geometry. Original procedural meshes provide posts, hardware, toys, and mechanisms, and the CC0 mechanical bank supplies physics audio without copying game samples. The table, official photograph, Pokemon/Stern imagery, reference manual, and assembled output are private-use artifacts 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, Space, A, or right trigger | Action/launch |
1 or Menu |
Start |
5 or View |
Coin |
7/8/9/0 or D-pad |
Service back/down/up/enter |
The QEMU guest keyboard relay now implements the same map as VPX. Previously,
only Shift was a cabinet control when the Cocoa backbox had focus; Return and
1 through 9 still used an obsolete diagnostic switch map. That produced the
observed failure where flippers navigated Guided Setup while Start, coin,
Action, and service keys did nothing useful. The corrected relay maps 1,
5, Return/Space, and 7/8/9/0 to the named cabinet inputs above.
The 2026-07-15 generated rootfs was rebuilt with the correction. The installed
AArch64 /usr/local/spike-emu/bin/keyboard-switch-relay was extracted from the
ext4 image and matched an independent source build at SHA-256
55054003950873900b5ce203292846f158b861060dbdd2e04b5be4a8afa1869c.
A headless QEMU monitor test injected all ten keys and
emulation/work/logs/keyboard-relay.log recorded matching press/release pairs
for both flippers, Start, coin, Action twice, and all four service controls.
That event-name check alone was insufficient: CPU and powerdist contacts do
not pass through a node input-bank poll. The playfield bridge now runs a
bounded host-event pump which routes those edges to their real transports
immediately while leaving ordinary node-bus edges queued until the relevant
bank read. The cabinet aliases and routes are loaded from
emulation/playfield-contract/pokemon-pro-hardware.json.
VPX normally 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 -vverifies recovered serial behavior, protocol ordering, output identity, safe pause, snapshot restore, synchronization, and lossless driver delivery.emulation/vpx-plugin/tests/run-tests.shverifies the generated mapping, background client, ordered input edges, output records, driver baseline, and reconnect behavior against a loopback server.python3 emulation/playfield-contract/validate_contract.py --jsonverifies the geometry-free schema, channel counts/routes, manual evidence, consumer loading, and generated-header parity.emulation/vpx-rebuild/build.shregenerates assets/source, verifies the assembled VPX, compares the round-trip VBS, and runs contract/object, geometry, clearance, lamp-route, audio, and no-ball-teleport checks recorded inemulation/vpx-rebuild/qa/rebuild-validation.json.emulation/vpx-rebuild/qa/runtime-smoke.logrecords the exact current table loading, compiling/starting its script, rendering two frames, and closing with exit code 0 under VPX 10.8.1CaptureAttract. It does not claim interactive ball or mechanism coverage.- The accepted live cabinet startup proves the real QEMU game returned lamp, LED, and driver output through the final table/plugin path and received QEMU-keyboard coin/Start edges. It does not prove a complete normal game.
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.