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:
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user