From 7ad7aa0afa438592526dc39dceef304eda6a933d Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 4 Aug 2026 15:35:17 -0700 Subject: [PATCH] fifa17-recon: club stats -- per-NATION buckets, and the sub-type sums LIVE 2026-08-04: the ENGLAND tile read 0 while drilling into it showed Premier League 17. Same bug as before, one level up: the LEAGUE buckets were keyed and the NATION buckets were not. The eight-row MY CLUB panel is FUN_180094ce0 (not FUN_180043b90, which is a different provider using a different string family), and it computes: PLAYERS_EMPLOYED = +0x7f8(nationId, 4) + (nationId, 3) + (nationId, 2) STAFF_EMPLOYED = +0x800 over 0xb, 0xc, 0xd, 0xe, 0xf TROPHIES_WON = +0x800 over 0x33 .. 0x38 STADIA_OWNED = +0x800(0x14) BALLS_EARNED = +0x800(0x1e) Two consequences: 1. The no-id modes (year / consumables / club / newcards), which is what the client fires on entering MY CLUB, now carry PER-NATION buckets keyed by nation id. The three screens are consistent at last: no id -> nation buckets (the tab strip and the eight-row panel) country/ -> league buckets (the leagues in that nation) league/ -> team buckets (the teams in that league) 2. STAFF_EMPLOYED and TROPHIES_WON are SUMS OF SUB-TYPES. Sending staff(0xa) or trophies(0x32) alone can never move those rows, whatever their value. The eleven sub-type rows are now emitted: staffManager/HeadCoach/GKCoach/Physio/FitnessCoach and trophiesOffline/Online/FeaturedOffline/FeaturedOnline/SeasonOffline. All zero today because the club owns no staff and has won nothing, but the mapping is what matters when it does. All eleven new type strings verified against docs/fut_atoms.tsv, 0 mismatches. Live: /club/stats/year now returns 117 rows across 16 nation buckets, England (nation 14) summing to 11 players. 439 + 61 checks green, zero tracebacks. This is the third correction to this one endpoint today. The pattern in all three is identical and worth stating once more: the parser accepts anything, and only the CONSUMER tells you which bucket and which type ids it reads. Every time I reasoned about the body instead of reading the reader, I shipped a wrong one. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW --- fifa17-recon/tools/utas_server.py | 38 +++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/fifa17-recon/tools/utas_server.py b/fifa17-recon/tools/utas_server.py index e7d7a2d..bfa9844 100755 --- a/fifa17-recon/tools/utas_server.py +++ b/fifa17-recon/tools/utas_server.py @@ -1112,7 +1112,13 @@ def _club_stat_context(kind): whether contextId carries further meaning; nothing read so far gives it one. """ players = [i for i in STORE.items() if i.get("itemType") == "player"] - field = {"country": "leagueId", "league": "teamid"}.get(kind) + # kind -> which id the SCREEN's rows are keyed by. + # "" the MY CLUB tab strip itself: its nation tiles and the eight-row + # panel FUN_180094ce0, which computes PLAYERS_EMPLOYED as + # +0x7f8(nationId, 4) + (nationId, 3) + (nationId, 2). Per NATION. + # country the leagues inside a nation (case 3, keyed by LEAGUE_ID) + # league the teams inside a league (case 4, keyed by TEAM_ID) + field = {"": "nation", "country": "leagueId", "league": "teamid"}.get(kind) if not field: return [], 0 ctxs = sorted({i.get(field) for i in players if i.get(field) is not None}) @@ -1149,11 +1155,26 @@ def _club_stat_set(): ("rarePlayers", len(rare)), # Honest zeros: this club holds no non-player items of any kind. ("staff", 0), - ("stadia", 0), - ("balls", 0), + ("stadia", 0), # 0x14, read directly by STADIA_OWNED + ("balls", 0), # 0x1e, read directly by BALLS_EARNED ("kits", 0), ("badges", 0), ("trophies", 0), + # The eight-row panel FUN_180094ce0 does NOT read staff(0xa) or trophies(0x32). + # It SUMS the sub-types: STAFF_EMPLOYED = +0x800 over 0xb..0xf, and + # TROPHIES_WON = +0x800 over 0x33..0x38. Sending the parent ids alone can + # never move those two rows. All zero today because the club owns no staff and + # has won nothing, but the mapping is what matters when it does. + ("staffManager", 0), # 0xb + ("staffHeadCoach", 0), # 0xc + ("staffGKCoach", 0), # 0xd + ("staffPhysio", 0), # 0xe + ("staffFitnessCoach", 0), # 0xf + ("trophiesOffline", 0), # 0x33 + ("trophiesOnline", 0), # 0x34 + ("trophiesFeaturedOffline", 0), # 0x35 + ("trophiesFeaturedOnline", 0), # 0x36 + ("trophiesSeasonOffline", 0), # 0x37 ] # All four keys in every element -- see note 3 above. return [{"contextId": 1, "contextValue": 0, "type": t, "typeValue": int(v)} @@ -1184,8 +1205,15 @@ def club_stats_route(h): % (mode, len(stats), stats[0]["typeValue"], n, {"country": "league", "league": "team"}.get(parts[0], parts[0]))) return 200, {"stat": stats} - log(" CLUBSTATS: %s -> %d stat rows (players=%d)" - % (mode or "(none)", len(stats), stats[0]["typeValue"])) + # No id in the URL: this is the MY CLUB tab strip (year / consumables / club / + # newcards). Its nation tiles and its eight-row panel read PER-NATION buckets. + # LIVE 2026-08-04: the ENGLAND tile read 0 while its own Premier League row read + # 17, which is this bug exactly one level up -- the leagues were keyed and the + # nations were not. + ctx_rows, n = _club_stat_context("") + stats = stats + ctx_rows + log(" CLUBSTATS: %s -> %d rows (global players=%d, %d nation buckets)" + % (mode or "(none)", len(stats), stats[0]["typeValue"], n)) return 200, {"stat": stats}