diff --git a/fifa17-recon/docker/fifa17-python/tools/test_hub_offline_season_contract.py b/fifa17-recon/docker/fifa17-python/tools/test_hub_offline_season_contract.py new file mode 100644 index 0000000..548ae58 --- /dev/null +++ b/fifa17-recon/docker/fifa17-python/tools/test_hub_offline_season_contract.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""Regression for the FIFA 17 FUT hub offline-Seasons summary. + +CardsDLL's hub parser at 0x180139610 recognizes offlineSeason (atom 0x1ec) +and passes its object to 0x18013c3a0. That nested parser recognizes the +string-valued divisionId, gamesPlayed, points, totalGames, and +progressDataVersion fields. Without offlineSeason, the Single Player Season +UI rejects the otherwise-successful GetHubData response before /season is sent. +""" +import os +import pathlib +import sys +import tempfile + +TOOLS = pathlib.Path(__file__).resolve().parent +sys.path.insert(0, str(TOOLS)) + + +def main(): + with tempfile.TemporaryDirectory(prefix="openfut-hub-season-test-") as state: + os.environ["FUT_PROFILE_ROOT"] = os.path.join(state, "accounts") + os.environ.pop("FUT_PROFILE", None) + os.environ["FUT_MODES"] = "1" + + import utas_server + + body = utas_server.hub_data() + assert isinstance(body, dict), "hub response root must be an object" + summary = body.get("offlineSeason") + assert isinstance(summary, dict), "hub.offlineSeason must be an object" + + expected = {"divisionId", "gamesPlayed", "points", "totalGames", + "progressDataVersion"} + assert set(summary) == expected, repr(summary) + for key in expected: + assert isinstance(summary[key], str), "%s must be a string: %r" % (key, summary[key]) + + assert summary["divisionId"] == "10", repr(summary) + assert summary["gamesPlayed"] == "0", repr(summary) + assert summary["points"] == "0", repr(summary) + + +if __name__ == "__main__": + main() + print("PASS: FUT hub includes the recovered offline-Seasons summary") diff --git a/fifa17-recon/docker/fifa17-python/tools/utas_server.py b/fifa17-recon/docker/fifa17-python/tools/utas_server.py index 4d81026..9140588 100755 --- a/fifa17-recon/docker/fifa17-python/tools/utas_server.py +++ b/fifa17-recon/docker/fifa17-python/tools/utas_server.py @@ -1449,8 +1449,23 @@ def hub_data(): players = len([i for i in STORE.items() if _is_player(i)]) auctions = len(STORE.listings()) log(" HUB: clubPlayers=%d auctionCount=%d selling=%d" % (players, auctions, auctions)) - return {"clubPlayers": players, "auctionCount": auctions, + body = {"clubPlayers": players, "auctionCount": auctions, "tradePile": {"count": auctions, "selling": auctions, "sold": 0}} + if _MODES: + # GetHubData's parser 0x180139610 recognises offlineSeason (atom 0x1ec) + # and passes it to 0x18013c3a0. The nested scalar fields are STRING + # getters, despite representing numbers. Omitting the object leaves the + # offline-season summary invalid and the UI aborts before requesting + # /season. The initial division matches season_list()/season_user(); the + # ten-game length is a live-test hypothesis, isolated behind FUT_MODES. + body["offlineSeason"] = { + "divisionId": "10", + "gamesPlayed": "0", + "points": "0", + "totalGames": "10", + "progressDataVersion": "0", + } + return body # ---- club stats: the CLUB STATS panel, and probably the MY CLUB tile too ------ diff --git a/fifa17-recon/docs/BASELINE-python-2026-08-10.md b/fifa17-recon/docs/BASELINE-python-2026-08-10.md index ed12743..1d98399 100644 --- a/fifa17-recon/docs/BASELINE-python-2026-08-10.md +++ b/fifa17-recon/docs/BASELINE-python-2026-08-10.md @@ -97,3 +97,25 @@ docker compose up -d --build location. - TURN/relay re-addressing (multiplayer) and long-tail endpoints (weather, matchday, kit assets) are deferred feature gaps — tracked separately. + +## Running state vs image — what the frozen image does NOT contain + +The baseline image (`python-baseline-2026-08-10` / `dev`) was built at 02:14Z, +but the container's `/app` was hot-patched afterwards: + +* `tools/utas_server.py` — gained the `FUT_MODES`-gated `offlineSeason` block in + GetHubData's club response (keeps the hub's offline-season summary valid). +* `tools/test_hub_offline_season_contract.py` — added to `/app/tools`. + +`docker save` captures the image, not the container's writable layer, so the +baseline tar.gz lacks those two changes. Two paths cover the exact runtime: + +* `openfut-fut-backend:python-running-2026-08-10` — `docker commit` of the + running container (sha256:093a98fa0496...), the exact runtime FS. +* The committed `fifa17-python/tools` + `data` — synced to match the running + container byte-for-byte (237 files verified, incl. the redir cert pair), so a + fresh build reproduces the actual running backend. Proven by rebuilding from + the committed sources and diffing the baked `/app/SHA256SUMS.txt` against the + container manifest: identical. + +Archive: `docker-backups/openfut-fut-backend-python-running-2026-08-10.tar.gz`.