From a2b0c32a70a20eb54b5623f0d2b49a45ed6c86ea Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 24 Aug 2026 22:35:28 +0000 Subject: [PATCH] docs(fifa17): numeric squad ids are real; our collapsing is safe, not authentic The numeric id in squad/ was being justified as "matching the oracle". That justification does not survive inspection, and the code now says why. FIFA 17 has genuine multi-squad semantics. The client's own shipped action table has SelectSquadById, RetrieveSquad as an action DISTINCT from LoadActiveSquad, CreateSquadWithName, RenameSquad, DeleteSquad, CopySquad, indexed SQUAD_ID-%d list entries and FUT_MAX_NUM_SQUAD_REACHED. The base template is `ut/%s/squad` with the id appended. The number identifies a squad. The inherited behaviour came from a bare prefix regex in the Python oracle - `re.compile(G + r"/squad")` calling squad_route(), which never reads the URL id (GET returns current_squad(), PUT echoes the id from the BODY). The comments around it show /squad/list and the draft routes had to be registered first because that rule was swallowing them. It was expedient, not evidence-driven. Collapsing the id is nonetheless SAFE today, and only for a specific reason: we advertise exactly one squad. ACTIVE_SQUAD_WIRE_ID is a constant 0, /squad/list returns a single-element array carrying it, and no create/rename/delete/copy route exists, so the client can only echo back the id we gave it. Every numeric path in retained captures is squad/0, all PUTs whose body id also reads 0; no numeric GET has ever been recorded. Behaviour is therefore UNCHANGED - no evidence justifies changing it, and unknown-id semantics are deliberately not invented. What changes is that the assumption is now explicit and enforced: numeric_squad_routing_is_safe_only_while_one_squad_is_advertised pins the wire id at 0 and /squad/list at one entry, and fails the moment a second squad becomes addressable. Verified by simulating a second advertised squad. Workspace 1252 passed (1251 + this test), 0 failed. --- openfut-utas-host/src/lib.rs | 36 ++++++++++++++++++--- openfut-utas-host/tests/host_test.rs | 47 ++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/openfut-utas-host/src/lib.rs b/openfut-utas-host/src/lib.rs index 41e5782..b2064e6 100644 --- a/openfut-utas-host/src/lib.rs +++ b/openfut-utas-host/src/lib.rs @@ -206,8 +206,8 @@ pub enum Route { /// Classify a request ONCE, before execution. Rust owns complete route families; /// there is no "try Rust then Python", so a mutation can never be double-applied. -/// Numeric `GET …/squad/` follows the oracle's single-current-squad behavior: -/// every numeric id returns the one Core-backed active squad. +/// Numeric `…/squad/` resolves to the one Core-backed squad. That is SAFE +/// rather than authentic — see [`is_numeric_squad_tail`]. pub fn classify(method: &str, path: &str) -> Route { let get = method.eq_ignore_ascii_case("GET"); let put = method.eq_ignore_ascii_case("PUT"); @@ -403,9 +403,35 @@ fn is_exact_club_path(path: &str) -> bool { ut_tail(path) == Some("club") } -/// `squad/` — the numeric full-squad target used by PUT and GET. Core -/// stores one current squad, matching the oracle: every numeric GET returns that -/// same squad regardless of the requested id. +/// `squad/` — the numeric full-squad target used by PUT and GET. +/// +/// Every numeric id resolves to the one Core-backed squad. Be precise about why +/// that is acceptable: it is SAFE, not authentic. +/// +/// FIFA 17 genuinely has multi-squad semantics. The client ships +/// `SelectSquadById`, `RetrieveSquad` as an action DISTINCT from +/// `LoadActiveSquad`, plus `CreateSquadWithName`, `RenameSquad`, `DeleteSquad`, +/// `CopySquad`, indexed `SQUAD_ID-%d` list entries and a +/// `FUT_MAX_NUM_SQUAD_REACHED` string. The number is therefore a real squad +/// identifier, not a placeholder. +/// +/// It is unreachable here because we advertise exactly ONE squad: +/// [`ACTIVE_SQUAD_WIRE_ID`] is a constant `0`, `/squad/list` returns a +/// single-element array carrying that id, and no create/rename/delete/copy +/// route exists. The client can only ever echo back the id we gave it — every +/// numeric path observed in retained captures is `squad/0`, all of them PUTs +/// whose body `id` also reads 0, and no numeric GET has ever been recorded. +/// +/// So collapsing the id costs nothing TODAY and is pinned by +/// `numeric_squad_routing_is_safe_only_while_one_squad_is_advertised`. The +/// moment a second squad becomes addressable, that test must fail and this +/// routing must gain a real lookup. Do NOT widen squad support without +/// revisiting it. +/// +/// Unknown-id behaviour is deliberately NOT invented: no FIFA 17 evidence shows +/// what the client expects for an id it was never given. (The FIFA 14 oracle +/// Impulsum14 returns an empty squad carrying that id, never the active one — +/// hypothesis only, not FIFA 17 truth.) fn is_numeric_squad_tail(tail: &str) -> bool { match tail.strip_prefix("squad/") { Some(id) => !id.is_empty() && id.bytes().all(|b| b.is_ascii_digit()), diff --git a/openfut-utas-host/tests/host_test.rs b/openfut-utas-host/tests/host_test.rs index 54fa3fb..51ae98a 100644 --- a/openfut-utas-host/tests/host_test.rs +++ b/openfut-utas-host/tests/host_test.rs @@ -1111,8 +1111,9 @@ fn classify_squad_and_usermassinfo_routes() { Route::Passthrough ); - // GET /squad/active and every numeric GET are the one Core-backed current - // squad, matching the oracle's single-squad response regardless of URL id. + // GET /squad/active and every numeric GET resolve to the one Core-backed + // squad. SAFE, not authentic — see is_numeric_squad_tail and the invariant + // test below. assert_eq!( classify("GET", "/ut/game/fifa17/squad/active"), Route::SquadActive @@ -2825,3 +2826,45 @@ fn no_shipped_training_card_exceeds_the_declared_ceiling() { } assert_eq!(rare, 6, "all 6 rare all-six cards must resolve"); } + +/// Collapsing every numeric `squad/` onto one squad is safe ONLY while +/// exactly one squad is advertised. This is the tripwire for that assumption. +/// +/// FIFA 17 has real multi-squad semantics — the client ships `SelectSquadById`, +/// `RetrieveSquad` (distinct from `LoadActiveSquad`), `CreateSquadWithName`, +/// `RenameSquad`, `DeleteSquad` and indexed `SQUAD_ID-%d` entries. We get away +/// with ignoring the id purely because the client can never learn another one: +/// the wire id is a constant 0 and `/squad/list` advertises a single squad. +/// +/// If either fact stops holding, the numeric route needs a real lookup and this +/// test must be the thing that says so. +#[test] +fn numeric_squad_routing_is_safe_only_while_one_squad_is_advertised() { + assert_eq!( + openfut_utas_host::ACTIVE_SQUAD_WIRE_ID, + 0, + "the single advertised squad id is what makes id-collapsing safe" + ); + + let projected = serde_json::json!({ + "id": openfut_utas_host::ACTIVE_SQUAD_WIRE_ID, + "squadName": "OpenFUT", + "formation": "f442", + "squadType": "REGULAR_SQUAD", + "rating": 90, + "chemistry": 52, + }); + let list = openfut_adapter_fifa17::fut::squad_projection::squad_list(&projected); + let squads = list["squad"].as_array().expect("squad list is an array"); + assert_eq!( + squads.len(), + 1, + "more than one advertised squad means the client can address a second \ + id, and every numeric GET/PUT collapsing onto one squad becomes wrong" + ); + assert_eq!( + squads[0]["id"], + openfut_utas_host::ACTIVE_SQUAD_WIRE_ID, + "the advertised id must be the one the client echoes back" + ); +}