fix(fifa17): send a club item's real wire assetId, not its carddbid
A club item's `assetId` (record +0x20) is family specific and is NOT the carddbid: per the client's own tables a kit carries the art class from fcc_kitcards.assetid - 14 for the 63xxxxx home/third band, 15 for the 64xxxxx away band - a badge carries its team id, and a ball and stadium their own asset number. The catalog shipped `asset_id`, the carddbid, in that slot. Measured on the live client with both kits resident: record +0x20 held 6300006 (home) and 6400003 (away) where the table says 14 and 15, while every other field - resourceId, cardassetid 35, category 2/3, teamid 21, year 0, itemState 101/102 - already matched. Operator reports both pre-match kit tiles rendering identically. assetId is the only field that diverges from the client's own data, and an assetId that is not a valid kit art class cannot resolve to distinct art. `resource_id` is derived from `asset_id`, and every home kit shares art class 14, so the two cannot be the same field: catalogs now carry an optional `club_asset_id`, defaulting to `asset_id` so a catalog predating the field and every non-club kind are unchanged. resolve_kit emits it as the wire `assetId`. Fixed at the source too - scripts/sold-staging-up.py emitted asset_id as the wire assetId for all four club families, so a re-emit would have regressed it. Field-offset note: club_items.json's _record_map is authoritative and my earlier working note had these transposed - assetId is +0x20 and cardassetid is +0x1c, not the reverse. Adds tools/live/diff_kit_records.py, which byte-diffs the two resident kit records and names the fields the decoded clone query consumes. Staging wire now reads assetId 14/15 with cardassetid 35 on both squad.actives and /club?type=equippables. Workspace 1250 passed, 0 failed. Client re-parse still to be confirmed visually.
This commit is contained in:
@@ -41,6 +41,20 @@ pub struct Fifa17CardIdentity {
|
||||
/// FIFA card-art class. Players default to `asset_id`; kit definitions carry
|
||||
/// the verified `fcc_kitcards.cardassetid` value (`35`).
|
||||
pub card_asset_id: u32,
|
||||
/// The wire `assetId` for a CLUB item (record `+0x20`), which is family
|
||||
/// specific and is NOT the carddbid: a kit carries the art class from
|
||||
/// `fcc_kitcards.assetid` (`14` home/third band, `15` away band), a badge
|
||||
/// carries its team id, a stadium and a ball their own asset number.
|
||||
///
|
||||
/// Distinct from [`Self::asset_id`], which for these definitions is the
|
||||
/// carddbid and is what `resource_id` is derived from — so the two cannot be
|
||||
/// the same field. Shipping the carddbid here is what left the client
|
||||
/// holding `assetId 6300006` at record `+0x20` where its own table says
|
||||
/// `14`, with both pre-match kit tiles rendering identically.
|
||||
///
|
||||
/// Defaults to `asset_id` when a catalog does not specify it, which is the
|
||||
/// pre-existing behaviour and is correct for every non-club kind.
|
||||
pub club_asset_id: u32,
|
||||
/// Source team id for a club kit, or a manager's real club. Zero for content
|
||||
/// kinds that do not use it.
|
||||
pub team_id: i64,
|
||||
@@ -206,6 +220,9 @@ struct RawCard {
|
||||
/// Separate card-art id for non-player definitions; absent → `asset_id`.
|
||||
#[serde(default)]
|
||||
card_asset_id: Option<u32>,
|
||||
/// Wire `assetId` for a club item; defaults to `asset_id`. See
|
||||
/// [`Fifa17CardIdentity::club_asset_id`].
|
||||
club_asset_id: Option<u32>,
|
||||
/// Source team id for a kit or manager definition; absent → `0`.
|
||||
#[serde(default)]
|
||||
team_id: Option<i64>,
|
||||
@@ -287,6 +304,7 @@ impl Fifa17CardCatalog {
|
||||
kind: ContentKind::from_str(&rc.kind),
|
||||
subtype: rc.subtype,
|
||||
card_asset_id: rc.card_asset_id.unwrap_or(rc.asset_id),
|
||||
club_asset_id: rc.club_asset_id.unwrap_or(rc.asset_id),
|
||||
team_id: rc.team_id.unwrap_or(0),
|
||||
category: rc.category.unwrap_or(0),
|
||||
year: rc.year.unwrap_or(0),
|
||||
@@ -380,6 +398,49 @@ mod tests {
|
||||
assert_eq!(cat.lookup("card_missing"), None);
|
||||
}
|
||||
|
||||
/// A club item's wire `assetId` is family specific and is NOT the carddbid.
|
||||
///
|
||||
/// Regression: the catalog shipped `asset_id` (the carddbid) as the wire
|
||||
/// `assetId`, so the client held `assetId 6300006` at record `+0x20` where
|
||||
/// its own `fcc_kitcards` says `14`, and both pre-match kit tiles rendered
|
||||
/// identically. `resource_id` is derived from `asset_id`, and every home kit
|
||||
/// shares art class 14, so the two genuinely cannot be one field.
|
||||
#[test]
|
||||
fn club_items_carry_their_own_wire_asset_id_distinct_from_the_carddbid() {
|
||||
let cat = Fifa17CardCatalog::from_json_str(
|
||||
r#"{"schema_version":1,"game":"fifa17","cards":{
|
||||
"fifa17_6300006":{"asset_id":6300006,"kind":"kit","subtype":9,
|
||||
"card_asset_id":35,"club_asset_id":14,"team_id":21,"category":2,"year":0},
|
||||
"fifa17_6400003":{"asset_id":6400003,"kind":"kit","subtype":9,
|
||||
"card_asset_id":35,"club_asset_id":15,"team_id":21,"category":3,"year":0},
|
||||
"fifa17_20801":{"asset_id":20801}
|
||||
}}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let home = cat.lookup("fifa17_6300006").unwrap();
|
||||
let away = cat.lookup("fifa17_6400003").unwrap();
|
||||
|
||||
// resourceId stays the carddbid — it is what the staff/kit merge keys on.
|
||||
assert_eq!(home.resource_id, 6300006);
|
||||
assert_eq!(away.resource_id, 6400003);
|
||||
// The card frame art is shared by the whole kit family.
|
||||
assert_eq!(home.card_asset_id, 35);
|
||||
assert_eq!(away.card_asset_id, 35);
|
||||
// The art class is what distinguishes home from away on the wire.
|
||||
assert_eq!(home.club_asset_id, 14);
|
||||
assert_eq!(away.club_asset_id, 15);
|
||||
assert_ne!(
|
||||
home.club_asset_id, away.club_asset_id,
|
||||
"home and away must not present the same assetId"
|
||||
);
|
||||
|
||||
// Absent: defaults to asset_id, which is correct for every non-club kind
|
||||
// and preserves the behaviour of a catalog that predates the field.
|
||||
let player = cat.lookup("fifa17_20801").unwrap();
|
||||
assert_eq!(player.club_asset_id, 20801);
|
||||
}
|
||||
|
||||
/// The non-player definition fields a consumable needs, and the ABSENCE that
|
||||
/// must stay an absence: a defaulted `amount` would draw "-1" on the card and
|
||||
/// a defaulted `contract` would invent the number of matches a card grants.
|
||||
|
||||
Reference in New Issue
Block a user