feat(fifa17): manager contracts, from a Core-owned staff tier
ROOT CAUSE, one line. openfut-import-fifa17 emitted `"overall": 0` for every non-player Core definition while `d.rating` already held EA's authoritative `value` -- and the very next block wrote that same number correctly to the adapter catalog. So the tier existed host-side but never reached Core: Core overall 0 -> /collection effective_overall 0 -> CoreOwnedItem.rating 0 -> tier_for_rating(0) = Bronze for a Gold (88) manager. That silent mis-grant is exactly what the 409 was protecting against, so the refusal was correct. The emitter now also writes `source_rating`, keeping `overall` at 0. Regenerating the production pack changes exactly 18 entries and exactly one field each (source_rating None -> value); same 1710 ids, same fingerprint 28c333f1e833338a. WHY value IS the tier source, and why the thresholds are the player ladder: LIVE_PROVEN, not inferred. The client re-rates staff from its own managercards/*coachcards/physiocards by carddbid and applies discard_level's 65/75 ladder; coach_probe/discard_probe agree 4/4 (manager value 88 -> level 3, coaches 66 -> level 2). The shipped coach tables corroborate: each family has exactly 3 tiers x 2 rarities, and only 65/75 splits them 2/2/2. Manager contracts stop refusing and now resolve the TARGET's tier from Core-owned state. Still fail-closed everywhere it matters: a coach or physio is `contract_target_not_a_manager` (only cardsubtypeid 4 is a manager), and a manager Core carries no source_rating for is `manager_tier_unknown` rather than a guessed tier. Core's own content_kind token is sent as target_kind, because Core calls the squad manager `manager` while the catalog classifies it `staff`+subtype 4. NOT implemented, unchanged: STORED_MANAGER_BONUS and MATCH_CONTRACT_DECREMENT.
This commit is contained in:
@@ -946,6 +946,17 @@ fn emit_content_writes_non_player_defs_catalog_kind_and_manifest() {
|
||||
assert_eq!(cons["nation"], "");
|
||||
assert_eq!(cons["rarity"], "bronze");
|
||||
assert!(cons["image_path"].is_null());
|
||||
// `source_rating` mirrors the wire verbatim: this consumable really sends
|
||||
// `"rating":0`, so 0 is the authored value, not a substituted default.
|
||||
assert_eq!(cons["source_rating"], 0, "{cons}");
|
||||
// This fixture's `entities()` carries no staff table, so the coach's value is
|
||||
// UNKNOWN — it must stay `null`, never a fabricated 0 a tier rule reads as
|
||||
// bronze.
|
||||
let coach = arr.iter().find(|c| c["id"] == "fifa17_3000083").unwrap();
|
||||
assert!(
|
||||
coach["source_rating"].is_null(),
|
||||
"an unknown authored value must stay null: {coach}"
|
||||
);
|
||||
|
||||
// Catalog: kind+subtype on player AND non-player; staff asset falls back to
|
||||
// resourceId; and the emitted catalog LOADS in the adapter with kind_of.
|
||||
@@ -1141,3 +1152,55 @@ fn enrich_staff_fills_staff_and_leaves_everything_else_alone() {
|
||||
"the wire rating is left untouched"
|
||||
);
|
||||
}
|
||||
|
||||
/// A MANAGER CONTRACT grant needs the target's TIER, and the only authoritative
|
||||
/// source is EA's authored `value` from the staff family table. Emit must carry
|
||||
/// it into Core's content pack as `source_rating` — the SAME number the host
|
||||
/// catalog carries as `rating`, so the two artifacts can never disagree — while
|
||||
/// `overall` stays 0, because `overall` is what Core prices and projects from.
|
||||
///
|
||||
/// Before this, a non-player reached Core with `overall: 0` and nothing else, so
|
||||
/// `/collection` reported `effective_overall: 0` and a gold (76) manager graded
|
||||
/// bronze.
|
||||
#[test]
|
||||
fn emit_content_carries_ea_authored_value_as_source_rating() {
|
||||
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../fifa17-recon/data/tables");
|
||||
let ent = Entities::from_tables_dir(dir).expect("committed tables load");
|
||||
|
||||
let items = vec![
|
||||
staff(100000427, 1000001, 4), // manager — managercards.value = 76 (gold)
|
||||
staff(100000280, 9000081, 6), // GK coach — gkcoachcards.value = 66 (silver)
|
||||
];
|
||||
let rep = analyze(&profile(&items, "[]", 100000500), &roster(), &ent, &none());
|
||||
assert!(!rep.has_blockers(), "blockers: {:?}", rep.blockers());
|
||||
|
||||
let out = tempfile::tempdir().unwrap();
|
||||
let sum = emit_content(&rep, out.path(), "fp").unwrap();
|
||||
let pack: serde_json::Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(&sum.content_pack).unwrap()).unwrap();
|
||||
let cat: serde_json::Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(&sum.host_catalog).unwrap()).unwrap();
|
||||
|
||||
for (card_id, value) in [("fifa17_1000001", 76), ("fifa17_9000081", 66)] {
|
||||
let def = pack
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|c| c["id"] == card_id)
|
||||
.unwrap_or_else(|| panic!("{card_id} must be in the content pack"));
|
||||
assert_eq!(
|
||||
def["source_rating"],
|
||||
serde_json::json!(value),
|
||||
"EA's authored value must reach Core: {def}"
|
||||
);
|
||||
assert_eq!(
|
||||
def["overall"],
|
||||
serde_json::json!(0),
|
||||
"overall stays 0 for a non-player: it feeds pricing and projection"
|
||||
);
|
||||
assert_eq!(
|
||||
def["source_rating"], cat["cards"][card_id]["rating"],
|
||||
"one source, two artifacts — they must never disagree on a tier"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user