diff --git a/README.md b/README.md index 15a889a..28c2c66 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ why, and the game runs stock. | Stock modes (`gamelog: MODE START/END`): the game's own 22 modes with score delta | **deployed on the machine, verified in play** (`mode_score` is approximate) | | Award screens (`AWARD id=…`): every award the game shows, extra-ball tracking | **deployed on the machine, verified in play** — except the extra-ball award id is not yet confirmed (below) | | Switch/button log (`/dump/switch.log`, `SW` lines with generated names) | **deployed on the machine, verified in play** (flippers on both edges; sites located with `portswitch.switch_source` and added to the port) | +| High-score reports (`HSTD`): the game's own high-score table report, with all players' scores | **built, deploy pending** — fires when the game sends `/api/v1/game/high_score_events` to Stern's agent; the entries (initials) need new per-game RE (below) | The machine currently runs the v3 build (events, shots, modes, awards, switch log). The v3 work needed more than a rebuild: the port had no `switch_edge` / @@ -90,6 +91,7 @@ reboots: [pad] gamelog: 2026-10-12T20:45:12.123Z MODE END 5 (Island Scene Escape the Swamp) player=1 score=... mode_score=... dur=...ms [GAMELOG] 2026-10-12T20:45:12.123Z AWARD EXTRA BALL (id=13) player=1 <- next ball logs as "ball N (EXTRA BALL)" [GAMELOG] 2026-10-12T20:45:12.123Z AWARD id= player=1 score=... <- every award screen +[GAMELOG] 2026-10-12T20:45:12.123Z HSTD high-score report - P1=95,421,190 P2=0 P3=0 P4=0 ``` Bus event ids (King Kong 0.97): `game_start`=0x4d, `ball_start`=0x25, diff --git a/pad_mode_runtime.c b/pad_mode_runtime.c index ec36149..bb95d04 100644 --- a/pad_mode_runtime.c +++ b/pad_mode_runtime.c @@ -8671,6 +8671,21 @@ static void gl_switch_log(unsigned id, unsigned level, int hit, int playing) if (fd >= 0) { write(fd, line, len); close(fd); } } +/* GAMELOG: the game's own high-score report (HSTD_REPORT — the /api/v1/game/high_score_events + * the agent gate sees being built): the players' scores as the game reports its table. */ +static void gl_hstd_report(void) +{ + char tbuf[40], buf[128], one[32]; + unsigned p, n = 0; + gl_rfc3339(tbuf, sizeof tbuf); + buf[0] = 0; + for (p = 1; p <= 4; p++) { + pm_commas(one, sizeof one, pm_score(p)); + n += (unsigned)pm_snprintf(buf + n, sizeof buf - n, "%sP%u=%s", n ? " " : "", p, one); + } + pm_log("[GAMELOG] %s HSTD high-score report - %s", tbuf, buf); +} + static void on_switch_edge(unsigned *r) { static volatile int said; @@ -8998,6 +9013,8 @@ static void on_agent_header(unsigned *r) const char *s = (const char *)(unsigned long)r[1]; if (s && s[0] == '/') str_copy(insider_endpoint, sizeof insider_endpoint, s, INSIDER_ENDPOINT_MAX - 1); else insider_endpoint[0] = 0; + if (insider_blocked(insider_endpoint) == 2) /* the high-score table report */ + gl_hstd_report(); } static int on_agent_begin(unsigned *r) diff --git a/shipper/init.d/log_shipper b/shipper/init.d/log_shipper index 92d8880..b4ed154 100755 --- a/shipper/init.d/log_shipper +++ b/shipper/init.d/log_shipper @@ -1,10 +1,12 @@ #!/bin/sh # log_shipper - tail /dump/*.log and push them to Loki (see shipper/README.md). # Config comes from the environment; defaults in the binary match the machine. +# The shipper's own complaints land in /dump/logshipper.log (a few lines a boot). case "$1" in start) - start-stop-daemon -S -b -m -p /run/logshipper.pid -x /usr/local/bin/logshipper + start-stop-daemon -S -b -m -p /run/logshipper.pid -x /bin/sh \ + -- -c 'exec /usr/local/bin/logshipper >>/dump/logshipper.log 2>&1' ;; stop) start-stop-daemon -K -p /run/logshipper.pid diff --git a/tests/test_multi_game.py b/tests/test_multi_game.py index 9e45a8b..2278779 100644 --- a/tests/test_multi_game.py +++ b/tests/test_multi_game.py @@ -406,3 +406,85 @@ def test_gl_switch_log_writes_the_ports_buttons(tmp_path): '2026-10-13T12:00:00.000Z SW 62 "RIGHT FLIPPER BUTTON" level=1 hit=1 game=1', '2026-10-13T12:00:00.000Z SW 36 "START BUTTON" level=0 hit=0 game=0', ], text + + +# ---- the high-score report event ------------------------------------------------------ + +_HSTD_PRELUDE = r""" +#define _GNU_SOURCE +#include +#include +#include +#include +#include +static void event_line(const char *s) { (void)s; } +static void lamp_line(const char *s) { (void)s; } +static void rule_line(const char *s) { (void)s; } +static void port_line(const char *s) { (void)s; } +static void say(const char *fmt, ...) { (void)fmt; } +static void on_agent_header(unsigned *r); +static void gl_rfc3339(char *buf, unsigned cap) { snprintf(buf, cap, "2026-10-22T23:59:01.123Z"); } +int pm_snprintf(char *out, unsigned long cap, const char *fmt, ...) +{ + va_list ap; + int n; + va_start(ap, fmt); + n = vsnprintf(out, cap, fmt, ap); + va_end(ap); + return n; +} +void pm_log(const char *fmt, ...) +{ + char b[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(b, sizeof b, fmt, ap); + va_end(ap); + printf("PMLOG %s\n", b); +} +static void pm_commas(char *out, unsigned cap, unsigned long long v) +{ + snprintf(out, cap, "%llu", (unsigned long long)v); +} +static unsigned long long pm_score(unsigned player) { return player == 1 ? 137615960 : 0; } +int main(int argc, char **argv) +{ + static unsigned r[4]; + /* the runtime reads the endpoint pointer as a 32-bit register: the path must live below 4 GiB */ + char *path = mmap((void *)0x08000000, 4096, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + if (path == (char *)-1) { printf("mmap failed\n"); return 1; } + snprintf(path, 4096, "%s", argv[1]); + r[1] = (unsigned)(unsigned long)path; + on_agent_header(r); + return 0; +} +""" + + +def _hstd_source(): + src = _src() + helpers = "\n".join(_lift(src, s) for s in ( + "static int str_eq(", "static void str_copy(", "static int hexval(", "static uint64_t number(")) + insider = _between(src, "#define INSIDER_ENDPOINT_MAX", "static void on_agent_header(unsigned *r)") + fn = "\n".join(_lift(src, s) for s in ( + "static void gl_hstd_report(void)", "static void on_agent_header(unsigned *r)")) + return _HSTD_PRELUDE + helpers + "\n" + insider + "\n" + fn + + +def test_a_high_score_report_logs_an_hstd_event_with_the_scores(tmp_path): + if sys.platform == "darwin": + pytest.skip("the runtime's pointers are 32-bit registers; darwin arm64 maps nothing " + "below 4 GiB - the Linux leg runs these") + out = _host_run(tmp_path, _hstd_source(), + args=("/api/v1/game/high_score_events",)) + assert "HSTD high-score report" in out, out + assert "P1=137615960 P2=0 P3=0 P4=0" in out, out + + +def test_other_agent_reports_log_no_hstd_event(tmp_path): + if sys.platform == "darwin": + pytest.skip("32-bit pointer registers: see the first HSTD test") + for path in ("/api/v3/game/session_start", "/ingest/v1/game/game_events", "junk", ""): + out = _host_run(tmp_path, _hstd_source(), args=(path,)) + assert "HSTD" not in out, "%s logged an HSTD event" % path