Fobly
Football manager

I’ve always enjoyed playing football manager games with friends. Eventually, I wanted to build one myself. Fobly grew out of that love of the game and the fun of building things.

Role
Solo development
Focus
Simulation & Product
Platform
Web app

01 · The players

Generating and developing players

What makes a good player? And how do they get better? I started with attributes, talent and statistical distributions. Before building the simulation, I tested different player generators and compared their results with those distributions. Normally distributed talent ratings give players different starting points. Age, training, playing time and performance determine how each attribute develops. Together, those attributes produce a rating for each position. Two players with similar talent can end up on very different paths.

Talent ratings and how likely they are

Players are generated with normally distributed talent ratings. Ratings near the middle are more common, while very low and very high ratings are rarer.

1 · RareTalent 1–1010 · Rare
Same start · Two pathsDay 0

Luca

Age 19 · CM · Talent 7/10
2 matches per week

Passing9 /20
Game intelligence9 /20
Finish9 /20

Emil

Age 19 · CM · Talent 7/10
Training without matches

Passing9 /20
Game intelligence9 /20
Finish9 /20
Day 0180 days

An example run locally with the real XP engine. Both players have the same talent and train in midfield every day. Luca also plays matches, with an assumed performance rating of 7/10. The curve above illustrates how talent is distributed.

02 · The match engine

How individual actions add up to a match

Once I had the players, I built the match engine. It simulates matches minute by minute, following connected sequences of play. The ball’s position, the players involved and the previous actions shape what happens next. A player’s abilities affect their chances of success, so a good pass is more likely but never guaranteed. When two players compete for the ball, the engine weighs their relevant attributes against each other. A sequence of play can continue into the next minute.

17′ · From buildup to goal

SV Abendlicht← Direction of attack
Richter
Minute 17 · Action 1/8

Pass

Nico Richter

→ Milan Berger

Chance of success64%

Event sequence from a local demo simulation, seed 20261033. Ball positions and success probabilities come from the match engine. Ball trajectories and shot speeds use the same visualisation as live matches in Fobly.

03 · Balancing

Making differences in strength feel right

The hardest part was getting better players to make smarter decisions without letting them dominate the game. I adjusted parameters and ran batches of 1,000 to 10,000 simulated matches. I put generated teams up against each other and compared the results and match events with real football statistics. A lot of it was trial and error. What mattered was whether differences in team strength showed up in a believable way, while the game still felt right.

Two teams · 10,000 simulated matches

FC Morgenrot

160

Avg. positional rating
vs.
SV Abendlicht

108

Avg. positional rating
8,337 home wins968 draws695 away wins
Per-team averages across 10,000 matches
FC MorgenrotStatisticSV Abendlicht
3.40Goals per match0.98
11.88Shots per match4.54

A new set of simulations using the current engine, with both teams in the same formation. 10,000 matches, seeds 20261011–20271010. All averages are per team.

04 · The product

Understanding the squad and making decisions

Once the game mechanics were in place, I started on the interface. What do I want to see after signing in? How do I make sense of my squad? From the team overview, you can open each player’s profile. Fitness, positional ratings and season stats show how they’re doing in matches. Talent, traits and position-specific attributes explain their strengths, helping you make informed lineup and training decisions. I kept refining these screens as I went.

05 · Under the hood

Connecting simulation, backend and interface

The backend releases precomputed matches and handles transfers.

The journey of a match

From pre-simulation to the final whistle.

Pre-simulate the matchday01 · In advance · Cron job & match engine

The engine computes each match in advance using the lineup and tactics.

Store in staging tables02 · In advance · PostgreSQL

Results, actions and the effects of each match are stored in separate staging tables.

Import the match and timeline03 · 1 hour before kickoff · Lifecycle service

The backend imports the match and timeline into the regular tables.

Enable the live view04 · 30 minutes before kickoff · Lifecycle service

The match goes live and the stream becomes available.

Stream the match events05 · From kickoff · Fastify → Web app

Fastify sends the precomputed actions to the web app on a timed schedule via SSE.

Apply match outcomes06 · After the final whistle · Lifecycle service

The backend updates the standings and players, then clears the staging data.

The journey of a transfer

From auction listing to changing clubs.

List a player for sale01 · Web app → Fastify → Base

The backend creates the listing and the auction on Base.

Deposit the bid in escrow02 · Smart wallet · USDC on Base

The contract holds the bid funds and refunds anyone who is outbid.

Update the bid and countdown03 · Confirmed transaction · PostgreSQL & SSE

Confirmed bids update the price; late bids extend the countdown.

Check whether the auction has ended04 · After expiry · Auction monitor

The monitor checks the expiry time and auction state on Base.

Confirm payment on Base05 · Backend → Escrow smart contract

The backend triggers payment and waits for blockchain confirmation.

Assign the player to the new club06 · PostgreSQL → Web app

The player joins the winning club and the transfer is recorded.

Instant purchases are handled by a separate transaction monitor.