feat: RFC 3339 timestamps, quoted switch names, buttons-only switch log

All GAMELOG lines (EV/SHOT/SCORES/AWARD in mode.log, the runtime's MODE
START/END and AWARD lines) and every switch.log line now carry the wall
clock as RFC 3339 UTC with millisecond precision; the +Nms stamps relative
to runtime load are gone (dur= on MODE END stays: it is a duration). The
timestamp comes from a new gl_rfc3339() in the runtime: clock_gettime
(CLOCK_REALTIME) plus the civil-from-days algorithm, no libc time calls.

switch.log now logs only the human-activated buttons (34 lockdown, 36
start, 62/63 flippers) with quoted names. The upper-flipper button
switches (64/65) fire in parallel with the main flipper buttons on this
cabinet and are left out with every non-button switch.

Deployed and verified on the machine: the boot banner reads
2026-10-13T01:39:39.640Z against the machine's own UTC clock.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-10-12 21:42:42 -04:00
co-authored by Claude Code
parent 18ab285e22
commit e8f812ba4a
3 changed files with 111 additions and 77 deletions
+56 -15
View File
@@ -7936,20 +7936,50 @@ 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; }
/* the wall clock as RFC 3339 UTC with milliseconds, e.g. 2026-10-12T20:45:12.123Z, into buf.
* The date is the civil-from-days algorithm (Hinnant); no libc time calls beyond clock_gettime. */
struct gl_ts { long sec, nsec; };
extern int clock_gettime(int, void *);
#define GL_CLOCK_REALTIME 0
void gl_rfc3339(char *buf, unsigned cap)
{
struct gl_ts t;
long days, rem, era, doe, yoe, doy, mp, y, m, d;
if (clock_gettime(GL_CLOCK_REALTIME, &t) != 0) {
pm_snprintf(buf, cap, "0000-00-00T00:00:00.000Z");
return;
}
days = t.sec / 86400;
rem = t.sec % 86400;
days += 719468; /* days since 1970-01-01 -> days since 0000-03-01 */
era = (days >= 0 ? days : days - 146096) / 146097;
doe = days - era * 146097; /* [0, 146096] */
yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; /* [0, 399] */
doy = doe - (365 * yoe + yoe / 4 - yoe / 100); /* [0, 365] */
mp = (5 * doy + 2) / 153; /* [0, 11] */
d = doy - (153 * mp + 2) / 5 + 1;
m = mp + (mp < 10 ? 3 : -9);
y = yoe + era * 400 + (m <= 2 ? 1 : 0);
pm_snprintf(buf, cap, "%04ld-%02ld-%02ldT%02ld:%02ld:%02ld.%03ldZ",
y, m, d, rem / 3600, (rem / 60) % 60, rem % 60, t.nsec / 1000000);
}
static void gamelog_mode_log(const char *kind, unsigned id, unsigned player,
unsigned long long score, unsigned long dur)
{
char key[24];
char key[24], tbuf[40];
const char *nm;
pm_snprintf(key, sizeof key, "block_name_%u", id);
nm = pm_port_text(key);
gl_rfc3339(tbuf, sizeof tbuf);
if (dur)
say("gamelog: MODE %s %u (%s) player=%u score=%llu mode_score=%llu dur=%lums",
kind, id, nm ? nm : "?", player, score,
say("gamelog: %s MODE %s %u (%s) player=%u score=%llu mode_score=%llu dur=%lums",
tbuf, 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);
say("gamelog: %s MODE %s %u (%s) player=%u score=%llu",
tbuf, kind, id, nm ? nm : "?", player, score);
}
static void gamelog_stock_tick(void)
@@ -7985,9 +8015,12 @@ static void gamelog_stock_tick(void)
/* the game's award screens (extra ball, special, replay...): logged, never refused */
static void on_award_screen(unsigned *r)
{
char tbuf[40];
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()));
gl_rfc3339(tbuf, sizeof tbuf);
say("gamelog: %s AWARD id=%u player=%u score=%llu", tbuf, r[0], pm_player(),
pm_score(pm_player()));
}
static void gamelog_arm(void)
@@ -8632,20 +8665,28 @@ static void on_switch_edge(unsigned *r)
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) */
/* GAMELOG: the buttons a human presses, to /dump/switch.log (34 lockdown, 36 start, 62/63 the
* flipper buttons). The upper-flipper button switches (64/65) are wired in parallel with the
* main flipper buttons on this cabinet, so they are left out with every non-button switch. */
{
static const unsigned gl_buttons[] = { 34, 36, 62, 63 };
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); }
char tbuf[40], nm[40] = "?", line[160];
int fd, len, want = 0;
for (k = 0; k < sizeof gl_buttons / sizeof gl_buttons[0]; k++)
if (gl_buttons[k] == id) { want = 1; break; }
if (want) {
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; }
gl_rfc3339(tbuf, sizeof tbuf);
len = pm_snprintf(line, sizeof line, "%s SW %u \"%s\" level=%u hit=%d game=%d\n",
tbuf, 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,