FUT Evolution: building an AI coach for Ultimate Team

Case study · Updated · 9 min read · Solo project, 2023 to present · iOS, Android, Web

FUT Evolution app screenshots showing the AI coach, squad analysis, and player views

FUT Evolution is a companion app for EA SPORTS FC Ultimate Team that I design, build, and run solo: iOS, Android, and web. It started as one more stats app in a crowded market. It became a bet that the next companion app is not a database but a coach: an AI agent that knows your squad, a scanner that imports that squad from a single screenshot, and a computer-vision layer that watches your actual matches. This page is the story of that bet, act by act, with the real numbers.

A market of lookup tables

Ultimate Team is the flagship mode of EA SPORTS FC, one of the most played sports games in the world. You collect player cards, assemble a squad under formation and chemistry constraints, and trade on a live transfer market with its own economy, price cycles, and daily meta shifts. Around that economy grew an entire ecosystem of companion tools: price trackers, tier lists, squad builders, stats databases.

They are good at what they do. And what they do is lookup.

What every companion tool showsWhat the player actually asks
The price of any card, updated liveWho should I buy with my budget?
Global tier lists and meta ratingsWho fits my formation and my chemistry?
Full stats for thousands of cardsWhere is the weak spot in my squad?
Price history graphsIs this the moment to sell my striker?

Every question on the right side is personal and combinatorial: formation times chemistry times budget times the market at this exact moment. Answering it is coaching, not lookup. No tool coached, because coaching cannot be produced editorially for millions of different squads. It has to be computed, per user, on demand.

The bet: a coach, not a database

FUT Evolution's bet is simple to state: you import your squad, and a coach does the rest. It knows your players, your budget, your weak positions. It scouts upgrades, checks chemistry, watches prices, and tells you what to do and why.

The app around the coach is deliberately boring. Kotlin Multiplatform holds the shared business logic, networking with Ktor, and the data models. iOS renders in SwiftUI with SKIE for clean Swift interop, Android renders in Compose Multiplatform, and the web companion is Next.js. The backend is Next.js API routes over PostgreSQL on Supabase, a Python FastAPI vision service on DigitalOcean, player images on S3, subscriptions through RevenueCat. All three clients and the coach share one database.

The interesting part is what the bet forced me to solve. A coach that is worth trusting needs three things no lookup app needs:

What a coach needs that a database does not
Act I
Act II
Act III
Grounded answers
answers from a real database, not model memory
Your squad
imported from one screenshot, not typed in
Your matches
seeing how you actually play, not just who you own
The coach
The three acts of this case study are the three inputs the coach needed. Each one was a separate engineering problem.

Act I: a coach that renders, not replies

The first coach was scripted: rules over ratings, prices, and positions. It worked, and it hit the wall every scripted assistant hits. Conversational requirements compound. "Compare these two" is easy to route to a template. "Compare these two, but prioritize chemistry with my current squad, within my budget, and tell me if prices are falling" is not one template, it is a combinatorial space of them, and every new dimension multiplies the routing states a human has to enumerate.

So the coach became an LLM agent, and the engineering problem became reliability. Two decisions carry it:

One message, five stages
The database is reached only through tools.
1 · Classify
plain TypeScript, no LLM
2 · Pre-fetch
players named in the message
3 · One streaming LLM call
UI blocks inline in the stream
4 · Validate + repair
one schema judges every block
5 · Render
native design-system components
Players DB
read by 11 tools
The coach pipeline. In production since the end of March 2026. A full deep dive into the prompt layers, the UI grammar, and the repair layer is coming as its own post.
The AI coach answering with rendered comparison UI inside FUT Evolution

And the injected views are not passive. Every component the model streams is a live piece of the app, carrying the same actions as any native screen. Ask the coach to compare Mbappé and Dembélé and the answer itself is the interface: tap a player card and the native player sheet opens with the full model behind it, prices, alternatives, chemistry. When the coach suggests an upgrade, the suggestion ships with its own button, and tapping it applies the transfer to your squad. The model decides which views to compose; the app guarantees every one of them can act.

Answers you can act on: tapping a card opens the comparison, the upgrade suggestion carries its own button, and one tap on Accept Upgrade rewrites the squad.

Act II: your squad in one screenshot

A coach that does not know your squad is a tier list with manners. But asking users to type 23 player names into a form is where onboarding goes to die. The squad already exists, rendered on their screen inside the game, so the app takes it from there: screenshot in, full squad analysis out, in 2.5 seconds.

