pod: bundled per-game deployment (portable config, --exit-with, build-pod)

Phase 10: RIO hardware exists only on pods + dev boxes, so production is one
RIOJoy copy inside each podized game folder, no resident tray. ConfigLocator
makes a config.json beside the exe win over %APPDATA%; --exit-with <exe|pid>
(CompanionTarget/CompanionExit, 60s startup grace) tears down and quits when
the game exits; a starting --exit-with instance waits up to 15s for the
predecessor mutex instead of silently exiting. deploy/build-pod.ps1 emits
the ~4.5MB drop-in (app + portable config wrapping the profile + start
script, no drivers) - verified against the shipped Descent profile. 455
tests; PLAN.md Phase 10 + INPUT-INTEGRATION.md pod section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-31 22:01:52 -05:00
co-authored by Claude Fable 5
parent 9a792193f9
commit 97caf124a6
10 changed files with 549 additions and 12 deletions
+42
View File
@@ -141,6 +141,14 @@ example: throttle → `RightThumbY` `UnipolarPositive`, rudder mix →
## Shipping a profile with your game
Two models, chosen per deployment:
- **Pod bundle (production — cockpit cabinets):** the game's folder carries
its own RIOJoy copy + profile; nothing is registered anywhere. See
[Pod-bundled deployment](#pod-bundled-deployment-production) below.
- **Import into a resident RIOJoy (dev boxes):** hand a profile document to
the shared tray install, as follows.
A game (or its installer/launcher) hands its profile to RIOJoy as a
**single-profile JSON document** plus one command:
@@ -186,6 +194,40 @@ repo as the source of truth — dxx-rebirth does this, and a RIOJoy test
(`ShippedDescentProfile_MatchesDxxRebirthReferenceCopy`) asserts the two
checkouts stay byte-identical so drift is caught in CI.
## Pod-bundled deployment (production)
On the pods (the cockpit cabinets) no resident RIOJoy runs at all. Each
podized game's install carries its own copy, built by:
```
deploy\build-pod.ps1 -ProfileJson <your-game-profile.json>
```
That emits a ~5 MB drop-in — `riojoy\` (app + a **portable** `config.json`
holding just this game's profile; a config beside the exe wins over the
per-user `%APPDATA%` store) plus `start-riojoy.bat` — which the game's
launch script calls before starting the game:
```
start "" "...\riojoy\RioJoy.Tray.exe" --exit-with <game exe>
```
`--exit-with` makes RIOJoy self-managing: it activates when the game's window
comes foreground, and once the game process has run and then exited it tears
itself down completely (ports released, wallpaper restored, plasma blanked)
and quits. If the game never appears within 60 s it also quits, so a failed
launch can't strand it. Back-to-back launches hand over cleanly: a starting
`--exit-with` instance waits up to 15 s for the previous game's copy to
release the single-instance lock.
Properties that matter on a cabinet: each game pins the RIOJoy build it was
verified with (updating RIOJoy for a new game can't regress an old one);
native games simply don't bundle RIOJoy, so the COM ports are free for them
by construction; and drivers stay a one-time pod provisioning step (the
universal package's `install-rio.ps1`) — pod bundles deliberately carry none.
The bundled exe is still the full tray app: run it with no arguments on the
pod and you have the profile editor.
## Testing without hardware or game
- **vRIO** (`pipe:vrio`): click buttons on the emulator's panel and watch them
+39
View File
@@ -490,6 +490,45 @@ spec + client snippets in [`docs/FEEDBACK.md`](FEEDBACK.md). Delivers the
settings (JSON-only today); shipped client examples (SimHub plugin / DCS
export script) beyond the FEEDBACK.md snippets.
### Phase 10 — Pod-bundled deployment — code-complete ✅
Deployment topology decision (2026-07-31): RIO hardware exists only on **pods**
(the cockpit cabinets) and dev boxes — no freestanding end-user PCs. Production
model is therefore **one RIOJoy copy bundled inside each podized game's
folder**, started by the game's launch script and exiting with the game; no
resident RIOJoy runs on a pod, and the native games simply don't bundle one
(making the COM-port yield machinery vestigial in production). The resident
tray + auto-switch reclassifies as the development harness. 455 xUnit tests
total across the suite.
- **Portable config**: `ConfigLocator.Resolve` — a `config.json` beside the
exe wins over `%APPDATA%\RIOJoy\config.json`; `TrayApplicationContext.
ConfigPath` resolves through it, so `--import-profile` targets the same
store. A pod bundle needs no import step: its config *is* the profile.
- **`--exit-with <exe|pid>`** (`CompanionTarget.Parse` — pid, or a name
normalized like auto-switch triggers): the tray polls the companion on its
existing 1 s timer and quits through the normal teardown (ports released,
wallpaper restored, plasma blanked) once the game has run and then gone.
`CompanionExit` holds the pure decision — launch order isn't guaranteed, so
a never-seen companion only triggers exit after a 60 s startup grace
(also covers "game failed to launch"). Clock-free and unit-tested
(`tests/.../Hosting/CompanionExitTests`).
- **Instance handoff**: with `--exit-with`, a starting instance waits up to
15 s for the predecessor's single-instance mutex (game A's copy tearing
down while game B's starts) instead of the historical silent exit-0; plain
launches keep the instant-exit behavior. Abandoned mutex (predecessor
crash) counts as acquired.
- **`deploy/build-pod.ps1`**: emits the per-game drop-in — `riojoy\` app
(flavor-selectable net48/net40, Skia-pruned) + portable `config.json`
wrapping the game's profile document verbatim + `start-riojoy.bat`
(`--exit-with` prefilled from the profile's first trigger) + README-POD —
zipped as `RIOJoy-pod-<name>-<stamp>.zip` (~4.5 MB). Deliberately **no
drivers**: pods are provisioned once by the universal package. Verified by
building the Descent bundle and round-tripping its emitted config through
`ConfigStore.Load`.
-**Remaining:** on-pod verification of the full launch/handoff cycle
(launcher → game A → quit → game B); podize a first real game with the
bundle; revisit the universal zip's `install.bat` framing (dev-setup only)
once pod deploys are routine.
---
## Open items / risks