feat(fifa17): import consumable + staff content as first-class Core content
Close 20 of the 33-record content gap (17 consumables + 3 staff; 13 Legends are unrecoverable from PC data). Verdict A (no Core change): consumables/staff become ordinary Core CardDefinitions (neutral player fields + honest family/role names) and owned instances via the SAME generic import path; a catalog kind lets the adapter exclude them from the player-only /club projection. - adapter fut::content_taxonomy: evidence-based cardsubtypeid->family/label (Ghidra-derived ranges) + staff role map; unknown subtype => defer, never fabricate. - adapter catalog: Fifa17CardIdentity/RawCard gain optional kind+subtype (backward-compat: legacy catalogs load as player); kind_of/subtype_of lookups. - adapter item/club_response: shape_club_response excludes non-player kinds (ShapeStats.excluded_non_player); ItemIdentityResolver::kind_of default=Player. - host Fifa17IdentityResolver overrides kind_of to delegate to the catalog so /club excludes consumables/staff in production. - import: Item gains cardsubtypeid/cardassetid/amount/contract; plan_non_player_definitions (resourceId-grouped, subtype-consistency gated); emit_content writes non-player defs + catalog kind + manifest; apply mints owned instances via owned_item_id. Real profile 33068179: 1962 players + 20 non-player = 1982 owned; 18 non-player defs (16 consumable + 2 staff, dup resourceIds shared); 0 deferred non-player; 0 blockers.
This commit is contained in:
@@ -20,6 +20,8 @@ use std::collections::HashMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::fut::content_taxonomy::ContentKind;
|
||||
|
||||
/// The FIFA 17 render identity of a card definition. `version` is the high byte
|
||||
/// of `resource_id`; `asset_id` (the low 24 bits) is the real FIFA player id.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -30,6 +32,12 @@ pub struct Fifa17CardIdentity {
|
||||
/// FIFA wire `rareflag` (rare/special card TYPE). Carried so specials render
|
||||
/// as specials; observed metadata, not a guessed label.
|
||||
pub rareflag: i64,
|
||||
/// Content class of this definition. A catalog authored before this field
|
||||
/// existed defaults to [`ContentKind::Player`] (backward compatible).
|
||||
pub kind: ContentKind,
|
||||
/// FIFA `cardsubtypeid` for a non-player definition (consumable family /
|
||||
/// staff role), `0` for a player or when absent.
|
||||
pub subtype: i64,
|
||||
}
|
||||
|
||||
/// The FIFA 17 numeric namespace policy for owned-item wire ids.
|
||||
@@ -115,6 +123,14 @@ struct RawCard {
|
||||
/// behaviour; the production catalog carries the observed value.
|
||||
#[serde(default = "default_rareflag")]
|
||||
rareflag: i64,
|
||||
/// Content class token ("player"|"consumable"|"staff"). Absent → default
|
||||
/// (empty) → [`ContentKind::Player`], so existing player-only catalogs load
|
||||
/// unchanged.
|
||||
#[serde(default)]
|
||||
kind: String,
|
||||
/// FIFA `cardsubtypeid` for a non-player entry; absent → `0`.
|
||||
#[serde(default)]
|
||||
subtype: i64,
|
||||
}
|
||||
|
||||
fn default_rareflag() -> i64 {
|
||||
@@ -169,6 +185,8 @@ impl Fifa17CardCatalog {
|
||||
version: rc.version,
|
||||
resource_id,
|
||||
rareflag: rc.rareflag,
|
||||
kind: ContentKind::from_str(&rc.kind),
|
||||
subtype: rc.subtype,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -197,6 +215,21 @@ impl Fifa17CardCatalog {
|
||||
self.by_resource.get(&resource_id).map(String::as_str)
|
||||
}
|
||||
|
||||
/// Classify a `card_id` as player/consumable/staff. An unknown definition is
|
||||
/// [`ContentKind::Player`] — the neutral, backward-compatible default (an
|
||||
/// un-catalogued id was always treated as a player-shaped card).
|
||||
pub fn kind_of(&self, card_id: &str) -> ContentKind {
|
||||
self.by_card
|
||||
.get(card_id)
|
||||
.map(|c| c.kind)
|
||||
.unwrap_or(ContentKind::Player)
|
||||
}
|
||||
|
||||
/// The FIFA `cardsubtypeid` for a definition, or `0` if unknown / a player.
|
||||
pub fn subtype_of(&self, card_id: &str) -> i64 {
|
||||
self.by_card.get(card_id).map(|c| c.subtype).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.by_card.len()
|
||||
}
|
||||
@@ -335,4 +368,44 @@ mod tests {
|
||||
assert_eq!(ron.version, 0);
|
||||
assert_eq!(ron.resource_id, 20801);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_catalog_without_kind_loads_as_player() {
|
||||
// A pre-taxonomy catalog (no `kind`/`subtype`) must load unchanged and
|
||||
// classify every entry as a player, with subtype 0.
|
||||
let cat = Fifa17CardCatalog::from_json_str(
|
||||
r#"{"schema_version":1,"game":"fifa17","cards":{
|
||||
"fifa17_20801":{"asset_id":20801},
|
||||
"fifa17_176580":{"asset_id":176580,"version":5,"rareflag":3}
|
||||
}}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let base = cat.lookup("fifa17_20801").unwrap();
|
||||
assert_eq!(base.kind, ContentKind::Player);
|
||||
assert_eq!(base.subtype, 0);
|
||||
assert_eq!(base.rareflag, 1, "absent rareflag still defaults to 1");
|
||||
assert_eq!(cat.kind_of("fifa17_20801"), ContentKind::Player);
|
||||
assert_eq!(cat.kind_of("fifa17_176580"), ContentKind::Player);
|
||||
// Unknown id -> neutral Player default.
|
||||
assert_eq!(cat.kind_of("fifa17_missing"), ContentKind::Player);
|
||||
assert_eq!(cat.subtype_of("fifa17_missing"), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kind_and_subtype_are_parsed_for_non_player_entries() {
|
||||
let cat = Fifa17CardCatalog::from_json_str(
|
||||
r#"{"schema_version":1,"game":"fifa17","cards":{
|
||||
"fifa17_20801":{"asset_id":20801,"kind":"player","subtype":0},
|
||||
"fifa17_5003012":{"asset_id":5003012,"kind":"consumable","subtype":54,"rareflag":0},
|
||||
"fifa17_3000083":{"asset_id":3000083,"kind":"staff","subtype":8,"rareflag":0}
|
||||
}}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(cat.kind_of("fifa17_20801"), ContentKind::Player);
|
||||
assert_eq!(cat.kind_of("fifa17_5003012"), ContentKind::Consumable);
|
||||
assert_eq!(cat.subtype_of("fifa17_5003012"), 54);
|
||||
assert_eq!(cat.kind_of("fifa17_3000083"), ContentKind::Staff);
|
||||
assert_eq!(cat.subtype_of("fifa17_3000083"), 8);
|
||||
assert_eq!(cat.lookup("fifa17_5003012").unwrap().rareflag, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user