From f315f16e8ead30d226847b59708ee95e43285429 Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 24 Aug 2026 21:38:33 +0000 Subject: [PATCH] feat(fifa17): emit squad.actives by default Serving the club's active home and away kit is normal FIFA 17 behaviour, not an experiment: it is what lets the client make the kits resident and render the pre-match selector. A correct deployment should not have to opt in, so the default is now ON and the environment variable survives only as a diagnostic off switch (OPENFUT_FIFA17_SQUAD_ACTIVES=0). The gate was added when a populated actives array was once seen to empty the squad. That justification no longer holds: - it never reproduced, and the feature is now proven end to end on a retail client - 29 resident nodes, both cardtype-7 kits in club slots 0 and 1 with itemState 101/102 and category 4, alongside 23/23 players and a resident manager, with the selector rendering correct distinct home and away kits; - it can no longer cause durable damage, because both squad write-back paths are guarded in Core (empty replacement refused; an absent manager field no longer read as "clear"). Scope audited before flipping: squad_actives() emits ONLY the home and away kit, each resolved from Core's active designations and required to be genuinely owned. Badge, ball and stadium are never emitted, so enabling this cannot surface an unproven active family - confirmed on the wire, where the emitted itemTypes are exactly {"kit"}. Env parsing moved into a pure parse_squad_actives() so the DEFAULT is testable rather than depending on process environment. Unrecognised values stay ON rather than silently disabling the feature. Verified on staging with the env var REMOVED from host.env entirely: the host logs squad_actives=true and serves both kits (assetId 14/15, states 101/102) with 23/23 players and the manager intact. --- openfut-utas-host/src/config.rs | 83 ++++++++++++++++++++++++--------- openfut-utas-host/src/lib.rs | 2 +- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/openfut-utas-host/src/config.rs b/openfut-utas-host/src/config.rs index 4847a49..91a1f58 100644 --- a/openfut-utas-host/src/config.rs +++ b/openfut-utas-host/src/config.rs @@ -59,21 +59,29 @@ pub struct HostConfig { /// Disabled by default. Non-off values require three explicit staging guards; /// see [`parse_sbc_post_commit_fault`]. pub sbc_post_commit_fault: SbcPostCommitFault, - /// Emit the club's active club items in `squad.actives`. **Disabled by - /// default.** + /// Emit the club's active club items in `squad.actives`. **Enabled by + /// default** — this is normal, correct FIFA 17 behaviour, not an experiment. /// - /// `actives` is the proven carrier that makes a kit resident (the squad - /// parser writes each element straight into a club-item slot), and with it - /// populated the pre-match kit selector works. But a non-empty array was - /// observed to cost the rest of the squad: the resident item map dropped from - /// 24 entries to just the 2 kits, the 23-slot player vector came back fully - /// null, and the starting-11 screen was empty. `actives` sorts first in the - /// object, so everything after it — `players`, `manager`, `captain`, - /// `formation` — is lost. + /// `actives` is the carrier that makes a club item resident: the squad + /// parser writes each element straight into a native club-item slot. With it + /// populated the pre-match kit selector works, proven end to end on a retail + /// client — 29 resident nodes with both cardtype-7 kits in club slots 0 and 1 + /// (`itemState` 101/102, category 4) alongside 23/23 players and the manager, + /// and the selector rendering the correct distinct home and away kits. /// - /// Off until that interaction is understood: an empty squad is far worse than - /// a missing kit. Env `OPENFUT_FIFA17_SQUAD_ACTIVES=1` to experiment on - /// staging. + /// Scope is deliberately narrow: [`squad_actives`] emits ONLY the home and + /// away kit, both resolved from Core's own active designations and required + /// to be genuinely owned. Badge, ball and stadium are never emitted, so + /// enabling this cannot surface an unproven active family. + /// + /// History, so the default is not naively flipped back: an early build was + /// once seen to empty the squad when this array was populated (resident map + /// down to the 2 kits, player vector fully null). That never reproduced, and + /// it can no longer cause durable damage — both squad write-back paths are + /// guarded in Core (`refuse to empty a populated squad`, and an absent + /// manager field no longer meaning "clear"). + /// + /// Set `OPENFUT_FIFA17_SQUAD_ACTIVES=0` to force it off for diagnostics. pub squad_actives: bool, } @@ -107,6 +115,17 @@ fn required_i64_nonzero(key: &str) -> Result { Ok(val) } +/// Whether to emit `squad.actives`. Correct FIFA 17 behaviour is ON, so an +/// absent or unrecognised value means ON; only an explicit `0`/`false`/`off`/`no` +/// turns it off, which exists for diagnostics. See +/// [`HostConfig::squad_actives`]. +fn parse_squad_actives(raw: Option<&str>) -> bool { + !matches!( + raw.unwrap_or_default().trim().to_ascii_lowercase().as_str(), + "0" | "false" | "off" | "no" + ) +} + fn parse_sbc_post_commit_fault( raw: Option<&str>, environment: Option<&str>, @@ -193,14 +212,8 @@ impl HostConfig { clientdata_path, account_path, sbc_post_commit_fault, - // Default OFF: see the field docs. Only an explicit 1/true/on enables it. - squad_actives: matches!( - env::var("OPENFUT_FIFA17_SQUAD_ACTIVES") - .unwrap_or_default() - .trim() - .to_ascii_lowercase() - .as_str(), - "1" | "true" | "on" | "yes" + squad_actives: parse_squad_actives( + env::var("OPENFUT_FIFA17_SQUAD_ACTIVES").ok().as_deref(), ), }) } @@ -231,6 +244,34 @@ mod tests { use super::*; #[test] + /// `squad.actives` is ON without any environment variable. + /// + /// Emitting the club's active home/away kit is normal FIFA 17 behaviour — + /// it is what lets the client make the kits resident and render the + /// pre-match selector — so a correct deployment must not have to opt in. + /// The off switch survives only as a diagnostic. + #[test] + fn squad_actives_is_on_by_default_and_only_explicitly_disabled() { + // The case that matters: nothing configured at all. + assert!( + parse_squad_actives(None), + "a deployment that sets nothing must still emit squad.actives" + ); + assert!(parse_squad_actives(Some(""))); + assert!(parse_squad_actives(Some(" "))); + + // Explicit disable, for diagnostics. + for off in ["0", "false", "off", "no", "OFF", " False "] { + assert!(!parse_squad_actives(Some(off)), "{off} must disable"); + } + + // Explicit enable stays valid, and anything unrecognised stays ON + // rather than silently disabling the feature. + for on in ["1", "true", "on", "yes", "TRUE", "banana"] { + assert!(parse_squad_actives(Some(on)), "{on} must leave it enabled"); + } + } + fn sbc_post_commit_faults_require_all_staging_guards() { assert_eq!( parse_sbc_post_commit_fault(None, None, None, "0.0.0.0:8099").unwrap(), diff --git a/openfut-utas-host/src/lib.rs b/openfut-utas-host/src/lib.rs index 2970f09..5e004da 100644 --- a/openfut-utas-host/src/lib.rs +++ b/openfut-utas-host/src/lib.rs @@ -3689,7 +3689,7 @@ impl Server { cfg.sbc_post_commit_fault ); eprintln!( - "utas-host squad_actives={} (OFF keeps squad.actives empty; ON makes kits resident but has been seen to empty the squad)", + "utas-host squad_actives={} (ON emits the club's active home/away kit so the client can make them resident; set OPENFUT_FIFA17_SQUAD_ACTIVES=0 to disable)", cfg.squad_actives ); Ok(Server::new(