fix(fifa17): squad.manager elements are bare item objects, not itemData wrappers

An owned manager assigned in Core was present everywhere on the server -- in
/club/manager, in club?type=staff, and in userMassInfo -- but the squad UI
showed no manager after a cold client load.

The squad parser FUN_18013d1f0 reaches the item parser FUN_18013fe00 by two
different routes:

  players: atom 568 -> per-element atoms 355 `index`, 363 `itemData`,
           378 `kitNumber`; the 363 arm at 0x18013d8d9 calls the item parser
           on the NESTED itemData object.
  manager: atom 424 -> array loop at 0x18013da29 calls that same item parser
           DIRECTLY on the array ELEMENT, into squad+0xC0. No `itemData` step.

So a manager element IS an item. We were nesting the fields one level deeper,
so the parser read only the two keys that happen to be item atoms -- `id` and
`dream` -- and left everything else at its default. Measured on a cold client,
the manager record existed at squad+0xC0 with the correct id and resourceId 0,
while sibling players in the same response carried theirs. resourceId is the
merge key compared RAW against carddbid, so 0 resolves no manager: no name, no
rating, no art, empty slot.

The client's own save corroborates the shape: it PUTs
`"manager":[{"id":...,"dream":false}]` -- flat, and both keys are item atoms.

Flatten the element to the item plus `dream`. Cold-load proven on staging: the
manager record now carries resourceId 1000509 in the same layout as its player
siblings (83906881, 84053575) in the same array region, and the operator
confirms a manager is assigned in the squad management screen.

Two earlier shapes are now both explained and covered by tests: `{id, dream}`
carries no merge key, and `{id, itemData, dream}` hides it from this path.
This commit is contained in:
funman300
2026-08-25 02:50:22 +00:00
parent c3d0e56f69
commit b91e707a7e
2 changed files with 78 additions and 46 deletions
@@ -410,10 +410,13 @@ fn persisted_read_round_trips_via_reconstructed_canonical_and_extension() {
}
// EXTENSION + SHADOW: sourced from the read, so they round-trip identically.
assert_eq!(projected["custom"], oracle["custom"]);
// The manager REF round-trips; the item now rides with it. The capture this
// oracle came from carried a bare `{id, dream}`, but its manager was the
// dangling one every retail capture has, so it never showed that a populated
// ref renders on its own — and in practice it did not.
// The manager REF round-trips; the item now rides AT ELEMENT LEVEL. The
// capture this oracle came from carried a bare `{id, dream}`, but its
// manager was the dangling one every retail capture has, so it never showed
// that a populated ref renders on its own — and in practice it did not.
// Wrapping the fields in `itemData` did not work either: the squad parser's
// manager branch calls the item parser on the element itself, so a nested
// item is never read and the merge key stays 0.
assert_eq!(
projected["manager"][0]["id"], oracle["manager"][0]["id"],
"the manager wire ref itself must still round-trip"
@@ -422,8 +425,11 @@ fn persisted_read_round_trips_via_reconstructed_canonical_and_extension() {
projected["manager"][0]["dream"],
oracle["manager"][0]["dream"]
);
let mgr_item = &projected["manager"][0]["itemData"];
assert_eq!(mgr_item["id"], oracle["manager"][0]["id"]);
let mgr_item = &projected["manager"][0];
assert!(
mgr_item.get("itemData").is_none(),
"the manager element IS the item; a wrapper hides the merge key"
);
assert_eq!(mgr_item["cardsubtypeid"], 4);
assert_eq!(mgr_item["resourceId"], 1_000_509);
assert_eq!(