Files
Verstack Local f73167de85 Add downloadable VMs and improve catalog processing
Package Apple Silicon VM exports and cached runtime switching with local cabinet controls and conagent provisioning. Preserve experimental SPIKE 2 emulation.

Improve preparation concurrency, Ghidra checkpoints, signature importing, sound indexing and client-side spectrograms. Include regression tests and validation notes.
2026-09-14 20:03:59 -05:00

6.4 KiB
Raw Permalink Blame History

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_SESSIONS inside 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.