fix(fifa17): project active club items through squad.actives
FIFA 17 makes a club item resident ONLY through squad.actives. The squad
parser's arm for atom 11 computes the address of the i-th element of the
client's five-element club-item array and hands it to the item
deserializer as the out-handle:
cmp edi,0x5 ; at most five entries are read
mov rax,QWORD PTR [r13+0x108] ; the club-item array
lea rcx,[rax+rcx*8] ; &array[edi]
call 0x18013fe00 ; item deserializer, writing that slot
That deserializer inserts the record into the client's resident item map
- keyed by wire instance id, gated only on the id being non-zero - and
binds the slot handle to it. So each element must be a full item object
like squad.manager[].itemData; an id reference alone installs nothing,
because the manager installer looks its id up in that same map and does
nothing on a miss.
We emitted actives: [] as an 'observed constant', which was circular: it
came from our own captures and the Python oracle seeded it. The client
then read the array-end token immediately, parsed nothing, and left all
five slots null, so every lookup resolved to the static not-found
sentinel whose item pointer is NULL. That is why the pre-match kit
selector had no kits, and it is also why /club?type=kit could never fix
it: no /club response feeds that array.
Core already owns the designations via /club/active-items, so the host
reuses get_active_kits() and the adapter shapes each entry with the same
shape_club_item primitive /club?type=kit uses, keeping one wire dialect.
A Core transport error yields no actives and is reported rather than
silently empty. userInfo.actives already mirrors the squad's.
Verified against the client's own fcc_kitcards table: 6300006 is team
21's home card (category 2) and 6400003 the away card (category 3).
This commit is contained in:
@@ -81,7 +81,7 @@ use openfut_adapter_fifa17::fut::squad_ext::{
|
||||
build_squad_write, Fifa17SquadExtensionV1, SquadBuildError, EXT_NAMESPACE, EXT_SCHEMA_VERSION,
|
||||
};
|
||||
use openfut_adapter_fifa17::fut::squad_projection::{
|
||||
project_squad, squad_list, user_mass_info_squad, ProjectionSlot, SquadExtInput,
|
||||
project_squad, squad_actives, squad_list, user_mass_info_squad, ProjectionSlot, SquadExtInput,
|
||||
SquadProjection, SquadProjectionInput,
|
||||
};
|
||||
use openfut_adapter_fifa17::fut::store_catalog::{
|
||||
@@ -2663,8 +2663,10 @@ pub struct SquadLog {
|
||||
|
||||
/// The active squad projected from Core, with the freshness policy applied.
|
||||
enum HostProjection {
|
||||
/// Fresh: the projected FIFA squad object (before any endpoint envelope).
|
||||
Squad(Value),
|
||||
/// Fresh: the projected FIFA squad object (before any endpoint envelope),
|
||||
/// plus the active club items for its `actives` array. They travel together
|
||||
/// because both are derived from the SAME bounded Core fetch.
|
||||
Squad { squad: Value, actives: Value },
|
||||
/// Stored extension is stale vs the canonical squad — NEVER applied.
|
||||
Stale,
|
||||
/// No extension stored — nothing fabricated.
|
||||
@@ -2747,8 +2749,28 @@ fn project_active_squad(deps: &SquadDeps<'_>) -> HostProjection {
|
||||
owned: &owned_by_id,
|
||||
manager,
|
||||
};
|
||||
// The active club designations Core already owns (`/club/active-items`), shaped
|
||||
// into the `actives` array the client needs to make a club item resident. A
|
||||
// transport error here is non-fatal and yields no actives: the squad still
|
||||
// renders, exactly as it did before this array was populated. It is reported,
|
||||
// because silently empty actives is precisely the failure that left the
|
||||
// pre-match kit selector blank.
|
||||
let actives = match deps.core.get_active_kits() {
|
||||
Ok(assignments) => squad_actives(
|
||||
&owned_by_id,
|
||||
deps.resolver,
|
||||
ActiveKitAssignments {
|
||||
home: assignments.home_owned_card_id.as_deref(),
|
||||
away: assignments.away_owned_card_id.as_deref(),
|
||||
},
|
||||
),
|
||||
Err(error) => {
|
||||
eprintln!("utas-host WARN active club items unavailable, squad.actives empty: {error}");
|
||||
json!([])
|
||||
}
|
||||
};
|
||||
match project_squad(&input, deps.resolver, deps.entities) {
|
||||
Ok(SquadProjection::Projected(v)) => HostProjection::Squad(v),
|
||||
Ok(SquadProjection::Projected(squad)) => HostProjection::Squad { squad, actives },
|
||||
Ok(SquadProjection::Stale) => HostProjection::Stale,
|
||||
Ok(SquadProjection::Missing) => HostProjection::Missing,
|
||||
Err(e) => HostProjection::Error(e.to_string()),
|
||||
@@ -2917,8 +2939,8 @@ pub fn handle_put_squad(body: &[u8], deps: &SquadDeps<'_>) -> (WireResponse, Squ
|
||||
/// to an empty list, NEVER served from Python and NEVER projected from stale ext.
|
||||
pub fn handle_squad_list(deps: &SquadDeps<'_>) -> (WireResponse, SquadLog) {
|
||||
match project_active_squad(deps) {
|
||||
HostProjection::Squad(v) => (
|
||||
json_response(&squad_list(&v)),
|
||||
HostProjection::Squad { squad, .. } => (
|
||||
json_response(&squad_list(&squad)),
|
||||
SquadLog {
|
||||
outcome: "ok",
|
||||
detail: String::new(),
|
||||
@@ -2955,8 +2977,8 @@ pub fn handle_squad_list(deps: &SquadDeps<'_>) -> (WireResponse, SquadLog) {
|
||||
/// (never 401/403, never a Python fallback that could mask split authority).
|
||||
pub fn handle_squad_active(deps: &SquadDeps<'_>, persona_id: i64) -> (WireResponse, SquadLog) {
|
||||
match project_active_squad(deps) {
|
||||
HostProjection::Squad(v) => (
|
||||
json_response(&user_mass_info_squad(v, persona_id)),
|
||||
HostProjection::Squad { squad, actives } => (
|
||||
json_response(&user_mass_info_squad(squad, persona_id, actives)),
|
||||
SquadLog {
|
||||
outcome: "ok",
|
||||
detail: String::new(),
|
||||
@@ -3081,8 +3103,8 @@ pub fn handle_user_mass_info(
|
||||
})
|
||||
.unwrap_or(0);
|
||||
let (squad_val, log) = match project_active_squad(deps) {
|
||||
HostProjection::Squad(v) => (
|
||||
user_mass_info_squad(v, persona),
|
||||
HostProjection::Squad { squad, actives } => (
|
||||
user_mass_info_squad(squad, persona, actives),
|
||||
SquadLog {
|
||||
outcome: "ok",
|
||||
detail: String::new(),
|
||||
@@ -4556,7 +4578,9 @@ impl Server {
|
||||
};
|
||||
let deps = self.squad_deps();
|
||||
let (squad, squad_outcome) = match project_active_squad(&deps) {
|
||||
HostProjection::Squad(v) => (user_mass_info_squad(v, self.persona_id), "ok"),
|
||||
HostProjection::Squad { squad, actives } => {
|
||||
(user_mass_info_squad(squad, self.persona_id, actives), "ok")
|
||||
}
|
||||
HostProjection::Stale => (empty_squad_overlay(self.persona_id), "stale_integrity"),
|
||||
HostProjection::Missing => (empty_squad_overlay(self.persona_id), "missing_integrity"),
|
||||
HostProjection::Error(_) => (empty_squad_overlay(self.persona_id), "core_error"),
|
||||
|
||||
Reference in New Issue
Block a user