diff --git a/fifa17-recon/docs/REBUILD_RESEARCH.md b/fifa17-recon/docs/REBUILD_RESEARCH.md index 3c1f853..6d21c0a 100644 --- a/fifa17-recon/docs/REBUILD_RESEARCH.md +++ b/fifa17-recon/docs/REBUILD_RESEARCH.md @@ -893,3 +893,66 @@ An earlier note in this file argued the counter was server-supplied by `/club/stats/`, and the live-evidence argument that the hub tile and the panel read the same store (both said "players", both zero) was mine and was wrong in its conclusion. The store is shared; the *selection* is not. + +--- + +## 20. Club stats ARE server-fixable (2026-08-04, live-proven) + +§19 concluded the MY CLUB counter was not server-fixable. That conclusion was **too +broadly scoped**. The nation and league drill-down screens are fixable, and are now +fixed and live-proven: the MY CLUB -> ENGLAND -> Premier League row reads **17**, the +first non-zero number ever rendered on that screen. + +### What made it work + +The per-context getter is `(+0x7f8)(store, contextValue, typeId)`, and the whole thing +turns on where `contextValue` comes from. From `FUN_180043b90` case 3: + +```c +uVar7 = (**(param_2 + 0x18))(param_2, row, "LEAGUE_ID"); // THE UI ROW'S OWN ID +bronze = (+0x7f8)(store, uVar7, 2); +silver = (+0x7f8)(store, uVar7, 3); +gold = (+0x7f8)(store, uVar7, 4); +publish("PLAYERS_EMPLOYED", gold + silver + bronze); // COMPUTED, never read +rare = (+0x7f8)(store, uVar7, 5); +kits = (+0x7f8)(store, uVar7, 0x28); +badges = (+0x7f8)(store, uVar7, 0x2d); +``` + +Three rules follow, and two wrong bodies were shipped before they were understood: + +1. **A response must carry a bucket per SCREEN ROW**, because the reader iterates the + rows and looks up each row's own id. Keying every row to the id in the URL fills one + bucket the screen never asks for. That was attempt two. +2. **`PLAYERS_EMPLOYED` is computed** as gold + silver + bronze in the per-context + cases. Sending `players` (type id 1) does nothing there. The tier counts are + mandatory. +3. **The screens nest**: `country/` lists the LEAGUES in that nation (case 3, keyed + by `LEAGUE_ID`); `league/` lists the TEAMS (case 4, keyed by `TEAM_ID`, reading + ids 1, 0x28, 0x2e). + +The guard still matters: `contextId == 1` or `5 <= contextId <= 9` forces `contextValue` +to 0, the global bucket. Per-context rows use `contextId 3` purely to sit outside it. +`TODO/CONFIRM` whether contextId means anything more. + +Because every response wipes the whole map, each response carries only its own screen's +buckets. That also dodges a real collision: the storage key is `contextValue` alone, so +nation 14 and league 14 would otherwise share a bucket. + +`FUT_CLUBSTATS` is now **default ON**, the live-proven value. + +### What is still open, and correctly scoped this time + +The hub tile's `0 TOTAL PLAYERS` and the MY CLUB summary panel rows are **unchanged**. +They read the GLOBAL bucket through `+0x800` in cases 1 and 5, and we do serve those +rows, so the remaining question is unchanged from §19: what selects those cases. The +mode tag is copied from the completed request, and the client requests `year`, +`consumables`, `staff`, `country/` and `league/` but never `club`. + +### The method note, which is the durable part + +Two rounds of reasoning about this endpoint produced two wrong bodies. Twenty lines of +the consumer produced the right one. The question **"what does the reader actually look +up"** was answerable from the start and was not asked until live screenshots forced it. +Reading the parser tells you what is accepted; only reading the CONSUMER tells you what +is used. diff --git a/fifa17-recon/tools/utas_server.py b/fifa17-recon/tools/utas_server.py index 89b7e77..b946fa3 100755 --- a/fifa17-recon/tools/utas_server.py +++ b/fifa17-recon/tools/utas_server.py @@ -997,7 +997,13 @@ ROUTES = [ # The test is unusually clean because the club holds 114 items and ALL of them are # players: every other row is an honest zero. So if this works, exactly two numbers # move (Players and Rare Players, 0 -> 114) and nothing else changes. -CLUBSTATS = os.environ.get("FUT_CLUBSTATS") == "1" +# DEFAULT ON since 2026-08-04: LIVE-PROVEN. With this serving, the MY CLUB -> +# ENGLAND -> Premier League row went from 0 to 17, the first non-zero number ever +# rendered on that screen. Nothing froze and no other screen changed. The global +# bucket rows ride along and are harmless; they are what the MY CLUB summary and +# hub tile WOULD read if anything ever selected those cases (see REBUILD_RESEARCH +# S19, still open). FUT_CLUBSTATS=0 reverts to the old {}. +CLUBSTATS = os.environ.get("FUT_CLUBSTATS", "1") == "1" def _counts_for(players):