fifa17-recon: RETRACT the FutMoveCard "no skip handler" claim; stage the ack shape

RETRACTION. This repo claimed, in REBUILD_RESEARCH S14c and in utas_server's
item_route comment, that FutMoveCard 0x180128600 "HAS NO SKIP HANDLER
(FUN_180135ff0 appears zero times, unique among FUT deserializers)" and "parses
only itemData -> dreamSquads". Every part of that is false. Full decompile:

  FUN_180135ff0 call sites : 2   (offsets 5006, 6080)
  atoms parsed             : 7   active dreamSquads id itemData pile reason success

Cause: the decompile was written out as src[:4000] and then searched. The function
is 6193 chars, so BOTH skip-handler call sites and four of the seven atoms lay past
the cut. An absence was reported from a truncated listing -- the same failure mode
as the Memory.getBytes bytearray scan that silently returned zero hits. Never
conclude an absence without asserting the searched region covers the function.

Cost: the false claim implied "any extra key desyncs this parser", which sent the
investigation after client-side state for seven attempts, and the derived premise
"the deciding factor is client-side state, not the wire" was wrong too.

VERIFIED SHAPE. PUT ut/%s/item is not an ack endpoint; it returns per-item VERDICT
records:
  id(0x15c)      INT     0x1801c79d0  -> record+0x00
  pile(0x226)    STRING  0x1801c7aa0  -> enum 0x180142650 (club=7 purchased=6 trade=5)
  success(0x2fa) BOOL    0x1801c7620  -> record+0x0c
  reason(0x279)  STRING               -> "Destination Full" = 0xf
  dreamSquads(0xe9) INT array;  else  -> FUN_180135ff0 (skip)

The completion handler raises EVENT_CARDS_MOVE_CARD_FAILURE when the record vector
is EMPTY or record+0x0c != 1, and success is initialised to '\0' per element. So
every body ever returned reported the move as FAILED, {} included. Quick sell
survives an identical {} because its callbacks read only the transport code and
ignore the body -- that is the whole asymmetry, and it was on the wire after all.

STAGED, NOT DEFAULTED. FUT_MOVE_BODY=ack emits the correct shape; the default stays
`empty` because sufficiency is untested. One launch settles it.

