fix(import-fifa17): carry the fields a consumable needs to exist

Two defects that together made the club's 17 owned consumables invisible while
club/stats still counted them — the count gate promised 17, the item route
served 0.

1. The catalog omitted `card_asset_id`, `amount`, `contract` and `rating` for
   non-player definitions. Without an art id the adapter refuses to emit the
   card (it would draw the notfound box), and the families that read
   `amount`/`contract` would render "-1" or grant nothing. All four values are
   present in the source wire and were simply dropped on the way out.

2. Owned rows were imported without a `content_kind`, and Core defaults an
   unstated row to `player` — durably recording a fitness coach and a contract
   card as players in the ownership authority, even though the catalog-driven
   wire looked right.

These are definition-level fields, so every owned copy must agree; a group that
disagrees is deferred rather than resolved by taking the first copy's value.
Measured on the real profile: observed `amount` equals the `fcc_*` table row for
every consumable that carries one (1, 2, 4, 5, 10, 15), and a wire omission
corresponds to a table amount of 0. It is therefore NOT a stack count — two
copies of 5003068 are two instances — so the import states no `quantity` at all.
This commit is contained in:
funman300
2026-08-21 19:50:13 +00:00
parent 802f0f580f
commit 770029f207
4 changed files with 126 additions and 5 deletions
+40 -4
View File
@@ -546,6 +546,21 @@ pub struct NonPlayerDefinition {
pub card_asset_id: Option<i64>,
/// Source team id for a kit definition, when present.
pub team_id: Option<i64>,
/// Consumable effect magnitude (`amount`), when the source carries one.
///
/// Definition-level, and measured to be so: across every owned consumable in
/// the real profile the observed `amount` equals its `fcc_*` table row
/// (1, 2, 4, 5, 10, 15 — no disagreements), and a wire omission corresponds
/// to a table `amount` of 0. It is NOT a stack count: two copies of 5003068
/// arrive as two separate instances, each with the same amount.
pub amount: Option<i64>,
/// Contract-card payload (`contract`), when the source carries one. Present
/// on exactly the contract families (subtypes 201/202) and absent from every
/// other owned consumable, so it is the card's own field rather than the
/// generic per-item contract atom that players and staff carry.
pub contract: Option<i64>,
/// Card rating, when the source carries one (matches the `fcc_*` row).
pub rating: Option<i64>,
/// Honest functional label (e.g. "Player Contract", "GK Coach", "Kit").
pub name: String,
/// Wire ids of every owned copy of this resourceId (preserved).
@@ -635,10 +650,20 @@ pub fn plan_non_player_definitions(profile: &Profile) -> NonPlayerPlan {
let card_asset_id = items[0].cardassetid;
let team_id = items[0].teamid;
if items
.iter()
.any(|item| item.cardassetid != card_asset_id || item.teamid != team_id)
{
// These are definition-level, so every owned copy must agree. Two copies
// of one consumable that disagreed would mean the field is really
// per-instance, and silently taking the first copy's value would bake a
// guess into the catalog — so defer the whole group instead.
let amount = items[0].amount;
let contract = items[0].contract;
let rating = items[0].rating;
if items.iter().any(|item| {
item.cardassetid != card_asset_id
|| item.teamid != team_id
|| item.amount != amount
|| item.contract != contract
|| item.rating != rating
}) {
plan.deferred.push(DeferredNonPlayer {
resource_id,
subtype: Some(subtype),
@@ -700,6 +725,9 @@ pub fn plan_non_player_definitions(profile: &Profile) -> NonPlayerPlan {
subtype,
card_asset_id,
team_id,
amount,
contract,
rating,
name: label.to_string(),
wire_ids,
});
@@ -1184,6 +1212,14 @@ pub fn emit_content(
serde_json::json!({
"card_asset_id": d.card_asset_id,
"team_id": d.team_id,
// A consumable is unrenderable without these: the adapter refuses
// to emit a card whose art id it does not know, and the families
// that read `amount`/`contract` draw "-1" or grant nothing when
// the field is missing. Omitting them here is what kept every
// owned consumable off the wire.
"amount": d.amount,
"contract": d.contract,
"rating": d.rating,
"asset_id": d.asset_id.unwrap_or(d.resource_id),
"version": 0,
"rareflag": 0,