fifa17-recon: club items crashed the client -- unestablished fields, and too wide a blast radius
The game hung and then crashed on the first equippables fetch. My fault twice over.
CAUSE, primary. I copied teamid, leagueid and value straight out of the fcc row as
extras. `value` appears elsewhere as an OBJECT member (displayGroup {"value": ...}),
and a scalar where an object is expected is the type-desync busy loop at 0x1801c7f1a
-- which presents exactly as "the game is taking its time" and then dies. Omission is
safe; an unestablished field is not. That is this project's own rule and I broke it
for three fields that were not needed to draw a card. All three are gone.
CAUSE, contributing. The last request before the crash was type=equippables&count=11
and we answered with 30 items spanning FIVE unverified cardsubtypeids at once: the
widest possible blast radius for a wrong shape, and it tells you nothing about which
subtype was wrong. equippables now answers [] until the subtypes are confirmed one
family at a time, and shelf() takes a families= filter so a test can serve exactly one.
Adds FUT_CLUBITEMS=probe:<family>, which serves one item per candidate subtype for a
single family, so the screen names the correct subtype instead of me guessing a third
time. Eight items, one family, one question.
The flag already defaulted off, so a plain restart cannot serve any of this.
439 + 414 checks green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
This commit is contained in:
@@ -92,21 +92,34 @@ def _item(item_id, carddbid, cardassetid, subtype, extra=None):
|
|||||||
return it
|
return it
|
||||||
|
|
||||||
|
|
||||||
def shelf(next_id=CLUBITEM_ID_BASE):
|
def shelf(next_id=CLUBITEM_ID_BASE, families=None):
|
||||||
"""The starter club-item shelf, {family: [item]}."""
|
"""The starter club-item shelf, {family: [item]}.
|
||||||
|
|
||||||
|
`families` limits which are built. The combined `equippables` view is what
|
||||||
|
crashed the client: 30 items across FIVE unverified subtypes in one response is
|
||||||
|
the widest possible blast radius for a wrong shape. One family at a time is the
|
||||||
|
only way to learn which subtype is wrong.
|
||||||
|
"""
|
||||||
out, nid = {}, next_id
|
out, nid = {}, next_id
|
||||||
for name, table, art, _sid, _sname, subtype in FAMILIES:
|
for name, table, art, _sid, _sname, subtype in FAMILIES:
|
||||||
|
if families is not None and name not in families:
|
||||||
|
out[name] = []
|
||||||
|
continue
|
||||||
rows = _rows(table)
|
rows = _rows(table)
|
||||||
picked = []
|
picked = []
|
||||||
for r in rows[:STARTER_N.get(name, 4)]:
|
for r in rows[:STARTER_N.get(name, 4)]:
|
||||||
cid = r.get("carddbid")
|
cid = r.get("carddbid")
|
||||||
if not cid:
|
if not cid:
|
||||||
continue
|
continue
|
||||||
extra = {}
|
# NO EXTRAS. An earlier version copied teamid/leagueid/value straight
|
||||||
for k in ("teamid", "leagueid", "value"):
|
# out of the fcc row and the game hung and then CRASHED on the first
|
||||||
if r.get(k):
|
# equippables fetch (2026-08-05). `value` is the prime suspect: it
|
||||||
extra[k] = r[k]
|
# appears elsewhere as an OBJECT member (displayGroup {"value": ...}),
|
||||||
picked.append(_item(nid, cid, r.get("cardassetid", art), subtype, extra))
|
# and a scalar where an object is expected is the type-desync busy loop
|
||||||
|
# at 0x1801c7f1a, which reads exactly like "the game is taking its time"
|
||||||
|
# and then dies. Omission is safe; an unestablished field is not. None of
|
||||||
|
# the three was needed to draw a card.
|
||||||
|
picked.append(_item(nid, cid, r.get("cardassetid", art), subtype))
|
||||||
nid += 1
|
nid += 1
|
||||||
out[name] = picked
|
out[name] = picked
|
||||||
return out
|
return out
|
||||||
|
|||||||
@@ -1204,7 +1204,10 @@ CONSUM_UNTRADEABLE = os.environ.get("FUT_CONSUM_UNTRADEABLE", "0") == "1"
|
|||||||
#
|
#
|
||||||
# Default OFF: nothing here has ever been requested by the client, so unlike the
|
# Default OFF: nothing here has ever been requested by the client, so unlike the
|
||||||
# consumables stat fix this is not correcting a demonstrably wrong answer.
|
# consumables stat fix this is not correcting a demonstrably wrong answer.
|
||||||
CLUBITEMS = os.environ.get("FUT_CLUBITEMS", "0") == "1"
|
# "1" serves each family on its own type= arm and serves NOTHING for equippables.
|
||||||
|
# "probe:<family>" serves one item per candidate cardsubtypeid for that family only.
|
||||||
|
_CLUBITEMS_MODE = os.environ.get("FUT_CLUBITEMS", "")
|
||||||
|
CLUBITEMS = bool(_CLUBITEMS_MODE) and _CLUBITEMS_MODE != "0"
|
||||||
|
|
||||||
|
|
||||||
def _consumable_stat_rows():
|
def _consumable_stat_rows():
|
||||||
@@ -1809,13 +1812,23 @@ def club_route(h):
|
|||||||
if CLUBITEMS and kind in ("stadium", "ball", "badge", "kit", "leaguelogos",
|
if CLUBITEMS and kind in ("stadium", "ball", "badge", "kit", "leaguelogos",
|
||||||
"equippables"):
|
"equippables"):
|
||||||
import fut_clubitems
|
import fut_clubitems
|
||||||
sh = fut_clubitems.shelf()
|
|
||||||
fam = {"stadium": "stadia", "ball": "balls", "badge": "badges",
|
fam = {"stadium": "stadia", "ball": "balls", "badge": "badges",
|
||||||
"kit": "kits", "leaguelogos": "leaguelogos"}.get(kind)
|
"kit": "kits", "leaguelogos": "leaguelogos"}.get(kind)
|
||||||
if fam:
|
if _CLUBITEMS_MODE.startswith("probe:"):
|
||||||
got = sh.get(fam, [])
|
want = _CLUBITEMS_MODE.split(":", 1)[1]
|
||||||
else:
|
got = fut_clubitems.probe_shelf(want) if fam == want else []
|
||||||
got = [i for fam_items in sh.values() for i in fam_items]
|
log(" CLUBITEMS: PROBE %s on type=%s -> %d item(s), one per candidate "
|
||||||
|
"subtype" % (want, kind, len(got)))
|
||||||
|
return 200, {"itemData": got}
|
||||||
|
if fam is None:
|
||||||
|
# equippables is the COMBINED view and it is what crashed the client on
|
||||||
|
# 2026-08-05: 30 items across five unverified subtypes in one response.
|
||||||
|
# Until the subtypes are confirmed one family at a time, answer it empty.
|
||||||
|
# An empty itemData is a shape the client already accepts everywhere.
|
||||||
|
log(" CLUBITEMS: type=equippables -> [] (combined view withheld until "
|
||||||
|
"the subtypes are verified; it crashed the client once)")
|
||||||
|
return 200, {"itemData": []}
|
||||||
|
got = fut_clubitems.shelf(families={fam}).get(fam, [])
|
||||||
log(" CLUBITEMS: type=%s -> %d item(s)" % (kind, len(got)))
|
log(" CLUBITEMS: type=%s -> %d item(s)" % (kind, len(got)))
|
||||||
return 200, {"itemData": got}
|
return 200, {"itemData": got}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user