diff --git a/docs/FIFA17_TRANSFER_MARKET_WIRE.md b/docs/FIFA17_TRANSFER_MARKET_WIRE.md index 75af4bf..d8d906b 100644 --- a/docs/FIFA17_TRANSFER_MARKET_WIRE.md +++ b/docs/FIFA17_TRANSFER_MARKET_WIRE.md @@ -466,3 +466,68 @@ state and auction lifecycle are separate authoritative facts; the wire may expos through different FIFA 17 resources. **A transition is complete only when a fresh FIFA session can reconstruct the same user-visible state** — re-entry is the acceptance test, not the immediate post-mutation response. + +--- + +## Q2 — LIVE-CONFIRMED: `tradeState: "inactive"` is the unlisted representation + +Operator-observed in the real FIFA 17 client, 2026-08-17, with the bounded one-item +probe (`OPENFUT_FIFA17_UNLISTED_PROBE`). Fixture: +`docs/evidence/market-lifecycle-2026-08-17/inactive-row-live-confirmed.json`. + +### The observed `inactive` UI contract + +| Wire | Client presentation | +|---|---| +| `tradeState: "inactive"` | row placed under the right-hand **TRANSFER LIST** section, NOT under LISTED ITEMS | +| `startingBid: 0` | Start Price = `0` | +| `buyNowPrice: 0` | Buy Now Price = `-` (absent) | +| `currentBid: 0` | Current Bid = `-` (absent) | +| `expires: 0` | Time Remaining = `-` (no timer) | +| `bidState: "none"` | no bid presentation | +| `itemData.itemState: "free"` | renders as a normal owned card | + +An `active` row in the same body continued to render separately under LISTED ITEMS, +so the two sections are driven by `tradeState` and the experiment did not disturb the +known-good active path. + +Token provenance: dumped from the client's own `{const char*, int}` table at +`0x180229e40` (`active=1 inactive=2 expired=3 closed=4`) — see +`cardsdll-vocab-tables.txt` and `fifa17-recon/tools/vocab_dump.py`. Nothing here was +guessed. + +Regression test: `market::tests::unlisted_candidate_is_a_non_active_pile_row` pins the +whole tuple, plus that the row is never emitted for an item outside the `trade` pile +and never duplicates a real auction +(`unlisted_candidate_never_duplicates_a_real_auction`). + +### Expired -> Club coupled transition, verified + +Operator returned the naturally-expired Ronaldo to the club. Server state after: + +``` +listing 1000000097 state = cancelled (the coupled transition fired) +pile Ronaldo = club +/tradePile Ronaldo absent +/club Ronaldo present, itemState free +hub clubPlayers 1964 -> 1965, auctionCount 1 +counts {count 1, selling 1, sold 0} +``` + +All of that is durable store state rather than a client-local view, so it survives a +session boundary by construction. Pending the operator's final exit/re-enter +confirmation before this is tagged CONFIRMED. + +### Counts observations so far (semantics still UNCHANGED and unresolved) + +| State | `count` | `selling` | `sold` | +|---|---|---|---| +| 1 active | 1 | 1 | 0 | +| 2 active | 2 | 2 | 0 | +| 1 active + 1 inactive (probe) | 1 | 1 | 0 | +| unlisted pile items (no probe) | not counted | not counted | 0 | + +So `count` currently tracks AUCTION entries, not total Transfer List membership. Do +not change this until the full state set (empty / unlisted / active / expired / sold / +mixed) has been observed — it remains an open question whether FIFA 17 expects +`count` to include non-active pile members. diff --git a/docs/evidence/market-lifecycle-2026-08-17/inactive-row-live-confirmed.json b/docs/evidence/market-lifecycle-2026-08-17/inactive-row-live-confirmed.json new file mode 100644 index 0000000..9406acb --- /dev/null +++ b/docs/evidence/market-lifecycle-2026-08-17/inactive-row-live-confirmed.json @@ -0,0 +1,129 @@ +{ + "auctionInfo": [ + { + "bidState": "none", + "buyNowPrice": 15000, + "coinsProcessed": 0, + "currentBid": 0, + "expires": 2674, + "itemData": { + "assetId": 158023, + "attributeList": [ + { + "index": 0, + "value": 89 + }, + { + "index": 1, + "value": 90 + }, + { + "index": 2, + "value": 86 + }, + { + "index": 3, + "value": 96 + }, + { + "index": 4, + "value": 26 + }, + { + "index": 5, + "value": 61 + } + ], + "cardassetid": 158023, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 158023, + "discardValue": 1500, + "fitness": 99, + "id": 100000155, + "itemState": "listFS", + "itemType": "player", + "leagueId": 53, + "nation": 52, + "owners": 1, + "playStyle": 250, + "preferredPosition": "RW", + "rareflag": 1, + "rating": 93, + "resourceId": 158023, + "teamid": 241, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 1000000155, + "tradeState": "active", + "watched": false + }, + { + "bidState": "none", + "buyNowPrice": 0, + "coinsProcessed": 0, + "currentBid": 0, + "expires": 0, + "itemData": { + "assetId": 158023, + "attributeList": [ + { + "index": 0, + "value": 89 + }, + { + "index": 1, + "value": 90 + }, + { + "index": 2, + "value": 86 + }, + { + "index": 3, + "value": 96 + }, + { + "index": 4, + "value": 26 + }, + { + "index": 5, + "value": 61 + } + ], + "cardassetid": 158023, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 158023, + "discardValue": 1500, + "fitness": 99, + "id": 100000059, + "itemState": "free", + "itemType": "player", + "leagueId": 53, + "nation": 52, + "owners": 1, + "playStyle": 250, + "preferredPosition": "RW", + "rareflag": 1, + "rating": 93, + "resourceId": 158023, + "teamid": 241, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 0, + "tradeId": 1000000059, + "tradeState": "inactive", + "watched": false + } + ], + "credits": 29843976, + "duplicateItemIdList": [], + "total": 2 +}