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

22 KiB
Raw Permalink Blame History

Emulation in Verstack

The current catalog entry is Download VM, which exports Apple Silicon bundles. See the export acceptance audit for current evidence and remaining checks. The server emulator described below remains available for experimental SPIKE 2 work; it is no longer the catalog's primary emulation view. Later sections record the export implementation and successive validation runs.

The testable Emulator workbench view and isolated session worker are implemented. See testing and setup for the current launch choices, commands, verification, and limitations. The diagnostic is a synthetic media test using the real software cabinet broker; it is explicitly distinct from game boot.

Current implementation

The user selected a separate emulator session per client. Each browser tab owns a process group, writable state, cabinet broker, input stream, framebuffer, audio queue, and private session credential. The worker supports two simultaneous sessions by default, with aggregate Docker CPU/RAM/process limits and RAM-backed scratch. Immutable runtime files are shared read-only. Closing a client expires its session; stop removes its processes and scratch. Reconnection with retained ownership and persistent saves are future work.

The Theia activity bar opens a dockable Emulator view. A dedicated same-origin /emulator proxy keeps its control requests independent of archive mutations. The current transport is framed JPEG video and PCM audio over streaming HTTP, with bounded buffers. This makes the LAN pilot usable without additional media ports. WebRTC with synchronized audio/video, codec negotiation and adaptive bandwidth remains a future transport improvement.

Input requests require the session credential and an increasing sequence number. Holds release on blur, stream disconnect, stop, and a server-side heartbeat timeout. All 45 table contacts use the supplied dashboard coordinates and node addresses; they are connected to named broker inputs without inventing CPU SPI assignments. The Pro mapping remains provisional for LE game testing. Physical outputs are still unavailable in the donor broker.

Runtime and donor adaptations

The source came from the user-supplied ~/pokemon_emulator. Vendored source and its local changes are recorded in provenance. Only process, device-shim, machine and netbridge source plus table assets are included; game binaries and system libraries are not committed.

Runtime selection is data-driven. Materialize any extracted SD snapshot, or pair an extracted game update with a separate compatible SD snapshot using --system-snapshot. ELF headers select the ARM32 SPIKE 2 or ARM64 SPIKE 3 lane; no Pokémon title, edition, version or partition-number allowlist remains. The archive retains source identity; materialization verifies BLAKE3 files and can share verified unchanged files between runtimes. The original runtime manifest remains readable for existing installations.

Each private session presents its selected game at /games/title, with an alias for its original directory name. It reconstructs library SONAME links and checks executable/loader architecture, dependencies and symbol versions. Optional menu and boot-display targets are discovered per runtime. Country/free-play/decoder patches and donor-specific debug offsets are disabled. Shared system runtimes do not imply that every game's switch/node profile or hardware protocol is emulated.

The browser worker is the only paced audio FIFO reader. It sends bounded PCM chunks instead of recording an indefinitely growing raw/WAV file. Capturing and streaming are independent of the guest's rendering performance.

Browser execution option

Rendering the table and controls in the browser works with server emulation and does not require moving game execution. This is the practical first hybrid.

Full game execution in WebAssembly is a separate research track. The supplied game is an ARM64 Linux executable, not C/C++ source that Emscripten can simply recompile. A browser implementation would need an ARM64 execution engine, Linux/runtime and graphics compatibility, and the cabinet interfaces, or a separate compatible source port. Existing browser VPX/display assets do not establish browser execution of the SPIKE 3 game. Prototype boot and measure memory/startup/frame timing before exposing this as a supported engine.

An ARM64 worker on the LAN is another option: clients keep the same stream and controls while execution avoids cross-architecture translation. A GPU primarily changes rendering/encoding costs; CPU translation is a separate bottleneck.

Technical references: QEMU user-mode emulation, Emscripten porting, and Emscripten build inputs.

Worker isolation configuration

The user explicitly approved disabling Docker's AppArmor profile for the emulator worker. manage.sh configures apparmor=unconfined on that container so bubblewrap can create its nested mount namespace. Host AppArmor is unchanged. The worker retains its capability allowlist, no-new-privileges setting, read-only runtime mounts, private session scratch, and aggregate resource limits.

The worker also uses the explicitly approved pivot_root seccomp exception. emulator/seccomp.json differs from the exported seccomp-baseline.json only by allowing that syscall. manage.sh applies this profile to the emulator worker. These files target the current x86-64 worker, not a general cross-architecture policy.

Next acceptance work

The real Stern boot displays render through the browser from the supplied King Kong LE 0.97 SPIKE 2 SD and Pokémon 0.83 SPIKE 3 SD. Pokémon 0.85 launches using the separate 0.81 SD system runtime and passes namespace/library startup, but still waits during cabinet hardware initialization. Game audio and gameplay remain unconfirmed. Cross-architecture tests cover shim symbol resolution, mmap ABI, GLES framebuffer completeness and color readback. Stream/switch diagnostics pass independently of that compatibility limitation.

