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/<id> -> league buckets   (the leagues in that nation)
     league/<id>  -> 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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
This commit is contained in:
funman300
2026-08-04 15:35:17 -07:00
parent 4c5cc3ab4b
commit 7ad7aa0afa
+33 -5
View File
@@ -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}