fifa17 store: real 6-pack economy + full-DB pool; drop extPrice

- store_catalog: replace the invented catalogue with the real always-available
  FUT17 regular packs (Bronze/Prem Bronze/Silver/Prem Silver/Gold/Prem Gold) at
  real prices + tier composition; PackDef now carries per-tier quantities.
- pack_body: drop extPrice (its mtx side-effect switched on the broken "or %1s"
  FIFA-Points tile line; plan-2026-08-05-store-subsystem.md section 3.4).
- pack_content: tier-aware generator draws each pack bronze/silver/gold
  composition with special_chance bias + empty-tier fallback.
- host: CoreAccess::all_definitions (GET /cards); build_content_pool draws the
  FULL card universe via non-minting catalog lookup, owned-inventory fallback.
- economy_differential: store ops reclassified DIFFERENT-BY-DESIGN (Rust is the
  authoritative store; Python oracle stays the untouched rollback baseline).
- fixtures/tests updated to the real catalogue.

Odds are DESIGNED placeholders (FUT17 pack probabilities were never published);
club items remain excluded (cardtype-9 mapping unknown). Full regression green;
real prices + tier-correct draws verified server-side on staging.
This commit is contained in:
funman300
2026-08-18 23:26:55 +00:00
parent 7116046195
commit c68c10cf04
10 changed files with 863 additions and 650 deletions
+13 -9
View File
@@ -244,7 +244,11 @@ fn pack_open_body(pid: u64, pack: &PackDef) -> WireResponse {
"firstPartyStoreId": 0,
"groupName": "fifa17",
"productId": pid.to_string(),
"purchasePackType": if pack.gold { "GOLD" } else { "BRONZE" },
"purchasePackType": match pack.category {
"gold" => "GOLD",
"silver" => "SILVER",
_ => "BRONZE",
},
}))
}
@@ -695,7 +699,7 @@ mod tests {
// ── Store BUY ──────────────────────────────────────────────────────────
#[test]
fn buy_pack1_debits_mints_and_reveals_five_cards() {
fn buy_pack1_debits_mints_and_reveals_twelve_cards() {
let econ = RecEcon::new(10_000);
let pool = pool();
let assets = FakeAssets::for_pool(&pool);
@@ -705,23 +709,23 @@ mod tests {
assert_eq!(resp.status, 200);
let b: Value = serde_json::from_slice(&resp.body).unwrap();
let cpr = &b["createPackResponse"];
assert_eq!(cpr["numberItems"], 5); // pack 1 count
assert_eq!(cpr["itemList"].as_array().unwrap().len(), 5);
assert_eq!(cpr["numberItems"], 12); // pack 1 count (10 bronze + 2 silver)
assert_eq!(cpr["itemList"].as_array().unwrap().len(), 12);
assert_eq!(cpr["purchasedPackId"], 1);
assert_eq!(cpr["duplicateItemIdList"], json!([]));
// Exactly one atomic debit of the pack price (400) minting 5 items.
// Exactly one atomic debit of the pack price (400) minting 12 items.
assert_eq!(econ.coins(), 10_000 - 400);
let purchased = econ.purchased.lock();
assert_eq!(purchased.len(), 1);
assert_eq!(purchased[0].0, 400);
assert_eq!(purchased[0].1.len(), 5);
assert_eq!(purchased[0].1.len(), 12);
for g in &purchased[0].1 {
assert!(pool.iter().any(|c| c.card_id == g.card_id));
}
}
#[test]
fn buy_pack5_debits_gold_price_and_mints_seven() {
fn buy_pack5_debits_gold_price_and_mints_twelve() {
let econ = RecEcon::new(10_000);
let pool = pool();
let assets = FakeAssets::for_pool(&pool);
@@ -729,7 +733,7 @@ mod tests {
let deps = store_deps(&econ, &assets, &ent, &pool);
let resp = handle_store_buy(&body(json!({ "packId": 5 })), &deps, &mut rng(2));
let b: Value = serde_json::from_slice(&resp.body).unwrap();
assert_eq!(b["createPackResponse"]["numberItems"], 7); // pack 5 count
assert_eq!(b["createPackResponse"]["numberItems"], 12); // pack 5 count (10 gold + 2 silver)
assert_eq!(econ.coins(), 10_000 - 5000);
}
@@ -860,7 +864,7 @@ mod tests {
assert_eq!(b["firstPartyStoreId"], 0);
assert_eq!(b["purchasePackType"], "GOLD"); // pack 5 is gold
assert_eq!(econ.coins(), 10_000 - 5000);
assert_eq!(econ.purchased.lock()[0].1.len(), 7);
assert_eq!(econ.purchased.lock()[0].1.len(), 12);
}
#[test]