Snapshot of the stern-event-agent work (as of 2026-10-12): - gamelog.c: passive PAD mode logging bus events (game/ball/bonus/tilt), all named shots and all-players' scores to /dump/mode.log - pad_mode_runtime.c: the SDK runtime + a GAMELOG section (passive stock-mode and award watching, gl_award_seq()/gl_last_award_id()), a per-edge switch logger to /dump/switch.log (switch_names.inc), and a passive Insider Connected gate; docs/runtime-patch.diff shows the exact changes vs the upstream runtime - event_probe.c / event_read.py: event-bus measurement tooling for identifying bus event ids on another game - ports/king_kong_le-0.97.port: 121 verified sites, 55 named shots, 6 bus events (emulator-proven by the SDK repo authors) Deployed and verified on the machine: events, shots, stock modes and awards. The /dump/switch.log feature is implemented but NOT yet deployed or verified, and the extra-ball award id (default 13) is unconfirmed. See README.md for build/install/log usage and unfinished work. Co-Authored-By: Claude Code <noreply@anthropic.com>
161 lines
7.2 KiB
Diff
161 lines
7.2 KiB
Diff
--- /Users/matt/src/pinball-asset-decryptor/tools/spike2_emu/modes/sdk/pad_mode_runtime.c 2026-10-11 18:38:42
|
|
+++ /Users/matt/src/pinball-assets/.ssh-build/sdk-patched/pad_mode_runtime.c 2026-10-12 12:04:22
|
|
@@ -7914,6 +7914,90 @@
|
|
static void stock_tick(void); /* item 160 */
|
|
static void magnet_tick(void); /* PAD-381 */
|
|
static void scoop_tick(void); /* PAD-381 */
|
|
+
|
|
+/* ---- GAMELOG: passive stock-mode and award watching*/
|
|
+#ifndef O_WRONLY
|
|
+#define O_WRONLY 1
|
|
+#define O_CREAT 0100
|
|
+#define O_APPEND 02000
|
|
+#endif for the gameplay logger.
|
|
+ * Added on top of the stock runtime (2026-10-12): logs the game's own modes
|
|
+ * (Island Scenes and the rest of the port's block_* set) starting and ending -
|
|
+ * with the player's score at each edge, so the mode's score total is the
|
|
+ * difference - and every award screen the game shows (extra ball, special,
|
|
+ * replay...), exposed to modes through gl_award_seq()/gl_last_award_id(). */
|
|
+static unsigned long gl_start_ms[BLOCK_IDS];
|
|
+static unsigned long long gl_start_score[BLOCK_IDS];
|
|
+static unsigned gl_start_player[BLOCK_IDS];
|
|
+static unsigned char gl_running[BLOCK_IDS];
|
|
+static unsigned gl_award_v;
|
|
+static unsigned long gl_award_n;
|
|
+
|
|
+unsigned long gl_award_seq(void) { return gl_award_n; }
|
|
+unsigned gl_last_award_id(void) { return gl_award_v; }
|
|
+
|
|
+static void gamelog_mode_log(const char *kind, unsigned id, unsigned player,
|
|
+ unsigned long long score, unsigned long dur)
|
|
+{
|
|
+ char key[24];
|
|
+ const char *nm;
|
|
+ pm_snprintf(key, sizeof key, "block_name_%u", id);
|
|
+ nm = pm_port_text(key);
|
|
+ if (dur)
|
|
+ say("gamelog: MODE %s %u (%s) player=%u score=%llu mode_score=%llu dur=%lums",
|
|
+ kind, id, nm ? nm : "?", player, score,
|
|
+ score > gl_start_score[id] ? score - gl_start_score[id] : 0, dur);
|
|
+ else
|
|
+ say("gamelog: MODE %s %u (%s) player=%u score=%llu",
|
|
+ kind, id, nm ? nm : "?", player, score);
|
|
+}
|
|
+
|
|
+static void gamelog_stock_tick(void)
|
|
+{
|
|
+ unsigned id;
|
|
+ if (!pm_in_game()) {
|
|
+ for (id = 0; id < BLOCK_IDS; id++)
|
|
+ if (gl_running[id]) {
|
|
+ gamelog_mode_log("END", id, gl_start_player[id],
|
|
+ pm_score(gl_start_player[id]), pm_ms() - gl_start_ms[id]);
|
|
+ gl_running[id] = 0;
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+ for (id = 0; id < BLOCK_IDS; id++) {
|
|
+ const unsigned *o = (const unsigned *)(unsigned long)block_obj[id];
|
|
+ int active;
|
|
+ if (!o || !bm_has(block_named, id) || !wizm_vfn(o, "stock_slot_active")) continue;
|
|
+ active = ((int (*)(const void *))(unsigned long)wizm_vfn(o, "stock_slot_active"))(o) & 0xff;
|
|
+ if (active && !gl_running[id]) {
|
|
+ gl_start_ms[id] = pm_ms();
|
|
+ gl_start_player[id] = pm_player();
|
|
+ gl_start_score[id] = pm_score(pm_player());
|
|
+ gamelog_mode_log("START", id, gl_start_player[id], gl_start_score[id], 0);
|
|
+ } else if (!active && gl_running[id]) {
|
|
+ gamelog_mode_log("END", id, gl_start_player[id],
|
|
+ pm_score(gl_start_player[id]), pm_ms() - gl_start_ms[id]);
|
|
+ }
|
|
+ gl_running[id] = (unsigned char)(active != 0);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* the game's award screens (extra ball, special, replay...): logged, never refused */
|
|
+static void on_award_screen(unsigned *r)
|
|
+{
|
|
+ gl_award_v = r[0];
|
|
+ gl_award_n++;
|
|
+ say("gamelog: AWARD id=%u player=%u score=%llu", r[0], pm_player(), pm_score(pm_player()));
|
|
+}
|
|
+
|
|
+static void gamelog_arm(void)
|
|
+{
|
|
+ struct site *as = site("award_screen");
|
|
+ if (as && as->ok && hook(as->addr, on_award_screen))
|
|
+ say("gamelog: award screen hooked at 0x%08x", as->addr);
|
|
+ else
|
|
+ say("gamelog: award screen NOT hooked");
|
|
+}
|
|
|
|
static void on_tick(unsigned *r)
|
|
{
|
|
@@ -7948,6 +8032,7 @@
|
|
roster_deferred_tick();
|
|
stock_generic_tick(); /* item 164: the game's base play, for the mode table route */
|
|
stock_tick(); /* item 160: the game's own rules' counts-as (after the modes: a probe wraps first) */
|
|
+ gamelog_stock_tick(); /* GAMELOG: the game's own modes' start/end edges */
|
|
lamps_tick(); /* named inserts: their patterns (lights section) */
|
|
}
|
|
|
|
@@ -8547,6 +8632,21 @@
|
|
mask = edge.mode_mask ? *(const unsigned short *)(unsigned long)edge.mode_mask : 0u;
|
|
hit = edge_is_hit(level, flags, pol, edge.mode_mask != 0, mask);
|
|
playing = pm_in_game(); /* attract, a tilt: no player up for a mode to score */
|
|
+ /* GAMELOG: every edge to /dump/switch.log (buttons included, attract included) */
|
|
+ {
|
|
+ static const struct { unsigned id; const char *name; } gl_sw[] = {
|
|
+#include "switch_names.inc"
|
|
+ };
|
|
+ unsigned k;
|
|
+ char nm[40] = "?", line[160];
|
|
+ int fd, len;
|
|
+ for (k = 0; k < sizeof gl_sw / sizeof gl_sw[0]; k++)
|
|
+ if (gl_sw[k].id == id) { pm_snprintf(nm, sizeof nm, "%s", gl_sw[k].name); break; }
|
|
+ len = pm_snprintf(line, sizeof line, "%lu SW %u %s level=%u hit=%d game=%d\n",
|
|
+ pm_ms(), id, nm, level & 1u, hit, playing);
|
|
+ fd = open("/dump/switch.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
|
|
+ if (fd >= 0) { write(fd, line, len); close(fd); }
|
|
+ }
|
|
if (edge_log_left > 0 && __sync_sub_and_fetch(&edge_log_left, 1) >= 0)
|
|
say("edge: switch %u level %u (flags 0x%04x, polarity 0x%04x, mode mask 0x%04x, in game %d)%s", id, level & 1u,
|
|
flags, pol, mask, playing, hit && playing ? " - a hit" : "");
|
|
@@ -8781,18 +8881,17 @@
|
|
|
|
static int on_agent_begin(unsigned *r)
|
|
{
|
|
- int i = insider_blocked(insider_endpoint);
|
|
- unsigned n;
|
|
- (void)r;
|
|
- if (!i) return 0;
|
|
- n = ++insider_dropped[i - 1];
|
|
- if (n == 1)
|
|
- say("insider: %s is not sent to Insider Connected (the game logs a failed message and plays on)",
|
|
- insider_endpoint);
|
|
- else if ((n & 15) == 0)
|
|
- say("insider: %s not sent, %u times", insider_endpoint, n);
|
|
+ /* GAMELOG build: passive gate - Insider Connected stays fully stock. Every endpoint the
|
|
+ * game sends is logged once (the machine's own reports are genuine; this build adds no
|
|
+ * scores of its own), nothing is refused. */
|
|
+ (void)r;
|
|
+ if (insider_endpoint[0] && insider_blocked(insider_endpoint) && insider_dropped[0] < 1) {
|
|
+ insider_dropped[0] = 1;
|
|
+ say("insider: passive gate - %s (and all reports) go to Insider Connected as stock",
|
|
+ insider_endpoint);
|
|
+ }
|
|
insider_endpoint[0] = 0;
|
|
- return 1;
|
|
+ return 0;
|
|
}
|
|
|
|
/* 1 = the gate is up. 0 = this port cannot keep scores off Insider Connected: NOTHING is hooked
|
|
@@ -9266,6 +9365,7 @@
|
|
}
|
|
display_arm(); /* item 154 display: the clip_play hook, display priority */
|
|
block_arm(); /* PAD-347: a mode may keep the game's modes from starting */
|
|
+ gamelog_arm(); /* GAMELOG: watch the game's award screens */
|
|
backdrop_arm(); /* hud-layers: a clip behind the HUD */
|
|
ck_arm(); /* PAD-530: a mode's clip comes back after the game's */
|
|
status_bar_arm(); /* PAD-521: the game's own status bar */
|