"""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()