Second session. FUT core loop, the EASFC/POW online layer, a central account
backend, and a lot of corrections. Everything risky is behind an env flag with
the default set to whatever was live-proven.
WORKING END TO END (live-verified this session):
* match loop -- POST/PUT/POST/DELETE ut/%s/match, rewards via FutDestroyMatch
(0x180121b60). Play a match, get coins, W/D/L updates.
* packs -- buy, cards land in the club, session survives (FUT_PACK_AUTOCLUB=1)
* quick sell -- POST ut/delete/%s/item was UNMAPPED and paid NOTHING; six cards
were destroyed for 0 coins. Now credits discardValue.
* POW/EASFC online -- the "EA FC servers unreachable" banner is powdll's layer,
a THIRD http api on :8094 nobody had served. Redirect needs no root: powdll
FUN_18005a460 reads FIFA_POW_URL from the same client-config store as
ROSTERUPDATE_URL. FUT_POW=1.
* account backend -- fut_account.py replaces 7 hardcoded copies of the persona
across 5 files; club/persona/online-profile editable via CLI.
CORRECTIONS TO ENDPOINT_MAP (all re-extracted from the deserializers):
* FutStoreGetPackTypes: id/packType/isPremium/quantity/saleType/purchaseLimit/
purchaseCount are NOT skipped no-ops -- all are parsed. extPrice inner objects
take externalPriceId(0x11a), not amount/currency.
* FutMoveCard 0x180128600 has NO skip handler (FUN_180135ff0 appears zero times,
unique among FUT deserializers) and parses only itemData -> dreamSquads.
* class -> deserializer resolution: the name literal is preceded by a 4-BYTE
HEADER and the factory LEA points at the header, so look up name_addr - 4.
Six attempts failed on this; now ghidra_env.class_deser(). Unlocked 11 SBC/
Draft schemas.
* live-only endpoints the request table never lists: ut/%s/squad/list,
ut/%s/user/club, ut/%s/club/stats/*, ut/%s/clientdata/<key>. The template
table is a floor, not a ceiling -- the log is the only ground truth.
* 163 RS4 call names exist; we served 17. All now served.
FIXED: club/stats/* was answering with the entire 28-item club inventory on every
poll (it fell through to the generic /club route).
UNSOLVED: the pack reveal's "Send to Club" (PUT ut/%s/item) kills the FUT session
whatever we answer -- {} included -- while its sibling quick-sell endpoint accepts
a bare {}. Seven hypotheses eliminated by live test, documented in
REBUILD_RESEARCH.md S14c so none get re-walked. FUT_PACK_AUTOCLUB routes around it.
Also unfixed: store tiles render "unknown" (displayGroup is parsed RECURSIVELY by
the same element parser; sending it FROZE the store, so FUT_STORE_GROUPS=1 is
default off).
Tests: test_fut_contract.py 380 (live, read-only) + test_match_rewards.py 51 (pure).
Note: fut_store.py carries some pre-existing uncommitted changes from before this
session (pack catalogue ids, pending-pile behaviour) that could not be separated
from this session's additions in the same file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
Complete the market loop (browse + buy + sell). POST auctionhouse (FutISStart)
lists an owned club item -> profile.listings + returns {id:tradeId}. tradePile
builds a validated auction record per listing from the owned item + prices
(freeze-safe, same 0x18013e410 shape). DELETE trade/{id} removes the listing.
fut_store gains list_for_sale/listings/remove_listing (tradeId space 900500000+).
test_market_buy.py extended with sell/delist checks (temp profile, no real-save
mutation): list -> tradePile shows it with prices -> delist empties it. All pass.
Read-only contract suite still 311/311. Functional (FIFA's exact sell params)
pending live test; freeze-safe by construction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
FIFA's updateActiveSquad PUT stores each slot as itemData={id:<clubItemId>}
(a reference), not the full player. We saved the bare references, so the
squad reloaded empty ("active squad resets"). Add Store.reconstruct_squad()
to re-embed the full club item by id on GET /squad/0, so the saved squad
reloads with its real players.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
Add a first-cut FUT store on top of the persistent profile:
- fut_store: PACK_CATALOG (Bronze/Gold/Premium), a curated real-player
PACK_POOL, and open_pack() (deduct coins -> generate items -> add to
club -> persist).
- utas_server routes: GET store/purchasegroup/all (catalog), PUT
(v2) store/transaction (buy + open, returns awarded itemData +
updated coins), GET purchased (last pack). Matches both /ut/game and
/ut/v2/game prefixes.
Verified via curl: buy Gold Pack -> 7 real players awarded, coins
15000->10000, club 10->17, persisted. Wire format is a best-guess
grounded in the CardsDLL store keys (packId/price/itemData/coins);
iterate against the in-game store next. Pool is curated for now --
replace with a full dbdata.dll extract later.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
Add fut_store.py: a JSON-backed profile store (coins, owned club items,
saved squads, record). First run grants a starter pack -- 15000 coins +
a 10-player starter club (real assetIds; identity resolves locally
in-game per docs/CARD_SYSTEM.md).
Wire utas_server to the store: /user/credits -> persisted coins,
/club -> persisted owned items, PUT /squad -> persists the squad the
user builds so it survives relaunches. Profile save file is gitignored.
Foundation for pack-opening (store/purchasegroup + transaction) next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o