If you're just here to check the league table, generate tonight's teams, or see how your Elo is doing, feel free to skip this section entirely. This blog sits completely outside the main app. It won't change your rating, fix your AntiChemistry with the goalkeeper, or help you win next Wednesday.

I built it for three reasons.

First, to document the real journey of Atmos Football — not as a dry changelog or technical manual, but as the actual story of how we got here, wherever "here" happens to be when you're reading this.

Second, to give Google AdSense the kind of substantial, human-written content it needs before it will approve the site for monetisation. I've sunk a lot of time and money into this project. If a few ads can help me recoup some of the API costs and server bills, I'm taking that path.

Third, it's become a place for honest reflection. When the AI tokens run low and I'm faced with the choice to step away or "feed the meter" for one more feature, writing here helps me remember why I started in the first place.

If none of that interests you, no worries — the rest of the app works perfectly fine without this side quest. Close the tab and carry on.

Still here? Alright.

Over the coming posts I plan to explore three main areas:

  1. The Group — The history of our weekly 5-a-side games, the people, the rivalries, and the day-to-day chaos since 2015.
  2. The Tech — The design and technical choices I made (and the ones I now regret).
  3. The AI — A no-BS look at working with large language models: what actually worked, what was frustrating, and what was just plain weird.

The Secret Origin: An AI Stress Test

Here's the part most people don't know: Atmos Football didn't start because I thought the world needed another sports app.

It started as a personal grudge match against AI.

In late 2025, Microsoft began aggressively pushing Copilot onto everyone's Windows PC — no ask, just installed. Like a lot of people, I wanted to know: Is this stuff actually useful, or is it just a fancy search engine?

I needed a proper test. Work data was too risky. Public data was pointless (the models had probably already been trained on it). I needed something private that I understood better than anyone else.

That's when I turned to my OneNote archives.

Since 2015 I've been keeping simple weekly notes for the casual 5-a-side group I organise and play in — Atmos Football. Just player lists, results, and who owed subs for the pitch. A messy, unique dataset that existed nowhere else on the internet.

I copied the raw data from the current year and asked the AI: "Provide some interesting stats about this dataset for my weekly game of football."

The Hallucination Phase

The first responses were laughable.

The model would think for a moment and then spit out a big, confident wall of text. It looked impressive… until you actually checked the facts. It invented players who had never existed and made up scores for games that never happened.

I tried something simpler: "How many times has 'James' played this year?"

It confidently answered: "James has played 10 times."

I looked at the list of over 30 games. It wasn't even close. When I pushed back, it "apologised" and corrected to 14 games. Still completely wrong. A few more challenges and the failures kept coming.

The free models of late 2025 simply weren't ready for real, messy, private data.

The Turning Point: Gemini 3

I kept testing every new model that dropped. Slowly, the systems improved — better context handling, fewer wild hallucinations.

Then Gemini 3 arrived.

For the first time, a model answered "How many games have I played?" with actual accuracy. It could parse the names, dates, and results without falling apart.

Encouraged, I asked it to generate a full list of players and results for the year. It started working… produced something that was nearly correct… and then hit me with the classic message: "Your daily allowance has been reached. Come back tomorrow."

The AI was finally capable enough to be useful — but now the question became whether it was worth the cost to keep feeding it.

Spoiler: I chose to keep going.

Not because I had a plan, or a product vision, or any expectation that it would turn into something real. Just because the data was finally answering back, and stopping felt like admitting it had all been a waste. Sometimes that's the only reason you need.

That decision turned a simple data test into a full React + Firebase app, a PWA, a Play Store release, and eventually this blog.

The rest of the story — how the OneNote files became real software, the rabbit holes I fell down, the features that actually mattered, and what building with AI really feels like day-to-day — is what I'll be unpacking in the posts ahead.

The Architecture of a 5-a-Side Match

Before I wrap up, here's one small taste of the technical side — because it illustrates something important about how simple the actual data model is, and why that simplicity matters.

Every game in Atmos Football is stored as a single document in Firestore. It looks roughly like this:

{
  "gameNumber": 47,
  "date": "2025-10-15",
  "teamA": ["James", "Dave", "Mike", "Chris", "Tom"],
  "teamB": ["Rob", "Phil", "Lee", "Dan", "Matt"],
  "scoreA": 7,
  "scoreB": 5,
  "winner": "A",
  "addedBy": "uid_abc123",
  "addedAt": "2025-10-15T21:30:00Z"
}

That's it. Ten fields. No nested objects. No foreign keys. No separate "player" or "team" table.

I chose this structure deliberately. When you're storing ten years of casual football data, the temptation is to over-engineer it — a players collection, a seasons collection, junction tables for appearances. But I knew the data was messy to begin with. Adding relational complexity on top of manual-entry chaos would have made every query a liability.

Flat documents meant I could import a year of data from a spreadsheet with a single script, query every game in a group with one Firestore read, and hand the raw JSON straight to the Elo engine without any transformation layer.

The trade-off is that player names are strings, not IDs. "James" in game 1 and "James" in game 200 are linked only by the fact that the string matches. Which means a single typo — "Jmes," "james," "JAMES" — quietly creates a ghost player in the stats. Handling that fragility is one of the reasons the data cleaning phase (which I'll cover in the next post) turned out to be far more work than I expected.

Thanks for reading the very beginning.

If you're curious about the football, the tech, or the AI side of this journey, stick around.

— James