fifa17-recon: SOLVED "Send to Club" -- it was the response body all along

Live 2026-08-04 with FUT_MOVE_BODY=ack and FUT_PACK_AUTOCLUB=0. Bought a bronze pack,
opened it, chose Send to Club. The session SURVIVED, the five cards persisted into the
club pile, and there was no ut/delete/auth logout -- the logout that accompanied all
seven previous attempts.

  11:15:48 PUT /item
    req {"itemData":[{"id":100000125,"pile":"club","swap":0,"tradeId":0}, ... x5]}
    res {"itemData":[{"id":100000125,"pile":"club","success":true}, ... x5]}
  11:15:49 GET /user/credits      session alive
  11:15:51 GET /hub               no error dialog
  11:16:06 GET /club?year=2017... MY CLUB opened

PUT ut/%s/item never was an ack endpoint. It builds per-item VERDICT records, and the
completion handler raises EVENT_CARDS_MOVE_CARD_FAILURE when the vector is empty or
success != 1. Every body this project ever returned, {} included, told the client the
move had FAILED, and the client ended the FUT session because that is what that event
does. We were failing our own move.

Defaults flipped: FUT_MOVE_BODY empty -> ack, FUT_PACK_AUTOCLUB 1 -> 0. The autoclub
workaround is retired.

New intel, captured for the first time because the client had never got this far: the
request carries `swap` and `tradeId` beside id/pile. We ignore both and the move
succeeded, so neither is load-bearing for a pending-to-club move.

PROCESS, and this is the part worth keeping. The FIRST attempt at this test produced
no PUT /item at all: FUT_PACK_AUTOCLUB=1 had already emptied the pending pile at
purchase time, so the reveal screen had nothing to assign and the client never issued
the request. The workaround for the bug was hiding the bug. Before testing a fix,
check the configuration still lets the client make the call the fix is for.

Two self-inflicted incidents, both recorded in REBUILD_RESEARCH S17:
- Restarting the server to inject a flag WHILE FIFA was running produced the exact
  "error connecting to FIFA 17 Ultimate Team" dialog this project spent weeks chasing,
  from a plain connection refusal during the ~30s window. Restart only at the main
  menu, and check the log for ProtoHttp requests before blaming a response.
- pgrep -f matched the invoking shell twice, killing it before the restart, because
  the same command contained the literal script name in a later clause.

Docs updated: REBUILD_RESEARCH S17 (the solve), priority-2026-08 S2/S3.1/S6 (next task
is now the MY CLUB counter), PROJECT_REPORT 6a, HANDOFF 5a plus the stale "FutMoveCard
has no skip handler" claim in S3 and a new 5d for Seasons/Draft.

