feat(utas-host): serve GET /squad/active from Core

Migrate the active-squad READ off the Python oracle to the existing Core-backed projector, completing the squad authority (read + write + /squad/list + userMassInfo overlay) on one projector.

- classify: GET /ut/game/<t>/squad/active -> Route::SquadActive. Numeric GET /squad/<n> stays on Python (no Core multi-squad model yet).
- handle_squad_active returns the projector object via user_mass_info_squad(v, persona) — byte-identical to userMassInfo.squad; degrades to an empty overlay on stale/missing/Core-error, never falls back to Python.
- persona: new REQUIRED OPENFUT_PERSONA_ID (non-zero) on HostConfig, injected not baked, must match LSX/Blaze/POW/UTAS identity.
- tests: squad_active parity test; classify updated; README config table + A/B command.

fmt + clippy -D warnings + tests (24 host + adapter) green.
This commit is contained in:
funman300
2026-08-12 17:10:04 +00:00
parent 7dbd878398
commit 37c2e5d7ee
5 changed files with 148 additions and 18 deletions
+18
View File
@@ -23,6 +23,10 @@ pub struct HostConfig {
/// id). Required: the wire `id` of every owned item is allocated/resolved
/// here so it survives restart and reverses exactly.
pub identity_store_path: String,
/// The launcher-selected FIFA persona id, injected via `OPENFUT_PERSONA_ID`.
/// Required, non-zero: it stamps `personaId` on the Core-backed
/// `GET /squad/active`, and must match the persona LSX/Blaze/POW/UTAS use.
pub persona_id: i64,
}
#[derive(Debug)]
@@ -42,6 +46,19 @@ fn required(key: &str) -> Result<String, ConfigError> {
}
}
/// Parse a required, non-zero i64 env var. A zero identity id is invalid (the
/// client rejects a zero persona), so unset/empty/non-integer/zero is a hard error.
fn required_i64_nonzero(key: &str) -> Result<i64, ConfigError> {
let raw = required(key)?;
let val: i64 = raw
.parse()
.map_err(|_| ConfigError(format!("{key} must be an integer, got {raw:?}")))?;
if val == 0 {
return Err(ConfigError(format!("{key} must be non-zero")));
}
Ok(val)
}
impl HostConfig {
pub fn from_env() -> Result<Self, ConfigError> {
Ok(HostConfig {
@@ -53,6 +70,7 @@ impl HostConfig {
.unwrap_or_else(|_| "fifa17-recon/data/tables".into()),
catalog_path: required("OPENFUT_FIFA17_CATALOG")?,
identity_store_path: required("OPENFUT_IDENTITY_STORE")?,
persona_id: required_i64_nonzero("OPENFUT_PERSONA_ID")?,
})
}
}