Support multiple programs per manifest; fix ScummVM launch and audio unlock
Replace the single start_cmd manifest field with a "programs" list so a game can expose more than one runnable binary. Duke Nukem 3D's manifests now offer Play and Setup (DOS sound hardware config), and every ScummVM manifest offers Start and Manager (ScummVM's own graphical Launcher). Also fixes two real bugs found along the way: - All 5 ScummVM manifests were passing the game id as `-f <id>`, but -f is ScummVM's --fullscreen flag; ScummVM rejected it as a stray argument and exited before the setup page's poll interval could notice, so clicking Start silently did nothing. Switched to --auto-detect. - The page's audio-unlock listener called ctx.resume() once on first click/keydown and unconditionally removed itself with no .catch(), so a silently failed first attempt left the AudioContext stuck suspended forever with no way to retry. Now retries on every click/keydown until ctx.state actually reports "running". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NiNnj78HGx1KWyCCo39HSz
This commit is contained in:
@@ -42,6 +42,27 @@
|
||||
- `indy3` — Indiana Jones and the Last Crusade
|
||||
- `tentacle` — Day of the Tentacle
|
||||
|
||||
**Real bug found later (all 5 of the above), reported as "Indiana Jones and the Fate of
|
||||
Atlantis doesn't start properly"**: every one of these manifests' command was
|
||||
`["/usr/games/scummvm", "-p", ".", "-f", "atlantis"]`-shaped, i.e. `-f <target>`. `-f` is
|
||||
ScummVM's `--fullscreen` flag, not "select this game" — there's no flag that takes a bare
|
||||
game-id positionally either; passing one is rejected outright ("Stray argument 'atlantis'"),
|
||||
and ScummVM exits near-instantly with a nonzero status. Because the exit happens faster than
|
||||
the setup page's ~3s poll interval, `is_running()`'s lazy cleanup silently reaped it before
|
||||
anyone saw a "Running" status or an error message — clicking Start just looked like nothing
|
||||
happened, no error surfaced anywhere. (This also means the "verified end-to-end" claim above,
|
||||
from when these were first added, was wrong — Stop/Uninstall working afterward isn't
|
||||
evidence the game itself ran; it only proves `is_running()` correctly saw it wasn't.)
|
||||
Root-caused via `docker exec` running the exact command by hand and reading ScummVM's own
|
||||
stderr, rather than through the setup page. Fixed by switching every one of these 5
|
||||
manifests' command to `["/usr/games/scummvm", "-p", ".", "--auto-detect"]` — ScummVM's own
|
||||
flag for "scan the given path and start whatever single game it finds there", rather than
|
||||
fighting its target/config-file model, which fits this container's one-game-per-directory
|
||||
layout exactly. Verified for real this time: manually launched under a live Xvnc, screenshotted,
|
||||
and confirmed the actual Indiana Jones and the Fate of Atlantis title screen renders (not
|
||||
just "process didn't crash") - then re-verified through the real setup page end-to-end
|
||||
(`POST /start` -> "Running (Start)" -> `POST /stop`).
|
||||
|
||||
Skipped the German CD release of Day of the Tentacle on the share (`Day Of The Tentacle
|
||||
(CD DOS, German).zip`) — unlike every other zip here, it doesn't wrap its contents in a
|
||||
single top-level folder (loose files at the zip root plus a stray `MANIAC/` dir for the
|
||||
@@ -199,3 +220,64 @@
|
||||
sound TODO item above. Left as a known gap for these two specifically; fixing it would mean
|
||||
either reverse-engineering `duke3d.cfg`'s binary format to patch the stored IRQ/DMA, or
|
||||
finding a way to script `SETUP.EXE`'s interactive hardware wizard.
|
||||
|
||||
- **Redesigned the manifest schema so a game can expose more than one runnable binary**,
|
||||
replacing the single `start_cmd` field with a `programs` list of `{id, label, cmd}` (see
|
||||
`setup_server.py`'s module docstring for the full schema). Direct motivation: the
|
||||
`duke`/`dn3d` audio gap above — rather than trying to script or reverse-engineer
|
||||
`SETUP.EXE`'s hardware wizard from outside, just expose `SETUP.EXE` itself as a second
|
||||
"program" alongside the game, so the interactive fix is a button click away. Both
|
||||
`duke.json`/`dn3d.json` now list `play` (`duke3d.exe`) and `setup` (`setup.exe`); every
|
||||
other existing manifest on the share (`stuntcar`, `monkey`, `monkey2`, `atlantis`, `indy3`,
|
||||
`tentacle`) was migrated to a single-entry `programs: [{"id": "start", "label": "Start", ...}]`
|
||||
so the "Installed" row still shows one plain "Start" button for them, unchanged in practice.
|
||||
`start_game()` now takes a `program_id` and looks up the matching entry; only one program per
|
||||
game can run at a time (same one-slot-per-game model as before — starting a second program
|
||||
while the first is still running is refused the same way restarting an already-running game
|
||||
is). The "Running" status now shows which program is active (e.g. "Running (Setup)") since
|
||||
that's no longer implied by the game name alone. Verified end-to-end: built the image,
|
||||
installed `duke`, POSTed `/start` with `program=setup`, confirmed via `ps aux` inside the
|
||||
container that `dosbox setup.exe` (not `duke3d.exe`) was the process actually running, and
|
||||
via a real screenshot that DOSBox's actual "Choose Sound FX Card" screen renders — i.e. the
|
||||
interactive fix path this was built for is genuinely reachable now. Did not go further and
|
||||
actually reconfigure the sound card/re-verify audio afterward — that's still a manual,
|
||||
per-game step left to whoever plays `duke`/`dn3d`, not something the container should do
|
||||
unattended.
|
||||
|
||||
- **Real bug found via user report ("no audio, no audio icon in browser")**: the page's
|
||||
audio-unlock listener (`unlockAudio()`) called `ctx.resume()` once on the page's first
|
||||
click/keydown and then unconditionally removed itself, with no `.catch()` on the
|
||||
`resume()` promise. Confirmed the server-side pipeline was fine the whole time (PulseAudio
|
||||
sink-input active and uncorked, `parec` capturing real non-silent PCM, the raw WebSocket
|
||||
bridge on `AUDIO_PORT` handshaking and streaming genuine non-zero frames when tested
|
||||
directly) — the browser's own `ctx.state` stayed `"suspended"` no matter how many times the
|
||||
user clicked the page. Root cause: if that very first resume attempt silently failed for any
|
||||
reason (rejected promise with no handler = no console error, easy to miss), the listener had
|
||||
already removed itself and there was no way left to ever retry — permanently stuck
|
||||
suspended for the rest of that page load. Confirmed directly: manually attaching a fresh
|
||||
one-off click listener that called `ctx.resume()` succeeded immediately (`state=running`),
|
||||
proving the `AudioContext` itself was fine and only the one-shot unlock logic was broken.
|
||||
Fixed by making `unlockAudio()` idempotent and non-removing: it now calls `ctx.resume()`
|
||||
(with a `.catch()` that logs any failure) on *every* click/keydown, and only detaches the
|
||||
listeners once `ctx.state` has actually become `"running"` — safe to call repeatedly since
|
||||
resuming an already-running context is a no-op. Verified fixed by the user after rebuilding
|
||||
and restarting the container: audio icon now appears and sound plays.
|
||||
|
||||
- **Added a "Manager" program to all 5 ScummVM manifests** (`monkey`, `monkey2`, `atlantis`,
|
||||
`indy3`, `tentacle`), alongside their existing "Start" (`--auto-detect`) - the SCUMM
|
||||
equivalent of Duke's "Setup" button. Plain `scummvm -p .` with no game argument opens
|
||||
ScummVM's own graphical Launcher (game list, Game Options, Global Options, MT-32/subtitle/
|
||||
audio-driver settings, etc.) instead of jumping straight into the game - but confirmed via
|
||||
screenshot that `-p .` alone shows an *empty* list ("None" selected, nothing in the game
|
||||
panel); the game has to be registered first via `--add` (a separate one-shot command that
|
||||
exits after adding, confirmed idempotent - running it again just logs "already been added,
|
||||
skipping" and doesn't error or duplicate the config entry) before the Launcher will show it
|
||||
pre-selected and ready for "Game Options...". So the "manager" program's command is
|
||||
`["bash", "-c", "/usr/games/scummvm -p . --add >/tmp/scummvm-add.log 2>&1; exec /usr/games/scummvm -p ."]`
|
||||
- `--add` first (idempotent, safe on every launch), then `exec`'d into the real interactive
|
||||
`scummvm -p .` process so the tracked PID (and therefore `_teardown()`'s terminate/kill) is
|
||||
the actual ScummVM process, not a wrapper shell with an orphaned child. Verified end-to-end
|
||||
through the real setup page: `POST /start` with `program=manager` -> "Running (Manager)" ->
|
||||
confirmed via `ps aux` that the real `scummvm -p .` process (not the wrapper) is what's
|
||||
running -> screenshot confirms the Launcher opens with "Indiana Jones and the Fate of
|
||||
Atlantis" already listed and selectable.
|
||||
|
||||
Reference in New Issue
Block a user