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:
funman300
2026-08-24 20:35:58 +00:00
parent 6bbc0eaf4f
commit 74768693ec
4 changed files with 227 additions and 17 deletions
+28 -16
View File
@@ -182,9 +182,15 @@ DISPOSABLE_CARD = "fifa17_232273" # Nelson Atiagli LB 51, rareflag 1
# Two authoritative modern kit-card definitions for one real source team. These
# are staging fixtures derived from fcc_kitcards, not synthetic FIFA identities.
KIT_TEAM_ID = 21
# The trailing value is the client's own `fcc_kitcards.assetid`, the wire
# `assetId` (record +0x20). It is the ART CLASS, not the carddbid: every
# 63xxxxx (home/third) kit carries 14 and every 64xxxxx (away) kit carries 15,
# per club_items.json and fifa17-kit-map.json's band_x_assetid evidence
# (6300000/14 x828, 6400000/15 x654). Shipping the carddbid here left both
# pre-match kit tiles rendering identically.
STAGING_KITS = [
("home", "owned-a-kit-home", "fifa17_6300006", 6_300_006),
("away", "owned-a-kit-away", "fifa17_6400003", 6_400_003),
("home", "owned-a-kit-home", "fifa17_6300006", 6_300_006, 14),
("away", "owned-a-kit-away", "fifa17_6400003", 6_400_003, 15),
]
# The remaining club-item families, so the rig exercises EVERY ownable class
@@ -198,11 +204,15 @@ STAGING_KITS = [
# badge 39, logo 40), which is what the importer gates on. A league logo has no
# equipped slot, so it is owned as generic `misc` content.
STAGING_CLUB_ITEMS = [
# (slot, owned_id, card_id, resource_id, kind, subtype, card_asset_id, team_id)
("badge", "owned-a-badge", "fifa17_6000005", 6_000_005, "badge", 11, 39, 21),
("ball", "owned-a-ball", "fifa17_8120194", 8_120_194, "ball", 30, 37, None),
("stadium", "owned-a-stadium", "fifa17_6200000", 6_200_000, "stadium", 10, 36, None),
(None, "owned-a-leaguelogo", "fifa17_8010015", 8_010_015, "misc", 31, 40, None),
# (slot, owned_id, card_id, resource_id, kind, subtype, card_asset_id, team_id,
# club_asset_id)
# club_asset_id is the wire `assetId` from club_items.json, family specific
# and never the carddbid: badge 6000005 -> its teamid 21, ball 8120194 -> 100,
# stadium 6200000 -> 1. A league logo has no equipped slot and keeps its own.
("badge", "owned-a-badge", "fifa17_6000005", 6_000_005, "badge", 11, 39, 21, 21),
("ball", "owned-a-ball", "fifa17_8120194", 8_120_194, "ball", 30, 37, None, 100),
("stadium", "owned-a-stadium", "fifa17_6200000", 6_200_000, "stadium", 10, 36, None, 1),
(None, "owned-a-leaguelogo", "fifa17_8010015", 8_010_015, "misc", 31, 40, None, None),
]
# The club manager. FIFA refuses to start a match without one ("your player or
@@ -524,7 +534,7 @@ def materialise(lay: Layout) -> None:
with open(safe_path(lay.catalog)) as fh:
catalog = json.load(fh)
existing = {definition["id"] for definition in definitions}
for _slot, _owned_id, card_id, resource_id in STAGING_KITS:
for _slot, _owned_id, card_id, resource_id, club_asset_id in STAGING_KITS:
if card_id not in existing:
definitions.append({
"id": card_id,
@@ -550,10 +560,11 @@ def materialise(lay: Layout) -> None:
"kind": "kit",
"subtype": 9,
"card_asset_id": 35,
"club_asset_id": club_asset_id,
"team_id": KIT_TEAM_ID,
}
for _slot, _owned, card_id, resource_id, kind, subtype, art, team in STAGING_CLUB_ITEMS:
for _slot, _owned, card_id, resource_id, kind, subtype, art, team, club_asset in STAGING_CLUB_ITEMS:
if card_id not in existing:
definitions.append({
"id": card_id,
@@ -580,6 +591,8 @@ def materialise(lay: Layout) -> None:
"subtype": subtype,
"card_asset_id": art,
}
if club_asset is not None:
entry["club_asset_id"] = club_asset
if team is not None:
entry["team_id"] = team
catalog["cards"][card_id] = entry
@@ -701,7 +714,7 @@ def assert_seed_cards_resolvable(lay: Layout, real_club: dict | None) -> None:
wanted = set(
[card for _, card in SELLER_SQUAD_CARDS]
+ [DISPOSABLE_CARD]
+ [card for _, _, card, _ in STAGING_KITS]
+ [card for _, _, card, _, _ in STAGING_KITS]
+ [STAGING_MANAGER["card_id"]]
)
what = f"all {len(wanted)} fixture seed card ids"
@@ -712,7 +725,7 @@ def assert_seed_cards_resolvable(lay: Layout, real_club: dict | None) -> None:
conn.execute("SELECT DISTINCT card_id FROM owned_cards")}
finally:
conn.close()
wanted |= {card for _, _, card, _ in STAGING_KITS}
wanted |= {card for _, _, card, _, _ in STAGING_KITS}
wanted.add(STAGING_MANAGER["card_id"])
what = (f"all {len(wanted)} distinct card ids owned by the real club "
"(plus the kit and manager fixtures)")
@@ -983,7 +996,7 @@ def seed_core_db(lay: Layout, real_club: dict | None) -> None:
# calling a kit a player, and Core is the ownership authority.
owned = [
(owned_id, seller_club, card_id, "kit", TS)
for _slot, owned_id, card_id, _resource_id in STAGING_KITS
for _slot, owned_id, card_id, _resource_id, _ca in STAGING_KITS
]
owned.append(
(
@@ -996,7 +1009,7 @@ def seed_core_db(lay: Layout, real_club: dict | None) -> None:
)
owned.extend(
(owned_id, seller_club, card_id, kind, TS)
for _slot, owned_id, card_id, _rid, kind, _st, _art, _team
for _slot, owned_id, card_id, _rid, kind, _st, _art, _team, _ca
in STAGING_CLUB_ITEMS
)
if real_club is None:
@@ -1010,7 +1023,6 @@ def seed_core_db(lay: Layout, real_club: dict | None) -> None:
"content_kind, acquired_at) VALUES (?, ?, ?, 0, ?, ?)",
owned,
)
if real_club is None:
conn.execute(
"INSERT INTO squads (id, club_id, name, formation, created_at, "
@@ -1034,14 +1046,14 @@ def seed_core_db(lay: Layout, real_club: dict | None) -> None:
"VALUES (?, ?, ?, ?)",
[
(seller_club, f"{slot}_kit", owned_id, TS)
for slot, owned_id, _card_id, _resource_id in STAGING_KITS
for slot, owned_id, _card_id, _resource_id, _ca in STAGING_KITS
]
# badge / ball / stadium are slot-keyed exactly like the kits.
# A league logo has no slot, so it stays owned-but-unequipped —
# which is itself worth exercising.
+ [
(seller_club, slot, owned_id, TS)
for slot, owned_id, _c, _r, _k, _s, _a, _t in STAGING_CLUB_ITEMS
for slot, owned_id, _c, _r, _k, _s, _a, _t, _ca in STAGING_CLUB_ITEMS
if slot is not None
],
)