feat(fifa17): project owned kits with active home/away designation

Closes the server side of the FUT kit selector. Ownership stays generic in
Core (submodule bump: club_kit_assignments + GET/PUT /club/kits); this
commit adds the FIFA17 representation, the host projection and importer
support.

adapter:
* ContentKind::Kit ("kit") so kits are classified alongside player/staff/
  consumable instead of being mistaken for 0-rated players.
* Fifa17CardIdentity carries card_asset_id and team_id; RawCard keeps both
  optional because the emitted catalog writes null for non-kit definitions.
* shape_kit_item emits only the fields the client's kit path reads
  (id/resourceId/assetId/cardassetid/cardsubtypeid/itemState/owners/
  untradeable/teamid) — no attributeList, no itemType.
* itemState on the wire is the STRING token activeHomeKit/activeAwayKit;
  the 101/102 integers are the client's post-deserialisation runtime enum
  (item+0x5c) and are never emitted.
* club_stats S_KITS (0x28) now counts owned kits instead of a hard zero.

host:
* CoreKitAssignments + CoreAccess::get_active_kits (GET /club/kits),
  defaulting to no active kits so a Core without the endpoint degrades
  instead of fabricating a designation.
* handle_club classifies type=player|kit, rejects any other type with an
  empty page and outcome=unsupported_type, and now always fetches
  unpaginated from Core: kind and transfer-pile membership are host-side
  concepts Core cannot express, so filtering and pagination must both
  happen after shaping or pages come back short.

importer:
* ItemClass::Kit (cardsubtypeid == 9 and resourceId in 6_300_000..=6_400_654),
  kit counts/balances, and card_asset_id/team_id carried into the emitted
  catalog and manifest.
* a kit group missing cardassetid == 35 or teamid is DEFERRED
  (missing_kit_render_metadata) rather than defaulted; conflicting render
  metadata across instances defers as render_metadata_conflict.

staging: sold-staging-up.py seeds two owned kits (6300006 home / 6400003
away, team 21) plus both active designations so the projection can be
verified over HTTP before involving the client.
This commit is contained in:
funman300
2026-08-21 03:17:57 +00:00
parent ab62440dbf
commit db743ffd1f
13 changed files with 616 additions and 169 deletions
@@ -18,7 +18,7 @@
//! resolves to `None` — the caller DEFERS it (mirroring the player NoName gate),
//! never fabricating a family.
/// The disjoint content classes a FIFA 17 owned card can belong to. Player is
/// The disjoint content classes a FIFA 17 owned item can belong to. Player is
/// the default so a catalog authored before this taxonomy existed (no `kind`
/// field) still classifies every entry as a player, unchanged.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@@ -27,6 +27,7 @@ pub enum ContentKind {
Player,
Consumable,
Staff,
Kit,
}
impl ContentKind {
@@ -36,6 +37,7 @@ impl ContentKind {
ContentKind::Player => "player",
ContentKind::Consumable => "consumable",
ContentKind::Staff => "staff",
ContentKind::Kit => "kit",
}
}
@@ -49,6 +51,7 @@ impl ContentKind {
match s {
"consumable" => ContentKind::Consumable,
"staff" => ContentKind::Staff,
"kit" => ContentKind::Kit,
_ => ContentKind::Player,
}
}
@@ -106,6 +109,7 @@ mod tests {
ContentKind::Player,
ContentKind::Consumable,
ContentKind::Staff,
ContentKind::Kit,
] {
assert_eq!(ContentKind::from_str(k.as_str()), k);
}