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:
funman300
2026-08-18 02:22:12 +00:00
parent 468bc0fba9
commit aa5fb2cc40
9 changed files with 2324 additions and 4 deletions
@@ -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
}
}
}
+114
View File
@@ -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
}
}
}
+114
View File
@@ -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)"
}
+375
View File
@@ -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())
+127
View File
@@ -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())
+7 -4
View File
@@ -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]