"""Disc-wide: which elements reach rest()'s dwell fallback, i.e. have no plateau? Reimplements the placement-region walk from ui_layout.rs::parse_placements so the census runs in seconds instead of via `screen info --geometry` (which decodes every texture). CONTROL: it must reproduce the three known fallback elements of GP_TITLE build 7 (indices 8, 13, 14) and find ptlogo_eff3 among them. """ import struct, zlib, glob, os, collections from disc import disc_root DECL_AT, DECL_ENTRY, KEYFRAME = 0x20, 60, 40 def entries(base): stub = open(base + ".pak", "rb").read() if stub[:4] != b"IPFB": return n = struct.unpack_from(">I", stub, 4)[0] segs = sorted(glob.glob(base + ".p[0-9][0-9]")) if not segs: return blob = b"".join(open(s, "rb").read() for s in segs) for i in range(n): h, off, sz = struct.unpack_from(">III", stub, 0x10 + 12 * i) st = blob[off:off + sz] if len(st) < 10: continue try: yield i, h, (zlib.decompress(st[10:]) if st[:2] == b"Z1" else st) except Exception: continue def elements(d): """(index, name, [poses]) per element with a keyframe group.""" if d[:4] != b"RATC": return [] count = struct.unpack_from(">I", d, 0x14)[0] if not (0 < count < 4096): return [] names = [] for i in range(count): o = DECL_AT + i * DECL_ENTRY if o + DECL_ENTRY > len(d): return [] names.append(d[o:o+28].split(b"\0")[0].decode("ascii", "replace")) out, pos = [], DECL_AT + count * DECL_ENTRY for _ in range(count): if pos + 8 > len(d): break idx, frames = struct.unpack_from(">II", d, pos) if idx >= count or frames == 0 or frames > 4096: break first = pos + 12 end = first + frames * KEYFRAME - 4 poses = [] for k in range(frames): blk = first + k * KEYFRAME if blk + 36 > len(d) or blk + 36 > end: break poses.append(struct.unpack_from(">IiiIIi i", d, blk)[:1] + struct.unpack_from(">II", d, blk + 16) + struct.unpack_from(">ii", d, blk + 28)) if poses: out.append((idx, names[idx], poses)) pos = end return out def has_plateau(poses): return any(poses[i] == poses[i+1] for i in range(len(poses) - 1)) # ---- CONTROL ------------------------------------------------------------- ctl = {i: (n, p) for i, n, p in elements( dict((i, d) for i, h, d in entries(disc_root() + "/dat/GP_TITLE"))[7])} fb = sorted(i for i, (n, p) in ctl.items() if not has_plateau(p)) print(f"CONTROL GP_TITLE build 7: fallback elements {fb} (want [8, 13, 14])") print(f" index 8 is {ctl[8][0]!r} (want ptlogo_eff3.t32)") assert fb == [8, 13, 14] and ctl[8][0].startswith("ptlogo_eff3"), "control failed" # ---- census -------------------------------------------------------------- tot = fb_n = 0 by_name = collections.Counter() worst = [] for pak in sorted(glob.glob(disc_root() + "/dat/GP_*.pak")): base = pak[:-4] for i, h, d in entries(base): for idx, name, poses in elements(d): tot += 1 if not has_plateau(poses): fb_n += 1 by_name[name] += 1 worst.append((os.path.basename(base), i, idx, name, len(poses))) print(f"\nelements with a keyframe group, disc-wide: {tot}") print(f" no plateau -> rest pose is GUESSED: {fb_n} ({100*fb_n/max(tot,1):.2f} %)") print(f"\nmost common guessed elements:") for n, c in by_name.most_common(12): print(f" {c:5d} {n}") # ---- candidate rules for a plateau-less element -------------------------- # pose tuple = (fade, scale_x, scale_y, x, y); times read separately. def dwell_pick(times, poses): best = (0, -1) for k in range(len(times) - 1): if times[k] is None or times[k + 1] is None: continue d = times[k + 1] - times[k] if d >= best[1]: best = (k, d) return best[0] def elements_t(d): if d[:4] != b"RATC": return [] count = struct.unpack_from(">I", d, 0x14)[0] if not (0 < count < 4096): return [] names = [] for i in range(count): o = DECL_AT + i * DECL_ENTRY if o + DECL_ENTRY > len(d): return [] names.append(d[o:o+28].split(b"\0")[0].decode("ascii", "replace")) out, pos = [], DECL_AT + count * DECL_ENTRY for _ in range(count): if pos + 8 > len(d): break idx, frames = struct.unpack_from(">II", d, pos) if idx >= count or frames == 0 or frames > 4096: break first = pos + 12; end = first + frames * KEYFRAME - 4 poses, times = [], [] for k in range(frames): blk = first + k * KEYFRAME if blk + 36 > len(d) or blk + 36 > end: break poses.append(struct.unpack_from(">I", d, blk) + struct.unpack_from(">II", d, blk + 16) + struct.unpack_from(">ii", d, blk + 28)) times.append(struct.unpack_from(">i", d, blk + 36)[0] if blk + 40 <= end else None) if poses: out.append((idx, names[idx], times, poses)) pos = end return out alpha = lambda p: (p[0] >> 24) & 0xff stats = collections.Counter() for pak in sorted(glob.glob(disc_root() + "/dat/GP_*.pak")): for i, h, d in entries(pak[:-4]): for idx, name, times, poses in elements_t(d): if has_plateau(poses): continue cur = poses[dwell_pick(times, poses)] last = poses[-1] stats["n"] += 1 stats["cur_invisible"] += alpha(cur) == 0 stats["last_invisible"] += alpha(last) == 0 stats["cur_zero_scale"] += (cur[1] == 0 or cur[2] == 0) stats["agree"] += cur == last n = stats["n"] print(f"\n--- the {n} plateau-less elements, by what each candidate rule returns") print(f" current rule (longest dwell) returns an INVISIBLE pose : {stats['cur_invisible']:5d} ({100*stats['cur_invisible']/n:5.1f} %)") print(f" current rule returns a ZERO-SCALE (degenerate) pose : {stats['cur_zero_scale']:5d} ({100*stats['cur_zero_scale']/n:5.1f} %)") print(f" 'rest = last keyframe' returns an INVISIBLE pose : {stats['last_invisible']:5d} ({100*stats['last_invisible']/n:5.1f} %)") print(f" the two rules agree : {stats['agree']:5d} ({100*stats['agree']/n:5.1f} %)")