diff --git a/fifa17-recon/docs/CLIENT_ROUTE_SURFACE.md b/fifa17-recon/docs/CLIENT_ROUTE_SURFACE.md index 20bedd4..1ebe243 100644 --- a/fifa17-recon/docs/CLIENT_ROUTE_SURFACE.md +++ b/fifa17-recon/docs/CLIENT_ROUTE_SURFACE.md @@ -300,3 +300,64 @@ Unresolved, and NOT to be guessed: the fcc tables are loaded by `FIFA17.exe`, no CardsDLL (the table-name and column literals are absent from the DLL), so the reader that would settle amount-vs-weight lives in the EXE. Status stays **EFFECT_UNKNOWN**. + +## Post-ACK behaviour — OUTCOME B, LIVE_PROVEN (2026-08-22) + +Captured with the staging probe answering `200 {"itemData":[]}` and mutating +nothing: + +``` +T0 POST /ut/game/fifa17/item/resource/5001004 {"apply":[{"id":100000003}]} +T1 200 {"itemData":[]} +T2 callback -> SUCCESS (no failure event; ZERO ut/delete/auth; session alive) +T4 GET club/consumables/contracts <- refresh of the SOURCE list +T5 GET club/consumables/development +T6 GET squad/active <- refresh of the TARGET +T7 no second mutation of any kind +``` + +So of the candidate protocols: + +``` +B) POST resource -> ACK -> client performs GET refresh + -> the SERVER is expected to have mutated state +``` + +Ruled out by observation: (A) the response carries the modified state — the body +was empty and the client was satisfied; (C) a follow-up generic PUT/item — none +was sent; (D) another route performs the mutation — nothing else was called. + +Three consequences. + +1. **The success verdict is transport-only, confirmed live.** The static read of + `0x180035520` said the body is never inspected; an empty `itemData` produced a + clean success and a surviving session, which is that prediction holding. +2. **The server owns the effect entirely.** The client does not compute one; it + re-reads. This is the good failure mode: a wrong server-side effect cannot be + masked by client-side optimism, and the refresh will always show server truth. + Here the refresh correctly showed `contracts copies=3` and an unchanged squad, + because the probe consumed nothing. +3. **There is no client-side amount to harvest.** Since the client never renders + an optimistic "+N games" of its own, the live path cannot reveal the grant + size. The number the client DISPLAYS on a contract card comes from the wire + `contract` atom (0xb8 -> record+0x8c; see `fut_consumables.py`, which notes + categories 2 and 3 ignore `amount` and read `contract`) — i.e. the server + tells the client what the card is worth. + +That last point matters for honesty: our oracle has been sending the placeholder +`7` for that atom, so every contract card this project has ever shown a player +said "7" because WE said 7. Recovering EA's real value is not reachable from the +client's behaviour; it needs the `FIFA17.exe` reader of `fcc_contractcards`, or +it becomes an explicit design decision. Status: **EFFECT_UNKNOWN**. + +### Boundary status + +| aspect | status | +|---|---| +| route, method, source encoding, target encoding | LIVE_PROVEN | +| success condition (`[obj+0x1c] == 0`, body ignored) | STATIC_REVERSED + LIVE_CONFIRMED | +| response shape accepted by the client | LIVE_PROVEN (`{"itemData":[]}`, session survived) | +| post-ACK protocol | LIVE_PROVEN — outcome B | +| batching | UNPROVEN — refused, never guessed | +| contract effect / grant size | UNKNOWN (placeholder source refuted) | +| source instance selection with multiple copies | UNDETERMINED (only 1 copy owned) |