A card in that screenshot is mostly art. Names are stylized, truncated, and overlaid on gradients, so text alone is a fragile identifier. The most reliable constant on every card is the player's face. Identification starts there and uses text only to break ties:

Squad scan, 2.5 seconds end to end
Screenshot
PNG or JPEG, from the game
InsightFace
face detection · ~1s
FAISS
similarity over 2,926 faces · ~300ms
OCR
rating + position · ~500ms
Formation
Hungarian assignment
DB match
strict, then Levenshtein
Upgrades
budget + chemistry aware
The scan pipeline runs on a Python FastAPI service. Faces identify, text disambiguates, the Hungarian algorithm decides who plays where.

Face similarity alone gets 82% of players exactly right. The remaining errors are the lookalike problem: two players sharing a haircut and a squad photo style. That is where the OCR pass earns its 500 milliseconds: a face match that disagrees with the card's rating or position is demoted, and the disambiguated result reaches 95% accuracy. Chasing that last stretch, percentage point by percentage point, was the project's real lesson in production ML: the first 82% came fast, the last 13 points were the real work.

StageBudgetResult
Face detection (InsightFace)~1severy card face found
FAISS similarity search~300ms82% exact player match
OCR disambiguation~500ms95% exact player match
Full scan, screenshot to analysis2.5ssquad, formation, weak spots, upgrades
The real import flow: upload your EA companion screenshot and the analyzer sweeps it, detecting players, reading the formation, and computing chemistry before suggesting upgrades.

Act III: Hypervision, teaching the app to watch FC 27

The coach knows your squad. It still cannot see the only thing that matters: how you actually play. Hypervision is the layer I am building for the FC 27 cycle: paste a video of your match and get back what a real analyst would produce, where your lines break, which flank leaks, whether your striker's runs match how you pass. No companion app does this today, because watching gameplay video is brutally hard. This is the prototype, and the honest state of it.

What already works

Ball detection came first, because every downstream event (passes, shots, turnovers) depends on it: 99.6% detection on gameplay footage.

Ball detection on open play. The hard part was not finding a ball, it was rejecting the decoys: crowd graphics, HUD icons, and boots.

Players came second, and detection alone is not enough: knowing that twenty-two bodies are on the pitch tells you nothing until you know which shirt each one wears. A YOLO11s model finds the players, then a kit classifier reads the torso of every box and splits the pitch into your team and theirs, with the referee separated out so he never pollutes either side. The threshold is not hardcoded; it is fitted per clip from the kit brightness of the players actually on screen, which is what lets the same code survive a change of strip.

On top of that sits the mapping layer. A pose model finds 32 pitch keypoints, used zero-shot from a public broadcast dataset because no FC 26 keypoint labels exist. RANSAC fits a homography from those keypoints, and every player's feet are projected from screen pixels into pitch metres, with a Hungarian assignment mapping positions to formation roles.

Player detection on FC 26 footage, twenty seconds of open play. Home players are marked in blue, away players in red and the referee in magenta so he is never mistaken for a player. Averaged over the clip: 9.9 home, 8.9 away, 1.8 referee per frame.
MetricMeasured
Ball detection99.6%
Frames with a valid pitch fit (FC 26 footage)69 of 72 (96%)
Median projection error, screen to pitch0.38 m
Worst-case projection error1.84 m
Goalkeeper recall0.583, the current weak spot

What blocks the product, measured

A prototype that works on curated clips is not a feature. The feature is "paste a YouTube URL of your match", and measuring on real YouTube FC 26 uploads surfaced two product-blocking facts that were invisible while working on curated footage:

Both findings are the top of the FC 27 backlog, and both are the reason this section exists on this page. Shipping the demo would have been easy. Measuring it against real user footage is what told me the truth.

How I build products

Three acts, one method. If a recruiter takes anything from this page, it should be these four habits, because every one of them is visible above:

Stack and scope

Solo project, 2023 to present. Kotlin Multiplatform, SwiftUI, Compose Multiplatform, SKIE, Ktor, Next.js, PostgreSQL on Supabase, Python FastAPI, InsightFace, FAISS, YOLO, RevenueCat, deployed on DigitalOcean, S3, and Cloudflare. One database shared by iOS, Android, web, and the coach.

Share this post X LinkedIn

Next project: Afrique Sports →