feat: multi-game port selection — the machine says which game it is on

The runtime tried one port file (game.port) and refused it if the game's
words did not match. Now it tries every *.port in /usr/local/padmode/ports
and /dump/ports (then the single files, the old layout), resetting the port
tables between candidates, and keeps the first whose core sites verify: a
wrong candidate hooks nothing, so trying it is safe, and the port that
verifies names the game (pm_game/pm_version).

The switch log's buttons and names come from the port's switch lines now —
the baked-in King Kong button list and switch_names.inc are gone (the port
lines carry the same ids and names, so King Kong's switch.log is unchanged).

Boot log: each refused candidate says its path and the game/version it
declares; no candidates at all says so; a game update whose port is not
deployed logs the refusal and runs stock.

tests/test_multi_game.py lifts the new code verbatim and compiles it for the
host: selection among candidates, table reset between them, the getdents64
*.port scan (it caught an inverted suffix test and an off-by-one), and the
port-driven switch log. The address-dependent tests need a Linux host (the
port's site addresses are 32 bits; darwin arm64 maps nothing below 4 GiB).

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-10-13 15:38:45 -04:00
co-authored by Claude Code
parent cbdbe75f00
commit 539bab3bdc
5 changed files with 582 additions and 158 deletions
+42 -8
View File
@@ -33,12 +33,37 @@ framework) and added to `ports/king_kong_le-0.97.port`.
| File | What it is |
|---|---|
| `gamelog.c` | the logger: a passive PAD mode. It never `pm_begin()s`, never blocks, allocates nothing — it only watches and writes `[GAMELOG]` lines |
| `pad_mode_runtime.c` | the PAD Modes SDK runtime, **patched**: (a) a GAMELOG section — passive stock-mode and award watching, exporting `gl_award_seq()` / `gl_last_award_id()` to modes; (b) the per-edge switch logger to `/dump/switch.log`; (c) the Insider Connected gate made passive. Our exact changes vs upstream: `docs/runtime-patch.diff` |
| `switch_names.inc` | King Kong LE 0.97 switch id → name table (104 entries, ids 1–104), `#include`d by the runtime's switch logger. Generated from the game's own data (method: `swnames.py` in the decryptor repo); fix names here if any are wrong |
| `pad_mode_runtime.c` | the PAD Modes SDK runtime, **patched**: (a) a GAMELOG section — passive stock-mode and award watching, exporting `gl_award_seq()` / `gl_last_award_id()` to modes; (b) the per-edge switch logger to `/dump/switch.log`, named by the port's `switch` lines; (c) the Insider Connected gate made passive; (d) multi-game port selection (`port_select` — below). Our exact changes vs upstream: `docs/runtime-patch.diff` |
| `pad_mode.h`, `pad_stock.h` | unmodified SDK headers (needed by the build) |
| `build_mode.sh` | unmodified SDK build script: compiles the runtime + your mode into one `-nostdlib` shared object and refuses banned libc calls |
| `event_probe.c`, `event_read.py` | the event-bus measurement instrument and its log reader — how bus event ids are identified for another game (MODE_SDK.md, "Measuring events on another game") |
| `ports/king_kong_le-0.97.port` | the King Kong LE 0.97 port: 121 verified sites, 55 named shots, 6 bus events. Installed on the machine as `/usr/local/padmode/game.port` |
| `tests/test_multi_game.py` | host-compiled tests for the port selection, the ports-directory scan and the switch log (they lift the runtime's own code verbatim; the address-dependent ones need Linux — run them in the Debian container below) |
| `ports/king_kong_le-0.97.port` | the King Kong LE 0.97 port: 121 verified sites, 55 named shots, 6 bus events. Installed on the machine as `/usr/local/padmode/ports/king_kong_le-0.97.port` |
## Multiple games
One `mode.so` runs on any game that has a port. The machine says which game it is
on by which port's sites verify — the runtime tries every candidate in order and
keeps the first whose core functions match the running build's instruction words
(the same gate that keeps a wrong port from hooking anything):
1. every `*.port` in `/usr/local/padmode/ports/` and `/dump/ports/` (name order
is the directory's; only the right build's port can verify, so the order
does not matter), then
2. the single files `/usr/local/padmode/game.port` and `/dump/game.port`, the
old one-game layout.
The boot log says what happened:
```
[pad] port /usr/local/padmode/ports/godzilla_le-1.16.port: says godzilla_le 1.16 - not this game's port, skipped
[pad] port /usr/local/padmode/ports/king_kong_le-0.97.port: king_kong_le 0.97, 121 sites, 55 shots
```
A game update changes the words, so its old port stops matching — the machine
logs the refusal and runs stock until the new game version's port is deployed.
The switch log's buttons and their names come from the port's `switch` lines;
a port that names none logs no switch edges.
| `docs/runtime-patch.diff` | this project's changes to the upstream runtime (base: `pinball-asset-decryptor` commit `7faf8f08`, the last upstream change to that file) |
## What gets logged
@@ -100,9 +125,9 @@ the main flipper buttons on this cabinet, so they are left out:
```
`level` is the switch's raw state after the edge, `hit` is the game's own
"counts as a hit" verdict, `game` whether a player is up. The logged ids are
the `gl_buttons[]` list in the runtime's switch-edge handler; names come from
`switch_names.inc`.
"counts as a hit" verdict, `game` whether a player is up. The logged ids and
names are the port's `switch` lines (on King Kong: 34 lockdown, 36 start,
62/63 the flipper buttons).
## Build
@@ -123,6 +148,15 @@ from the game's own libc when the object loads.
The event probe builds without the runtime:
`build_mode.sh -p -o event_probe.so event_probe.c`
The port-selection tests run on any Linux host (the address-dependent ones skip
on macOS, whose arm64 maps nothing below 4 GiB):
```bash
docker run --rm --platform linux/arm64 -v "$PWD:/work" -w /work debian:12 \
sh -c 'apt-get update -qq && apt-get install -y -qq python3 python3-pytest gcc >/dev/null \
&& python3 -m pytest tests/test_multi_game.py -q'
```
## Install / deploy (the machine)
The machine (hostname `stern-spike2`) has root SSH (key-only, port 22). The
@@ -131,7 +165,7 @@ logger lives on the rootfs, which is normally read-only:
| Path on the machine | From this repo |
|---|---|
| `/usr/local/padmode/mode.so` | the built `mode.so` |
| `/usr/local/padmode/game.port` | `ports/king_kong_le-0.97.port` |
| `/usr/local/padmode/ports/king_kong_le-0.97.port` | `ports/king_kong_le-0.97.port` (one file per game; see "Multiple games") |
| `/etc/init.d/game_monitor` | boot hook: preloads `mode.so` into the game **only if it exists** (stock script kept at `game_monitor.stock`) |
Deploy a new build:
@@ -200,8 +234,8 @@ Built on the PAD Modes SDK
Everything in this repo is unmodified SDK content except:
- `gamelog.c` (new)
- `switch_names.inc` (new)
- `pad_mode_runtime.c` (upstream runtime + the patch in `docs/runtime-patch.diff`)
- `tests/test_multi_game.py` (new)
- `README.md`
`ports/king_kong_le-0.97.port`, `event_probe.c`, `event_read.py`,