diff --git a/fifa17-recon/docs/CLIENT_ROUTE_SURFACE.md b/fifa17-recon/docs/CLIENT_ROUTE_SURFACE.md index d345b41..9cd2b22 100644 --- a/fifa17-recon/docs/CLIENT_ROUTE_SURFACE.md +++ b/fifa17-recon/docs/CLIENT_ROUTE_SURFACE.md @@ -189,3 +189,63 @@ GET /ut/game/fifa17/club/consumables/development -> outcome=unknown_category e served empty. The client demonstrably asks for it, which is exactly the condition that function's own doc says should add an arm. Which families it should map to is NOT guessed here. + +### Success contract — STATIC_REVERSED (2026-08-22) + +The apply completion handler is `0x180035520`: + +```asm +0x180035529 mov ecx,DWORD PTR [rdx+0x1c] ; the ONLY field tested +0x18003552c test ecx,ecx +0x18003552e jne 0x18003555c ; nonzero -> FAILURE +0x18003553c lea rdx,[EVENT_CARDS_APPLY_CARD_SUCCESS] ; 0x1801f37f0 +0x180035569 lea rdx,[EVENT_CARDS_APPLY_CARD_FAILURE] ; 0x1801f3810 +``` + +It tests exactly one 32-bit field — the transport code — and **never inspects +the body**. `EVENT_CARDS_APPLY_CARD_SUCCESS` has precisely one reference in the +module, so this is the whole verdict path. + +This does NOT resemble the move ack (`0x180128600`), which builds per-item +verdict records and reports FAILURE on an EMPTY vector. The "`{}` is +known-broken" precedent is specific to that route and does not transfer here. + +Supporting structure: the response object's constructor `0x1800a4ce0` installs +vtable `0x1801fb5b0` and initialises its record vector at `+0x50`/`+0x58`/`+0x60` +EMPTY (0x20-byte elements); `0x1800682b0` is the matching destructor, freeing +that range with a 0x20 stride. An empty result is therefore a legal parsed state +for this response, unlike the move. + +Registration site: `0x1800357da` installs the completion handler and +`0x1800357e5` the response factory, back to back. + +**Probe response**: `{"itemData":[]}` — an object root (matching how the oracle's +method-agnostic `item/resource` route answers this path) containing an empty +vector (legal per the constructor). Labelled a PROBE. The client's SUCCESS only +requires transport code 0. + +## Consumables categories — nine, not seven (2026-08-22) + +Correcting the earlier claim that the two formation-modifier families "have no +group code, so no segment can reach them — the client's own gap". The client's +own switch says otherwise. Literal table at `0x1801f5a38` (under +`MyClubAdapterClass` / `CONSUMABLE_TYPE`); switch at `0x180048820` indexing by +`enum + 1` through the byte table at `0x180048a90` into the case table at +`0x180048a6c`: + +| CONSUMABLE_TYPE | segment | +|---|---| +| **-1 (unset)** | `development` | +| 1, 2 | `contracts` | +| 3 | `healing` | +| 4 | `fitness` | +| **16** | `formation` | +| 17 | `position` | +| 23 | `playStyle` | +| 24 | `managerLeagueModifier` | +| 0, 5..15, 18..22 | `training` (switch default) | + +`formation` was a SERVER gap, not a client one. `development` is the type-unset +bucket — index 0 of an `enum + 1` table — i.e. the unfiltered view; the eight +typed segments already reach all thirteen families exactly once, so it owns no +family privately and maps to their union.