From 42229e5782ef860f3b30264e918fbfd6ad27445b Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 24 Aug 2026 18:58:41 +0000 Subject: [PATCH] tools(re): report club-item slot indices in the residency probe The squad parser writes actives element i to club-item slot r15d+i, and r15d is shared scratch that other atom handlers clobber. Which slots are filled therefore reveals the index the parse actually started from, which is the open question behind the regression in vault section 18. probe_resident_fields.py now prints the slot index of every populated entry plus the empty ones, and verify_kits.sh runs it next to the map census so one command reports both residency and whether the squad survived. --- .../tools/live/probe_resident_fields.py | 26 ++++++++++++------- fifa17-recon/tools/live/verify_kits.sh | 12 +++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/fifa17-recon/tools/live/probe_resident_fields.py b/fifa17-recon/tools/live/probe_resident_fields.py index c707135..4819fed 100755 --- a/fifa17-recon/tools/live/probe_resident_fields.py +++ b/fifa17-recon/tools/live/probe_resident_fields.py @@ -77,7 +77,7 @@ for label, vbeg, vend in (("players (cardtype 1)", mgr + 0xd8, mgr + 0xe0), if span % stride: continue n = span // stride - recs, nulls = [], 0 + recs, nulls = [], [] ok = True for k in range(n): try: @@ -85,23 +85,29 @@ for label, vbeg, vend in (("players (cardtype 1)", mgr + 0xd8, mgr + 0xe0), except OSError: ok = False; break if not rec: - nulls += 1; continue + nulls.append(k); continue d = decode(rec) if d is None: ok = False; break - recs.append((rec, d)) + recs.append((k, rec, d)) if not ok: continue - print(f" stride {stride} (ptr at +{ptr_off:#x}): {n} slots, {len(recs)} populated, {nulls} null") - if not recs and nulls != n: + print(f" stride {stride} (ptr at +{ptr_off:#x}): {n} slots, {len(recs)} populated, {len(nulls)} null") + if not recs and len(nulls) != n: continue - hist = collections.Counter(d[0:2] for _, d in recs) + hist = collections.Counter(d[0:2] for _, _, d in recs) for key, c in sorted(hist.items(), key=lambda x: -x[1]): print(f" (cardtype,subtype)={key} x{c}") - print(f" {'ptr':>14} " + " ".join(f"{f:>8}" for f in FIELDS)) - for rec, d in recs[:8]: - print(f" {rec:#14x} " + " ".join(f"{v:>8}" for v in d)) - cats = collections.Counter(d[3] for _, d in recs) + # The SLOT INDEX is load-bearing evidence: the squad parser's `actives` + # arm writes element i to slot `r15d + i`, and r15d is shared scratch + # that other atom handlers clobber. Which slots are filled therefore + # reveals the index the parse actually started from. + print(f" {'slot':>4} {'ptr':>14} " + " ".join(f"{f:>8}" for f in FIELDS)) + for k, rec, d in recs[:8]: + print(f" {k:>4} {rec:#14x} " + " ".join(f"{v:>8}" for v in d)) + if nulls: + print(f" empty slots: {nulls[:16]}") + cats = collections.Counter(d[3] for _, _, d in recs) if cats: print(f" CATEGORY (+0x60) distribution: {dict(cats)}") break diff --git a/fifa17-recon/tools/live/verify_kits.sh b/fifa17-recon/tools/live/verify_kits.sh index 81b1c08..4037146 100755 --- a/fifa17-recon/tools/live/verify_kits.sh +++ b/fifa17-recon/tools/live/verify_kits.sh @@ -23,3 +23,15 @@ echo cd "$(dirname "$0")" || exit 1 python3 probe_map2.py "$PID" 100004873 100004874 100004870 + +echo +echo " ================ squad survival + slot indices ================" +# The kit milestone is only real if the REST of the squad survives with it. +# A populated `squad.actives` was once seen to leave the map holding just the +# 2 kits with a fully null 23-slot player vector and an empty starting 11, so +# the player-vector fill below is a PASS/FAIL gate, not decoration. +# +# The club-item slot indices are the other half: the parser writes element i to +# slot r15d+i, and r15d is scratch other atom handlers clobber. Kits landing +# somewhere other than slots 0 and 1 means the index did not start at zero. +python3 probe_resident_fields.py "$PID"