fix(fifa17): subtypes 57/67 are the rare all-six training card

They were failing closed as "squad fitness". Four facts say otherwise: each
family holds exactly 21 rows = 7 types x 3 levels matching the published
"6 single attributes + 1 ALL" list; those six rows are the ONLY ones with
weightrare=2 while all 36 single-attribute rows are 0, and the published list
marks ALL rare; their amounts are exactly 3/6/10 against the documented ALL
card's +3/+6/+10; and bc=6 is one past the six real slots, an "all" sentinel,
with c0=0 reading as "not single-ATTRIBUTE" rather than "not single-target".

The misreading came from consumables.json's FUT_FITNESS_UC/MC label, which is
tool-authored -- build_consumables.py names the 7th element of its attribute
array -- and has no documented provenance. The bc/c0 values beside it ARE
reversed; only the name was not. The genuine squad-fitness card is subtype
220 in fcc_healingcards (10/20/30) and player fitness is 219; that family
stays unsupported.

The two families carry different authored ceilings, 15 single-attribute and
10 all-six, so ceiling_for() reports the right one per effect -- sending the
single-attribute ceiling for an all-six card would let a +15 all-six boost
through, granting 90 attribute points from a card worth 60.

Also corrects ENDPOINT_MAP row 7: ApplyCardByRes carries urlIndex 0x0e, which
resolves to ut/%s/item/resource, and the live verb is POST. The row claimed
PUT ut/%s/item for both apply RPCs, and that conflation is what kept the
"apply must ride PUT ut/%s/item" hypothesis alive until the POST capture
settled it -- every observed PUT ut/%s/item is a pile move.
This commit is contained in:
funman300
2026-08-22 23:33:43 +00:00
parent a9bac8be8e
commit 3bb6b4fc9d
2 changed files with 114 additions and 32 deletions
+12 -1
View File
@@ -939,7 +939,18 @@ freezes any of these — GAPs are "feature missing", not "crash".
| 4 | FutViewCards | `0x1801293d0` | GET `ut/%s/item` | `itemData`(0x16b) → **array[card-item]** via `0x18013fe00` [FREEZE-RISK] | HANDLED (utas `/item` `defs_route` serves `itemData`) | HIGH |
| 5 | FutActivateCard | `0x1801642c0` | PUT `ut/%s/item` (FUT_CLUB_ACTIVATE_ITEM_DP) | **none** (immediate `ret`) | ack — `{}` fine | HIGH |
| 6 | FutApplyCard | `0x18012a710` | PUT `ut/%s/item` (apply by itemId) | `itemData`(0x16b) → **array[updated card-item]** via `0x18013fe00` [FREEZE-RISK] | GAP | HIGH |
| 7 | FutApplyCardByRes | `0x18012ad10` | PUT `ut/%s/item` (apply by resourceId) | `itemData`(0x16b) → **array[updated card-item]** [FREEZE-RISK] | GAP | HIGH |
| 7 | FutApplyCardByRes | `0x18012ad10` | **POST** `ut/%s/item/resource/<rid>` (apply by resourceId) | `itemData`(0x16b) → **array[updated card-item]** [FREEZE-RISK] | **SERVED** (Rust host, contracts + attribute training) | HIGH |
> **Rows 6 and 7 are NOT the same route.** `ApplyCardByRes` carries urlIndex
> `0x0e`, which resolves to `ut/%s/item/resource` — not `ut/%s/item`
> (`plan-2026-08-05-pack-opening.md:505-506`, shared with `DiscardCardByRes` and
> `MoveCardByRes`). The verb is **POST**, live-proven by a real-client capture:
> `POST /ut/game/fifa17/item/resource/5001004` `{"apply":[{"id":100000003}]}`.
> This row previously read `PUT ut/%s/item` for both, and that conflation is what
> kept the "apply must ride `PUT ut/%s/item`" hypothesis alive
> (`CLIENT_ROUTE_SURFACE.md:104-106`) until the POST capture settled it — every
> observed `PUT ut/%s/item` is a pile MOVE, never an apply.
| 8 | FutDiscardCard | `0x180127300` | DELETE `ut/delete/%s/item` (CardsDiscardCard) | `items`(0x171) → **array[int ids]** [FREEZE-RISK]; `totalCredits`(0x326) → int; `id`(0x15c) → int | GAP | HIGH |
| 9 | FutDiscardCardByRes | `0x1801279c0` | DELETE `ut/delete/%s/item` (by res) | `totalCredits`(0x326) → int | GAP | HIGH |
| 10 | FutMoveCard | `0x180128600` | PUT `ut/%s/item` (move) | `itemData`(0x16b) → **array** [FREEZE-RISK]; `chemistry`(0x81) → bool | GAP | HIGH |