host: answer club?type=equippables by default — kits now render in My Club
OPERATOR-CONFIRMED on the retail client: the My Club kit tab shows both kits, the
first time kits have ever rendered in this project.
The tab asks for `?type=equippables`, and that arm was withheld, so the screen got
an empty body and showed nothing. That is exactly what "0 kits in my club" was —
not a shaping problem, a refused question. Log line that identified it:
route=club outcome=withheld filter=[type=equippables,
reason=multi_family_crash_2026_08_05] total=0 emitted=0
TWO fixes were both required, so neither alone is the cause:
* this arm answers (KITS ONLY), and
* GET ut/%s/item (FutViewCards) returns the OWNED INSTANCE rather than a
definition placeholder, so the kit arrives as cardsubtypeid 9 /
itemState active{Home,Away}Kit instead of "a free player" (d4a39ba).
The 2026-08-05 crash that motivated the withholding was 30 items across FIVE
families. This arm serves one family, and the live body is two items. Serving the
other four families here is untested and stays unserved.
Default flips from opt-in to opt-out: OPENFUT_FIFA17_EQUIPPABLES=0 restores the
withheld body with a restart and no rebuild. The switch is kept rather than
deleted because the only live evidence is a club holding exactly TWO kits; a club
holding many is untested and the original crash was about size and family mixing.
Verified with no env flag set: ?type=equippables -> 2 items, family {9}.
cargo test -p openfut-utas-host: 190 passed, 0 failed. clippy -D warnings clean.
Production untouched — staging only.
This commit is contained in:
@@ -2339,34 +2339,38 @@ fn club_type_filter(token: Option<&str>) -> Option<ClubTypeFilter> {
|
||||
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<bool> = 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<bool> = 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.
|
||||
|
||||
Reference in New Issue
Block a user