feat: add passive GAMELOG event logger for Spike 2 King Kong LE 0.97
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>
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
--- /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 */
|
||||
Reference in New Issue
Block a user