diff --git a/openfut-utas-host/src/lib.rs b/openfut-utas-host/src/lib.rs index 79a8af3..436e0ea 100644 --- a/openfut-utas-host/src/lib.rs +++ b/openfut-utas-host/src/lib.rs @@ -2339,34 +2339,38 @@ fn club_type_filter(token: Option<&str>) -> Option { Some("stadium") => ClubTypeFilter::kind("stadium", ContentKind::Stadium), Some("ball") => ClubTypeFilter::kind("ball", ContentKind::Ball), Some("misc") => ClubTypeFilter::kind("misc", ContentKind::Misc), - // WITHHELD, each for a recorded reason. Some("equippables") => 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). + // The combined club-customisation view, and the screen the My Club + // KITS tab actually asks for. It is ALSO the one response that has + // ever crashed this client — but the crash was 30 items across five + // families at once (2026-08-05), not this arm. // - // 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. + // RESOLVED 2026-08-23, operator-confirmed on the retail client: with + // this arm answering KITS ONLY, the My Club kit tab renders both kits + // for the first time. Before it, the tab asked `?type=equippables`, + // got the withheld empty body, and showed nothing — which is exactly + // what "0 kits in my club" was. // - // 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 { + // Two things had to be true together, so neither alone is the fix: + // * this arm answers, and + // * `GET ut/%s/item` (FutViewCards) returns the OWNED INSTANCE + // rather than a definition placeholder, so the kit arrives with + // `cardsubtypeid` 9 and `itemState` active{Home,Away}Kit instead + // of "a free player" (see [`UtasHost::handle_view_cards`]). + // + // Still KITS ONLY, deliberately. Serving the other four families here + // is the shape that crashed, and nothing has retested it. + // + // `OPENFUT_FIFA17_EQUIPPABLES=0` restores the withheld body with a + // restart and no rebuild, because the ONLY live evidence is a club + // holding exactly TWO kits. A club holding many is untested, and the + // crash that motivated the original withholding was about response + // size and family mixing. + selector: if equippables_disabled() { ClubSelector::Withheld("multi_family_crash_2026_08_05") + } else { + ClubSelector::Kind(ContentKind::Kit) }, }, Some("leaguelogos") => ClubTypeFilter { @@ -5717,16 +5721,15 @@ 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. +/// Withhold `club?type=equippables` again (`OPENFUT_FIFA17_EQUIPPABLES=0`). /// -/// 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")) +/// Default is now ON: the My Club kit tab requests this family, and answering it +/// with a KITS-ONLY body is what made both kits render on the retail client +/// (operator-confirmed 2026-08-23). This is the OFF switch, kept because the only +/// live evidence is a club holding exactly two kits. +fn equippables_disabled() -> bool { + static DISABLED: std::sync::OnceLock = std::sync::OnceLock::new(); + *DISABLED.get_or_init(|| std::env::var("OPENFUT_FIFA17_EQUIPPABLES").as_deref() == Ok("0")) } /// A JSON response with an explicit status.