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
@@ -311,7 +311,7 @@ fn case_a_two_buys(h: &Harness) -> String {
refs, 1,
"exactly one BUY refused 461 (statuses {statuses:?})"
);
// The winner minted 5 cards; the loser minted nothing.
// The winner minted 12 cards (real Bronze Pack); the loser minted nothing.
for r in &rs {
if r.status == 200 {
assert_eq!(
@@ -319,7 +319,7 @@ fn case_a_two_buys(h: &Harness) -> String {
.as_array()
.unwrap()
.len(),
5
12
);
}
}
+54 -29
View File
@@ -26,15 +26,15 @@
//! | userMassInfo economy | PARITY | `userInfo.currencies[coins].funds == credits coins`; both |
//! | | | carry `unopenedPacks.recoveredPacks==1`. Coins consistent |
//! | | | across the credits & massinfo surfaces on each side. |
//! | purchasegroup pack70 | PARITY | owned pack present -> id set {1,5,6,7,70}, NO 65534 sentinel. |
//! | purchasegroup sentinel | PARITY | empty My Packs + unverified session -> {1,5,6,7,65534}, |
//! | | | sentinel `state:"active"`. |
//! | purchasegroup clean-v1 | PARITY | empty My Packs + verified capability session -> {1,5,6,7}, |
//! | purchasegroup pack70 | DIFFERENT-BY-DESIGN| STORE DIVERGED: Rust serves the real 6-pack catalogue |
//! | | | {1,2,3,4,5,6,70}; oracle (rollback) keeps {1,5,6,7,70}. |
//! | purchasegroup sentinel | DIFFERENT-BY-DESIGN| empty My Packs + unverified -> rust {1..6,65534} vs oracle |
//! | | | {1,5,6,7,65534}; sentinel `state:"active"` on both. |
//! | purchasegroup clean-v1 | DIFFERENT-BY-DESIGN| empty + verified capability -> rust {1..6}, oracle {1,5,6,7};|
//! | | | sentinel stripped. Rust drives the REAL `SessionStore` state |
//! | | | machine (register_capability+open_session+freeze) exactly as |
//! | | | the oracle's launcher/auth handshake does. |
//! | Store BUY (pack 1) | PARITY | 200; `createPackResponse{itemList(5),numberItems:5, |
//! | | | purchasedPackId,duplicateItemIdList}`; coin delta -400. |
//! | | | machine (register_capability+open_session+freeze). |
//! | Store BUY (pack 1) | DIFFERENT-BY-DESIGN| 200; rust real Bronze Pack -> itemList(12),numberItems:12; |
//! | | | oracle placeholder -> 5; both debit 400 + purchasedPackId 1. |
//! | POST /purchased open70 | PARITY | 200; envelope `{packId:70,firstPartyStoreId,productId:"70", |
//! | | | purchasePackType:"GOLD"}`; coin delta 0; entitlement -1. |
//! | GET /purchased reveal | PARITY | Durable single-profile purchased pile on BOTH (Rust reveal |
@@ -550,29 +550,43 @@ fn run_differential(core_base: &str, oracle: &Oracle, dir: &std::path::Path) {
None,
)
.1;
// STORE DIVERGED: Rust is the authoritative store owner and serves the real
// 6-pack regular catalogue (ids 1..6 + owned 70). The Python oracle (rollback
// baseline, never modified) still serves the old placeholder set {1,5,6,7,70},
// so this is Rust-authoritative, NOT oracle parity.
assert_eq!(
pack_ids(&opg),
vec![1, 5, 6, 7, 70],
"oracle owned pack70 id set"
"oracle (rollback) placeholder id set"
);
assert_eq!(
pack_ids(&rpg),
vec![1, 5, 6, 7, 70],
"rust owned pack70 id set"
vec![1, 2, 3, 4, 5, 6, 70],
"rust authoritative real-catalogue id set"
);
for b in [&opg, &rpg] {
assert!(
!pack_ids(b).contains(&SENTINEL_PACK_ID),
"no 65534 sentinel while a pack is owned"
);
// packType parity per id (BRONZE for 1, GOLD for the rest).
for p in b["purchase"].as_array().unwrap() {
let id = p["id"].as_u64().unwrap();
let want = if id == 1 { "BRONZE" } else { "GOLD" };
assert_eq!(p["packType"], want, "packType parity for pack {id}");
}
}
matrix.push(("purchasegroup pack70", "PARITY"));
// packType by side: oracle BRONZE for 1 else GOLD; rust by real category
// (1,2 bronze / 3,4 silver / 5,6,70 gold).
for p in opg["purchase"].as_array().unwrap() {
let id = p["id"].as_u64().unwrap();
let want = if id == 1 { "BRONZE" } else { "GOLD" };
assert_eq!(p["packType"], want, "oracle packType for pack {id}");
}
for p in rpg["purchase"].as_array().unwrap() {
let id = p["id"].as_u64().unwrap();
let want = match id {
1 | 2 => "BRONZE",
3 | 4 => "SILVER",
_ => "GOLD",
};
assert_eq!(p["packType"], want, "rust packType for pack {id}");
}
matrix.push(("purchasegroup pack70", "DIFFERENT-BY-DESIGN"));
// ── OP 4: Store BUY (pack 1 Bronze, price 400, 5 cards) ────────────────
let o_bal0 = oracle.coins();
@@ -600,11 +614,22 @@ fn run_differential(core_base: &str, oracle: &Oracle, dir: &std::path::Path) {
.as_array()
.expect("rust itemList")
.clone();
assert_eq!(o_items.len(), 5, "oracle pack1 -> 5 cards");
assert_eq!(r_items.len(), 5, "rust pack1 -> 5 cards");
// STORE DIVERGED: rust serves the real Bronze Pack (10+2 = 12 cards); the
// oracle placeholder awards 5. Both debit the same 400-coin price.
assert_eq!(o_items.len(), 5, "oracle (rollback) pack1 -> 5 cards");
assert_eq!(r_items.len(), 12, "rust pack1 real Bronze Pack -> 12 cards");
assert_eq!(
o_buy["createPackResponse"]["numberItems"].as_i64().unwrap(),
5,
"oracle numberItems==5"
);
assert_eq!(
r_buy["createPackResponse"]["numberItems"].as_i64().unwrap(),
12,
"rust numberItems==12"
);
for b in [&o_buy, &r_buy] {
let cpr = &b["createPackResponse"];
assert_eq!(cpr["numberItems"].as_i64().unwrap(), 5, "numberItems==5");
assert_eq!(
cpr["purchasedPackId"].as_i64().unwrap(),
1,
@@ -619,9 +644,9 @@ fn run_differential(core_base: &str, oracle: &Oracle, dir: &std::path::Path) {
assert_eq!(
client.balance().unwrap() - r_bal0,
-400,
"rust BUY debits 400"
"rust BUY debits 400 (price parity)"
);
matrix.push(("Store BUY (pack1)", "PARITY"));
matrix.push(("Store BUY (pack1)", "DIFFERENT-BY-DESIGN"));
// Minted wire ids for the item ops that follow (both sides put them in the
// pending purchased pile).
let o_wire: Vec<i64> = o_items.iter().map(|i| i["id"].as_i64().unwrap()).collect();
@@ -1176,8 +1201,8 @@ fn run_differential(core_base: &str, oracle: &Oracle, dir: &std::path::Path) {
);
assert_eq!(
pack_ids(&r_pg_s),
vec![1, 5, 6, 7, SENTINEL_PACK_ID],
"rust empty (unknown SID) -> sentinel"
vec![1, 2, 3, 4, 5, 6, SENTINEL_PACK_ID],
"rust empty (unknown SID) -> real catalogue + sentinel"
);
for b in [&o_pg_s, &r_pg_s] {
let s = b["purchase"]
@@ -1188,7 +1213,7 @@ fn run_differential(core_base: &str, oracle: &Oracle, dir: &std::path::Path) {
.unwrap();
assert_eq!(s["state"], "active", "sentinel state active");
}
matrix.push(("purchasegroup sentinel", "PARITY"));
matrix.push(("purchasegroup sentinel", "DIFFERENT-BY-DESIGN"));
// ── OP 3c: purchasegroup — clean-v1 (empty + verified capability session) ─
// Oracle: register the launcher capability, open a session (auth), present the
@@ -1240,11 +1265,11 @@ fn run_differential(core_base: &str, oracle: &Oracle, dir: &std::path::Path) {
let r_pg_c: Value = serde_json::from_slice(&r_pg_c_resp.body).unwrap();
assert_eq!(
pack_ids(&r_pg_c),
vec![1, 5, 6, 7],
"rust clean-v1 strips the sentinel"
vec![1, 2, 3, 4, 5, 6],
"rust clean-v1 real catalogue, sentinel stripped"
);
assert!(!pack_ids(&r_pg_c).contains(&SENTINEL_PACK_ID));
matrix.push(("purchasegroup clean-v1", "PARITY"));
matrix.push(("purchasegroup clean-v1", "DIFFERENT-BY-DESIGN"));
// ── Emit the matrix for the run log. ────────────────────────────────────
eprintln!("\n===== economy_differential PARITY / DIFFERENT-BY-DESIGN matrix =====");
@@ -373,8 +373,8 @@ fn economy_sequence(base: &str, dir: &std::path::Path) -> SeqResult {
.as_array()
.expect("itemList")
.clone();
assert_eq!(items.len(), 5, "pack 1 awards 5 cards");
assert_eq!(bv["createPackResponse"]["numberItems"], 5);
assert_eq!(items.len(), 12, "pack 1 (real Bronze Pack) awards 12 cards");
assert_eq!(bv["createPackResponse"]["numberItems"], 12);
assert!(
items[0]["id"].as_i64().unwrap() >= 100_000_000,
"minted wire id above the FIFA floor"