Projects

Things I have built or finished. Each one says what it does in a sentence, then the tools, then more if you want it. Ideas still in progress are under Future work.

Live

A maths tutor that tells you which mistake you made

A practice app for Ontario high school maths, grades 9 to 12. Tap a wrong answer and it names the mistake you made, not the right answer.

More

Every wrong option is one specific mistake students actually make, like dropping a sign when expanding a bracket. That is why the report can say "you keep distributing the negative" instead of "6 out of 10". Six courses, 1,600 questions, 219 lessons, and a report a parent or tutor can read. The whole app is one Dart file, on purpose, because the person maintaining it found one file easier than seven.

Courses
6
Questions, each with a named mistake
1,600
Lessons
219

Live

A Digital SAT trainer that tracks skills, not topics

"Weak at algebra" is not something you can revise for. "Missing nonlinear functions" is. This app tracks 29 skills separately and builds practice from the ones you keep getting wrong.

More

The College Board question bank already tags every question by domain, skill and difficulty, so mock tests can be built with the right mix instead of random sampling. It starts with a 20-minute diagnostic that sets every recommendation after it. Hosting is a convenience rather than a dependency: the database does the syncing, so it works even from a folder on a laptop.

Practice questions
4,054
Skills tracked separately
29
Lessons
52

Live in a shop

Threadline, a staff system for a small clothes shop

Staff scan a code on the shop phone to clock in, log the day's takings and tick off their tasks. The owner gets attendance, a monthly payroll report and alerts on Telegram.

More

Hours are never stored, only worked out from the taps, so any correction stays honest and visible. It keeps working when the signal drops and syncs when it comes back. I built it end to end for one non-technical owner, changed it with them week by week, and it is still running.

Published, IEEE ICECET 2026

Spotting a drowsy driver on video, tested honestly

My final-year project at Warwick, later an IEEE paper. I compared five ways of detecting drowsiness on video and made sure no person appeared in both the training and the test data.

More

I expected the biggest model to win. It did on the internal test, but on a dataset it had never seen the smaller CNN-GRU held up better and ran about four times faster, so that is the one I would put in a car. Every model struggled on YawDD, and when I looked at what they were attending to, the reason was simple: talking looks like yawning. The dissertation scored 88 percent and won the IMechE Best Project Certificate.

External F1, CNN-GRU
0.714
Latency per clip
75 vs 305 ms
Dissertation
88%

Part-built

A plate that balances a ball, to compare three controllers

Two servos tilt a plate to keep a ping-pong ball where I point. On its own that is a demo. I am building it to run PID, LQR and MPC on the same rig and compare them properly.

More

Before touching the hardware I measured the camera delay, because too much delay means no controller can be tuned to work and I would have blamed the controller for a week. The PID gains I found in simulation survived all 13 disturbance tests. The same loop is running live on the start page.

050 ms100 ms 60 fps 30 fps 15 fpsfails at any latency
comfortable, about 27 of 60 gain sets workworkablebest case only
The camera latency gate, measured over 20 trials per configuration before any hardware tuning. Transport delay caps achievable gains, so it is checked first.
Disturbance tests passed
13 of 13
Loop rate
30 to 60 Hz
Parts
about £25

Shipped, v24

A chess engine in pure Python

Built for the AI Chessathon. Twenty-four versions in eight days, and none shipped without beating the last one in a measured match. It finished 47th of 465 on the rated ladder and 28th of 334 in the final Swiss.

More

The engine is bitboards, alpha-beta search and a small neural evaluation, all Python compiled at import time. About 600 thousand positions a second on one core. The useful part was the discipline: an A/B harness that plays two versions against each other and reports the Elo difference with an error bar, and a written record of the fifty ideas that did not help. The ladder ran 109 rated rounds over eight days and the record across both stages was 42 wins, 41 draws and 33 losses.

Builds
24
Best ladder rank
7th
Ladder close, of 465
47th
Final Swiss, of 334
28th

Running, 96 videos so far

A pipeline that turns a folder of audio into published video

Around 250 recordings need to become videos with a fixed cover image, be renamed to a scheme, and be published to a channel in one consistent format. Doing that by hand is not an option, so I wrote a pipeline that runs unattended and checks its own output before I trust it.

More

Nine steps run in order: work out the true recording order, rename to the scheme, measure each file's exact length, encode the still image against the audio, verify the result, upload, add to the right playlist, rewrite metadata if the template changes, and rebuild the local record from the channel if it is ever lost. Uploads retry on server errors with exponential backoff, and a state file means a re-run picks up where it stopped instead of duplicating anything.

The verify step is the part worth keeping. It compares every finished video against its source audio, and it caught two defects that would otherwise have shipped 96 times over. Every video had almost half a minute of dead air on the end, because the encoder was overrunning a looped still image. And the output was being upscaled past the source image, so I was making bigger files that looked worse. Measuring against the original PNG put that at 35.23 dB against 41.14 dB, and fixing it cut about a fifth off every file.

Before 29.3 s of silence After 0.3 s the recording itself, 439.7 s
audiooverrun
One chapter, before and after. The same fault was on all 96.

It is resumable and it never duplicates, but it cannot undo a publish. That is a real limit rather than an oversight: deleting a published video is a manual job, so the pipeline is built to be re-run safely instead of rolled back.

Videos built
96
Audio processed
about 19 h
More queued
about 150

Complete

Hand gestures from a webcam, and tic-tac-toe played by hand

I photographed 14 hand gestures about 8,700 times, trained ResNet-18 to recognise them, and wired it to a webcam so you can play tic-tac-toe without touching anything.

More

99.5 percent on the validation set. The dataset was self-collected, which taught me more about lighting and backgrounds than the model did.

Images
8,700
Gestures
14
Validation accuracy
99.5%

Complete

Landing a drone on Mars with a Kalman filter

A Warwick assessment: model the drone, estimate where it is through noisy sensors, and control it from the estimate rather than the truth. The same maths I now use on the balancer.

Complete

Controlling a rotary pendulum

Designing a PID controller and a lead compensator for a Quanser rotary pendulum, to hit set rise-time and overshoot targets in MATLAB and Simulink.

Complete

A truss analysis app

A MATLAB app that takes a 2D truss and tells you the forces, the stresses and which members would yield. Part of a team project that later extended to 3D.

Also on GitHub

Coursework and smaller tools.