Keep measuring real game rendering, audio continuity, input effects and ball lifecycle on this x86-64 server. Establish game-specific device mappings and patch signatures before claiming gameplay compatibility. Add owner resume, persistent save policy and stronger per-session resource isolation before broader use. WASM game execution is not implemented.

Game startup fixes (2026-09-14)

Actual game launches exposed failures not covered by boot-display checks:

  • The SPIKE 2 Mesa overlay omitted glibc compatibility libraries such as librt, mixing the worker's loader/pthread library with an older SD library. The overlay now carries the matching family together.
  • Game renderer threads request explicit real-time scheduling. When pthread_create returns EPERM, the shim retries with inherited scheduling while retaining the caller's other attributes. No extra worker capabilities are required.
  • Virtual UART replies now signal an eventfd, waking select and poll even when a reply arrives after the wait starts. SPI polling includes the real controller's transfer delay.
  • SPIKE 3 power-board version replies use the selected runtime's firmware metadata. The startup-ready event uses the four-byte contract recovered from the game's analysis.
  • SPIKE 2 sessions provide a private i.MX6 sysfs model, based on the supplied spike2_emu/run_game.sh, for revision, backlight and power attributes.

The UI distinguishes initialization from rendering, shows elapsed startup time, and opens logs after a prolonged no-frame wait or process exit. A broker socket alone no longer means that the game is running.

The actual Pokémon LE 0.81 game now renders its Pokémon “Startup In Progress” screen and produces PCM data. This establishes game rendering, not a completed gameplay acceptance test. Evidence is in data/validation/emulator-games/. Native, ARM32 and ARM64 regressions exercise delayed UART replies and the denied-real-time-thread fallback.

King Kong's game-level exit code 4 came from its NVRAM migration check: it created a mode-000 .crc32 file and then could not reopen it without the cabinet's root DAC override. Private /data/nv/ files now receive owner read/write access when created with no owner permissions. Missing hardware mixer elements no longer trigger ALSA's NULL-element assertion. The game now remains alive and renders the Stern splash; its SPIKE 2 node-bus transport and hardware video decoding remain incomplete, and King Kong audio/gameplay are not verified. Session logs include dump/debug_log.txt so fatal game errors are visible alongside launcher output.

Final King Kong validation stayed alive for 210 seconds and produced 1,848 captured frames after the private-NVRAM and absent-mixer fixes. Its missing /dev/ttymxc1 node bus is now reported explicitly instead of implying cabinet startup is complete. The browser test received 463 actual Pokémon frames and roughly 3.9 MiB of PCM during its capture window; the visible screen still said “Startup In Progress.” No gameplay acceptance claim is made.

Linux video selection and SPIKE 2 UART experiment (2026-09-14)

The web worker explicitly sets SPIKE3_VIDEO_BRIDGE=0 in the guest environment. The inherited macOS VideoToolbox bridge remains available to its original launcher; this Linux worker uses guest software decoding. This removes the unsupported bridge probe, but does not fix the malformed HEVC packets reported by Pokémon.

ARM32 sessions set SPIKE3_EXPERIMENTAL_SPIKE2_UART=1, which routes /dev/ttymxc1 through the existing SPIKE 3 netbridge transport. The shim logs that this is experimental. Other launchers retain the original device behavior unless they explicitly enable the flag. New sessions pick up both changes.

A live King Kong LE 0.97 test opened the device and reached ExchangeData, then reported empty replies (received 0, expected lengths 118). The machine-service log captured requests including 0a00, 070101, 8002f18d00, and 8003f0226b00. These are not the STX/ETX-framed messages accepted by the SPIKE 3 decoder. The alias therefore exposes the next failure; it does not establish a working SPIKE 2 protocol. An adapter must translate legacy wire requests into the shared node model and encode legacy responses. The first 24 raw transport writes are logged (at most 64 bytes each), including writes that the current frame decoder cannot recognize.

ARM32 and ARM64 UART tests cover delayed replies through both device names. The ARM64 run also passed the server/runtime/startup regression suite (15 tests). These tests verify transport behavior, not complete game boot.

Browser video offload would use WebCodecs for decoding and optionally WebGPU for composition. The current guest consumes decoded frames as DRM textures; a browser-only video stream cannot replace those textures without moving composition or returning decoded pixels to the guest. Neither option repairs invalid compressed packets. No browser decoder was added by this experiment.

Download VM display repair (2026-09-14)