380 + 51 checks green.

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:20:03 -07:00
parent 18d864908e
commit 5b139864ee
5 changed files with 222 additions and 96 deletions
+71
View File
@@ -643,3 +643,74 @@ wire the whole time.
**Status: necessary condition identified, sufficiency UNTESTED.** Staged behind
`FUT_MOVE_BODY=ack`, default still `empty`. One launch settles it.
---
## 17. SOLVED: "Send to Club" (2026-08-04, one launch)
`FUT_MOVE_BODY=ack`, `FUT_PACK_AUTOCLUB=0`. Bought a bronze pack, opened it, chose
Send to Club. **The session survived.** The five cards persisted into the club pile
and the client carried on to the hub and then into MY CLUB.
```
11:15:42 POST /purchased/items pack bought, 8400 -> 8000
11:15:43 GET /purchased/items 5 items in the pending pile
11:15:48 PUT /item
req {"itemData":[{"id":100000125,"pile":"club","swap":0,"tradeId":0}, ... x5]}
res {"itemData":[{"id":100000125,"pile":"club","success":true}, ... x5]}
11:15:49 GET /user/credits session alive
11:15:51 GET /hub no error dialog
11:16:05 GET /club/stats/{staff,year,consumables}
11:16:06 GET /club?year=2017&type=player&count=11&... MY CLUB opened
```
**No `ut/delete/auth`.** Every one of the seven previous attempts logged that logout
within a second or two. This run has none. Profile on disk afterwards: 109 items, 86
in the club pile, all five new ids present.
### What this settles
The failure was always the response body. The client was told, by every body this
project ever returned including a bare `{}`, that the move had **failed**, and it
ended the FUT session because that is what `EVENT_CARDS_MOVE_CARD_FAILURE` does. The
"deciding factor is client-side state, not the wire" premise recorded in §14c was
wrong, and §16 explains exactly which truncated decompile produced it.
Both defaults are flipped in `utas_server.py`: `FUT_MOVE_BODY=ack`, and
`FUT_PACK_AUTOCLUB` now defaults **off**.
### New intel: the request shape
Captured for the first time (the client had never successfully reached this path):
```json
{"itemData":[{"id":100000125,"pile":"club","swap":0,"tradeId":0}, ...]}
```
`swap` and `tradeId` accompany `id` and `pile`. We ignore both and the move
succeeded, so neither is load-bearing for a pending-pile-to-club move. `TODO/CONFIRM`
what `swap` means for a squad-slot exchange, where it plausibly is.
### Process note, and it is not a small one
The first attempt at this test produced **no `PUT /item` at all**. `FUT_PACK_AUTOCLUB=1`
had already emptied the pending pile at purchase time, so the reveal screen had
nothing to assign and the client never issued the request. The workaround for the bug
was hiding the bug: it removed the only path that exercises the broken endpoint.
The lesson generalises. A workaround that suppresses a request suppresses the evidence
too. Before testing a fix, check that the configuration still lets the client make the
call the fix is for.
Two self-inflicted incidents in the same session, both worth recording:
- The server was restarted to inject the flag **while FIFA was already running**, and
the user walked into the FUT hub during the roughly 30-second window with nothing on
:8099. That produced the exact "error connecting to FIFA 17 Ultimate Team" dialog
this project has spent weeks chasing, from a plain connection refusal. Restart only
when the client is at the main menu, and check the log for `ProtoHttp` requests
before attributing a failure to a response.
- `pgrep -f`/`pkill -f` matched the running shell twice, because the command that ran
the pattern also contained the literal script name in a later clause. It killed the
invoking shell before it reached the restart. Split the kill and the start into
separate commands, or obfuscate every occurrence.
+23 -3
View File
@@ -109,6 +109,20 @@ prove there is no second one.
## 2. The next task
> **RESOLVED 2026-08-04, the same day this was written.** The launch happened and it
> worked: five cards sent to the club, session survived, no logout, cards persisted.
> `FUT_MOVE_BODY=ack` and `FUT_PACK_AUTOCLUB=0` are now the defaults. Full account in
> `REBUILD_RESEARCH.md` §17. The section below is left as written, because the
> reasoning that put this first is the part worth reusing, not the outcome.
>
> One correction it earned: the pre-launch instruction to leave `FUT_PACK_AUTOCLUB=1`
> alone was wrong. Autoclub empties the pending pile at purchase time, so the reveal
> screen has nothing to assign and the client never sends the request. The first
> attempt produced no `PUT /item` at all. A workaround that suppresses a request
> suppresses the evidence for the bug it works around.
>
> **The next task is now §3.2, the MY CLUB counter.**
**One launch. Set `FUT_MOVE_BODY=ack`. Buy a pack, reveal it, press Send to Club.**
Everything about this is cheap. One flag, one existing menu path, one binary outcome
@@ -163,7 +177,13 @@ it, because an entry that cannot be disconfirmed does not belong on this list.
### 3.1 `Send to Club` under `FUT_MOVE_BODY=ack`
Covered in §2. Cost: one launch, one flag. Status: staged, unrun.
Covered in §2. Cost: one launch, one flag. **Status: run, and it worked.** Both flags
are now defaults. The measurement cost one pack and about four minutes.
Worth noting against the "what could make this plan wrong" section below: the outcome
was the good one, but §7's warning still stands unchanged for everything else. This
result does not make the rest of the queue more likely to be right; it makes the
method more likely to be right.
### 3.2 The MY CLUB counter, `GET /club/stats/<mode>`
@@ -417,8 +437,8 @@ corrected and must be fixed before they propagate into `ENDPOINT_MAP.md`.
|---|---|---|
| `FUT_MASSINFO` | `full` | live-proven; boot-critical |
| `FUT_USERINFO` | `roster` | live-proven; `packs` and `full` are untested rungs |
| `FUT_PACK_AUTOCLUB` | `1` | the `Send to Club` workaround; revisit after §2 |
| `FUT_MOVE_BODY` | `empty` | `ack` is correct-by-decompiler but sufficiency is unproven |
| `FUT_PACK_AUTOCLUB` | `0` | the workaround is retired; it emptied the pending pile and suppressed the move request entirely |
| `FUT_MOVE_BODY` | `ack` | **live-proven 2026-08-04**; `empty`, `full` and `dreamsquads` all report the move as failed |
| `FUT_MARKET` | `sample` | live-proven |
| `FUT_MODES` | off | changes `/season`, whose array-root freeze risk cannot fire unless the client asks, and it has never asked |
| `FUT_ACCOUNTINFO` | off | queued at §3.4 |