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(