test(market): make the sold A/B one-field attributable, add classified differential
The brief's gate: if the harness varies bidState AND coinsProcessed together, the client's reaction is attributable to neither. The env knobs were already orthogonal (--variant and --coins-processed are independent, cp defaults to 0), but sold-wire-check.py was flipping BOTH for variant B as a convenience, which is exactly the contaminated A/B the brief forbids. Fixed: the primary pair now holds coinsProcessed at 0 and asserts the differing-field set is exactly ['bidState']. New scripts/sold-ab-differential.py is the pre-live gate. It settles ONE synthetic sale, then re-reads every seller-facing surface under each variant by restarting only the host (same Core, same DBs, same sale), and diffs with explicit classification -- MISSING / EXTRA / TYPE_MISMATCH / VALUE_MISMATCH -- rather than a boolean "equal?". Two orthogonal pairs: PRIMARY bidState highest vs buyNow, coinsProcessed held at 0 ORTHOGONAL coinsProcessed 0 vs 1, bidState held at highest Result, 36/36: the ONLY finding on /tradePile is VALUE_MISMATCH auctionInfo[0].bidState A='highest' B='buyNow'; /trade/status differs in exactly the same one path; counts are byte-identical. The orthogonal pair's only finding is auctionInfo[0].coinsProcessed. C_cp0's sha256 equals A_highest's, so the capture is reproducible rather than merely consistent. Counts states the live run has to interpret, measured not guessed: S1 0 active + 1 sold -> count 0, selling 0, sold 1 S2 1 active + 1 sold -> count 1 (active mode) vs 2 (membership mode) That divergence IS the open question for the client; production is unchanged. scripts/sold-client-ports.py switches ONLY the two client Blaze port lines, and is built so restoration cannot depend on memory: it records the production values to a sidecar on the client BEFORE the first edit and restore reads that sidecar, refusing if it is absent. It rewrites only known keys (a missing key is an error, never a silent append), re-reads and verifies afterwards, and REFUSES to edit while a FIFA client is running because the hook reads the file at connect time. Phase 0 evidence under docs/evidence/sold-ab-2026-08-18/ with a sha256 per surface, one file per variant so A can never overwrite B. Live client A/B NOT run: a production FIFA session is currently live on 10.10.0.105 (pid 32188), and live-session mutual exclusion applies. The client config was NOT touched -- the switcher's guard refused, as designed. Production untouched: prod-host pid 3631953, coins 29,843,976, /tradePile 0, counts.sold 0, club 1966; nothing under /home/alex/openfut-promotion/state/ opened.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"env": {
|
||||
"OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0",
|
||||
"OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest"
|
||||
},
|
||||
"surfaces": {
|
||||
"counts": {
|
||||
"body": {
|
||||
"count": 0,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 0,
|
||||
"sold": 1
|
||||
},
|
||||
"sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206",
|
||||
"status": 200
|
||||
},
|
||||
"tradePile": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143,
|
||||
"duplicateItemIdList": [],
|
||||
"total": 1
|
||||
},
|
||||
"sha256": "caecc67a615ddd0f209b36c7c8ea6a04e0002552d9a42b8eeca20df84feedf3c",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_filtered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_unfiltered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855",
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"env": {
|
||||
"OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0",
|
||||
"OPENFUT_FIFA17_SOLD_EXPERIMENT": "buyNow"
|
||||
},
|
||||
"surfaces": {
|
||||
"counts": {
|
||||
"body": {
|
||||
"count": 0,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 0,
|
||||
"sold": 1
|
||||
},
|
||||
"sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206",
|
||||
"status": 200
|
||||
},
|
||||
"tradePile": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "buyNow",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143,
|
||||
"duplicateItemIdList": [],
|
||||
"total": 1
|
||||
},
|
||||
"sha256": "0b6e5cc5ab58fdbdd76586b1cff685a562e669fd37750cbd1397db9ee9e97407",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_filtered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "buyNow",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "fab8d43b7ac0f0e09d6902155f7c5f6d91e57b4b595ec8f6093a5a9dcb02bcdb",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_unfiltered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "buyNow",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "fab8d43b7ac0f0e09d6902155f7c5f6d91e57b4b595ec8f6093a5a9dcb02bcdb",
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"env": {
|
||||
"OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0",
|
||||
"OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest"
|
||||
},
|
||||
"surfaces": {
|
||||
"counts": {
|
||||
"body": {
|
||||
"count": 0,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 0,
|
||||
"sold": 1
|
||||
},
|
||||
"sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206",
|
||||
"status": 200
|
||||
},
|
||||
"tradePile": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143,
|
||||
"duplicateItemIdList": [],
|
||||
"total": 1
|
||||
},
|
||||
"sha256": "caecc67a615ddd0f209b36c7c8ea6a04e0002552d9a42b8eeca20df84feedf3c",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_filtered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_unfiltered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855",
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"env": {
|
||||
"OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "1",
|
||||
"OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest"
|
||||
},
|
||||
"surfaces": {
|
||||
"counts": {
|
||||
"body": {
|
||||
"count": 0,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 0,
|
||||
"sold": 1
|
||||
},
|
||||
"sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206",
|
||||
"status": 200
|
||||
},
|
||||
"tradePile": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 1,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143,
|
||||
"duplicateItemIdList": [],
|
||||
"total": 1
|
||||
},
|
||||
"sha256": "6e228a34b5d7fac6bf70710cbd4ca35b58740e04ae455ffeee04b6ce7faab8e5",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_filtered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 1,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "c1557013bf20285d0512ca0d055a16df8036260559d640da11102a2379ac5986",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_unfiltered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 1,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "c1557013bf20285d0512ca0d055a16df8036260559d640da11102a2379ac5986",
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
{
|
||||
"counts_states": {
|
||||
"S1_zero_active_one_sold": {
|
||||
"count": 0,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 0,
|
||||
"sold": 1
|
||||
},
|
||||
"S2_one_active_one_sold_count_active": {
|
||||
"count": 1,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 1,
|
||||
"sold": 1
|
||||
},
|
||||
"S2_one_active_one_sold_count_membership": {
|
||||
"count": 2,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 1,
|
||||
"sold": 1
|
||||
}
|
||||
},
|
||||
"diffs": {
|
||||
"orthogonal_coinsprocessed": [
|
||||
{
|
||||
"a": 0,
|
||||
"b": 1,
|
||||
"kind": "VALUE_MISMATCH",
|
||||
"path": "auctionInfo[0].coinsProcessed"
|
||||
}
|
||||
],
|
||||
"primary_bidstate": [
|
||||
{
|
||||
"a": "highest",
|
||||
"b": "buyNow",
|
||||
"kind": "VALUE_MISMATCH",
|
||||
"path": "auctionInfo[0].bidState"
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated": "2026-08-18T02:19:25Z",
|
||||
"settlement": {
|
||||
"buyer": 19850,
|
||||
"fee": 7,
|
||||
"instances": 1,
|
||||
"owner": "club-buyer-b",
|
||||
"seller": 1143
|
||||
},
|
||||
"variants": {
|
||||
"A_highest": {
|
||||
"env": {
|
||||
"OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0",
|
||||
"OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest"
|
||||
},
|
||||
"surfaces": {
|
||||
"counts": {
|
||||
"body": {
|
||||
"count": 0,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 0,
|
||||
"sold": 1
|
||||
},
|
||||
"sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206",
|
||||
"status": 200
|
||||
},
|
||||
"tradePile": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143,
|
||||
"duplicateItemIdList": [],
|
||||
"total": 1
|
||||
},
|
||||
"sha256": "caecc67a615ddd0f209b36c7c8ea6a04e0002552d9a42b8eeca20df84feedf3c",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_filtered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_unfiltered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855",
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
},
|
||||
"B_buyNow": {
|
||||
"env": {
|
||||
"OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0",
|
||||
"OPENFUT_FIFA17_SOLD_EXPERIMENT": "buyNow"
|
||||
},
|
||||
"surfaces": {
|
||||
"counts": {
|
||||
"body": {
|
||||
"count": 0,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 0,
|
||||
"sold": 1
|
||||
},
|
||||
"sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206",
|
||||
"status": 200
|
||||
},
|
||||
"tradePile": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "buyNow",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143,
|
||||
"duplicateItemIdList": [],
|
||||
"total": 1
|
||||
},
|
||||
"sha256": "0b6e5cc5ab58fdbdd76586b1cff685a562e669fd37750cbd1397db9ee9e97407",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_filtered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "buyNow",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "fab8d43b7ac0f0e09d6902155f7c5f6d91e57b4b595ec8f6093a5a9dcb02bcdb",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_unfiltered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "buyNow",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "fab8d43b7ac0f0e09d6902155f7c5f6d91e57b4b595ec8f6093a5a9dcb02bcdb",
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
},
|
||||
"C_cp0": {
|
||||
"env": {
|
||||
"OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0",
|
||||
"OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest"
|
||||
},
|
||||
"surfaces": {
|
||||
"counts": {
|
||||
"body": {
|
||||
"count": 0,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 0,
|
||||
"sold": 1
|
||||
},
|
||||
"sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206",
|
||||
"status": 200
|
||||
},
|
||||
"tradePile": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143,
|
||||
"duplicateItemIdList": [],
|
||||
"total": 1
|
||||
},
|
||||
"sha256": "caecc67a615ddd0f209b36c7c8ea6a04e0002552d9a42b8eeca20df84feedf3c",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_filtered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_unfiltered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855",
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
},
|
||||
"D_cp1": {
|
||||
"env": {
|
||||
"OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "1",
|
||||
"OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest"
|
||||
},
|
||||
"surfaces": {
|
||||
"counts": {
|
||||
"body": {
|
||||
"count": 0,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 0,
|
||||
"sold": 1
|
||||
},
|
||||
"sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206",
|
||||
"status": 200
|
||||
},
|
||||
"tradePile": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 1,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143,
|
||||
"duplicateItemIdList": [],
|
||||
"total": 1
|
||||
},
|
||||
"sha256": "6e228a34b5d7fac6bf70710cbd4ca35b58740e04ae455ffeee04b6ce7faab8e5",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_filtered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 1,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "c1557013bf20285d0512ca0d055a16df8036260559d640da11102a2379ac5986",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_unfiltered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 1,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 212188,
|
||||
"id": 100000178,
|
||||
"itemState": "forSale",
|
||||
"preferredPosition": "ST",
|
||||
"rating": 75,
|
||||
"resourceId": 212188,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "c1557013bf20285d0512ca0d055a16df8036260559d640da11102a2379ac5986",
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,850 @@
|
||||
{
|
||||
"captured": "2026-08-18T02:21:49Z",
|
||||
"phase": "0 \u2014 staging validated, sold row present, awaiting live client",
|
||||
"settlement": {
|
||||
"clubs": {
|
||||
"club-buyer-b": 19850,
|
||||
"club-seller-a-cage": 1143
|
||||
},
|
||||
"disposable_owner": [
|
||||
"club-buyer-b"
|
||||
],
|
||||
"fee": 7,
|
||||
"gross": 150,
|
||||
"instances": 1,
|
||||
"proceeds": 143
|
||||
},
|
||||
"sold_row": {
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 232273,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 62
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 28
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 33
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 49
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 53
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 50
|
||||
}
|
||||
],
|
||||
"cardassetid": 232273,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 232273,
|
||||
"discardValue": 150,
|
||||
"fitness": 99,
|
||||
"id": 100000012,
|
||||
"itemState": "forSale",
|
||||
"itemType": "player",
|
||||
"leagueId": 32,
|
||||
"nation": 117,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "LB",
|
||||
"rareflag": 1,
|
||||
"rating": 51,
|
||||
"resourceId": 232273,
|
||||
"teamid": 112487,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
},
|
||||
"surfaces": {
|
||||
"club": {
|
||||
"body": {
|
||||
"itemData": [
|
||||
{
|
||||
"assetId": 20801,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 97
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 97
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 86
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 96
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 38
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 85
|
||||
}
|
||||
],
|
||||
"cardassetid": 20801,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 83906881,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000001,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 53,
|
||||
"nation": 38,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "LW",
|
||||
"rareflag": 11,
|
||||
"rating": 99,
|
||||
"resourceId": 83906881,
|
||||
"teamid": 243,
|
||||
"untradeable": false
|
||||
},
|
||||
{
|
||||
"assetId": 158023,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 94
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 95
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 91
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 99
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 31
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 66
|
||||
}
|
||||
],
|
||||
"cardassetid": 158023,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 84044103,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000002,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 53,
|
||||
"nation": 52,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "RW",
|
||||
"rareflag": 11,
|
||||
"rating": 98,
|
||||
"resourceId": 84044103,
|
||||
"teamid": 241,
|
||||
"untradeable": false
|
||||
},
|
||||
{
|
||||
"assetId": 167495,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 94
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 95
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 99
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 94
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 64
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 96
|
||||
}
|
||||
],
|
||||
"cardassetid": 167495,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 84053575,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000003,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 19,
|
||||
"nation": 21,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "GK",
|
||||
"rareflag": 11,
|
||||
"rating": 97,
|
||||
"resourceId": 84053575,
|
||||
"teamid": 21,
|
||||
"untradeable": false
|
||||
},
|
||||
{
|
||||
"assetId": 176580,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 85
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 93
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 82
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 90
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 45
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 82
|
||||
}
|
||||
],
|
||||
"cardassetid": 176580,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 117617092,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000004,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 53,
|
||||
"nation": 60,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "ST",
|
||||
"rareflag": 22,
|
||||
"rating": 95,
|
||||
"resourceId": 117617092,
|
||||
"teamid": 241,
|
||||
"untradeable": false
|
||||
},
|
||||
{
|
||||
"assetId": 177003,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 78
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 79
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 90
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 93
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 76
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 70
|
||||
}
|
||||
],
|
||||
"cardassetid": 177003,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 151171947,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000005,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 53,
|
||||
"nation": 10,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "CM",
|
||||
"rareflag": 24,
|
||||
"rating": 93,
|
||||
"resourceId": 151171947,
|
||||
"teamid": 243,
|
||||
"untradeable": false
|
||||
},
|
||||
{
|
||||
"assetId": 164240,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 77
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 76
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 76
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 93
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 82
|
||||
}
|
||||
],
|
||||
"cardassetid": 164240,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 134381968,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000006,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 16,
|
||||
"nation": 54,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "CB",
|
||||
"rareflag": 23,
|
||||
"rating": 92,
|
||||
"resourceId": 134381968,
|
||||
"teamid": 73,
|
||||
"untradeable": false
|
||||
},
|
||||
{
|
||||
"assetId": 182493,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 69
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 52
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 69
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 67
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 92
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 83
|
||||
}
|
||||
],
|
||||
"cardassetid": 182493,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 151177437,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000007,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 53,
|
||||
"nation": 60,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "CB",
|
||||
"rareflag": 24,
|
||||
"rating": 92,
|
||||
"resourceId": 151177437,
|
||||
"teamid": 240,
|
||||
"untradeable": false
|
||||
},
|
||||
{
|
||||
"assetId": 168651,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 71
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 89
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 91
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 87
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 64
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 71
|
||||
}
|
||||
],
|
||||
"cardassetid": 168651,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 84054731,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000008,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 53,
|
||||
"nation": 10,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "CM",
|
||||
"rareflag": 11,
|
||||
"rating": 92,
|
||||
"resourceId": 84054731,
|
||||
"teamid": 241,
|
||||
"untradeable": false
|
||||
},
|
||||
{
|
||||
"assetId": 182521,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 48
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 83
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 91
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 82
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 72
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 73
|
||||
}
|
||||
],
|
||||
"cardassetid": 182521,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 134400249,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000009,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 53,
|
||||
"nation": 21,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "CM",
|
||||
"rareflag": 23,
|
||||
"rating": 91,
|
||||
"resourceId": 134400249,
|
||||
"teamid": 243,
|
||||
"untradeable": false
|
||||
},
|
||||
{
|
||||
"assetId": 197445,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 90
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 77
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 85
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 87
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 87
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 77
|
||||
}
|
||||
],
|
||||
"cardassetid": 197445,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 151192389,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000010,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 19,
|
||||
"nation": 4,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "LB",
|
||||
"rareflag": 24,
|
||||
"rating": 91,
|
||||
"resourceId": 151192389,
|
||||
"teamid": 21,
|
||||
"untradeable": false
|
||||
},
|
||||
{
|
||||
"assetId": 121939,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 70
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 58
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 84
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 86
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 88
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 66
|
||||
}
|
||||
],
|
||||
"cardassetid": 121939,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 100785235,
|
||||
"discardValue": 1500,
|
||||
"fitness": 99,
|
||||
"id": 100000011,
|
||||
"itemState": "free",
|
||||
"itemType": "player",
|
||||
"leagueId": 19,
|
||||
"nation": 21,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "RB",
|
||||
"rareflag": 21,
|
||||
"rating": 90,
|
||||
"resourceId": 100785235,
|
||||
"teamid": 21,
|
||||
"untradeable": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"sha256": "0a916e8e129a9c05294784738c305d651beedd58ebd914a6abfa94bd0a59abc7",
|
||||
"status": 200
|
||||
},
|
||||
"counts": {
|
||||
"body": {
|
||||
"count": 0,
|
||||
"maxAuctionsAllowed": 100,
|
||||
"offered": 0,
|
||||
"selling": 0,
|
||||
"sold": 1
|
||||
},
|
||||
"sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206",
|
||||
"status": 200
|
||||
},
|
||||
"tradePile": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 232273,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 62
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 28
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 33
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 49
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 53
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 50
|
||||
}
|
||||
],
|
||||
"cardassetid": 232273,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 232273,
|
||||
"discardValue": 150,
|
||||
"fitness": 99,
|
||||
"id": 100000012,
|
||||
"itemState": "forSale",
|
||||
"itemType": "player",
|
||||
"leagueId": 32,
|
||||
"nation": 117,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "LB",
|
||||
"rareflag": 1,
|
||||
"rating": 51,
|
||||
"resourceId": 232273,
|
||||
"teamid": 112487,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143,
|
||||
"duplicateItemIdList": [],
|
||||
"total": 1
|
||||
},
|
||||
"sha256": "c792ebcf179a5c617ffe02859ad39eba830a4d5b5224a0953ecef774ee4cf75b",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_filtered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 232273,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 62
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 28
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 33
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 49
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 53
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 50
|
||||
}
|
||||
],
|
||||
"cardassetid": 232273,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 232273,
|
||||
"discardValue": 150,
|
||||
"fitness": 99,
|
||||
"id": 100000012,
|
||||
"itemState": "forSale",
|
||||
"itemType": "player",
|
||||
"leagueId": 32,
|
||||
"nation": 117,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "LB",
|
||||
"rareflag": 1,
|
||||
"rating": 51,
|
||||
"resourceId": 232273,
|
||||
"teamid": 112487,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "db79d9fcfc47ae680b44311598e82918b8afd3284160143c33e525adf693f30e",
|
||||
"status": 200
|
||||
},
|
||||
"trade_status_unfiltered": {
|
||||
"body": {
|
||||
"auctionInfo": [
|
||||
{
|
||||
"bidState": "highest",
|
||||
"buyNowPrice": 150,
|
||||
"coinsProcessed": 0,
|
||||
"currentBid": 150,
|
||||
"expires": 0,
|
||||
"itemData": {
|
||||
"assetId": 232273,
|
||||
"attributeList": [
|
||||
{
|
||||
"index": 0,
|
||||
"value": 62
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"value": 28
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"value": 33
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"value": 49
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"value": 53
|
||||
},
|
||||
{
|
||||
"index": 5,
|
||||
"value": 50
|
||||
}
|
||||
],
|
||||
"cardassetid": 232273,
|
||||
"cardsubtypeid": 0,
|
||||
"contract": 7,
|
||||
"definitionId": 232273,
|
||||
"discardValue": 150,
|
||||
"fitness": 99,
|
||||
"id": 100000012,
|
||||
"itemState": "forSale",
|
||||
"itemType": "player",
|
||||
"leagueId": 32,
|
||||
"nation": 117,
|
||||
"owners": 1,
|
||||
"playStyle": 250,
|
||||
"preferredPosition": "LB",
|
||||
"rareflag": 1,
|
||||
"rating": 51,
|
||||
"resourceId": 232273,
|
||||
"teamid": 112487,
|
||||
"untradeable": false
|
||||
},
|
||||
"sellerEstablished": 1,
|
||||
"sellerName": "CAGE",
|
||||
"startingBid": 150,
|
||||
"tradeId": 900500150,
|
||||
"tradeState": "closed",
|
||||
"watched": false
|
||||
}
|
||||
],
|
||||
"credits": 1143
|
||||
},
|
||||
"sha256": "db79d9fcfc47ae680b44311598e82918b8afd3284160143c33e525adf693f30e",
|
||||
"status": 200
|
||||
}
|
||||
},
|
||||
"topology": {
|
||||
"advertises_utas": "http://<host>:8299/",
|
||||
"blaze": [
|
||||
42327,
|
||||
42330,
|
||||
42331
|
||||
],
|
||||
"core": 18081,
|
||||
"utas_host": 8299
|
||||
},
|
||||
"variant": "A_highest (bidState=highest, coinsProcessed=0, countMode=active)"
|
||||
}
|
||||
Executable
+375
@@ -0,0 +1,375 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PRE-LIVE machine differential for the seller-facing sold-row A/B.
|
||||
|
||||
Purpose: prove, before any operator time is spent at a real FIFA client, that the
|
||||
two variants differ in EXACTLY the field under test. A contaminated A/B cannot
|
||||
attribute the client's reaction to anything, so this gate runs first.
|
||||
|
||||
It performs ONE synthetic settlement, then re-reads every seller-facing surface
|
||||
under each variant by restarting only the host (same Core, same DBs, same sale), and
|
||||
diffs the payloads with explicit classification:
|
||||
|
||||
MISSING key present in A, absent in B
|
||||
EXTRA key absent in A, present in B
|
||||
TYPE_MISMATCH same key, different JSON type
|
||||
VALUE_MISMATCH same key and type, different value
|
||||
|
||||
Two orthogonal pairs are checked:
|
||||
|
||||
PRIMARY bidState highest vs buyNow coinsProcessed held at 0
|
||||
ORTHOGONAL coinsProcessed 0 vs 1 bidState held at highest
|
||||
|
||||
Plus the two counts states the live experiment needs to interpret:
|
||||
|
||||
S1 0 active + 1 sold
|
||||
S2 1 active + 1 sold
|
||||
|
||||
Evidence is written per variant under docs/evidence/sold-ab-<date>/ with a sha256
|
||||
of each payload, so variant A evidence can never be confused with variant B.
|
||||
|
||||
ISOLATION: reuses the verified staging bring-up from sold-wire-check.py, which binds
|
||||
only ephemeral loopback ports, refuses every production port, and opens nothing under
|
||||
/home/alex/openfut-promotion/state/.
|
||||
|
||||
python3 scripts/sold-ab-differential.py [--out DIR] [--keep]
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Reuse the proven harness helpers rather than re-deriving them. The filename has
|
||||
# hyphens, so it cannot be imported normally.
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
"sold_wire_check", os.path.join(REPO, "scripts", "sold-wire-check.py"))
|
||||
H = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(H)
|
||||
|
||||
SURFACES = {
|
||||
"tradePile": "/ut/game/fifa17/tradePile",
|
||||
"counts": "/ut/game/fifa17/tradePile/counts",
|
||||
"trade_status_filtered": f"/ut/game/fifa17/trade/status?tradeIds={H.TRADE_ID}",
|
||||
"trade_status_unfiltered": "/ut/game/fifa17/trade/status",
|
||||
}
|
||||
|
||||
results = []
|
||||
|
||||
|
||||
def check(label, ok, detail=""):
|
||||
results.append((label, bool(ok), detail))
|
||||
print(f" [{'PASS' if ok else 'FAIL'}] {label}{(': ' + detail) if detail else ''}")
|
||||
return ok
|
||||
|
||||
|
||||
def banner(t):
|
||||
print("\n" + "=" * 74)
|
||||
print(f"== {t}")
|
||||
print("=" * 74)
|
||||
|
||||
|
||||
def jtype(v):
|
||||
if isinstance(v, bool):
|
||||
return "bool"
|
||||
if isinstance(v, int):
|
||||
return "int"
|
||||
if isinstance(v, float):
|
||||
return "float"
|
||||
if isinstance(v, str):
|
||||
return "str"
|
||||
if isinstance(v, list):
|
||||
return "list"
|
||||
if isinstance(v, dict):
|
||||
return "dict"
|
||||
return "null"
|
||||
|
||||
|
||||
def classify(a, b, path=""):
|
||||
"""Recursive classified diff. Returns a list of (kind, path, a, b)."""
|
||||
out = []
|
||||
if jtype(a) != jtype(b):
|
||||
return [("TYPE_MISMATCH", path or "<root>", jtype(a), jtype(b))]
|
||||
if isinstance(a, dict):
|
||||
for k in a:
|
||||
if k not in b:
|
||||
out.append(("MISSING", f"{path}.{k}".lstrip("."), a[k], None))
|
||||
for k in b:
|
||||
if k not in a:
|
||||
out.append(("EXTRA", f"{path}.{k}".lstrip("."), None, b[k]))
|
||||
for k in a:
|
||||
if k in b:
|
||||
out += classify(a[k], b[k], f"{path}.{k}".lstrip("."))
|
||||
return out
|
||||
if isinstance(a, list):
|
||||
if len(a) != len(b):
|
||||
out.append(("VALUE_MISMATCH", f"{path}[len]", len(a), len(b)))
|
||||
for i, (x, y) in enumerate(zip(a, b)):
|
||||
out += classify(x, y, f"{path}[{i}]")
|
||||
return out
|
||||
if a != b:
|
||||
out.append(("VALUE_MISMATCH", path or "<root>", a, b))
|
||||
return out
|
||||
|
||||
|
||||
def capture(port):
|
||||
"""Every seller-facing surface, as parsed JSON plus a sha256 of the raw bytes."""
|
||||
snap = {}
|
||||
for name, path in SURFACES.items():
|
||||
st, body = H.req(port, "GET", path)
|
||||
raw = json.dumps(body, sort_keys=True, separators=(",", ":")).encode()
|
||||
snap[name] = {
|
||||
"status": st,
|
||||
"body": body,
|
||||
"sha256": hashlib.sha256(raw).hexdigest(),
|
||||
}
|
||||
return snap
|
||||
|
||||
|
||||
def sold_row(snap):
|
||||
rows = snap["tradePile"]["body"].get("auctionInfo", [])
|
||||
return rows[0] if rows else {}
|
||||
|
||||
|
||||
def report_diff(label, a, b, allowed):
|
||||
"""Print the classified diff and assert only `allowed` paths differ."""
|
||||
diffs = classify(a, b)
|
||||
print(f" classified diff ({len(diffs)} finding(s)):")
|
||||
for kind, path, av, bv in diffs:
|
||||
print(f" {kind:15s} {path:34s} A={av!r} B={bv!r}")
|
||||
kinds = {k for k, _, _, _ in diffs}
|
||||
for bad in ("MISSING", "EXTRA", "TYPE_MISMATCH"):
|
||||
check(f"{label}: no {bad}", bad not in kinds,
|
||||
", ".join(p for k, p, _, _ in diffs if k == bad) or "none")
|
||||
paths = sorted(p for _, p, _, _ in diffs)
|
||||
check(f"{label}: only {allowed} differ", paths == sorted(allowed), str(paths))
|
||||
return diffs
|
||||
|
||||
|
||||
def restart_host(state, variant, coins_processed):
|
||||
"""Same Core, same DBs, same settled sale — only the host's variant changes."""
|
||||
if state.get("host"):
|
||||
state["host"].terminate()
|
||||
state["host"].wait(timeout=20)
|
||||
state["host"] = H.start_host(state["tmp"], state["host_port"], state["core_port"],
|
||||
state["host_log"], variant,
|
||||
coins_processed=coins_processed)
|
||||
return state["host"]
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--out", default=os.path.join(
|
||||
REPO, "docs/evidence", f"sold-ab-{time.strftime('%Y-%m-%d')}"))
|
||||
ap.add_argument("--keep", action="store_true")
|
||||
args = ap.parse_args()
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
|
||||
tmp = tempfile.mkdtemp(prefix="openfut-sold-ab-")
|
||||
state = {"tmp": tmp, "host": None}
|
||||
evidence = {"generated": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
"variants": {}, "diffs": {}, "counts_states": {}}
|
||||
try:
|
||||
state["core_port"] = H.free_port()
|
||||
state["host_port"] = H.free_port()
|
||||
state["core_log"] = os.path.join(tmp, "core.log")
|
||||
state["host_log"] = os.path.join(tmp, "host.log")
|
||||
banner("ISOLATED STAGING")
|
||||
print(f" tmp {tmp}\n core 127.0.0.1:{state['core_port']}"
|
||||
f"\n host 127.0.0.1:{state['host_port']}")
|
||||
core, core_db = H.start_core(tmp, state["core_port"], state["core_log"])
|
||||
state["core"] = core
|
||||
state["core_db"] = core_db
|
||||
mdb = os.path.join(tmp, "market.db")
|
||||
|
||||
# ---- one authentic listing + one real settlement -------------------
|
||||
restart_host(state, "highest", "0")
|
||||
con = sqlite3.connect(mdb)
|
||||
con.execute(
|
||||
"INSERT INTO listings (listing_id, card_id, core_item_id, wire_item_id, "
|
||||
"wire_resource_id, start_price, buy_now_price, owner, state, created_at, "
|
||||
"item_json, duration_secs) VALUES (?,?,?,?,?,?,?,?, 'active', ?, ?, ?)",
|
||||
(H.TRADE_ID, H.CARD, H.ITEM, 100000178, 212188, H.GROSS, H.GROSS, "CAGE",
|
||||
str(int(time.time() * 1000)), json.dumps({
|
||||
"id": 100000178, "resourceId": 212188, "rating": 75,
|
||||
"preferredPosition": "ST", "itemState": "forSale",
|
||||
"untradeable": False, "assetId": 212188}), 3600))
|
||||
con.commit(); con.close()
|
||||
banner(f"SETTLE — synthetic Buyer B buys at {H.GROSS}")
|
||||
out = subprocess.run(
|
||||
[os.path.join(REPO, "target/release/staging_sell"),
|
||||
"--market-db", mdb, "--core-url", f"http://127.0.0.1:{state['core_port']}",
|
||||
"--trade-id", H.TRADE_ID, "--item", H.ITEM,
|
||||
"--seller", H.SELLER_CLUB, "--buyer", H.BUYER_CLUB,
|
||||
"--gross", str(H.GROSS)],
|
||||
capture_output=True, text=True, timeout=120)
|
||||
print(" " + "\n ".join((out.stdout + out.stderr).strip().splitlines()))
|
||||
check("settlement succeeded", out.returncode == 0)
|
||||
own, n, coins = H.owner_of(core_db, H.ITEM)
|
||||
fee = H.GROSS * 5 // 100
|
||||
check("ownership -> buyer", own == H.BUYER_CLUB, str(own))
|
||||
check("exactly one authoritative instance", n == 1, str(n))
|
||||
check("seller credited net", coins[H.SELLER_CLUB] == 1000 + H.GROSS - fee,
|
||||
str(coins[H.SELLER_CLUB]))
|
||||
check("buyer debited gross", coins[H.BUYER_CLUB] == 20000 - H.GROSS,
|
||||
str(coins[H.BUYER_CLUB]))
|
||||
economy = {"seller": coins[H.SELLER_CLUB], "buyer": coins[H.BUYER_CLUB],
|
||||
"fee": fee, "owner": own, "instances": n}
|
||||
evidence["settlement"] = economy
|
||||
|
||||
# ---- PRIMARY A/B: bidState only ------------------------------------
|
||||
banner("PRIMARY A/B — bidState highest vs buyNow (coinsProcessed held at 0)")
|
||||
snaps = {}
|
||||
for name, variant in (("A_highest", "highest"), ("B_buyNow", "buyNow")):
|
||||
restart_host(state, variant, "0")
|
||||
snaps[name] = capture(state["host_port"])
|
||||
evidence["variants"][name] = {
|
||||
"env": {"OPENFUT_FIFA17_SOLD_EXPERIMENT": variant,
|
||||
"OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0"},
|
||||
"surfaces": snaps[name],
|
||||
}
|
||||
row = sold_row(snaps[name])
|
||||
print(f" {name}: tradeState={row.get('tradeState')} "
|
||||
f"bidState={row.get('bidState')} currentBid={row.get('currentBid')} "
|
||||
f"coinsProcessed={row.get('coinsProcessed')} atoms={len(row)}")
|
||||
# Every variant must independently be a well-formed sold row.
|
||||
check(f"{name}: tradeState closed", row.get("tradeState") == "closed")
|
||||
check(f"{name}: twelve atoms", len(row) == 12, str(len(row)))
|
||||
check(f"{name}: currentBid == gross", row.get("currentBid") == H.GROSS)
|
||||
check(f"{name}: expires 0", row.get("expires") == 0)
|
||||
check(f"{name}: counts.sold == 1",
|
||||
snaps[name]["counts"]["body"].get("sold") == 1)
|
||||
evidence["diffs"]["primary_bidstate"] = [
|
||||
{"kind": k, "path": p, "a": av, "b": bv}
|
||||
for k, p, av, bv in report_diff(
|
||||
"PRIMARY", snaps["A_highest"]["tradePile"]["body"],
|
||||
snaps["B_buyNow"]["tradePile"]["body"],
|
||||
["auctionInfo[0].bidState"])
|
||||
]
|
||||
# /trade/status must move in lockstep, or the screen would contradict itself.
|
||||
report_diff("PRIMARY /trade/status",
|
||||
snaps["A_highest"]["trade_status_filtered"]["body"],
|
||||
snaps["B_buyNow"]["trade_status_filtered"]["body"],
|
||||
["auctionInfo[0].bidState"])
|
||||
report_diff("PRIMARY counts",
|
||||
snaps["A_highest"]["counts"]["body"],
|
||||
snaps["B_buyNow"]["counts"]["body"], [])
|
||||
|
||||
# ---- ORTHOGONAL A/B: coinsProcessed only ---------------------------
|
||||
banner("ORTHOGONAL A/B — coinsProcessed 0 vs 1 (bidState held at highest)")
|
||||
for name, cp in (("C_cp0", "0"), ("D_cp1", "1")):
|
||||
restart_host(state, "highest", cp)
|
||||
snaps[name] = capture(state["host_port"])
|
||||
evidence["variants"][name] = {
|
||||
"env": {"OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest",
|
||||
"OPENFUT_FIFA17_SOLD_COINS_PROCESSED": cp},
|
||||
"surfaces": snaps[name],
|
||||
}
|
||||
row = sold_row(snaps[name])
|
||||
print(f" {name}: bidState={row.get('bidState')} "
|
||||
f"coinsProcessed={row.get('coinsProcessed')}")
|
||||
evidence["diffs"]["orthogonal_coinsprocessed"] = [
|
||||
{"kind": k, "path": p, "a": av, "b": bv}
|
||||
for k, p, av, bv in report_diff(
|
||||
"ORTHOGONAL", snaps["C_cp0"]["tradePile"]["body"],
|
||||
snaps["D_cp1"]["tradePile"]["body"],
|
||||
["auctionInfo[0].coinsProcessed"])
|
||||
]
|
||||
check("C_cp0 is identical to A_highest (same env => same bytes)",
|
||||
snaps["C_cp0"]["tradePile"]["sha256"]
|
||||
== snaps["A_highest"]["tradePile"]["sha256"],
|
||||
"reproducible")
|
||||
|
||||
# ---- counts states the live run must interpret ----------------------
|
||||
banner("COUNTS STATES")
|
||||
restart_host(state, "highest", "0")
|
||||
s1 = H.req(state["host_port"], "GET", SURFACES["counts"])[1]
|
||||
print(f" S1 0 active + 1 sold -> {json.dumps(s1)}")
|
||||
con = sqlite3.connect(mdb)
|
||||
con.execute(
|
||||
"INSERT INTO listings (listing_id, card_id, core_item_id, wire_item_id, "
|
||||
"wire_resource_id, start_price, buy_now_price, owner, state, created_at, "
|
||||
"item_json, duration_secs) VALUES (?,?,?,?,?,?,?,?, 'active', ?, ?, ?)",
|
||||
("900500999", H.CARD, "core-second-x", 100000179, 212188, 300, 400, "CAGE",
|
||||
str(int(time.time() * 1000)), json.dumps({
|
||||
"id": 100000179, "resourceId": 212188, "rating": 75,
|
||||
"preferredPosition": "ST", "itemState": "forSale",
|
||||
"untradeable": False, "assetId": 212188}), 3600))
|
||||
con.commit(); con.close()
|
||||
s2_active = H.req(state["host_port"], "GET", SURFACES["counts"])[1]
|
||||
print(f" S2 1 active + 1 sold -> {json.dumps(s2_active)} (count mode: active)")
|
||||
restart_host(state, "highest", "0")
|
||||
os.environ["_"] = "_" # no-op; count mode is a host env, set below
|
||||
state["host"].terminate(); state["host"].wait(timeout=20)
|
||||
state["host"] = H.start_host(tmp, state["host_port"], state["core_port"],
|
||||
state["host_log"], "highest",
|
||||
coins_processed="0",
|
||||
count_mode="active_plus_sold")
|
||||
s2_member = H.req(state["host_port"], "GET", SURFACES["counts"])[1]
|
||||
print(f" S2 1 active + 1 sold -> {json.dumps(s2_member)} (count mode: membership)")
|
||||
evidence["counts_states"] = {
|
||||
"S1_zero_active_one_sold": s1,
|
||||
"S2_one_active_one_sold_count_active": s2_active,
|
||||
"S2_one_active_one_sold_count_membership": s2_member,
|
||||
}
|
||||
check("S1 reports sold 1 / selling 0", s1.get("sold") == 1 and s1.get("selling") == 0)
|
||||
check("S2 reports sold 1 / selling 1",
|
||||
s2_active.get("sold") == 1 and s2_active.get("selling") == 1)
|
||||
check("S2 count differs by mode (1 vs 2) — the open question for the client",
|
||||
s2_active.get("count") == 1 and s2_member.get("count") == 2,
|
||||
f"{s2_active.get('count')} vs {s2_member.get('count')}")
|
||||
|
||||
# ---- emit evidence --------------------------------------------------
|
||||
banner("EVIDENCE")
|
||||
for name, v in evidence["variants"].items():
|
||||
path = os.path.join(args.out, f"{name}.json")
|
||||
with open(path, "w") as f:
|
||||
json.dump(v, f, indent=2, sort_keys=True)
|
||||
print(f" {os.path.relpath(path, REPO)} "
|
||||
f"tradePile sha256={v['surfaces']['tradePile']['sha256'][:16]}…")
|
||||
idx = os.path.join(args.out, "differential.json")
|
||||
with open(idx, "w") as f:
|
||||
json.dump(evidence, f, indent=2, sort_keys=True)
|
||||
print(f" {os.path.relpath(idx, REPO)}")
|
||||
|
||||
banner("PRODUCTION UNTOUCHED")
|
||||
alive = subprocess.run(["ps", "-o", "pid=", "-p", "3631953"],
|
||||
capture_output=True, text=True).stdout.strip()
|
||||
check("prod-host pid 3631953 alive", alive == "3631953", alive or "gone")
|
||||
|
||||
banner("RESULT")
|
||||
passed = sum(1 for _, ok, _ in results if ok)
|
||||
failed = [l for l, ok, _ in results if not ok]
|
||||
print(f" {passed}/{len(results)} checks passed")
|
||||
if failed:
|
||||
print(" FAILED: " + "; ".join(failed))
|
||||
print("\n A/B IS CONTAMINATED — do NOT run the live experiment")
|
||||
return 1
|
||||
print("\n A/B IS CLEAN — one-field attribution proven; ready for the live client")
|
||||
return 0
|
||||
finally:
|
||||
for k in ("host", "core"):
|
||||
p = state.get(k)
|
||||
if p:
|
||||
try:
|
||||
p.terminate(); p.wait(timeout=15)
|
||||
except Exception:
|
||||
try:
|
||||
p.kill()
|
||||
except Exception:
|
||||
pass
|
||||
if args.keep:
|
||||
print(f"\n kept {tmp}")
|
||||
else:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Switch the FIFA client's Blaze ports between PRODUCTION and STAGING, reversibly.
|
||||
|
||||
The staging sold experiment needs the client to talk to the staging Blaze (which
|
||||
advertises the staging UTAS base). The ONLY client change required is the two Blaze
|
||||
port lines in `openfut.cfg`; `host` and `https_port` are left alone.
|
||||
|
||||
Safety properties, in order of importance:
|
||||
|
||||
* The production values are recorded to a sidecar file on the client BEFORE the
|
||||
first edit, and `restore` reads that sidecar rather than assuming what
|
||||
production was. If the sidecar is missing, restore refuses.
|
||||
* `restore` is idempotent and safe to run at any time, including after a crash.
|
||||
* Every operation re-reads the file afterwards and prints it, so the result is
|
||||
verified rather than assumed.
|
||||
* It refuses to edit while a FIFA client is running: the hook reads this file at
|
||||
connect time, so changing it under a live session is unsafe.
|
||||
* Only the two known keys are rewritten. Unknown lines are passed through
|
||||
untouched, and a missing key is an error rather than a silent append.
|
||||
|
||||
python3 scripts/sold-client-ports.py show
|
||||
python3 scripts/sold-client-ports.py staging # 42327 / 42330
|
||||
python3 scripts/sold-client-ports.py restore # back to the recorded values
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
CLIENT = "alex@10.10.0.105"
|
||||
CFG = "/mnt/games/FIFA 17/openfut.cfg"
|
||||
SIDECAR = "/mnt/games/FIFA 17/openfut.cfg.openfut-prod-ports"
|
||||
KEYS = ("blaze_redirector_port", "blaze_main_port")
|
||||
STAGING = {"blaze_redirector_port": "42327", "blaze_main_port": "42330"}
|
||||
|
||||
|
||||
def ssh(script):
|
||||
r = subprocess.run(["ssh", "-o", "BatchMode=yes", CLIENT, script],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
if r.returncode != 0:
|
||||
raise SystemExit(f"ssh failed ({r.returncode}): {r.stderr.strip()}")
|
||||
return r.stdout
|
||||
|
||||
|
||||
def read_cfg():
|
||||
return ssh(f'cat "{CFG}"')
|
||||
|
||||
|
||||
def parse(text):
|
||||
out = {}
|
||||
for line in text.splitlines():
|
||||
if "=" in line and not line.strip().startswith("#"):
|
||||
k, _, v = line.partition("=")
|
||||
out[k.strip()] = v.strip()
|
||||
return out
|
||||
|
||||
|
||||
def fifa_running():
|
||||
return bool(ssh("pgrep -f FIFA17.exe || true").strip())
|
||||
|
||||
|
||||
def show():
|
||||
text = read_cfg()
|
||||
print(f"--- {CFG}")
|
||||
print(text.rstrip())
|
||||
cur = parse(text)
|
||||
print("--- blaze ports:", {k: cur.get(k) for k in KEYS})
|
||||
side = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip()
|
||||
print("--- recorded production ports:", side or "(none recorded yet)")
|
||||
print("--- FIFA client running:", "YES" if fifa_running() else "no")
|
||||
return cur
|
||||
|
||||
|
||||
def write_ports(values, label):
|
||||
if fifa_running():
|
||||
raise SystemExit(
|
||||
"REFUSING to edit openfut.cfg while a FIFA client is running.\n"
|
||||
"The hook reads this file at connect time; exit FIFA first."
|
||||
)
|
||||
text = read_cfg()
|
||||
cur = parse(text)
|
||||
for k in KEYS:
|
||||
if k not in cur:
|
||||
raise SystemExit(f"key {k!r} is absent from {CFG}; refusing to guess it")
|
||||
# Record production values once, before the first mutation, so restore never
|
||||
# has to assume what they were.
|
||||
existing_sidecar = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip()
|
||||
if not existing_sidecar and label == "staging":
|
||||
rec = "\n".join(f"{k}={cur[k]}" for k in KEYS)
|
||||
ssh(f'cat > "{SIDECAR}" <<\'EOF\'\n{rec}\nEOF')
|
||||
print(f"recorded production ports to {SIDECAR}:\n{rec}")
|
||||
|
||||
out = []
|
||||
for line in text.splitlines():
|
||||
k = line.partition("=")[0].strip()
|
||||
out.append(f"{k}={values[k]}" if k in values else line)
|
||||
body = "\n".join(out) + "\n"
|
||||
ssh(f'cat > "{CFG}" <<\'EOF\'\n{body.rstrip()}\nEOF')
|
||||
after = parse(read_cfg())
|
||||
for k, v in values.items():
|
||||
if after.get(k) != v:
|
||||
raise SystemExit(f"VERIFY FAILED: {k} is {after.get(k)!r}, expected {v!r}")
|
||||
print(f"--- now pointing at {label}")
|
||||
print(read_cfg().rstrip())
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2 or sys.argv[1] not in ("show", "staging", "restore"):
|
||||
print(__doc__)
|
||||
return 2
|
||||
cmd = sys.argv[1]
|
||||
if cmd == "show":
|
||||
show()
|
||||
return 0
|
||||
if cmd == "staging":
|
||||
write_ports(STAGING, "staging")
|
||||
return 0
|
||||
side = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip()
|
||||
if not side:
|
||||
raise SystemExit(
|
||||
f"no recorded production ports at {SIDECAR}; refusing to guess.\n"
|
||||
"Set them by hand and verify with `show`."
|
||||
)
|
||||
write_ports(parse(side), "production (restored)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -299,8 +299,11 @@ def main():
|
||||
|
||||
banner("VARIANT B — same state, restart host with closed / buyNow")
|
||||
host.terminate(); host.wait(timeout=20); procs.remove(host)
|
||||
# coinsProcessed is held CONSTANT at 0 here: the primary A/B must be
|
||||
# attributable to bidState alone. The orthogonal coinsProcessed test is a
|
||||
# separate variant pair (see scripts/sold-ab-differential.py).
|
||||
host = start_host(tmp, host_port, core_port, host_log, "buyNow",
|
||||
coins_processed="1", count_mode="active_plus_sold")
|
||||
coins_processed="0", count_mode="active_plus_sold")
|
||||
procs.append(host)
|
||||
st, pileb = req(host_port, "GET", "/ut/game/fifa17/tradePile")
|
||||
st2, countsb = req(host_port, "GET", "/ut/game/fifa17/tradePile/counts")
|
||||
@@ -308,13 +311,13 @@ def main():
|
||||
print(f" bidState={b_row['bidState']} coinsProcessed={b_row['coinsProcessed']} "
|
||||
f"counts={json.dumps(countsb)}")
|
||||
check("bidState buyNow (variant B)", b_row.get("bidState") == "buyNow")
|
||||
check("coinsProcessed 1 when asked", b_row.get("coinsProcessed") == 1)
|
||||
check("coinsProcessed held constant at 0", b_row.get("coinsProcessed") == 0)
|
||||
check("count_mode active_plus_sold counts the sold row",
|
||||
countsb.get("count") == 1 and countsb.get("sold") == 1,
|
||||
json.dumps(countsb))
|
||||
differing = sorted(k for k in a_row if a_row.get(k) != b_row.get(k))
|
||||
check("A/B differ ONLY in bidState and coinsProcessed",
|
||||
differing == ["bidState", "coinsProcessed"], str(differing))
|
||||
check("A/B differ ONLY in bidState (one-field attribution)",
|
||||
differing == ["bidState"], str(differing))
|
||||
|
||||
banner("CLEAR — the PE-proven bulk verb DELETE .../trade/sold")
|
||||
pre_coins = owner_of(core_db, ITEM)[2]
|
||||
|
||||
Reference in New Issue
Block a user