The ack is answered BEFORE the `if moved:` gate: under FUT_PACK_AUTOCLUB=1 the
cards are already in the club when the reveal asks to move them, so move_items()
returns nothing and a moved-derived body would be zero-record -- failing in exactly
the configuration ack exists to fix. Caught in review before it ran. success is
asserted only for ids that were moved now or are already in the club; anything else
gets an honest success:false rather than an invented verdict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
This commit is contained in:
funman300
2026-08-04 11:05:38 -07:00
parent a93a8bcdd7
commit f488793b34
2 changed files with 217 additions and 30 deletions
+113 -1
View File
@@ -507,7 +507,7 @@ POSTs `ut/delete/auth`. **Eliminated, each by live test:**
| hypothesis | result |
|---|---|
| missing `chemistry`(0x81) | failed without it too |
| unknown keys + no skip handler in `0x180128600` | real finding (it genuinely has none), but `dreamSquads`-only still failed |
| unknown keys + no skip handler in `0x180128600` | **RETRACTED, FALSE** -- it has TWO skip handlers, see S16 |
| POW saturating the HTTP layer (109k reqs) | same failure with POW off |
| missing `FUT_RS4_URL_<CALL>` keys | netwatch logged **ZERO** non-loopback dials; 146 were genuinely missing and are now served, but they were not the cause |
| the response body at all | **`{}` fails too** |
@@ -531,3 +531,115 @@ Four of six failed hypotheses were things testable before proposing them. The tw
findings that actually moved this forward were the USER's: the screenshot with the
error text, and the Quick Sell result. **Ask for the on-screen text and try the
neighbouring action FIRST** — both were cheaper than any decompile done here.
## 15. SURPRISE FOUR: `ut/%s/squad/mode/draft/state` (2026-08-04, observation session)
Recorded immediately per the session checklist. **A live client request to a URL that is
not in the 45-template table.**
```
[10:35:28] GET /ut/game/fifa17/squad/mode/draft/state
User-Agent: ProtoHttp 1.3/DS 15.1.2.1.0 (Windows) <- the real client
[10:35:28] -> 200 {"id":0,"personaId":...,"squadName":"OpenFUT","formation":"f442",...}
```
The template table holds `ut/%s/squad/mode` (SQUADMODE) and `ut/%s/draft/mode` (DRAFT)
as separate entries. The real endpoint is a **suffixed composition of neither**:
`squad/mode` + `/draft/state`. This is the fourth time a suffix endpoint has been
invisible to the static table (`squad/list`, `user/club`, `club/stats/*`, now this).
### What we answered, and why it is wrong
Our generic `G + r"/squad"` route matched first and returned the **full active-squad
object** (23 slots, nested itemData, the 33-int `custom` string) to an endpoint asking for
DRAFT STATE. Identical bug class to `club/stats/*`, which was found the same way and
returned the entire club inventory to a stats endpoint.
Per §9, `FutGetDraftCurrentState` (deser `0x180147070`) parses `roundsInfo`(0x293) plus a
state enum: `CAPTAIN_DRAFT`, `FORMATION_DRAFT`, `PLAYER_DRAFT`, `MANAGER_DRAFT`,
`COMPLETED_DRAFT`, `READY_FOR_MATCH`, `READY_FOR_REWARDS`, `PICK_DIFFICULTY`, `INVALID`.
Feeding that parser a squad object is a textbook type-mismatch freeze candidate.
### Status
**PROVEN:** the client requested this URL; we answered with a squad object; the game
became unresponsive immediately afterwards. `FIFA17.exe` was still running with **no
crash dump written**, so this was a hang, not an access violation.
**NOT PROVEN:** that the wrong body caused the hang. It is the obvious candidate and the
timing is exact, but no measurement has isolated it. Do not record this as the cause until
something confirms it.
**NOT FIXED.** Observed during an assessment-only session; recorded and left alone
deliberately.
### Also observed this session
`Single Player -> Seasons` raises *"There was a problem communicating with the FIFA
Ultimate Team servers"* **with ZERO requests to any layer** (UTAS, Blaze and POW logs all
show nothing but unrelated pings and one `CensusData::subscribeToCensusDataUpdates`).
Nothing failed because nothing was asked. This is the third time FIFA's error text has
described a network problem that did not happen; the message is a generic client-side
refusal. POW/EASFC was OFF this session and is the leading untested hypothesis for the
gate, disconfirmable in one launch with `FUT_POW=1`.
## 16. RETRACTION: FutMoveCard's "no skip handler" claim was FALSE
**Retracted 2026-08-04.** Sections 14c and the `utas_server.item_route` comment
claimed:
> FutMoveCard `0x180128600` HAS NO SKIP HANDLER. Every other FUT deserializer routes
> an unrecognised key to `FUN_180135ff0`; this one calls it ZERO times. It parses
> exactly two atoms, `itemData` and `dreamSquads`.
**Every part of that is wrong.** Verified on a full decompile:
```
FUN_180135ff0 call sites : 2 (offsets 5006 and 6080)
atoms parsed : 7 active, dreamSquads, id, itemData, pile, reason, success
```
**Cause of the error:** the decompile was written out as `src[:4000]` and then
searched. The function is **6193 characters**. Both skip-handler call sites and four
of the seven atoms lie beyond the cut. An absence was reported from a truncated
listing.
**Lesson, and it has now cost twice:** this is the same failure mode as the
`Memory.getBytes` bytearray scan that silently returned zero hits. *Never conclude an
absence from a truncated or unverified-length extraction.* Print the length, or
assert the region searched covers the whole function.
**Cost:** the false claim implied "any extra key desyncs this parser", which pointed
the whole investigation at client-side state for seven attempts. The runbook premise
"the deciding factor is client-side state, not the wire" derived from it and was
also wrong.
### The verified schema
`PUT ut/%s/item` is **not** an ack endpoint. It returns per-item VERDICT records:
```json
{"itemData":[{"id": 100000123, "pile": "club", "success": true}]}
```
| atom | key | getter | destination |
|---|---|---|---|
| 0x15c | `id` | INT `0x1801c79d0` | record+0x00 |
| 0x226 | `pile` | STRING `0x1801c7aa0` -> enum `0x180142650` (club=7, purchased=6, trade=5) | record+0x08 |
| 0x2fa | `success` | BOOL `0x1801c7620` | record+0x0c |
| 0x279 | `reason` | STRING; `"Destination Full"` maps to 0xf | record+0x10 |
| 0xe9 | `dreamSquads` | INT array | |
| else | | `FUN_180135ff0` (skip) | |
The completion handler raises `EVENT_CARDS_MOVE_CARD_FAILURE` when the record vector
is **empty** or when `record+0x0c != 1`, and `success` is initialised to `'\0'` at
the top of every element loop. So **every body ever returned reported failure**:
full cards, `+chemistry`, `dreamSquads`-only, and `{}` (zero records, fails the first
guard outright).
Quick sell survives an identical `{}` because its callbacks check only the transport
error code and never read the body. That is the entire asymmetry, and it was on the
wire the whole time.
**Status: necessary condition identified, sufficiency UNTESTED.** Staged behind
`FUT_MOVE_BODY=ack`, default still `empty`. One launch settles it.