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.
This commit is contained in:
funman300
2026-08-24 21:38:33 +00:00
parent ead0426ea0
commit f315f16e8e
2 changed files with 63 additions and 22 deletions
+62 -21
View File
@@ -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<i64, ConfigError> {
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(),
+1 -1
View File
@@ -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(