# The client's complete UTAS route surface Read out of the running client's own `.rdata` on 2026-08-21 (pid 6580) with `fifa17-recon/tools/url_template_probe.py`, then each route probed against staging. This bounds the server: FIFA 17 cannot ask for a route that is not in this list. Staging's Python upstream is deliberately dead, so a `502` there means the Rust host does not own the route — which makes the coverage column a measurement rather than an audit of the source. ## Route templates in CardsDLL `%s` is the sku segment, built from `game/%s` (`0x18021fac8`) → `game/fifa17`. ``` ut/auth ut/delete/auth ut/%s/user ut/delete/%s/user ut/%s/user/list ut/%s/club ut/%s/clubUser ut/%s/item ut/%s/item/resource ut/delete/%s/item ut/%s/defid ut/%s/squad ut/delete/%s/squad ut/%s/squad/mode ut/%s/purchased ut/%s/store ut/v2/%s/store ut/%s/trade ut/delete/%s/trade ut/%s/tradePile ut/%s/watchList ut/delete/%s/watchList ut/%s/auctionhouse ut/%s/marketdata ut/%s/match ut/%s/sbs ut/%s/season ut/%s/season/user ut/%s/season/%%s/user ut/%s/season/%%s/reset ut/%s/season/friendly ut/%s/tournament ut/%s/tournament/user ut/delete/%s/tournament/user ut/%s/champion ut/%s/draft/mode ut/%s/leaderboards ut/%s/leaderboards/options ut/%s/activeMessage ut/%s/livemessage ut/%s/clientdata ut/%s/phishing ut/%s/captcha ut/%s/tfa ``` Suffixes appended to the above, not standalone routes: `/consumables/%s`, `/items`, `/purchasegroup`, `/squadBuildingSets`, `/challenge/%d/squad`, `/choices/manager`, `/purchase/mode/%d/draft`, `/transfermarket?type=%s&start=%d&num=%d`. ## THE TRAP when reading this list A literal in `.rdata` is a **fragment**, not necessarily a callable path. Probing fragments bare manufactures fake gaps. Every one of these looked unserved and was not: | looked missing | actually | |---|---| | `clientdata` | real route is `clientdata/`; served (`clientdata/userHubData` → 200) | | `purchasegroup` | a suffix of `store`; `store/purchasegroup/all` is served | | `sbs/challenges` | not a route; the real ones are `sbs/sets`, `sbs/setId//challenges`, `sbs/challenge/` — all served | | `squadBuildingSets` | not a route in the oracle either | | `club/items` | `items/...` literals are ART ASSET paths, not UTAS | | `item` | only ever PUT (move/pile) and DELETE (quick-sell) | Check a candidate gap against `tools/utas_server.py`'s regex table before believing it. ## Genuinely unserved, and why that is correct * `squad/mode` — bare form is never used. The oracle only has Draft sub-paths (`squad/mode/draft/state`, `squad/mode//draft/choices/*`). Draft is out of scope, so this correctly stays on Python. ## Fixed by this measurement Four handlers existed and were unreachable because `classify` never produced their route, so every request fell through to Python. This is a **recurring defect class** in `openfut-utas-host` — `season/list` and `watchList` were the first two, and their fix comments are still in the file: | route | handler | was | |---|---|---| | `captcha` | `handle_static_ack`, returns the oracle's exact `{encodedImg,sequence,sizeBeforeEncode}` | fell to Python | | `tfa` / `livemessage` / `activeMessage` | `handle_static_ack`, `{}` | fell to Python | | `tournament/user` | `FeatureOffEmpty`, `{}` — the oracle's answer with `FUT_MODES` off | fell to Python | `Route`'s own doc comment already claimed the first four as "Rust-owned UNCONDITIONAL", so the documentation had been wrong rather than the intent. All five are byte-identical to the oracle, so claiming them is parity, not new behaviour. Invisible in production (the upstream answers); a 502 on staging. Two regression tests now pin the vocabularies — `every_static_ack_tail_is_actually_routed` and `the_disabled_mode_reads_are_all_claimed` — so a handler cannot go unreachable a fifth time. ## No consumable apply endpoint exists Support level L5 for consumables was open, with an inherited note saying there is "no training/position/chemistry/manager-league endpoint at all". **The route table confirms it from the binary**: there is no apply/training/position/ chemistry route anywhere in CardsDLL. The only owned-item mutations the client can express are: ``` PUT ut/%s/item move / pile DELETE ut/%s/item/ quick sell POST ut/delete/%s/item bulk quick sell PUT ut/%s/squad squad write ``` So applying a consumable is **not** a dedicated server route. If it reaches the server at all it must ride `PUT ut/%s/item`, and L5/L6 should be pursued by capturing that PUT's payload while applying a card — not by looking for an endpoint that does not exist.