fifa17-recon: transfer market buy/bid flow (stateful, tested)

trade_route now resolves the auction from its tradeId and, on buy-now
(bid >= buyNowPrice), spends coins + grants the won card to the club + echoes
the CLOSED auction (FutISOfferTrade shape). Reuses the validated auction record
(0x18013e410) so it stays freeze-safe; whether FIFA surfaces the won item
post-buy is functional (needs live test). Insufficient funds -> 461.

tools/test_market_buy.py: offline unit test on a TEMP profile (never touches the
real save) -- verifies coin deduction, card grant, closed-auction shape, and the
461 path. PASS. Read-only contract suite still 311/311.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
This commit is contained in:
funman300
2026-08-02 20:17:52 -07:00
parent b05ccd7ce5
commit f38231d89d
2 changed files with 113 additions and 4 deletions
+39 -4
View File
@@ -365,6 +365,14 @@ def _market_auctions(limit=21):
for i in range(min(limit, len(PACK_POOL)))]
def _auction_by_tradeid(tid):
# tradeId space is _TRADE_ID_BASE + index into PACK_POOL -> reconstruct the auction.
i = tid - _TRADE_ID_BASE
if 0 <= i < len(PACK_POOL):
return _auction_record(i, PACK_POOL[i])
return None
def _market_body(auctions):
return {"auctionInfo": auctions, "credits": STORE.coins(),
"total": len(auctions), "duplicateItemIdList": []}
@@ -385,10 +393,37 @@ def auctionhouse_route(h):
def trade_route(h):
# GET view one auction / POST place bid -> {auctionInfo:[record], credits}.
# Echo a sample record so a viewed/bid auction resolves.
rec = _market_auctions(1)
return 200, {"auctionInfo": rec, "credits": STORE.coins()}
# GET view one auction -> {auctionInfo:[record], credits}
# POST/PUT place bid / buy-now -> stateful: on buy-now (bid >= buyNowPrice) deduct
# coins, grant the won card to the club, echo the CLOSED auction. Reuses the
# validated auction-record shape (0x18013e410) so it's freeze-safe; whether FIFA
# surfaces the won item post-buy is functional (needs live test). FutISOfferTrade.
m = re.search(r"/trade/(\d+)", h.path)
tid = int(m.group(1)) if m else -1
rec = _auction_by_tradeid(tid)
if h.command in ("POST", "PUT"):
try:
body = json.loads(h._body.decode()) if getattr(h, "_body", b"") else {}
except Exception:
body = {}
if rec is None:
return 200, {"auctionInfo": [], "credits": STORE.coins()}
bid = body.get("bid") or rec["buyNowPrice"]
if bid >= rec["buyNowPrice"]: # BUY NOW
if not STORE.spend(rec["buyNowPrice"]):
return 461, {"reason": "insufficient_coins", "credits": STORE.coins()}
won = dict(rec["itemData"]); won.pop("id", None)
won["itemState"] = "free" # unassigned/won
granted = STORE.add_items([won])[0]
rec = dict(rec)
rec.update({"tradeState": "closed", "bidState": "highest",
"currentBid": rec["buyNowPrice"], "itemData": granted})
log(" MARKET: bought tradeId %d for %d, coins=%d, card->club"
% (tid, rec["currentBid"], STORE.coins()))
else: # simple bid (we're sole bidder)
rec = dict(rec); rec.update({"currentBid": bid, "bidState": "highest"})
return 200, {"auctionInfo": [rec], "credits": STORE.coins()}
return 200, {"auctionInfo": [rec] if rec else [], "credits": STORE.coins()}
def tradepile_route(h):