"""D4 Q1: trace packOpeningAnimationEnabled (atom 0x20e = 526) through the
settings switch FUN_18013c6d0 -> settings-struct field index -> the applier
FUN_18011dc50 -> gate byte offset -> IS_* key published by FUN_18006cc60.

HYPOTHESIS: atom 0x20e has an arm in FUN_18013c6d0 that stores into some field
index N of the settings struct; FUN_18011dc50 copies index N to a gate byte;
FUN_18006cc60 publishes that byte under an IS_* string key.

CONTROLS (must reproduce before trusting the new answer):
  friendlySeasonsEnabled -> field [0x16] -> gate 0x1fd3a -> IS_FRIENDLY_SEASON_ENABLED
  enableDraftMode        -> field [0x17] -> gate 0x1fd3d -> IS_DRAFT_MODE_ENABLED

Also dumps every string in the image matching PACK/ANIM/REVEAL/WALKOUT so Q2/Q3
have a target list.
"""
import traceback, re

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres/"


def w(name, text):
    p = OUT + name
    with open(p, "w") as f:
        f.write(text)
    print("WROTE %s  (%d chars)" % (p, len(text)))


try:
    # ---------- 1. the settings deserializer ----------
    src = dec(0x18013C6D0, 300)
    print("=== FUN_18013c6d0 len=%d ===" % len(src))
    w("d4_settings_deser.txt", src)

    for needle in ("526", "0x20e", "0x20E"):
        for m in re.finditer(re.escape(needle), src):
            a, b = max(0, m.start() - 400), min(len(src), m.end() + 400)
            print("--- hit %r at %d ---" % (needle, m.start()))
            print(src[a:b])
            print("--- end hit ---")

    # ---------- 2. the applier ----------
    ap = dec(0x18011DC50, 300)
    print("=== FUN_18011dc50 len=%d ===" % len(ap))
    w("d4_applier.txt", ap)

    # ---------- 3. the IS_* publisher ----------
    pub = dec(0x18006CC60, 300)
    print("=== FUN_18006cc60 len=%d ===" % len(pub))
    w("d4_publisher.txt", pub)

    # ---------- 4. strings of interest ----------
    pats = [b"PACK", b"Pack", b"pack", b"WALKOUT", b"Walkout", b"walkout",
            b"REVEAL", b"Reveal", b"reveal", b"ANIMATION", b"Animation",
            b"animation", b"Anim"]
    blocks = [b for b in mem.getBlocks() if b.isInitialized()]
    seen = {}
    for p in pats:
        try:
            hits = find_all(p, blocks)
        except Exception as e:
            print("find_all failed for %r: %s" % (p, e))
            continue
        for h in hits:
            va = int(h.getOffset()) if hasattr(h, "getOffset") else int(h)
            # walk back to string start
            start = va
            for k in range(1, 96):
                try:
                    bb = read_bytes(va - k, 1)
                except Exception:
                    break
                c = bb[0] & 0xFF
                if c < 0x20 or c > 0x7E:
                    start = va - k + 1
                    break
            else:
                start = va - 95
            s = rd_str(start)
            if s and 3 < len(s) < 160:
                seen.setdefault(s, start)
    lines = ["%#x  %s" % (v, k) for k, v in sorted(seen.items(), key=lambda kv: kv[1])]
    print("=== %d distinct strings ===" % len(lines))
    w("d4_strings.txt", "\n".join(lines))
    for l in lines[:400]:
        print(l)

except Exception:
    traceback.print_exc()
