# The /settings feature gate - live-test script

> **CORRECTION, 2026-08-05 evening. Section 1 of this document is FALSE and the
> test in section 3 should not be run as written.**
>
> Section 1 claims `IS_FRIENDLY_SEASON_ENABLED` and `IS_DRAFT_MODE_ENABLED` "have
> never been set to true by anything, on any run". They are measured as **1**, on
> two separate launches, while `/settings` was answering `{"configs": []}`:
>
> ```
> disp 0x1fd3a (friendlySeasonsEnabled)      value = 1
> disp 0x1fd3d (enableDraftMode)             value = 1
> disp 0x1fd45 (packOpeningAnimationEnabled) value = 1
> ```
>
> Reproduce with `tools/gate_byte_probe.py` (needs the client at the FUT hub, since
> CardsDLL loads only then): it resolves the pid by comm,
> re-derives the CardsDLL slide from `/proc/<pid>/maps`, proves it against the FNV
> prologue at `0x180180d00` read from disk, walks the model singleton at
> `DAT_1802e6398`, and decodes each displacement out of its accessor stub
> (`0f b6 81 <disp32>`) rather than assuming it.
>
> **Where the reasoning went wrong.** The finding that `FUN_18011dc50` is the only
> writer and that the `FutDataManagerImpl` constructor never touches those bytes was
> correct. The inference drawn from it was not. The applier runs whether or not the
> configs array has content, and the settings struct it is handed defaults these
> fields to 1, so the bytes were being written all along. "Nothing populates the
> array" was treated as "nothing writes the byte". Those are different claims and
> only the first one was established.
>
> Seasons therefore does not refuse because its gate byte is false. Its gate byte is
> true. The mechanism is still unknown and needs a fresh diagnosis. Everything below
> the correction is kept as the record of a wrong turn, not as a plan.

Written 2026-08-05, after reversing `FutGetSettingsServerResponse` end to end.
Nothing here has been in front of the game yet. The code default is `off`, which
serves the exact historical `{"configs": []}`, so the tree is currently at the
proven baseline and this test is opt-in.

Full schema, atom ids, gate bytes and accessor addresses are in `ENDPOINT_MAP.md`
under `FutGetSettingsServerResponse`. This file is only the experiment.

---

## 1. The claim being tested

`GET /settings` is requested 11 times a session and has always been answered with
an empty array. The array is not decoration:

- Each element is `{"type": "<name>", "value": <scalar>}`. The client hashes the
  **string value** of `type` through the atom hasher and switches on it, 42 arms
  wide, so a flag is a row rather than a key.
- `FUN_18011dc50` is the **only** writer of the `IS_*` UI gate bytes inside
  `FutDataManagerImpl`, and every line of it is `byte = (field == 1)`.
- The `FutDataManagerImpl` constructor never touches those bytes. The whole
  16620-char decompile was scanned for the block; it is absent.

So `IS_FRIENDLY_SEASON_ENABLED` and `IS_DRAFT_MODE_ENABLED` have never been set
to true by anything, on any run, in the whole history of this project.

That is a mechanism for the standing bug in which **Seasons refuses while making
zero requests to any of the four servers.** No response shape could ever explain
that. A UI key evaluated from a byte nobody wrote does.

**The store is the control that makes this readable.** `IS_STORE_ENABLED` is the
same kind of byte read the same way, and the store screen works. It works because
`storeEnabled` and its siblings already reach the client through the **Blaze**
client-config store (`FUT_RS4_CONFIG`). That list contains no seasons flag, no
draft flag, no tournament flag. Same mechanism, one populated, one blank.

This is a hypothesis with a mechanism, not a demonstrated cause.

---

## 2. Pre-flight, from the terminal, costs nothing

```bash
cd fifa17-recon/tools
FUT_SETTINGS=gates python3 check_settings_flags.py     # expect: 14 rows, PASS
python3 check_settings_flags.py                        # expect: mode=off, PASS
```

The checker asserts every shipped flag name against **both** the atom table and
the recovered switch arms. Both are needed: `enableSquadBuildingSetsFeature` is a
genuine atom with no arm in this switch, so the atom table alone would wave
through a flag that does nothing. A misnamed flag is silently inert and looks
exactly like a failed fix, which is the failure mode this guards.

---

## 3. The run

Budget: **one launch.**

```bash
cd fifa17-recon/tools
FUT_SETTINGS=gates ./openfut-fut.sh start
~/Desktop/launch-fifa17.sh
```

Then, in order, and write down what each one does:

1. **Store.** Open it. This is the control and it goes first, because if
   populating the array broke the store then the applier demonstrably ran and
   everything after this reads differently.
2. **Transfer list capacity.** Transfers → Transfer List. Read the capacity
   number. We send `maximumTradePileSize = 77`, a number FUT would never choose
   on its own.
3. **Seasons.** Single-player Seasons, the exact path that has been refusing.
4. **FUT Draft.** Both the offline and online entries.
5. **Tournaments**, for `tournamentQuitEnabled`.

---

## 4. Reading the result

The control in step 2 is what makes a negative result informative, so read it
before concluding anything about steps 3 to 5.

| Store (1) | Capacity (2) | Seasons (3) | Reading |
|---|---|---|---|
| works | **77** | opens | Confirmed. The gate was the empty array. Make `gates` the default and move to the `/match` shape, which has been blocked behind this. |
| works | **77** | still refuses | The array reached the consumer and the flag was applied, so the gate is **upstream of the UI key**. The settings line is then dead for Seasons and the next move is a live probe of the refusal path, not more response work. This is a real result, not a null one. |
| works | not 77 | still refuses | The array never reached the consumer at all. Everything above is untested rather than refuted. Suspect the massinfo `settings` member (the deser's other caller) is what the client actually reads, and check which of the two paths fires in `/tmp/utas.log`. |
| **breaks** | any | any | The applier ran and re-asserting the store flags did not hold them. Fall back to `FUT_SETTINGS=keep`, which sends only the already-working flags plus the control. If `keep` also breaks the store, populating the array is harmful in itself and the whole approach is wrong. |

`keep` exists precisely so that "populating the array at all" and "the new gates"
can be separated without guessing, and it costs one restart to use.

---

## 5. What would make this whole plan wrong

**The gate might not be a UI key at all.** Seasons could be refusing on an
entitlement, a persona attribute, or a Blaze session property evaluated inside
the Denuvo-packed executable, in which case no `/settings` body reaches it. The
step-2 control is what tells these apart: it distinguishes "the flag did not
help" from "the array was never consumed", and no boolean flag can do that alone.

**The store control could be weaker than it looks.** The argument assumes
`IS_STORE_ENABLED` currently comes from the Blaze store rather than from a
default. If it turns out the store screen does not read that key at all, then it
is not a control for anything and the reasoning in §1 loses its anchor.

**Draft has a second known suspect.** `GET ut/%s/squad/mode/draft/state` is still
answered by the generic `/squad` handler with a full active-squad object, which
is a textbook type-desync candidate. If Draft still fails while Seasons opens,
that route is the next thing to look at, not the flag.

**A negative result here is worth having.** The settings array has been the
standing suspect for the greyed-out entry points for two rounds without anyone
sending a single flag. Ruling it out costs one launch and removes it from the
backlog permanently.
