The client now issues PUT /squad and the hub renders coins, record and the
squad roster. Three separate root causes, all verified live.
Squad blocker (the long-standing "client never sends PUT /squad"):
AddPlayerToSquad, GetSquads and SelectSquadById issue ZERO network requests
(pure local model reads/mutations, FutSquadServiceImpl vtable 0x180233ff0);
only SaveCurrentSquad writes, and it is unguarded. The client simply needed a
populated ACTIVE squad model, which arrives via the massinfo `squad` member.
No response of ours was ever being rejected.
userMassInfo is NOT required to be {}:
0x180174630 is a FLAT {userInfo, squad, settings, userData} body -- the old
"wrapper key is user" note was wrong, and the historical freeze was the
malformed squad member, not the envelope.
clubNameChangeAllowed must be false:
sending true advertises a club-rename flow whose UI model is never populated;
the client shows a naming prompt and dies confirming it (ACCESS_VIOLATION
reading 0x0 at FIFA17.exe+0x71b8651, 4/4 runs, no CardsDLL frame and no request
in flight). Isolated by a single-variable run; guarded by a contract check.
Endpoint/schema corrections found in live traffic, invisible to static analysis:
* GET ut/%s/squad/list is a real endpoint and must return {"squad":[...]},
not the active-squad object (the /list suffix is appended by the caller, so
it never appeared in the request table)
* PUT lands on ut/%s/squad/<id>, not a bare ut/%s/squad
* userInfo currencies are read as name/funds/finalFunds/active -- there is no
"value" key, so coins always rendered 0
* squad-list elements take STRING formation/squadType, not ints
* the CardsDLL script-API thunk<->name table was off by one (AddPlayerToSquad
is 0x18004aa70; 0x18004aff0 is GetPotentialChemistry_Club)
FUT_MASSINFO / FUT_USERINFO ladders keep every step of the bisect reproducible.
Contract suite 311 -> 358 checks. Tooling added: PyGhidra harness (Ghidra's
Java/OSGi script path is broken on this box), minidump reader, live code grabber.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
Serve 18 real-player auctions on GET auctionhouse (search) built to the reversed
auction record schema (deser 0x18013e410) field-for-field: itemData reuses
fut_store._item (the proven club/squad card parser 0x18013fe00), scalar fields
all HIGH-confidence reversed. Rating-based buy-now pricing; tradeId space
900000000+. tradePile/watchList stay empty (no live sell/watch flow yet). Toggle
off with FUT_MARKET=empty.
Extend test_fut_contract.py to validate EVERY populated record field type
(numbers/strings/bool/object) so the listings are proven freeze-safe OFFLINE
before the game parses them. 311 checks, all passing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
Add tools/test_fut_contract.py -- stdlib-only, read-only tests that hit the live
utas_server and assert each response matches the shape reversed from CardsDLL
(docs/ENDPOINT_MAP.md). Encodes freeze-safety invariants (auctionInfo/players/
itemData/currencies/purchase must be array/object per the SAX deserializers;
scalar-where-container = busy-loop freeze at 0x1801c7f1a) plus the specific
contracts: v2/store gate == SUCCESS, coin counter binds currencies[coins].funds,
userMassInfo stays {}, empty squad slots carry itemData=null (proven-safe).
Catches the regression class that previously bit us (phantom packs, coins-0,
squad reset). 58 checks, all passing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o