Test emulation in Verstack
Open the Emulator play-circle button in the workbench activity bar, or open
/emulator/ on the same Verstack host and port. Start Stream & switch
diagnostic, then click Enable audio. You should see a moving test pattern,
hear a quiet 440 Hz tone, and see table/button events in the diagnostic picture
and Machine state. The diagnostic runs the supplied software machine broker;
it does not run the Pokémon game.
Open a second browser tab to start a separate session. Each session has its own machine state, controls, framebuffer, audio, and ephemeral owner credential. Stop each session when finished. Closing a tab expires its session after 60 seconds; held controls release after three seconds without input heartbeat. Reloading creates a new client; session resume is not implemented in this pilot.
The emulator worker runs with apparmor=unconfined, explicitly approved by the
user so bubblewrap can create its nested mount namespace. This setting is scoped
to that container; it does not disable host AppArmor. Real game compatibility
remains experimental.
The user also approved the worker-only pivot_root seccomp exception required
by bubblewrap. seccomp-baseline.json records the previous applied x86-64
filter; seccomp.json adds only that syscall and is used by manage.sh.
The Release selector lists materialized SPIKE 2 (ARM32) and SPIKE 3 (ARM64) runtimes. The launcher detects the executable architecture and selects the matching QEMU, loader, compiler and Mesa libraries. A release may supply a game, boot display and/or SPIKE menu; only available targets are offered.
System libraries and game files can come from different snapshots. Import any
full SD image of the matching generation, then use its extracted snapshot as
--system-snapshot for other game updates. There is no title/version allowlist.
The loader and dependency checks still apply: matching architecture alone does
not guarantee compatible library versions or hardware behavior. Unchanged files
are verified and hard-linked between runtime bundles to avoid duplicate copies.
These are experimental launch tests. The supplied Pokémon switch/node profile is provisional and is not a universal physical table mapping. Binary patches and donor debugging offsets are disabled across releases. A running process or allocated framebuffer does not establish playable gameplay or audible game sound.
Local setup
The current installation retains its original runtime and discovers additional runtime manifests without rebuilding the UI. After importing and extracting an SD image through Verstack, materialize its snapshot:
bash emulator/manage.sh build
bash emulator/manage.sh materialize --snapshot EXTRACTED_SD_SNAPSHOT
bash emulator/manage.sh start
npm --prefix workbench/stern-catalog run build
npm --prefix workbench run build:browser
systemctl --user restart verstack-workbench
For a game update that does not contain the system partition:
bash emulator/manage.sh materialize --snapshot GAME_SNAPSHOT --system-snapshot SD_SNAPSHOT
Use --game-path PATH if a snapshot contains multiple main executables. Source
paths and partition numbers are discovered from the manifest and ELF headers.
The system SD may be from another title of the same architecture; compatible
libraries remain required. Existing sessions retain their selected runtime.
Docker access is required; the script uses sudo when direct Docker access is unavailable. Materialization downloads the selected system and game trees through Verstack's read-only API, verifies every BLAKE3 artifact, and resumes by verifying existing files. Credentials are not copied into the source tree or image. The emulator's game namespace has no network and uses read-only game/system mounts. The worker receives namespace capabilities needed by the donor's bubblewrap launcher; it is a local trusted-LAN pilot.
The worker binds host loopback port 8095. The Theia backend proxies
/emulator separately from archive operations. VERSTACK_EMULATOR_PORT changes
the proxy port. No machine-broker ports are published. Runtime files live in
ignored data/emulator/runtime; session scratch lives in a private 2 GiB
container tmpfs and is deleted on stop. Library SONAME aliases are reconstructed
in each private runtime view because this SD extraction omitted symlinks.
The worker restarts with Docker unless explicitly stopped. start starts an
existing container without replacing it. To load edits to the Python worker,
restart that container after stopping test sessions. Recreating it is needed
after changing container dependencies or resource limits.
Pilot limits
- Two active sessions maximum (
VERSTACK_EMULATOR_MAX_SESSIONSinside the worker). Shared worker limits: 8 GiB RAM, six CPU cores, 512 processes and 2 GiB scratch. These are aggregate limits, not per-client resource guarantees. - JPEG video at up to 12.5 fps, scaled to about 960×540, and 48 kHz stereo PCM over a bounded same-origin HTTP stream. This is a simple test transport with higher bandwidth and less precise A/V synchronization than WebRTC. Slow streams disconnect rather than queue indefinitely. The client limits queued audio to 250 ms. Real frame rate depends on guest execution and rendering.
- Each client has an unguessable credential required for control, state, logs, and media. This isolates sessions but does not add user login or access control to the existing LAN deployment. Use its existing trusted network.
- The browser must enable audio with a click. A stream reconnect currently requires stopping and starting a session. Closing the Emulator tab discards the local credential; saves are ephemeral.
- A runtime manifest means files were materialized; it does not mean a game has booted. A process marked running does not establish playable gameplay.
Verification
sudo docker exec verstack-emulator python3 -m unittest -v test_runtimes test_server
npm --prefix workbench/stern-catalog test
node tests/ui-emulator.mjs
The Python tests exercise two real brokers, credential isolation, capacity, switch sequencing, media packet contents, disconnect release and process/scratch cleanup. The browser test opens the workbench Emulator view and a second client, checks rendered video and nonzero scheduled audio, sends a switch event, and checks that stopping one session leaves the other streaming. These tests create temporary diagnostic sessions; run with both worker slots free.