fifa17: price quick-sell from the client's own discard table

Quick-sell paid an invented five-tier rating ladder (its own comment said
"PLACEHOLDER, not EA-authentic"). It was blind to card type and rareflag, so a
94-rated TOTW special and a 94-rated gold common both sold for 1500, and every
non-player -- whose Core overall is 0 -- sold for the flat 150 floor. The ladder
existed in three places (adapter wire, host payout, an integration test's private
copy), which is a drift waiting to happen.

Add openfut-adapter-fifa17::fut::discard: the client's own fcc_discardcoins
table and its formula, round_half_up(rating * price / 100), keyed
(cardtype, level, rare). All of it is already reversed in
plan-2026-08-05-store-subsystem.md 3.6 and was verified there against 22 live
club items, 22 of 22 exact. DISCARD_COINS is generated from
fifa17-recon/data/tables/fcc_discardcoins.json and a test re-reads that file and
asserts row-for-row agreement, so the transcription cannot drift.

Collapse the three ladders into one method. ItemIdentityResolver::discard_value
both stamps the wire discardValue and prices the sale, because a non-zero
discardValue suppresses the client's local computation -- whatever is sent is
what the player is promised. The host's quick_sell_value is deleted and the
integration test's copy now calls the single implementation. A test with a
resolver double returning an impossible price proves the credit follows the wire;
reverting the payout to a ladder fails it.

Gated on OPENFUT_FIFA17_DISCARD_TABLE=1, default off: switching revalues the real
1991-item club 10.5x (1,820,400 -> 19,128,955 coins if wholly liquidated), up for
specials and DOWN for consumables, which the ladder overpaid 5.5x. That is an
operator's decision.

Staff decline to the ladder rather than pay 0: the client re-rates cardtypes
2/3/4/5/10 from its own DB and their rating is not imported. Deliberately not
guessed -- see the falsifier in the doc.

Verified on staging with the real club, both modes: flag off 1500 wire / 1500
paid; flag on 23760 wire / 23760 paid on an r99 rareflag-11 card (99*24000/100).
Consumables price from their catalog rating and agree with the client's own
computation. Adapter 244 lib tests, host 121 lib + 45 host_test, fmt and clippy
clean.
This commit is contained in:
funman300
2026-08-21 22:40:04 +00:00
parent 274838cc2e
commit 49b18dd4ac
10 changed files with 693 additions and 35 deletions
@@ -607,6 +607,45 @@ cardtype 6, live-confirmed on the two resident consumables, so for exactly the
items the warning was aimed at, the server's rating and rare flag are
authoritative.
**APPLIED (2026-08-21), behind a default-off flag.** The table and the formula
above are now in Rust as `openfut-adapter-fifa17::fut::discard`:
`cardtype_for_subtype` is the decode, `discard_level` the 3/2/1 ladder,
`table_price` the 141-row lookup (`0` for an absent key) and `discard_value` the
`round_half_up(rating * price / 100)` formula. `DISCARD_COINS` is generated from
`fifa17-recon/data/tables/fcc_discardcoins.json` and a test re-reads that file
and asserts they still agree row for row, so the two cannot drift. The four
worked examples above (`8 * rating`, `4 * rating`, the 50-rated bronze at 15,
and an absent key paying 0) are tests.
Wire and wallet are now ONE method. `ItemIdentityResolver::discard_value` both
stamps the card's `discardValue` and prices the sale, because a non-zero
`discardValue` suppresses the client's local computation — so whatever is sent
is what the player is promised. The host's separate `quick_sell_value` ladder is
deleted (it was a second copy that could drift), and a test with a resolver
double returning an impossible price proves the credit follows the wire.
`OPENFUT_FIFA17_DISCARD_TABLE=1` turns the table on; the default keeps the old
placeholder ladder because switching revalues an existing club by **10.5x**
(measured over the real 1991-item club: 1,820,400 -> 19,128,955 coins if wholly
liquidated). Players drive it (an r93 special goes 1500 -> 74,400); consumables
move the OTHER way (2,400 -> 437, i.e. the ladder was overpaying 5.5x).
NOT yet exact: the five staff families. Measured on staging, a staff wire record
carries NO `rating`, NO `rareflag` and NO `discardValue`, so the client prices it
wholly from its own re-rate of cardtypes 2/3/4/5/10 and the server never sees the
number. The catalog's `rating` is `None` and Core's `overall` is 0, so pricing
DECLINES for staff and falls back to the ladder rather than paying 0 coins. The
server therefore pays 150 while the client displays something else — a divergence
that PRE-DATES this work (the old ladder paid the same 150 and also sent no
`discardValue`).
It is left open deliberately. The only rating-shaped column in those five tables
is `value` (66 GK coach, 88 manager), but that `value` is the discard rating is an
INFERENCE: §3.6 names the tables and never the column. Importing it would guess an
economy, which is what this change removed. FALSIFIER, one launch: read the
discard value the client shows on a staff card — 36 on the `value`-66 GK coach
confirms it and the fix is to carry `value` through `openfut-import-fifa17`.
### 3.7 `duplicateItemIdList`
CONFIRMED shape, INFERRED effect, never observed. Element deser `FUN_180138e10`,