feat(fifa17): gated kits-only club?type=equippables, to test the locked-kit cause

A live kit_trace run on the retail client showed the kit clone driver only ever
sees PLAYERS - about 19 records, all cardtype 1 / itemState 1 / +0x60 = 1 - and
never a kit, with no KIT_DBCLONE line at all. So the locked-kit failure is
UPSTREAM of the item+0x60 == 4 gate that this arm's comment blamed.

In the same session the client asked for ?type=kit six times, which we answer
and which feeds the items browser, and ?type=equippables twice, which we answer
empty. If the equippable view is what populates the collection FUN_1800d73d0
scans to build the active-kit triple, an empty answer explains precisely why the
triple stays zero and the engine falls back to its own catalogue kit.

The arm now selects ContentKind::Kit behind OPENFUT_FIFA17_EQUIPPABLES=1, so it
answers with TWO items rather than the thirty across five families that crashed
the client on 2026-08-05. Default OFF: that crash is real and reproducible, and
the narrow body is a hypothesis under test rather than an established safe
response. Reverting is an env change plus a restart, no rebuild.
This commit is contained in:
funman300
2026-08-23 17:38:32 +00:00
parent e18304d365
commit 11b7991d81
+36 -6
View File
@@ -2316,12 +2316,30 @@ fn club_type_filter(token: Option<&str>) -> Option<ClubTypeFilter> {
label: "equippables",
// The combined customisation view, and the one response that has ever
// crashed this client: 30 items across five families at once
// (2026-08-05). A single-family answer here is not available either —
// the view is by definition multi-family — and the kit swap it feeds
// needs `item+0x60 == 4`, which a server can never produce (it can
// only ever produce 1 = club and 6 = purchased). So this stays empty
// until that is a client-side change, not a wire one.
selector: ClubSelector::Withheld("multi_family_crash_2026_08_05"),
// (2026-08-05).
//
// 2026-08-24: this is also the prime suspect for the LOCKED kit. A
// live `kit_trace` run showed the kit clone driver only ever sees
// PLAYERS (~19 records, cardtype 1 / itemState 1 / `+0x60` 1) and
// never a kit, with no `KIT_DBCLONE` at all — so the failure is
// upstream of the `item+0x60 == 4` gate. In the same session the
// client asked for `?type=kit` (6x, which we answer and which feeds
// the items BROWSER) and `?type=equippables` (2x, which we answer
// empty). If the equippable view is what populates the collection
// `FUN_1800d73d0` scans for the active-kit triple, an empty answer
// is exactly why the triple stays zero and the engine falls back to
// its own catalogue kit.
//
// So the arm is now selectable behind `OPENFUT_FIFA17_EQUIPPABLES=1`
// and answers with KITS ONLY — two items, not the thirty across five
// families that crashed the client. Default OFF: the crash is real
// and reproducible, and this narrower body is a hypothesis under
// test, not an established safe response.
selector: if equippables_enabled() {
ClubSelector::Kind(ContentKind::Kit)
} else {
ClubSelector::Withheld("multi_family_crash_2026_08_05")
},
},
Some("leaguelogos") => ClubTypeFilter {
label: "leaguelogos",
@@ -5571,6 +5589,18 @@ fn discard_table_enabled() -> bool {
*ENABLED.get_or_init(|| std::env::var("OPENFUT_FIFA17_DISCARD_TABLE").as_deref() == Ok("1"))
}
/// Answer `club?type=equippables` with a KITS-ONLY body.
///
/// Default OFF. The multi-family form of this response crashed the client on
/// 2026-08-05, so serving it at all is an experiment: the narrow two-kit body is
/// a hypothesis about why the pre-match kit selector reports every kit locked,
/// not a response we have established as safe. Flip the env var off to revert
/// with a restart and no rebuild.
fn equippables_enabled() -> bool {
static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ENABLED.get_or_init(|| std::env::var("OPENFUT_FIFA17_EQUIPPABLES").as_deref() == Ok("1"))
}
/// A JSON response with an explicit status.
fn json_status(status: u16, v: &Value) -> WireResponse {
let body = serde_json::to_vec(v).unwrap_or_default();