Document VPX emulator status and recovery plan
This commit is contained in:
197
native-playable-table.md
Normal file
197
native-playable-table.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user