The first exported Pokémon 0.83 VM (9bc62c7bcd1241bcbec91b60de623179) booted with working sound but a blank QEMU window on the user's Mac. Rebooting that exact image under isolated QEMU TCG with virtio-gpu reproduced missing /dev/dri: modprobe drm, drm_kms_helper, and virtio_gpu failed. The Debian kernel package had been extracted without its post-install depmod hook; only the separate initramfs tree had dependency indexes. Sound worked because emu-init explicitly inserted its dependency modules.

The vendored image builder now generates module indexes in the final rootfs. Adding those indexes to the same image made all three GPU modules load and created card0 and renderD128. A real QEMU framebuffer capture then showed the Stern logo and SPIKE 3 EMU text from boot_display. This proves display recovery, not complete gameplay or macOS VideoToolbox behavior.

The exported dashboard now includes Service / Enter, Back, +, and in the always-visible cabinet button row. Browser clicks on all four emitted switch events, and an actual guest control channel acknowledged service Enter press and release. Five dashboard tests and fourteen export tests passed. No analysis, existing emulator, or workbench services were interrupted by these tests. Full universal image updating and native Mac gameplay remain open.

Universal client validation (2026-09-14)

Downloaded quick export 9221e0c6d9fc49049f3c7b85f0f692ed from the live catalog and executed its bundled client against actual cached components:

  • Built Pokémon LE 0.83 with explicit shared runtime legacy (0.81 SD). The image's game SHA-256 was 1e5becb85840d0cc7a1d334b7cc43bdb83e51c7c7f8243bead8db679e9ffb22d.
  • Built Pokémon LE 0.85 with automatic selection. The API selected 1314f21f-4f8f-4ed8-a1b3-cdc6a0505ea1, the nearest 0.83 SD. The image's game SHA-256 was 5f8bd90b23c35d15252315b2003fb99d0c5950bca2017a085db49f7b52862293.
  • Compared each image's extracted game and libc against its downloaded component; both matched. Both filesystems passed read-only e2fsck. The 0.85 launch alias points to pokemon_le/game.
  • Switched to 0.85 and back to cached 0.83; neither image was rebuilt or modified. Then invoked the actual packaged Update.command --game ... --default-runtime against the live API. It reused 0.85 and saved both active.json and connection.json.
  • Booted the quick-built 0.83 image in isolated QEMU TCG. virtio_gpu loaded, DRM devices appeared, and a framebuffer capture showed the Stern boot display. This is guest/display validation, not native macOS full-game validation.

Temporary evidence is in /tmp/verstack-universal-build-result.json, /tmp/verstack-universal-switch-result.json, and /tmp/verstack-universal-gpu.log. The obsolete blank-display export was moved to /tmp/verstack-retired-exports to free archive disk space; the repaired standard export remains published as 614867564d0d4f0f9a2ef334956e5514. All validation containers were stopped after use. Existing emulator, workbench, and active catalog jobs were not interrupted.

Remaining operational work includes standard-build scratch/storage behavior on this nearly full host and the user's native Mac gameplay confirmation. The bundles README's earlier statement that universal image updating is untested is superseded by the validation above.

Standard export scratch validation (2026-09-14)

Standard exports now build in a private temporary directory below VERSTACK_EXPORT_SCRATCH (default /tmp/verstack-vm-exports). Admission checks persistent output space and scratch space separately, with reserves; when both paths share a filesystem their requirements are combined. Success and failure remove the private build tree, including Docker-owned files. Source runtimes are never removed. Persistent export storage keeps the final ZIP and job logs.

Standard artifact cache keys now cover the builder, SD-selection policy, repair script, Mac launcher, and vendored payload. Scheduler/HTTP/documentation edits no longer force an unchanged standard VM rebuild. The validated 0.83 archive was migrated only after verifying its previous source revision and all payload input hashes; its previous revision is retained in the job receipt.

A real API request built standard Pokémon 0.85 as export 4630e41d9efd461285a985c74ae24f83, selecting the nearest 0.83 SD. It completed with the export scratch directory empty and no persistent staging tree. The full 2,634,755,536-byte download through the workbench proxy matched SHA-256 1b3e8ee5db6b8deff2f4948d651537faaa02569ca166d9877b8925313a72c781. Both 0.83 and 0.85 standard requests then reused their completed downloads. Seventeen export tests passed, including success/failure cleanup and cache invalidation boundaries. Native Mac full-game verification remains pending.

Exported local conagent follow-up (2026-09-14)

