diff --git a/openfut-utas-host/src/lib.rs b/openfut-utas-host/src/lib.rs index 532591f..c72278b 100644 --- a/openfut-utas-host/src/lib.rs +++ b/openfut-utas-host/src/lib.rs @@ -2316,12 +2316,30 @@ fn club_type_filter(token: Option<&str>) -> Option { 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 = 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();