From 06d94bb37d8c8c59ad687eefc2f404cc32a9205c Mon Sep 17 00:00:00 2001 From: funman300 Date: Fri, 21 Aug 2026 23:53:14 +0000 Subject: [PATCH] fix(fifa17): club items are zero-value, not a fallback to an invented price `value_for_definition` declined for anything non-player without a catalog rating, which sent club items into the legacy ladder and paid an invented 150 each. That is wrong, and the client says so. `shape_club_item` sends neither `rating` nor `discardValue`, and cardtype 7/9 are NOT re-rated by the client (the merge jump table sends them to the shared tail), so the client computes for itself from record +0xb4 == 0: level 1, `0 * price / 100` == 0. It DISPLAYS 0. Paying 150 invents value the player was never shown. Move the decline boundary onto the real distinction, which is `client_rerates`: * NOT re-rated (cardtypes 1, 6, 7, 8, 9) -> the server's rating is what the client prices with, so Core's value is authoritative even at 0. * RE-RATED (2, 3, 4, 5, 10 -- the staff families) -> the client substitutes its own database value, so without a catalog rating we genuinely cannot match it and must decline rather than guess. Over the 1717-definition corpus this takes "declined -> legacy" from 6 to ZERO: every definition is now priced by the one authoritative table and no generic fallback is reachable in the current corpus. The six club items price at exactly 0; staff and consumables are unchanged. Adapter 248 lib, host 120 lib, fmt and clippy clean. --- openfut-adapter-fifa17/src/fut/discard.rs | 58 +++++++++++++++++++---- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/openfut-adapter-fifa17/src/fut/discard.rs b/openfut-adapter-fifa17/src/fut/discard.rs index 567ad8c..86fdead 100644 --- a/openfut-adapter-fifa17/src/fut/discard.rs +++ b/openfut-adapter-fifa17/src/fut/discard.rs @@ -256,17 +256,23 @@ pub fn discard_value(cardtype: u8, rating: u8, rare: i64) -> i64 { /// and the number credited cannot diverge. /// /// `None` means "not known", never "worthless", and the caller falls back to the -/// legacy ladder rather than paying 0: +/// legacy ladder rather than inventing a price: /// /// * `cardtype == 0` — the subtype decodes to no table row at all. -/// * a NON-PLAYER with no `catalog_rating`. Core models every non-player's -/// `overall` as 0, and rating 0 prices at 0 coins, so trusting it would pay -/// nothing for a real card. +/// * a CLIENT-RE-RATED cardtype ([`client_rerates`]: the five staff families) +/// with no `catalog_rating`. Those price from the client's OWN database, so +/// without that value we cannot match what it displays. /// -/// A PLAYER with no catalog rating legitimately uses Core's rating: cardtype 1 -/// is not re-rated by the client, so Core is authoritative there. For the -/// cardtypes that ARE re-rated ([`client_rerates`]) the catalog rating is the -/// client's own value and must be present, or we cannot match its display. +/// Everything else uses `catalog_rating`, falling back to Core's rating. For the +/// cardtypes the client does NOT re-rate (1, 6, 7, 8, 9) the server's rating is +/// authoritative — whatever we send is what the client prices with — so Core's +/// value is the right answer even when it is 0. +/// +/// That zero is not a gap. A club item's wire record +/// ([`super::item::shape_club_item`]) carries neither `rating` nor +/// `discardValue`, so the client computes for itself from `+0xb4 == 0`: level 1, +/// and `0 * price / 100 == 0`. **The client displays 0, so 0 is the correct +/// payout.** Paying anything else would invent value the player was never shown. pub fn value_for_definition( subtype: i64, rareflag: i64, @@ -279,7 +285,10 @@ pub fn value_for_definition( } let rating = match catalog_rating { Some(r) => r, - None if cardtype == 1 => core_rating, + // Not re-rated by the client => whatever the server sends is what it + // prices with, so Core's rating is authoritative even at 0. + None if !client_rerates(cardtype) => core_rating, + // Re-rated => the client substitutes its own value and we cannot match it. None => return None, }; Some(discard_value(cardtype, rating, rareflag)) @@ -417,6 +426,37 @@ mod tests { assert_eq!(value_for_definition(6, 0, Some(66), 0), Some(36)); } + /// CLUB ITEMS ARE ZERO-VALUE under the current projection, and that is a + /// derived fact rather than a gap. `shape_club_item` sends no `rating` and no + /// `discardValue`, so the client computes for itself from record `+0xb4 == 0`: + /// level 1, `0 * price / 100 == 0`. It DISPLAYS 0, so 0 is the only payout + /// that matches. The old ladder invented 150 for each of these. + #[test] + fn club_items_are_zero_value_not_a_fallback_to_an_invented_price() { + use crate::fut::content_taxonomy as tax; + for subtype in [ + tax::KIT_SUBTYPE, + tax::STADIUM_SUBTYPE, + tax::BADGE_SUBTYPE, + tax::BALL_SUBTYPE, + tax::LEAGUE_LOGO_SUBTYPE, + ] { + let ct = cardtype_for_subtype(subtype); + assert!( + !client_rerates(ct), + "subtype {subtype} must not be re-rated" + ); + assert_eq!( + value_for_definition(subtype, 0, None, 0), + Some(0), + "subtype {subtype} must price at exactly 0, not decline to a ladder" + ); + } + // A staff family, by contrast, DECLINES without its rating -- we cannot + // know what the client re-rated it to. + assert_eq!(value_for_definition(6, 0, None, 0), None); + } + /// The subtype decode must agree with the settled club-item subtypes and the /// staff family selector this crate already carries. #[test]