A reward pack advertised its own id (70-75) as assetId, which is not a client art
asset, so its My Packs tile rendered blank. Reward tiles now carry their tier
store pack as the cover asset (bronze->1, silver->3, gold->5) while `id` stays the
pack own id (the open packId / SERVER_ID); the sentinel keeps its own id so it
stays an inert placeholder.
LIVE-MAPPED on the retail client 2026-08-18 across all three store tabs and two
reward tiles, which corrected an earlier wrong assumption:
* assetId only gates whether art renders AT ALL (unknown id -> blank tile).
* WHICH art is drawn comes from packType + packContentInfo.rareQuantity, NOT
assetId: BRONZE+1rare -> bronze card, BRONZE+3 -> silver, SILVER+1 -> gold,
SILVER+3 -> silver trio, GOLD+1 -> blue special, GOLD+3 -> red inform.
Remapping assetId 5->3 and 3->2 left both frames unchanged and only rotated
the featured player, which proves art is content-driven.
So a reward tile now shows the same cover as the equivalent purchasable pack (a
gold reward shows the blue-special art the 5000-coin Gold Pack shows - EA art
advertises an aspirational card rather than the tier colour). Regression test
reward_tiles_carry_a_renderable_cover_asset added; pack70 golden regenerated.
Reward/My-Packs tiles were emitted with no currencies row (dropped to avoid an
"undefined" payment label). But FIFA 17 opens My Packs through the store PURCHASE
flow (My Packs is a client-side filter over the store catalogue; the open-vs-buy
fork is client-side and no response selects it), so a tile with no purchase path
is not actionable — clicking it navigates instead of opening, sending no request.
Fix: keep the coins currency at the pack price (0 for reward packs; no extPrice,
so no `or %1s` mtx bug). The client then treats the tile as free and clicking
sends POST /purchased, which the server opens for free (owned-only redeem, no
debit). The empty-My-Packs sentinel keeps no currency row so it stays
non-openable. pack70 golden regenerated.
LIVE-PROVEN 2026-08-18: a reward Silver Pack opened and revealed cards on the
retail client (host log: POST /purchased/items 200 -> GET /purchased/items).
The all-groups landing (shown on first store entry) renders one tile per group
whose background is the client-bundled packs_backgrounds_%d.dds, selected by
displayGroupAssetId. Live-probed on the retail client 2026-08-18: index 0 is
blank; indices 1/2/3 render real pack art. Assign non-zero per category
(bronze=1, silver=2, gold/mypacks=3) so that landing shows native pack art
instead of blank shields. The persistent tabbed store (MY PACKS/BRONZE/SILVER/
GOLD PACKS) draws pack art from the packs themselves and is unaffected.
LIVE-CONFIRMED end-to-end: native tabbed store renders the real 6-pack catalogue
with correct prices (Gold 5000 / Premium Gold 7500), counts (12 items, 10 gold,
1/3 rares), pack art, and no "or %1s" line.
- 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.
Adds openfut-adapter-fifa17 fut::store_catalog — a pure, faithful Rust port of the
Python oracle's PACK_CATALOG + _pack_body + store_catalog assembly at production
flag defaults (FUT_STORE_DISPLAYGROUP=1, GROUPID=0, PRICE_PROBE=0):
- PackDef + PACK_CATALOG (ids 1/5/6/7/70; economy numbers are OpenFUT PLACEHOLDER,
wire shape is oracle-verified; 65534 deliberately absent).
- pack_body() (_pack_body port), sentinel_body() (id-65534 compatibility shim),
build_purchasegroup(unopened_ids, StoreMode) mirroring store_catalog(3627).
- Differential parity: fixtures generated from the Python oracle
(tests/fixtures/purchasegroup_{zero_sentinel,zero_clean,pack70}.json); Rust output
matches semantically (6 tests). Adapter 140 tests, host 24, fmt/clippy clean,
Python A-R oracle green.
PURE wire shaping — NOT wired into the live host. Serving purchasegroup from Rust
requires an authoritative Rust owner of unopenedPackIds, which is blocked on the
economy-authority prerequisite (R3): coins are one shared balance written by many
Python-oracle routes (BUY spend, quick-sell credit, SBC/match/objective rewards)
persisted to fut_profile.json, so no single coin-touching route can move without a
whole-cluster migration. No dual-write introduced; no production deployment.