market: stop advertising unlisted pile members as tradeState:"inactive"
RE of the FUT front-end closed the question the Actions-panel investigation left open, and the answer retracts Q2 rather than completing it. `tradeState` reaches exactly ONE native branch in CardsDLL — `cmp …,0x4` at `0x18013e619`, "is it closed?" — and `inactive`(2) and `expired`(3) take the same edge, producing bit-identical `flagA`/`flagB` (exhaustive 22-site census of `[reg+0x88]` reads across the PE; confirmed live, both classes read glow=0 inbox=0). The value is then handed to the movie verbatim as the Flash property `STATE`, and the action gate lives in the APT/ActionScript FUT front-end: the trade-pile class partitions rows with `getCardsInAuction`/`isInActiveAuction` (traces `initPile() - IN AUCTION:` / `- NOT IN AUCTION:`) and only auction rows reach `PreCheckCardOptions` -> `handleTradeCardAction`. A non-auction row renders and can never be acted on, which is exactly what the operator saw. So the rows were never usable. "LIVE-CONFIRMED" established that they RENDER, which is not the same claim, and I treated it as if it were. The corpus said this before any of it was built — `plan-2026-08-06-transfer-market.md:731-733`: "`inactive` decodes but no client path treats it specially; do not emit it." The earlier note explaining that the warning "was written about the PRESENTATION function" was motivated reasoning. This also fires the corpus's own pre-registered falsifier E3 (:368-373). Removed: the `inactive` projection from `GET …/tradePile` and `…/trade/status`, `UnlistedCandidate`, `resolve_unlisted_pile`, `unlisted_record`, `Server::resolve_trade_pile`, and the two helpers that existed only to feed them (`MarketStore::blocking_core_items`, `Fifa17IdentityResolver::wire_for_owned_id`). Unlisted trade-pile membership is now internal state with no wire expression. Nothing is stranded: `/club` excludes only items with an ACTIVE listing, so an unlisted pile member stays visible in the club, which is where the client can act on it. Verified live after deploy — `/tradePile` total 7 -> 1 with zero `inactive` rows, `/trade/status` resolving only the real auction, coins unchanged at 29,843,976, and all six former rows present in `/club` (1965 items). Tests: 126 pass, fmt + clippy clean. Two guards replace the three tests that pinned the old behaviour: `the_trade_pile_advertises_only_real_auctions` and `trade_status_answers_only_about_real_auctions`. NOT fixed here, deliberately: `itemData.itemState: "listFS"` is not a FIFA 17 token (0 occurrences in CardsDLL md5 4de3493131d7d2ff7f8b360c5ac9b655, 0 in 4.26 GiB of process memory, decodes to -1; the real value is `forSale` = 5, and the Python oracle emits `listFS` too — which is why the differential never caught it). `CARD_OFFERSTATE` is one of three unresolved action-gate candidates and every actionable row observed carried -1, so that change ships alone with its own live A/B.
This commit is contained in:
@@ -399,7 +399,7 @@ Tests: `returning_an_expired_listing_to_the_club_ends_its_auction`,
|
||||
|
||||
---
|
||||
|
||||
## Q2 representation — narrowed to ONE candidate by elimination (2026-08-17)
|
||||
## Q2 representation — narrowed to ONE candidate by elimination (2026-08-17) — **SUPERSEDED, see the final section**
|
||||
|
||||
Re-entry discriminator result: **CONFIRMED BUG.** After fully leaving and re-entering
|
||||
FUT, the active auction reconstructs correctly under LISTED ITEMS, but the unlisted
|
||||
@@ -469,7 +469,7 @@ test, not the immediate post-mutation response.
|
||||
|
||||
---
|
||||
|
||||
## Q2 — LIVE-CONFIRMED: `tradeState: "inactive"` is the unlisted representation
|
||||
## Q2 — LIVE-CONFIRMED: `tradeState: "inactive"` is the unlisted representation — **SUPERSEDED, see the final section**
|
||||
|
||||
Operator-observed in the real FIFA 17 client, 2026-08-17, with the bounded one-item
|
||||
probe (`OPENFUT_FIFA17_UNLISTED_PROBE`). Fixture:
|
||||
@@ -534,7 +534,7 @@ mixed) has been observed — it remains an open question whether FIFA 17 expects
|
||||
|
||||
---
|
||||
|
||||
## Q2 status downgrade + the "Expired" differential EXPLAINED (2026-08-17)
|
||||
## Q2 status downgrade + the "Expired" differential EXPLAINED (2026-08-17) — **SUPERSEDED, see the final section**
|
||||
|
||||
Phase C was a PARTIAL pass. Corrected conclusions:
|
||||
|
||||
@@ -588,3 +588,70 @@ touched and neither is implicated. If the actions still do not appear, those rem
|
||||
the next candidates — and `itemState`'s vocabulary should be dumped statically with
|
||||
`fifa17-recon/tools/vocab_dump.py` (already validated against the tradeState table)
|
||||
rather than guessed.
|
||||
|
||||
---
|
||||
|
||||
## Q2 — RETRACTED, and CLOSED as unrepresentable (2026-08-17, RE of the FUT front-end)
|
||||
|
||||
**Everything in the two Q2 sections above is superseded.** The `tradeState:"inactive"`
|
||||
projection has been removed from `GET …/tradePile` and `…/trade/status`, and the
|
||||
`UnlistedCandidate` machinery deleted. Live after the fix: `total` 7 → 1, only the real
|
||||
auction, `inactive` rows 0, coins unchanged, and all six former rows still present in
|
||||
`/club` (invariant intact).
|
||||
|
||||
### Why the earlier conclusion was wrong
|
||||
|
||||
It was `LIVE-CONFIRMED` that the row **renders**. That was never in doubt, and it is
|
||||
not the same claim as the row being usable. The rows were never actionable, and could
|
||||
not have been:
|
||||
|
||||
| Fact | Confidence |
|
||||
|---|---|
|
||||
| `tradeState` reaches ONE native branch, `cmp …,0x4` at `0x18013e619` ("is it `closed`?"). `inactive`(2) and `expired`(3) take the same edge → bit-identical `flagA`/`flagB`. Exhaustive 22-site census of `[reg+0x88]` reads across the PE. | CONFIRMED |
|
||||
| `tradeState` is handed to the movie verbatim as the Flash property `STATE` (publisher `0x1801bf030`); the action gate is in the APT/ActionScript FUT front-end, not CardsDLL. | CONFIRMED |
|
||||
| The trade-pile movie class partitions rows with `getCardsInAuction`/`isInActiveAuction` (traces `initPile() - IN AUCTION:` / `- NOT IN AUCTION:`), and only auction rows reach `PreCheckCardOptions` → `handleTradeCardAction`. | CONFIRMED (interned AS symbols + trace strings, live) |
|
||||
| A client rule excludes non-auction pile rows from the action path (Category C). | STRONGLY SUPPORTED |
|
||||
| The gate term is `STATE == 2`. | **NOT PROVEN** — `STATE`, `CARD_OFFERSTATE` and the `RESERVEDPRICE`/`MAX_CREDITS` pair all differ together in every row we can observe |
|
||||
| In the shipped game, unlisted pile rows were a CLIENT-SIDE transient built by the movie from a `TO_TRADEPILE` Flash message (`FUN_1801be6a0`), never server-delivered. | STRONGLY SUPPORTED |
|
||||
|
||||
This fires the corpus's own pre-registered falsifier E3
|
||||
(`plan-2026-08-06-transfer-market.md:368-373`): the native predicates all pass
|
||||
(`IS_TRADING_ENABLED=1`, `TRADE_PILE_SIZE=100`, `tradeable=1`) and an `expired` row in
|
||||
the same section IS actionable, so "the Flash layer has its own gate and CardsDLL is no
|
||||
longer the right place to look" is now an observation.
|
||||
|
||||
**The corpus said so before any of this was written**, at
|
||||
`plan-2026-08-06-transfer-market.md:731-733`: "`inactive` decodes but no client path
|
||||
treats it specially; **do not emit it**." The projection was built anyway, and the
|
||||
earlier reasoning above (that the warning "was written about the PRESENTATION
|
||||
function") was motivated rather than evidenced.
|
||||
|
||||
**Consequence for the product:** a server cannot put an unlisted item on the Transfer
|
||||
List in an actionable state. `/tradePile` carries only twelve-atom auction records and
|
||||
`pile` has no deserializer arm, so anything rendered there must be a real auction.
|
||||
Unlisted trade-pile membership is now internal state with no wire expression; those
|
||||
cards live in `/club`, which is where the client can act on them.
|
||||
|
||||
### Open: `itemData.itemState: "listFS"` is not a FIFA 17 token
|
||||
|
||||
Independent of the gate, and CONFIRMED: `listFS` occurs **0 times** in
|
||||
`CardsDLL_Win64_retail.dll` (md5 `4de3493131d7d2ff7f8b360c5ac9b655`) and 0 times in
|
||||
4.26 GiB of live process memory; it decodes to **−1** through `FUN_180166660`, observed
|
||||
live on our listed row. FIFA 17's value for a listed item is `forSale` (5), from the
|
||||
12-row table at `0x180229cc0`. We emit `listFS` on `/tradePile` and `/trade/status`, and
|
||||
so does the Python oracle (`utas_server.py:3921`) — which is exactly why the
|
||||
differential never caught it: **oracle parity is necessary but not sufficient.**
|
||||
|
||||
Deliberately NOT fixed in the same change: `CARD_OFFERSTATE` (itemState) is one of the
|
||||
three unresolved gate candidates, and every actionable row observed so far carried
|
||||
`itemState = −1`. Flipping it to `forSale` could perturb the one flow that currently
|
||||
works (expired → Return to Club), so it ships alone and gets its own live A/B.
|
||||
|
||||
### Proving the gate (not yet done)
|
||||
|
||||
Disassemble the AVM1 action buffer of `tradepile.isInActiveAuction` (and
|
||||
`PreCheckCardOptions`): either walk from the interned symbol at `0xb84d7498` to its APT
|
||||
function object in the live process, or extract
|
||||
`external/ion_fut/screens/trading/tradepile` from `ui.sb` + the `cas` archives offline.
|
||||
Nothing else in reach separates `STATE` from `CARD_OFFERSTATE` from the price pair,
|
||||
because all three differ together in every observable row.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"date": "2026-08-17",
|
||||
"change": "removed tradeState:'inactive' projection from /tradePile and /trade/status",
|
||||
"why": "RE of the FUT front-end: the action gate is in the APT/ActionScript layer; only rows classified as auctions (getCardsInAuction/isInActiveAuction) reach PreCheckCardOptions -> handleTradeCardAction, so an 'inactive' row renders and can never be acted on. The corpus already said 'do not emit it' (plan-2026-08-06-transfer-market.md:731-733).",
|
||||
"before": {
|
||||
"total": 7,
|
||||
"by_state": {
|
||||
"expired": 1,
|
||||
"inactive": 6
|
||||
},
|
||||
"credits": 29843976,
|
||||
"trade_status_returned": 7
|
||||
},
|
||||
"after": {
|
||||
"total": 1,
|
||||
"by_state": {
|
||||
"expired": 1
|
||||
},
|
||||
"credits": 29843976,
|
||||
"trade_status_returned": 1
|
||||
},
|
||||
"club_invariant": {
|
||||
"formerly_inactive_members_visible_in_club": 6,
|
||||
"of": 6,
|
||||
"stranded": 0,
|
||||
"club_items": 1965
|
||||
},
|
||||
"not_fixed_here": {
|
||||
"itemState": "listFS",
|
||||
"finding": "0 occurrences in CardsDLL (md5 4de3493131d7d2ff7f8b360c5ac9b655) and 0 in 4.26 GiB of process memory; decodes to -1. FIFA 17 value is forSale (5).",
|
||||
"reason_deferred": "CARD_OFFERSTATE is one of three unresolved action-gate candidates; every actionable row observed carried itemState -1, so changing it could perturb the working expired -> Return-to-Club flow. Ships alone with its own live A/B."
|
||||
},
|
||||
"gate_status": "Category C, STRONGLY SUPPORTED. Gate term NOT PROVEN: STATE, CARD_OFFERSTATE and RESERVEDPRICE/MAX_CREDITS all differ together in every observable row. Proof needs AVM1 disassembly of tradepile.isInActiveAuction."
|
||||
}
|
||||
Reference in New Issue
Block a user