198 lines
9.7 KiB
Markdown
198 lines
9.7 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
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.
|