"""Night 13: census every 'wreck with no following un-wreck' across all logs. A peer's view of a mech death is a pair: [BTrender] wreck: ... (entity=H:E) <- the wreck appears [respawn] replicant H:E un-wrecked + warp <- it comes back An UNPAIRED wreck -- one with no un-wreck before that log's session ends -- is the ghost signature. Report per SESSION so 'was it just one game?' is answerable, and dump the lines immediately before the first one. """ import re, sys, os, io LOGDIR = r"C:\git\bt411\scratchpad\night13" SESS = re.compile(r"===== BT411 SESSION.*?local=(\S+ \S+)") WRECK = re.compile(r"\[BTrender\] wreck:.*?entity=(\d+:\d+)") UNWRECK = re.compile(r"\[respawn\] replicant (\d+:\d+) un-wrecked") EJECT = re.compile(r"\[eject\]|PUNCH-OUT") def scan(path): sessions = [] # (startline, stamp) events = [] # (line, kind, ent) with io.open(path, "r", encoding="latin-1", errors="replace") as f: for n, line in enumerate(f, 1): m = SESS.search(line) if m: sessions.append((n, m.group(1))) continue m = WRECK.search(line) if m: events.append((n, "wreck", m.group(1))); continue m = UNWRECK.search(line) if m: events.append((n, "unwreck", m.group(1))); continue if EJECT.search(line): events.append((n, "eject", "-")) return sessions, events def session_of(sessions, line): idx, stamp = 0, "?" for i, (sl, st) in enumerate(sessions): if sl <= line: idx, stamp = i + 1, st else: break return idx, stamp for fn in sorted(os.listdir(LOGDIR)): if not fn.endswith(".log") or fn.startswith("FAILURE"): continue path = os.path.join(LOGDIR, fn) sessions, events = scan(path) # pair wrecks to the next un-wreck of the same entity IN THE SAME SESSION pending = {} # ent -> (line, sessidx) unpaired = [] for (n, kind, ent) in events: si = session_of(sessions, n)[0] if kind == "wreck": if ent in pending and pending[ent][1] == si: unpaired.append(pending[ent]) # wreck superseded by another wreck pending[ent] = (n, si) elif kind == "unwreck": if ent in pending and pending[ent][1] == si: del pending[ent] for ent, (n, si) in pending.items(): unpaired.append((n, si, ent)) norm = [] for u in unpaired: norm.append(u if len(u) == 3 else (u[0], u[1], "?")) norm.sort() print("=" * 72) print("%s sessions=%d wrecks=%d unwrecks=%d ejects=%d" % (fn, len(sessions), sum(1 for e in events if e[1] == "wreck"), sum(1 for e in events if e[1] == "unwreck"), sum(1 for e in events if e[1] == "eject"))) if not norm: print(" no unpaired wrecks") continue bysess = {} for (n, si, ent) in norm: bysess.setdefault(si, []).append((n, ent)) for si in sorted(bysess): stamp = sessions[si - 1][1] if 0 < si <= len(sessions) else "?" print(" SESSION %d (%s): %d unpaired -> %s" % (si, stamp, len(bysess[si]), ", ".join("%s@%d" % (e, n) for n, e in bysess[si][:6])))