fifa17-recon: decouple contract suite from the Python implementation

test_fut_contract.py no longer imports ACCOUNT from fut_account. The expected
persona comes from FUT_TEST_PERSONA_ID and the target from FUT_TEST_BASE, so the
suite now imports nothing but stdlib and talks to a server at a URL.

That is what lets these 380 checks certify ANY implementation of the reversed
spec, a future Rust openfut-core included, without replaying the reverse
engineering. The original reason for reading ACCOUNT still holds and is preserved
in the comment: suite and server must not each hold a private copy of the
constant, or the identity-consistency checks would only prove two copies matched.

Also adds pileSizeClientData(0x227) behind FUT_PILESIZES (default off). A probe
run with 16 uniquely-valued entries did NOT move the MY CLUB counter, so that
member is eliminated as its source; the code is kept for the record and flagged
off.

Docs: OPENFUT_PROJECT_REPORT.md and OPENFUT_HANDOFF.md. The report now separates
"built but untested" from "never requested by the client" -- the server log records
User-Agent, and splitting real client traffic (ProtoHttp) from this project's own
probes shows /season, /tournament, /champion, /match, /clubUser and /user/list are
at ZERO client requests. /clubUser (0 client, 93 probe) and /user/list (0 client,
180 probe) are the starkest: work was done on both assuming the client wanted them.

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 10:31:37 -07:00
parent 5d5198f5d1
commit a93a8bcdd7
4 changed files with 604 additions and 8 deletions
+43
View File
@@ -499,6 +499,45 @@ SETTINGS = {"configs": []}
_MI = os.environ.get("FUT_MASSINFO", "full")
# ---- pileSizeClientData: the MY CLUB counter --------------------------------
# LIVE EVIDENCE (2026-08-04): the user opened MY CLUB, the client fetched GET /club
# and DISPLAYED all 99 players -- and the MY CLUB counter still read 0. So that
# counter is NOT derived from the item list; it is a PILE SIZE, delivered
# separately. massinfo's pileSizeClientData(0x227) is that member and we have never
# sent it. Parser 0x18013adb0: {"entries":[{"key":<int>,"value":<int>}]} -- key and
# value BOTH read with the int getter 0x1801c79d0, and the parser IS skip-safe.
#
# The pile-id enum is not recoverable from the strings (the "club"/"tradepile"
# literals are just atom names in the alphabetical key table). So rather than guess:
#
# FUT_PILESIZES=probe -> emit one entry per candidate key 0..15 with a UNIQUE
# recognisable value (100+key). Whatever number MY CLUB then displays names the
# club pile's key: 103 means key 3. One launch identifies the enum.
# FUT_PILESIZES=1 -> emit the REAL counts once PILE_KEY_CLUB below is known.
#
# Default OFF: this adds a member to boot-critical massinfo. It is a documented
# member of that parser and carries only ints, so the risk is low -- but "low" is
# what I said about displayGroup before it froze the store, so it ships behind a flag.
_PILESIZES = os.environ.get("FUT_PILESIZES", "")
PILE_KEY_CLUB = int(os.environ.get("FUT_PILE_KEY_CLUB", "-1")) # set once probed
def pile_size_body():
"""massinfo.pileSizeClientData -- see the note above."""
if _PILESIZES == "probe":
return {"entries": [{"key": k, "value": 100 + k} for k in range(16)]}
counts = {
"club": len(STORE.items()),
"purchased": len(STORE.purchased()),
"tradepile": len(STORE.listings()),
}
if PILE_KEY_CLUB >= 0:
return {"entries": [{"key": PILE_KEY_CLUB, "value": counts["club"]}]}
# No verified key yet -> announce the club count on every candidate key. Crude,
# but every value is truthful, so no pile can be told a wrong number.
return {"entries": [{"key": k, "value": counts["club"]} for k in range(16)]}
def massinfo():
if _MI == "empty":
return {} # old known-hub-reaching body
@@ -518,6 +557,10 @@ def massinfo():
# body and adding a member to it is the change class behind the last two
# live regressions. Instant fallback: FUT_MASSINFO=squad.
body["clubUser"] = club_user_body()
if _PILESIZES:
# pileSizeClientData(0x227) -> parser 0x18013adb0, int-only, skip-safe.
# This is what the MY CLUB counter reads (see pile_size_body above).
body["pileSizeClientData"] = pile_size_body()
return body
# ---- FUT item-definition serving (wf_e41070d8) -------------------------------