The native Mac test now reaches the game boot screen but reports the country restriction. The exported raw Pokémon 0.83 game hash 1e5becb85840d0cc7a1d334b7cc43bdb83e51c7c7f8243bead8db679e9ffb22d was outside the upstream provisioning allowlist, causing the export to disable its local backend. Its two factory comparison sites match the original 0.83 profile; an exact-binary encrypted EEPROM round-trip test passed with that hash. The generated export now provisions its private image during the Linux build, ships its local TLS/config files, and starts the existing supervised service on Mac without first-launch Docker/LLVM compilation. Guest startup explicitly loads virtio_net before configuring the restricted local network. Original source binaries are unchanged; the generated image records installed hashes and retains the pre-provisioning game/EEPROM for recovery.

Quick-client image cache identity now includes the local VM recipe revision. A finished-image marker and required image/kernel files prevent a partial build with an existing launcher from being treated as ready. Component downloads stay cached independently. Regression coverage checks reuse, missing-image rebuilds, and recipe updates while retaining the previous VM.

These changes are not yet proof of native Mac gameplay or successful country check completion. Guest conagent startup validation is still in progress.

Replacement standard export 5cc3a773fd354a5f868710cc5e2a454c contains 2,641,996,757 bytes, SHA-256 908c6fe94be57123446bc19b78053f4b6efb2abc6b215b593315344c498e166d. A full download through the workbench matched both values. Validation booted the extracted ZIP in a two-vCPU TCG container, substituting only Linux host options, QEMU 7's equivalent reconnect option, software audio/video and snapshot mode. The guest had eth0 10.0.2.15/24 with its default route, DRM devices, the real conagent process with local_transport.so mapped, and a successful guest HTTP health response from the local service. The game displayed the Pokémon startup screen and software HEVC produced a frame. The later framebuffer was black; no genuine conagent authentication request was observed during this run. This does not establish country-check completion, service-menu navigation, attract mode, or native Mac gameplay. The native test is pending.

The isolated test VM was stopped and its extracted bundle removed. Build staging was automatically removed after both the initial packaging-permissions failure and the successful retry. Small diagnostic records remain under /tmp/verstack-conagent-validation; published downloads remain in the export store. The initial failure was root ownership of private local IC files; the builder now transfers ownership to the export-stage user while preserving 0600 permissions. No catalog job or existing emulator session was stopped.

Pristine cabinet records and version switching follow-up

Reconstructing upstream's original 0.83 generated-game hash confirmed exactly which legacy changes it contained. Applying the supplied country-renderer, free-play, node-update and factory-feature scripts to raw hash 1e5bec... reproduces e8add023b60be684509f25cd54972981cd2adf15b0ad09d1e99ba33c29a4f54a with 82 changed bytes. This comparison used a temporary copy, removed afterward. It explains why the upstream 0.83 provisioning path did not need to establish all pristine factory feature predicates.

The pristine 0.83 reader at ELF VA 0x79dc90 and writer at 0x79dc30 validate and install cabinet marker 5a a5 00 ff at logical EEPROM 0x3c; the writer clears factory feature 3. Its checksum helper at 0x6f3830 is byte-identical to the verified LE 0.85 helper. The export provisioner now writes that record for pristine 0.83 as well as 0.85, through the existing EEPROM mask. The earlier 0.83 export 5cc3a773... lacked this record and is superseded by the corrected build. The actual game still receives only the two synthetic digest constants; legacy feature-getter, country-renderer and node-update patches are not applied. The user subsequently reported that Pokémon runs on their Mac, identifying upstream revision 37d70e6. Both exports use that revision, so the tested export ID remains uncertain; this confirms a native successful launch without assigning it specifically to either archive. Service-menu navigation remains unconfirmed. Preserve both published archives until the tested export is identified.

The isolated headless test of 723fe5e4 completed the game's own factory and cabinet record validators under GDB: both returned 1. This checks record validity, not the complete startup or authentication flow. The test VM was stopped and its extracted bundle removed; small logs remain in /tmp/verstack-conagent-headless.

The exact LE 0.85 hash 5f8bd90b23c35d15252315b2003fb99d0c5950bca2017a085db49f7b52862293 uses digest literals at 0x7a3348 / 0x7a3700, codec constants at Ghidra 0x02716800, 0x02727020, 0x02727030 (image base 0x00100000), and cabinet reader/writer at ELF VA 0x8929d0 / 0x892970. Checksum helper 0x6f0690 sums bytes modulo 65536. Exact-binary tests cover both releases' encrypted records, marker bytes, unchanged regions and patched constants.

Node firmware identity now comes from the selected game's LPC node HEX names: 0.83 contains 1.33.0, LE 0.85 contains 1.35.0. Conflicting versions are rejected; when no metadata exists the existing 1.33.0 default remains. This changes the advertised version, not the existing board/switch model. Quick client retries failed component preparation once per fresh invocation; ordinary polling never starts a retry loop. The export API integration test proves recovery after a stored failure and checks that only one replacement worker is created.