Records the auction-record field set, route spellings, pile encoding and the four open UNKNOWNs so future agents neither reopen settled questions nor re-guess enum values. Each claim tagged CONFIRMED / FIFA17-HISTORICAL / INFERRED / UNKNOWN. Captures the key methodological lesson: oracle parity is necessary but NOT sufficient for a flow the oracle itself never served -- our auction record matched the oracle key-for-key while both omitted the FIFA 17 ownership fields.
8.1 KiB
FIFA 17 Transfer Market — wire findings
Reverse-engineering record for the FIFA 17 UTAS transfer-market surface, kept so future agents do not reopen settled questions or re-guess enum spellings.
Every claim carries a confidence tag:
| Tag | Meaning |
|---|---|
| CONFIRMED | Observed from our own FIFA17.exe client or live host capture |
| FIFA17-HISTORICAL | Supported by contemporaneous FIFA 17 implementations (lorenzh/fut-api, futapi/fut v0.2.18 — the last pre-FIFA-18 release) |
| INFERRED | Best explanation, not directly captured |
| UNKNOWN | Requires instrumentation; do NOT implement from guesswork |
Authority reminder: FIFA 17 field names, enum spellings, sentinel ids and empty-state shapes come from captures or the Python oracle — never from a modern FUT toolkit. Later-FIFA API drift is a known hazard, and reversing a container type or inventing an enum is the documented client-freeze class.
The auction record (auctionInfo[])
What we emit today, on /tradePile, /trade/status and market browse:
{
"tradeId": 1000000097,
"itemData": { "...full shaped card...": "", "itemState": "listFS" },
"tradeState": "active",
"buyNowPrice": 15000,
"startingBid": 150,
"currentBid": 0,
"offers": 0,
"bidState": "none",
"expires": 3600,
"tradeOwner": true,
"sellerId": 33068179,
"sellerName": "CAGE",
"sellerEstablished": 1,
"watched": false,
"coinsProcessed": 0
}
| Field | Confidence | Note |
|---|---|---|
tradeOwner (bool) |
FIFA17-HISTORICAL | Exists in FIFA 17 auctionInfo. That it is the Actions-panel gate is UNKNOWN pending live confirmation. |
sellerId |
FIFA17-HISTORICAL exists; type numeric is INFERRED | Set to the configured persona so it agrees with tradeOwner. Never baked in. |
sellerName |
CONFIRMED it must be the player | fut_account.py annotates the persona property as "Blaze PDTL.DSNM / LSX GetProfileResponse Persona / UTAS sellerName". EA's "EASFC" here is wrong for an own listing. |
offers |
FIFA17-HISTORICAL | 0 valid for active/unbid. |
bidState: "none" |
FIFA17-HISTORICAL | Valid for active/unbid. Other observed concepts: highest, buyNow. Do NOT "fix" this. |
expires |
FIFA17-HISTORICAL | SECONDS REMAINING, not an epoch. Historical durations: 3600, 10800, 21600, 43200, 86400, 259200. |
itemData.itemState: "listFS" |
UNKNOWN | Plausible and unchanged. Public FIFA 17 material gives no trustworthy enumeration. Do not guess replacements — capture. |
itemData.untradeable |
FIFA17-HISTORICAL field; our blanket false is INFERRED |
See "Known debt" below. |
marketDataMinPrice / marketDataMaxPrice |
do NOT add | These entered the public parser only after its FIFA 18 migration. |
Why the differential could not catch the missing fields
Our record's key set was identical to the Python oracle's, so field-for-field
parity was green. The oracle omits tradeOwner / sellerId / offers as well,
because its remove flow was never driven by a real client either — the only
historical live datapoint is a counts-tile bug. Oracle parity is therefore
necessary but not sufficient for any flow the oracle never actually served.
The differential now asserts we cover every oracle key AND that our extra keys are
exactly {offers, sellerId, tradeOwner}, so the deliberate superset is pinned
while a new unexplained divergence still fails.
Routes
| Route | Confidence | Behaviour |
|---|---|---|
GET …/trade/status |
CONFIRMED the client polls it continuously | Live auction-state refresh. It previously fell through starts_with("trade") into the buy/view arm, where the tail has no numeric id, so every poll returned {"auctionInfo": []}. Now a real handler: optional tradeIds filter, else the whole active pile. Unknown ids are absent, never an error. |
DELETE /ut/game/<sku>/trade/<id> |
FIFA17-HISTORICAL | The spelling contemporaneous FIFA 17 clients use, no body, no meaningful response body. Previously landed in the buy/view arm and silently cancelled nothing while returning 200. Now maps to MarketCancel. |
DELETE /ut/delete/game/<sku>/trade/<id> |
CONFIRMED (oracle) | The oracle's spelling; retained because the differential exercises it. Whether FIFA17.exe ever uses it is UNKNOWN. |
POST …/auctionhouse |
CONFIRMED | List for sale. The client sends only itemData.id; the server resolves wire id → Core instance → card_id/resourceId and enforces ownership. |
GET …/tradePile/counts |
INFERRED | Five scalar ints (count, maxAuctionsAllowed, offered, selling, sold); a DISTINCT deserializer from /tradePile. Exact FIFA 17 semantics of count (active auctions vs whole pile) is UNKNOWN — we report active auctions and deliberately did NOT speculate. |
PUT …/item (move) |
FIFA17-HISTORICAL | `{"itemData":[{"pile":"trade" |
Pile encoding
- MOVE commands take a string pile (
"trade","club") — FIFA17-HISTORICAL. - Returned
itemData.pileis documented numeric in FIFA 17 auction data — FIFA17-HISTORICAL. - The numeric mapping is UNKNOWN. Do not unify the two representations, and do
not derive a mapping from unrelated
pileSizekeys.
Q2 — Transfer List item that is not currently auctioned
A real FUT state: an item in the Transfer List with no active auction (freshly moved, or expired unsold). CONFIRMED to exist as a concept (the external hub spec §27, and move-vs-list being separate operations).
Its wire representation is UNKNOWN: tradeId 0 / omitted / null, tradeState
value or omission, and itemData.itemState are all unestablished.
Consequence, and the reason this matters: our /tradePile renders only active
listings, so keying the /club exclusion on the trade pile stranded 4 cards
in no screen at all (hidden from the club, absent from the Transfer List).
Commit f2c4927 keys exclusion on the active listing instead, which is
self-healing. That is a workaround, not fidelity — the faithful model needs the
unlisted state represented.
Required capture (four states, full structural diff, not just a shortlist):
A. moved Club -> Transfer List, NEVER listed
B. actively listed
C. listing expired unsold
D. listing sold
Diff at least: tradeId, tradeOwner, tradeState, bidState, expires,
offers, currentBid, startingBid, buyNowPrice, sellerId, sellerName,
itemData.id, itemData.itemState, itemData.pile, itemData.untradeable.
Do NOT drop the unlisted state from the model just because its encoding is unknown.
Deferred, with reasons
- 5% transfer tax — INFERRED architecture only: auction closes → Core
settles → seller credited gross × 0.95, with
auctionInfocontinuing to carry gross. No trustworthy FIFA 17 field namedtax/netPrice/sellerProceedswas recovered, and no separate settle operation. Not blocking; do not couple settlement to clearing the sold auction without a capture. - Bid / Transfer Targets — not implemented. Watched / active bid / winning / outbid / won / expired are distinct states and must not collapse to a flat list.
- Unassigned — FIFA 17 had a dedicated Unassigned service; the exact FIFA 17
URL is UNKNOWN. Our 29-item
purchasedpile is this state and is currently rendered inside/club. Do not manufacture a route from a modern toolkit. - Match CREATE / READY / PLAY — UNKNOWN and explicitly not portable from public FUT web-app work (the web app could not start matches). Instrument the real client from Play Match to kickoff before implementing.
Known debt
shape_item reports untradeable: false for every owned instance. Correct
today (Core models no untradeable items) and necessary — a hardcoded true greyed
out both list buttons — but it will misrepresent SBC / promo / loan rewards once
those exist. untradeable belongs on the owned-item instance as authoritative
state, not inferred from definition, resourceId or rarity.