--- /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 */