fifa17-recon: answer the CONSUMABLES panel with consumable counts, not player counts

The tab was empty because we answered the wrong question. The client asks
GET club/stats/consumables 41 times a session; we replied with the PLAYER stat set
(players 205, playersGold 189 ...), which that panel does not read. Confirmed on
screen: seven categories present and selectable, every one reading 0.

Now appends 14 consumables* rows counted from the SHELF. The shelf, not STORE.items():
the consumables we serve are a synthetic overlay never granted into the save, so
counting the store gives fourteen zeros, which on screen is byte-identical to failure
and would have made the experiment unreadable. Counts match the independently derived
expectation exactly: 126 total, 21 healing, 7 player contracts, 21 player training,
3 player fitness, 20 position, 21 GK training, 6 manager contracts, 19 playstyle.

Safe by construction: the vocabulary is an ATOM switch (FUN_18012fd40, 40 arms,
default return 0), so an unrecognised name is inert rather than fatal, and the rows
are APPENDED -- the player, nation and league rows that drive the working screens are
untouched. Zero rows unless FUT_CONSUMABLES is armed.

Default ON because answering the consumables panel with player counts is wrong by
inspection rather than a judgement call. FUT_CONSUM_STATS=0 reverts.

439 + 414 checks green.

The open question this sets up: whether a non-zero count makes the client request an
item list at all. If it does, the log names the route and /consumables/%s is settled
for free. If the numbers move and no request follows, the panel renders from counts
alone and the 126-item shelf was never needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
This commit is contained in:
funman300
2026-08-05 11:19:29 -07:00
parent 0f83d73364
commit 2f8a6512db
+43
View File
@@ -1163,6 +1163,48 @@ def _club_stat_context(kind):
# FUT_CONSUM_STATS: answer the CONSUMABLES panel with CONSUMABLE counts.
#
# LIVE 2026-08-05, and this is the whole reason the tab was empty. The client asks
# GET club/stats/consumables 41 times a session and we answered it with the PLAYER
# stat set (players 205, playersGold 189 ...). The panel reads a different set of
# names entirely, so it was told about footballers when it asked about contracts.
# The player screenshot is unambiguous: seven categories -- Training, Contracts,
# Fitness, Healing, Chemistry Style, Manager League, Position Modifier -- every one
# reading 0, on a tab that is present and selectable.
#
# The vocabulary is not a string table: FUN_18012fd40 looks the `type` string up in
# the ATOM table and switches on 40 atom ids, default `return 0`. An unrecognised
# name is therefore INERT, not fatal, which is what makes appending these rows safe.
#
# COUNT THE SHELF, NOT THE STORE. The consumables we serve are a synthetic overlay
# and are never granted into the save, so STORE.items() holds none of them and
# counting it yields fourteen zeros -- which looks exactly like failure on screen and
# would make the experiment unreadable.
#
# Default ON: answering the consumables panel with player counts is wrong by
# inspection, not a judgement call. Set FUT_CONSUM_STATS=0 to go back.
CONSUM_STATS = os.environ.get("FUT_CONSUM_STATS", "1") == "1"
def _consumable_stat_rows():
"""[(stat name, count)] for the consumables panel, counted from the shelf."""
if not (CONSUM_STATS and CONSUMABLES):
return []
try:
import fut_consumables
import fut_club_stats
except Exception as e: # never break the panel over this
log(" CLUBSTATS: consumable rows unavailable (%s)" % e)
return []
shelf = fut_consumables.starter_consumables(fut_consumables.CONSUMABLE_ID_BASE)
g = fut_club_stats.global_counts(shelf)
rows = [(fut_club_stats.VOCAB[sid], v) for sid, v in sorted(g.items())
if sid >= 0x3C and sid in fut_club_stats.VOCAB]
log(" CLUBSTATS: %d consumable row(s) from a shelf of %d" % (len(rows), len(shelf)))
return rows
def _club_stat_set():
"""The complete global stat set, computed from what the club actually holds."""
items = STORE.items()
@@ -1211,6 +1253,7 @@ def _club_stat_set():
("trophiesFeaturedOnline", 0), # 0x36
("trophiesSeasonOffline", 0), # 0x37
]
counts += _consumable_stat_rows()
# All four keys in every element -- see note 3 above.
return [{"contextId": 1, "contextValue": 0, "type": t, "typeValue": int(v)}
for t, v in counts]