"""Per-weapon-class damage breakdown from a bench log (#95 burst-loop audit). Classifies each [dmghit] by the damage TYPE it carried and the burst count, and reports the delivered damageLevel delta, so a change to the shared burst loop cannot silently rescale one class without showing up here. """ import re, sys, collections path = sys.argv[1] d = open(path, encoding="latin-1", errors="replace").read() # [dmghit] mech=1:323 zone=4 vital=0 type=2 amt=35 burst=1 lvl 0->0.555556 rx = re.compile(r"\[dmghit\] mech=(\S+) zone=(\d+) vital=(\d) type=(\d+) " r"amt=([0-9.eE+-]+) burst=([0-9.]+) lvl ([0-9.eE+-]+)->([0-9.eE+-]+)") rows = [m.groups() for m in rx.finditer(d)] print("total zone applications: %d\n" % len(rows)) TYPE = {0: "COLLISION(diverted)", 1: "type1", 2: "EXPLOSIVE(missile/AC)", 3: "ENERGY(beam)", 4: "type4(short-gen)"} byType = collections.defaultdict(list) for mech, zone, vital, ty, amt, burst, a, b in rows: byType[int(ty)].append((float(amt), float(burst), float(b) - float(a), int(zone))) print("%-24s %6s %10s %10s %10s %8s" % ("class", "hits", "amt", "burst", "dlvl", "zones")) for ty in sorted(byType): v = byType[ty] amts = set(round(x[0], 3) for x in v) bursts = collections.Counter(int(x[1]) for x in v) zones = len(set(x[3] for x in v)) print("%-24s %6d %10s %10s %10.4f %8d" % ( TYPE.get(ty, "type%d" % ty), len(v), ("%.3f" % list(amts)[0]) if len(amts) == 1 else "%d vals" % len(amts), ",".join("%dx%d" % (b, c) for b, c in sorted(bursts.items())), sum(x[2] for x in v), zones)) # burst distribution for the cluster class -- should span 1..N, not be pinned mis = [int(x[1]) for x in byType.get(2, []) if x[1] > 1] if mis: print("\ncluster burst rolls: %s" % dict(sorted(collections.Counter(mis).items()))) print(" -> expect a SPREAD in [n/4 .. n], not a constant") # collision must never touch armour print("\ncollision applications that reached a zone: %d (must be 0)" % len(byType.get(0, [])))