ResolveActiveSeason() auto-detected the active MythicPlusSeason as the
highest-StartTimeEvent row of the highest expansion. In Midnight that row is
season 122, a placeholder with zero MythicPlusSeasonRewardLevels rows, while the
real Season 1 content season is 117 (reward-level table 233-272, consistent with
the resolved display season 34). Picking 122 made GetVaultRewardLevelCap() and
GetVaultActivityTierId() return 0, so the Great Vault reward level was left
UNCAPPED (a +18 key granted a +18-scaled vault item instead of the retail +10
cap) and the wrong season id was reported to the client.
Verified against the 69299 client DB2 (MythicPlusSeason, MythicPlusSeasonReward-
Levels, MythicPlusSeasonTrackedMap/TrackedAffix, DisplaySeason, KeystoneAffix):
season 122/118 carry no reward levels; 117 = Midnight S1, 120 = Midnight S2
(vault 305-318, matching the community mythicpl.us S2 tables).
Auto-detect now considers only seasons that carry reward-level rows (a live
vault-granting season always does), falling back to the previous unfiltered scan
only when the DB2 has no reward-level data at all (stripped/older client). This
keeps the active season a real content season and consistent with the display
season on both the live snapshot (-> 117) and the newer S2 snapshot (-> 120).
Affix rotation and dungeon pool were checked against the same DB2 and are already
correct (both DB2-derived; affix set {9,10,147,148,158,160,162,165} matches
TrackedAffix for display season 34 exactly).
rebuilt from the feature branches, so anything living there alone is effectively unsaved. This puts
the work on the golden source where it belongs.
What lands here:
1. THE 94-RECORD CATALOG WRITER. The writer parsed only the first 9 product records; it now decodes
the whole message - 94 products, 116 deliverables, 21 groups, 97 shop entries, 0 bytes remainder -
with a byte-exact round trip verified over 12 captures across 3 client builds, plus a mutation
test (fields rewritten to DIFFERENT lengths) and a splice test. The old ceiling was
FRAME_AFTER_BLOCKA = 18 standing in for DisplayInfo presence bits; product 9 (Argi, id 108) is the
first with hasFileDataID = 0, which shifts everything by 4 bytes.
It also corrects three field mappings that were reading the wrong data entirely: ProductID at
record+81 is really DisplayInfo.fileDataID (card artwork), Flags at record+85 is
DisplayInfo.modelSceneID, and the header is 24 bytes / 6 u32, not 28 / 7.
2. THE PURCHASE FIX. SMSG_BATTLE_PAY_PURCHASE_UPDATE has no leading Result field; we were writing
one, so the client read our always-zero Result as the record count and parsed ZERO purchase
records. That silently broke every purchase: with confirmation on, the status-9 record never
reached the client so no prompt appeared and nothing was charged; with it off, the goods arrived
but the UI hung on "Connecting". Its sibling GET_PURCHASE_LIST_RESPONSE genuinely does have a
leading Result - the two share a record type but not a header.
3. THE OWNERSHIP FIX. The catalog blob is a capture from a real retail account and carried that
account's ownership state, which we shipped back verbatim: Product.Eligibility == 2 (Owned) and
Deliverable.AlreadyOwns == 1 on 21 deliverables, affecting 12 products. Both are now cleared -
clearing only Eligibility restores the Buy button but PurchaseProduct still never sends the CMSG.
4. NOT overwriting Product.Flags with our admin DisplayFlags. Product.Flags is a separate unreflected
enum whose bits 1/3 drive buyableHere; writing our value (0 for all 66 rows) over it would have
made every slot-pinned product unpurchasable. Display flags go to DisplayInfo.Flags, which is what
sharedData.flags actually reads.
Enum.BattlepayDisplayFlags is now fully recovered (12 values, registrar RVA 0x13EF840).
5. VAS status queries - CMSG_UPDATE_VAS_PURCHASE_STATES and CMSG_VAS_GET_SERVICE_STATUS, which every
client sends at character select and which were dropped by STATUS_IGNORED without even a log line.