Things I’ve built, paid and unpaid, roughly newest first. Each one has the lesson I actually took from it attached, often the mistake rather than the result. Some of these are finished, some are paused, and at least one sat in a box for a year.
A top-down wasteland roguelike: drive, fight, salvage, bolt modules onto sockets, then take bigger risks for better parts. Upgrades grant new behaviours rather than percentage modifiers, and the shape of your vehicle tells you what build you are running without a stats screen.
Lesson learned
This one is on pause, and I would rather say so than pretend otherwise. The scope grew faster than I could keep up with and the summer of 2026 has been a glorious one. Due to pick it back up in October. Writing the design pillars down before building anything was the best decision on it: driving has to be fun with zero upgrades, behaviours over stat modifiers, readable chaos. Those three lines killed a lot of bad ideas cheaply.
A six-wheeled rocker-bogie rover on a 3D-printed chassis. Every wheel has its own drive motor and its own steering servo, so it can crab sideways, spin in place and pick its way up a staircase. A Raspberry Pi handles video and the web UI, an ESP32 does the real-time motor work, and you fly it from a browser with a gamepad over WebRTC. It is finished, and alive and well.
Lesson learned
I shelved this for the best part of a year over a serial connection. I was set on running it over the GPIO pins, and it worked perfectly in isolation - Pi to ESP32 to drivers to motors and servos - and fell apart every single time it was integrated. I came back a year later, plugged in a USB cable instead, and it worked immediately. Weeks of pain resolved by the most boring option available. Keep it simple, stupid rang very true that day.
Magic has over 27,000 unique cards.. this app makes it a little easier to find what you're looking for
An AI-assisted Magic: The Gathering card discovery and deckbuilding app. A mono-repo of four services: a FastAPI backend, a query microservice that turns natural language into an intermediate representation and then into Scryfall queries, a Redis-cached card image proxy, and a Next.js frontend, with Prometheus and Grafana wired in from the start.
Lesson learned
Asking a language model to emit search syntax directly is brittle and impossible to debug. Putting a structured intermediate representation in between turned an unpredictable component into a testable one - I could exercise the whole query layer with no model involved at all. That indirection is what made the results trustworthy, and it is the bit I would do first next time rather than last.
Unity build pipeline orchestration on Temporal and .NET 8: validate the project, fan out to platform builds in parallel, fan back in to a report. Running Unity several times over on one machine is the awkward part, because the licence and the Library folder both assume a single instance, so each build gets a cheap read-only copy of the project to work against. It ships with architecture decision records covering retry strategy, observability, graceful cancellation and project-copy strategy, and a simulation mode so the whole thing runs without Unity installed.
Lesson learned
The ADRs turned out to be the most useful thing in the repository, which was not what I expected when I started writing them. A README tells you what a system does. An ADR tells you which options were rejected and why, and that is the part you cannot reconstruct six months later. The other surprise was where the real problem lived: not in the orchestration, which Temporal largely handles, but in Unity's assumption that only one copy of it runs at a time. I did not get the role, but a week inside Unity's build tooling and its licensing model was worth having regardless.
To assist with the making of Cheddar, I needed to dial in my servos before fitting them to the chassis
A compact ESP32-C3 servo tester with an EC11 rotary encoder, a small OLED angle display, eased motion and tap-to-cycle angle increments. The dial from the original enclosure design did not fit the encoder shaft properly, so I remodelled it.
Lesson learned
Instead of adjusting back and for, over and over again; make yourself a little tool to get it right. For me this is the "jig" of the robotics world.
Rocket Science Group, for the UK's largest MMO Studio
Moving our clients source control and workflow orchestration onto modern, supported platforms: hundreds of repositories from Bitbucket to GitHub, Airflow from 2 to 3, and the Kubernetes infrastructure around it reconfigured in step - all while day-to-day delivery carried on.
Lesson learned
Hundreds of repositories is a coordination problem wearing a technical costume. Automating the migration was the straightforward half; the work was sequencing it so nobody's Monday got ruined, and earning the trust of engineers who quite reasonably did not want an external team anywhere near their source control. You win that by being visibly careful in public, not by giving assurances in private.
A large games studio with a long-lived live-service title and millions of players transacting in it.
Migrating a fraud-detection data source
Rocket Science Group, for a long-lived live-service title
Moving a client from Kount to SEON as their fraud-detection provider, and upgrading everything downstream to match: new ETL pipelines in Snowflake and rebuilt reporting and dashboards in Periscope. Because fraud data feeds both live operations and reporting, it all had to move without a visible wobble in anything people depended on.
Lesson learned
The pipeline work was the small part. Getting several teams with genuinely different priorities to agree what 'the same number' meant after the switch was the actual project, and I underestimated it at the start. Matching a schema field-for-field is easy; keeping the outputs semantically equivalent is what preserves trust. Ambiguity surfaced early becomes a shared problem, and ambiguity left hidden becomes yours alone.
2025
For myself
Teaching
Pokemon SQL Trainer
Progressive SQL challenges for someone with no prior knowledge, themed around building and querying a Pokedex. It starts at basic SELECT statements and works up to queries Professor Oak would be proud of, running against a real Postgres instance in Docker.
Lesson learned
Teaching something forces you to find the order the ideas actually need to arrive in, which is almost never the order you learned them in yourself.
An independent US games publisher, behind a free-to-play mobile fighting RPG that has run as a live service since 2017 and passed ten million downloads.
Rocket Science Group, for a mobile games publisher
A long-running free-to-play mobile fighting game had revenue reporting that drifted out of alignment with reality, drawn from several sources that disagreed with each other. The tell was a report showing a single battle pass sold for forty thousand dollars - a product that has never cost anything remotely like that. It was a currency conversion error, and it was not the only one. I reconciled attribution, transaction, user and currency data, then rebuilt the lot as a layered BigQuery architecture: filtered raw events, cleaned and currency-normalised transactions, and trusted reporting views on top.
Lesson learned
Correct numbers were not enough. Confidence had eroded to the point where teams hesitated to act on their own data, and belief does not come back just because the figures happen to be right this week. It comes back when someone can follow the pipeline and see why they are right. I spent about as long making the corrections legible as making them, and that was the right split. It helped that the forty-thousand-dollar battle pass was a story everyone could repeat - a single absurd number did more to explain why the work mattered than any amount of describing the architecture.
A fun weekend project experimenting with the combination of genetic algorithms and video games
A 2D side-scrolling racing game in Unity where players breed and evolve vehicles across generations. Traits like speed, suspension, wheel count and chassis shape combine through breeding, and vehicles compete in short rounds with procedurally generated physics.
Lesson learned
Turning an algorithm into a game mechanic taught me that the interesting part is not the evolution. It is giving the player a meaningful hand in it. A genetic algorithm left to run is a screensaver.
A real-time notification pipeline inside the game's backend, one workstream in a larger platform engagement. It pushes events to players while they are in session: a friend coming online, an invite, an account or session notice, an alert when someone signs in on more devices than they are allowed. Producers do not deliver anything themselves. They write a message to a Redis Stream and return, and a background consumer group drains that stream across however many backend instances are running, sending over AWS API Gateway's WebSocket API, which holds the socket state so the application stays stateless.
Lesson learned
The WebSockets were never the interesting part. A notification that arrives after the session has ended may as well not have been sent, so the whole design is about the awkward cases rather than the happy path: the player who is offline, the instance that dies holding a message, the player signed in on three headsets at once. Putting a stream between the producer and the delivery is what turned those from incidents into states, because each one becomes a message that is still pending and a consumer that can pick it up. The other half was refusing to own the sockets. Connection state is the thing that stops a service scaling, and handing it to a managed service kept the problems worth solving in our code and left the tedious one somewhere else.
An observability system for studios running dedicated game servers on Amazon GameLift Servers, shipped as telemetry SDKs in C#, C++ and Go, covering Unity and Unreal, on Windows and Linux. I worked on the design of the system as a whole: the metric set, the OpenTelemetry pipeline, the naming and semantics that had to hold identically across three languages, and the dashboards studios get without configuring anything. The binding constraint was the tick: this is extra software on a studio's game backend, and a 60Hz server has about 16ms to do everything in, so the collection could not take a millisecond of it or introduce spikes. I built the Unity SDK myself, end to end, and wrote the public metrics documentation that ships with it. It became the platform's standard observability solution, folded into the main server SDKs so studios install one package rather than two.
Lesson learned
Averages hide exactly the frames you care about. Once the timing signals moved to histograms and percentiles the problems became obvious, and they had been sitting there the whole time. Measuring the right thing beats measuring more things, and on anything latency-sensitive the right thing is almost always the tail. The tick budget taught me the rest: the measurement has to be cheaper than the thing it measures, and cheap on every tick rather than on average, because a spike from the collector is indistinguishable from the bug you were sent to find. Designing for three languages added the last piece: the hard part is not the code, it is agreeing what a metric means and holding that meaning identical across implementations, because the moment two SDKs disagree the dashboard stops being trustworthy. And an SDK nobody can work out how to switch on measures nothing at all, which is why the docs and the default dashboards mattered as much as the instrumentation.
Rocket Science Group, for one of the UK's largest MMO's
The studio's desktop launcher had been built for free-to-play games, so every download path in it assumed the content was free to take. Their first premium title changed that. I worked across the stack on authorization-gated distribution: entitlement checks in AWS Lambda before a download is issued, Akamai enforcing the same decision at the CDN so the files are not reachable by URL alone, and the TypeScript and React launcher refusing to offer the download until authorization comes back. The traffic shape drove as much of it as the security did: a premium unlock is a spike, not a curve, and the authorization path had to survive the whole player base arriving inside the same few minutes. Alongside it, a single-use voucher path so developers and beta testers could be let in without minting permanent entitlements.
Lesson learned
The instinct was to find the one correct place to enforce this, and there is no such place. The launcher is a Chromium app on the player's machine, so it enforces nothing that cannot be switched off. A backend check on its own still leaves the file sitting on a CDN for anyone holding the URL. CDN gating on its own leaves a player staring at a button that did nothing, with no idea why. Each layer is bypassable, unhelpful or both when it stands alone; three of them are a system. What surprised me was how much of the work was arguing for deliberate redundancy in a review culture that reads a second check as a wasted one. The argument that landed was not about attackers: it was that the layers do different jobs. The launcher explains, the backend decides, the CDN enforces.
A game jam entry built over a weekend for Ludum Dare 53 by five of us: Sam Neale, Daniel Neenan, Luke Berry, Amber Hornsby and me. You play an ordinary package that has realised it is not on the delivery truck, and you roll through a warehouse - over planks at height, around water puddles, past the objects that turn out to be friendly - to reach the loading bay before it leaves. Three levels, built in Unity and playable in the browser. It is the most successful thing we have submitted to a jam: it ranked well, it was well received, and people came back to compete over best times.
Lesson learned
Keep the scope tight, and get something playable in the first few hours. The player is a box that rolls and jumps - two inputs, no grab, no inventory - and it was in our hands by the end of the first evening. That is what let us find the fun before committing to anything, and only then grow the concept around the part that was already working. The jump is a good example of growing rather than adding: it does not just lift you, it adds to the force you have already built, so a well-timed one carries speed and a mistimed one kills it. That nuance came out of playing what we had, not out of a design document, and it is the reason people kept racing the levels.
An internal developer platform for a siloed engineering team
In-house at Cazoo
Centralised developer experience work: standardising observability and alerting practices, establishing CI/CD conventions, making infrastructure as code the default across environments, and launching an internal developer platform in a quarter. Alongside it, helping create a Champions Network to spread engineering standards across around 250 engineers.
Lesson learned
Standards do not scale by being written down. The platform was the easy half - what actually moved the organisation was the Champions Network, which is to say real people embedded in real teams who could answer a question in context. I had assumed a good enough golden path would sell itself. It does not; you need strong advocates embedded into teams to really sell it.
Extensible data pipelines and machine-learning models supporting the UK adult social care workforce: AWS infrastructure in Terraform, models developed in Jupyter across a small Spark cluster and then productionised properly, with a full suite of unit, integration and quality tests. The repository is public and still being worked on.
Lesson learned
Tuition was as much the deliverable as the pipeline. What made it work was building the relationship first and earning credibility before writing anything, then taking the team on the journey - why SQL, why Python, why this shape - rather than handing it over. They could barely use a command line at the start, so the developer experience and the process had to be genuinely strong: Jupyter notebooks for exploration, modular Python for anything going to production, and a clear line between the two. Three analysts went from zero software experience to owning a large, multifaceted pipeline.
The DVLA's first fully digital provisional licence service
Made Tech, for the DVLA
Part of a multi-squad Nexus Scrum team digitising the DVLA's first provisional licensing service: an internal staff portal in React, a public frontend in Ruby on Rails built to GOV.UK standards, and a serverless AWS backend of Step Functions, Lambda, DynamoDB, API Gateway, SQS and SNS, with Elasticsearch for reads and reporting.
Lesson learned
The public sector style guides are rock solid - intuitive, standardised, simple to build against, genuinely excellent. The risk culture around them was harder. Everything was front-loaded into verification: end-to-end tests that took ninety minutes, and a rollback path cumbersome enough that nobody wanted to use it. Mistakes still got through, because they always do. I left convinced that being able to deploy a fix in ten minutes beats being certain nothing is wrong, and that thorough and slow are not the same thing.
A physics-based apocalyptic vehicular combat game in Unity, built on PhysX for vehicles that feel realistic but still arcade. Multiple cars, weapons and environments, an arcade mode for instant action, and behaviour-tree vehicle AI running on a custom navmesh agent model built specifically for cars. And a lot of explosions.
Lesson learned
Unity's navmesh agents assume something that walks. A car cannot strafe, stop dead or turn on the spot, so I ended up using the navmesh purely for pathfinding and writing a separate layer to decide how a vehicle should follow that path. It was the first time I had to properly separate what a system decides from how a body executes it, and that same split has shown up in nearly everything I have built since.
A squad-based RTS where survivors band together through the apocalypse. Its best feature was an Overwatch-style ability: characters automatically target any zombie passing through their overwatch cone, with line of sight properly accounted for.
Lesson learned
The mechanic only became understandable once I drew the cone on screen. A rule the player cannot see is a rule they will not trust, and they will assume the game is cheating rather than assume they have misread it.
A small tank battle game set in an office. Tanks, projectiles and the world are all physically simulated, with a simple enemy AI, and it runs in the browser so there is nothing to install before playing.
Lesson learned
Publishing it to the web is the only reason anyone played it. A build people have to download is a build almost nobody tries, and I had already learned that the hard way twice before it stuck.
iComply is the software UK dental practices use to stay on top of compliance and turn up to a CQC inspection with everything in order. Four of us rebuilt it from scratch over eighteen months in Django and React: compliance tasks and calendars, audit readiness, dashboards, subscriptions and single sign-on across the rest of the Agilio suite. The old product was functional and unloved, and the replacement was a genuine step up for the people living in it every day.
Lesson learned
The delivery was the best I had worked on: weekly demos, feedback gathered in the room, decisions made in front of the client rather than guessed at. Then late on, they mentioned in passing that they wanted a dual-ledger system, and we took the phrase at face value. Six weeks of double-entry accounting and tax handling later it turned out they had read the term somewhere and did not know what it meant. What they wanted was a simple finance ledger: about a week of work. Instead we handed them a system they had to live with every time they asked for a new feature, and the pain compounded for months. The demo habit that served us for eighteen months is exactly what we skipped on the one thing nobody had put on a screen. Ask what a term means even when you are certain you will look daft asking.
A mobile game built around gravitational mechanics, released on the Google Play Store. One idea, explored properly, and taken all the way through the unglamorous part.
Lesson learned
Shipping to a real store is a separate project from building a game. The last ten percent was store listings, build signing and screenshots at seven resolutions, and it is the part I learned most from. Finishing is a skill, and you only get it by finishing.
A self-scaling analytics engine collating logs and metrics across an entire Global Services application stack, giving live insight and Kibana visualisations over systems nobody previously had a full view of. Built from scratch on Docker and Docker Swarm, moving data with Kafka, the ELK stack, Hadoop, Sqoop, Hive and Spark.
Lesson learned
This one started because I noticed a gap: we were trying to problem-solve in the dark, and I wanted it fixed. I managed to carve out scope for myself and two others to build it, and it got the attention of senior management, who made it part of the team's official roadmap. That was the first time I saw something I had argued for become someone else's priority, and it is still the clearest lesson I have about how work gets started. Distributed computing, image-based deployment, big data, observability - I learned more from that one self-inflicted project than from anything handed to me.
2017
For myself
Research
Beacon Investigation
My final-year BSc dissertation: an investigation into applying Bluetooth beacon technology in the festival industry. Primary research into whether proximity-based technology could improve how people navigate a large temporary site, rather than whether it was technically possible.
Lesson learned
Writing for a sceptical reader forces a precision that building alone never demands. It was the first time I had to defend a technical position to people whose job was to push back on it, and that is a different skill from making the thing work.
An Android personal-finance app with wallets, budgets, recurring transactions and saving goals. Three of us built it over a university summer: me, another developer and a graphic designer. It got far enough to be a real app with a real icon grid and real people entering real expenses into it.
Lesson learned
Nothing broke. No architecture collapsed, no bug beat us. Final year started, the funding ran out, and we simply stopped. Almost nothing dies of a technical problem - it dies because the people building it run out of time or money. If you are about to start something with friends, that is the risk worth planning for, not the tech stack.