"""Does GET /hub go through a three-token root, and is clubPlayers being silently eaten?

THE WORRY. The envelope rule (q_envelope_{1,2,3}.py, and section 2 of
docs/plan-2026-08-05-pack-opening.md) established that a three-token root consumes
`{`, the FIRST field name, and the token opening that field's value, all WITHOUT
dispatching them. 67 of 86 roots are three-token. We answer GET /hub with a flat
two-key body:
    {"clubPlayers": 205, "auctionCount": 0}
If /hub is a three-token root then `clubPlayers` (atom 0x90) is being discarded on
every hub load and only `auctionCount` (atom 0x33) is read.

WHY IT IS NOT ALREADY SETTLED. There is no RS4:FutGetHubServerResponse literal in the
image, and both atoms appear only as atom-table entries with no code xref, so /hub may
not be parsed by a generated root at all. Absence of the class literal is NOT evidence
that no deserializer exists: class_deser() is known to produce false negatives, and the
project rule is that an empty result means UNKNOWN.

METHOD, three independent angles so no single failure decides it:
  A. the UTAS route template array at 0x18021df80..0x18021e278, resolved to strings,
     which is the authoritative list of paths the client can request
  B. a direct hunt for whichever deserializer ladder tests atom 0x90 or 0x33, by
     decompiling every begin-object caller and grepping. This is the one that actually
     answers the question, because it finds the consumer regardless of class naming.
  C. token count for whatever B turns up, using the same rule as q_envelope_2

CONTROL: the same scan must find atom 0x16e (itemList) in FUN_180162880, which we know
it is in. If the scan cannot find a known-present atom, its negative results are void.
"""
import traceback

ROUTE_TABLE = (0x18021DF80, 0x18021E278)
A_CLUBPLAYERS = 0x90
A_AUCTIONCOUNT = 0x33
CONTROL_ATOM = 0x16E          # itemList, known present in 0x180162880
CONTROL_FUNC = 0x180162880

try:
    # ---- A. the route table -------------------------------------------------
    print("=" * 78)
    print("A. UTAS ROUTE TEMPLATE ARRAY %#x..%#x" % ROUTE_TABLE)
    print("=" * 78)
    lo, hi = ROUTE_TABLE
    n = 0
    for va in range(lo, hi, 16):
        try:
            p0, p1 = qword(va), qword(va + 8)
            s0 = rd_str(p0) if 0x180000000 <= p0 < 0x181000000 else ""
            if not s0:
                continue
            n += 1
            print("  %#x  %-46r  tok=%#x" % (va, s0, p1 & 0xFFFFFFFF))
        except Exception:
            pass
    print("resolved %d entries" % n)

    print("\nliteral 'hub' occurrences in the image:")
    for h in find_all(b"hub\x00"):
        print("   %#x  %r" % (h, rd_str(h - 24, 60)))

    # ---- B. who tests these atoms ------------------------------------------
    print("\n" + "=" * 78)
    print("B. WHICH DESERIALIZER LADDER TESTS clubPlayers %#x / auctionCount %#x"
          % (A_CLUBPLAYERS, A_AUCTIONCOUNT))
    print("=" * 78)
    roots = sorted({e for _, _, _, e in xrefs_to(0x1801C8270) if e})
    # include sub-parsers too: a root may delegate, as /purchased does
    subs = sorted({e for _, _, _, e in xrefs_to(0x180135FF0) if e})
    cands = sorted(set(roots) | set(subs))
    print("scanning %d functions (%d begin-object callers, %d skip-handler callers)"
          % (len(cands), len(roots), len(subs)))

    pats = {
        "clubPlayers": "0x%x" % A_CLUBPLAYERS,
        "auctionCount": "0x%x" % A_AUCTIONCOUNT,
        "CONTROL itemList": "0x%x" % CONTROL_ATOM,
    }
    hits = {k: [] for k in pats}
    scanned = 0
    for ent in cands:
        try:
            src = dec(ent)
        except Exception:
            continue
        scanned += 1
        for label, lit in pats.items():
            # match a comparison against the atom, not any incidental use of the number
            if ("== " + lit) in src or ("," + lit + ")") in src:
                hits[label].append(ent)
    print("scanned %d decompiles\n" % scanned)
    for label in ("CONTROL itemList", "clubPlayers", "auctionCount"):
        v = hits[label]
        print("  %-18s %d hit(s): %s" % (label, len(v), " ".join("%#x" % a for a in v[:12])))
    ok = CONTROL_FUNC in hits["CONTROL itemList"]
    print("\n  CONTROL: itemList found in %#x ? %s" % (CONTROL_FUNC, "YES" if ok else "NO"))
    if not ok:
        print("  !! CONTROL FAILED. Every negative result above is VOID.")

    # ---- C. token count for any consumer found ------------------------------
    print("\n" + "=" * 78)
    print("C. TOKEN COUNT for any function that consumes either hub atom")
    print("=" * 78)
    for ent in sorted(set(hits["clubPlayers"]) | set(hits["auctionCount"])):
        f = func(ent)
        toks, begin = [], []
        for ad in f.getBody().getAddresses(True):
            ins = listing.getInstructionAt(ad)
            if ins is None:
                continue
            for r in ins.getReferencesFrom():
                t = int(r.getToAddress().getOffset())
                if t == 0x1801C7F10:
                    toks.append(int(ad.getOffset()))
                elif t == 0x1801C8270:
                    begin.append(int(ad.getOffset()))
        print("  %#x  %-22s begin-object=%s  tokenizer calls=%d"
              % (ent, f.getName(), ("%#x" % min(begin)) if begin else "NONE", len(toks)))
        print("     callers: %s" % " ".join("%#x" % a for a, _ in callers(ent)[:8]))

except Exception:
    traceback.print_exc()
