A few things I've built
Ranging from minigames to university coursework to hackathon submissions. For the full list, code, and READMEs, visit my GitHub profile.
Outside of coursework
Things I've built for myself, not for a grade.
LydOS - in progress
Rebuilding Git's internals from scratch
Built as part of LydOS, starting from Nikita Leshenko's "Write Yourself a Git!" tutorial, fixed bugs in it, and added a full test suite — then kept extending it well past where the tutorial stops.
What I built:
- Content-addressed blob, tree, and commit storage, refs, and an index-based staging workflow.
- Branches, tags, and checkout — including detached HEAD.
- Diff between the working directory, the index, and any commit (including
--cached). - Fast-forward and three-way merges via
diff3. - Local-disk remotes with fetch and push.
HTTP server from scratch
Currently developing for LydOS since August 2026: a basic HTTP/1.0 server built from raw TCP sockets in Python, using only the standard socket module.
What it does so far:
- Opens an IPv4 TCP socket, binds it, and listens for incoming connections.
- Reads raw HTTP requests and parses the requested path from the request line.
- Serves static HTML files from an
htdocsdirectory, defaulting toindex.htmlfor the root path. - Returns a proper HTTP/1.0 404 response when a requested file does not exist.
- Builds and sends HTTP responses directly over the socket.
Currently single-threaded and blocking. Next steps are comparing thread-per-connection and
select-based concurrency, parsing full request headers, and handling POST bodies
via Content-Length.
Sudoku for Android Android app
Modernising a graph-based Sudoku app
A fork of GraphSudokuOpen, modernised as a learning project to deepen my understanding of Jetpack Compose, MVI architecture, and graph algorithms.
- Generates and solves 4x4, 9x9, and 16x16 Sudoku puzzles across three difficulty levels.
- Uses an adjacency-list graph model for puzzle construction and validation.
- Modernised the build system with Kotlin DSL, version catalogs, Java 17 toolchains, and Material 3.
- Persists games, settings, and best completion times locally.
Third-year deep dives
Write-ups of the coursework I'm proudest of from third year. Computer Graphics, Geometry & Simulation (CGGS) and Automated Speech Recognition (ASR) are normally fourth-year courses — I specifically requested to take both a year early. Click one to expand.
Software Testing built on the ILP codebase
Test suite for the ILP drone delivery API
Designed and implemented a comprehensive test suite for the ILP drone delivery API (built for the Informatics Large Practical, above), covering 27 requirements across functional, performance, robustness, safety, security, and reliability categories.
Scale: I counted ~676 @Test/@ParameterizedTest-annotated methods directly across 26 test classes; the project's own docs claim 741 total test executions, which is plausible once each parameterized case is counted as its own JUnit invocation.
Seven testing techniques, one directory convention per type:
- Unit tests — individual model/domain classes in isolation (DroneTest, PositionTest, RegionTest, DeliveryTest, DronePathTest, AvailabilityTest, A* components like RouteFinderTest, PositionScorerTest).
- Mock tests — service layer with dependencies stubbed via Mockito (DroneServiceImplMockTest, DroneControllerTest).
- Integration tests — full Spring context + MockMvc hitting real controllers (IlpCw1ApplicationTests, DroneServiceImplIntegrationTest).
- Performance tests — assertTimeout()-based checks that A* pathfinding completes within the autograder's time budget (DeliveryPathPerformanceTest), run under 50+ delivery workloads.
- Parameterized tests — DroneParameterizedTest, PositionParameterizedTest using @ValueSource/@CsvSource/@MethodSource/@NullAndEmptySource to sweep boundary values (angles, coordinates, capabilities) without duplicating test bodies.
- API contract tests — REST Assured (DroneApiContractTest, DroneApiContractEdgeCaseTest) validating HTTP status codes, JSON schema shape, and content types against the actual running Spring context.
- Code coverage gate — JaCoCo wired into the Maven build (prepare-agent → test → check), enforcing a minimum 50% line coverage per package, with the build failing if it drops below that. Final numbers: 100% pass rate, 44% overall line coverage, 65%+ on the service layer.
Notable engineering details:
- Deliberate handling of a subtlety in the spec: CW1 endpoints return 400 on bad input, but CW2 endpoints must always return 200 (even for empty/invalid results) — the test suite explicitly encodes and checks this asymmetry rather than treating it as a bug.
- Test-driven fix for a real edge case: an "available drones" test initially failed by assuming a non-empty result was required, when an empty list was actually the correct behaviour for that date/time — documented as a lesson in the test docs rather than papered over.
- mvn test / mvn test -Dtest=*ParameterizedTest / mvn test -Dtest=*ContractTest are wired up as selectable subsets, so the test taxonomy is runnable on its own.
- Built a GitHub Actions CI pipeline (Ubuntu, JDK 21 Temurin) running mvn clean verify on every push, failing the build automatically on a coverage threshold violation; 40+ supplementary Postman end-to-end tests validated all endpoints against the live service, including GeoJSON no-fly-zone verification.
Systems Design Project — TeeUp 70% · 8-person group
Autonomous golf-ball collection & re-teeing robot
Worked on Robot Base and Autonomous Vision integration for TeeUp, an autonomous mobile robot that collects golf balls and re-tees them in indoor golf simulator bays, eliminating the repetitive bending responsible for lower back strain in 36% of amateur golfers. Built on a TurtleBot3 Waffle platform, the system combined LIDAR-based navigation, a YOLOv8 computer vision pipeline, and a custom conveyor and placement mechanism into a fully autonomous detect-collect-place cycle.
My role: integrated mechanical subsystems onto the TurtleBot chassis and implemented manual control for early hardware validation, then moved onto the vision team to help develop and integrate the autonomous collection and drop-off logic into the ROS navigation stack. This included leading the training of a custom CNN on over 1,100 hand-labelled images to extend detection from golf balls to tee positions, iterating through eight model versions to reach 89.6% precision on the final YOLOv8 deployment.
Results: by the final demo, the system met all six functional, quality, and safety requirements — 92% ball detection accuracy, 95% intake reliability, 90% end-to-end cycle completion, 4.2cm mean navigation error, and a 0.3s emergency stop response. A Demo 2 user study (n=62) found 92% of respondents rated TeeUp a good idea, with strong comfort and trust scores for sharing space with the robot.
Commercial case: the project also involved a full commercial case — a TAM of ~£213M in the global indoor simulator market, a costed manufacturing model, and a competitive analysis showing TeeUp as the only product on the market combining autonomous collection, placement, and full autonomy in one unit.
Key contributions: hardware/software integration, CNN training and dataset labelling, ROS navigation logic, technical report writing.
Automated Speech Recognition 78% (pair)
Decoding "Peter Piper" with WFSTs 78
Designed and progressively improved a small-vocabulary ASR system for decoding student recordings of the "Peter Piper" tongue twister, working with a pre-trained neural acoustic model and implementing everything at the WFST/decoder level. Done in a pair.
What I built:
- Baseline system: word-loop WFST over 10-word vocabulary, 3-state left-to-right monophone HMMs, Viterbi decoding with negative log costs. Baseline WER: 133.63% — dominated by 1,694 insertions (more than the total reference word count) because the decoder exploited short words to minimise cumulative path cost.
- System tuning: introduced a Word Insertion Penalty on hub-state transitions to penalise excessive word-to-word transitions. Grid searched WIP ∈ {10…90} and self-loop probability Pself ∈ {0.2…0.8} on a 30-utterance development subset, then refined on the full 236-utterance set. Best configuration (WIP=40, Pself=0.3) reduced WER to 54.41%. Added an optional 5-state ergodic silence HMM and unigram word probabilities, reaching 52.13%.
- Beam pruning: implemented frame-synchronous beam pruning discarding hypotheses beyond a threshold of the best-scoring hypothesis at each step. Beam=60 reduced forward computations by 30% with negligible WER impact; beam=50 gave a 40% reduction at marginal cost (54.47% WER). Below beam=40 the system began failing to find valid final paths entirely.
- Advanced improvements: implemented a bigram language model (WER → 32.07%), a prefix tree-structured lexicon to share common phone sequences (WER → 42.28%), and a full HLG composition pipeline combining both. The naive HLG graph had 1,265 states; after epsilon removal, determinisation, minimisation, and arc sorting, it reduced to 323 states. The standalone bigram outperformed the combined system, suggesting the optimisation pipeline interacted unfavourably with weight distributions under the tree-structured lexicon.
Computer Graphics, Geometry & Simulation (CGGS) 96% overall
Coursework 1 — Rigid-Body Simulation 94
Implemented a 3D rigid-body physics engine from scratch in C++. The simulation supports arbitrary convex meshes in free-fall and under collision, driven by an impulse-based dynamics model.
What I built:
- Semi-implicit Euler integration for linear and angular velocity, with position and orientation updated each frame. Orientation is tracked as a unit quaternion and updated via the quaternion exponential map to avoid the large-step artefacts that arise from linearising the rotation derivative. I re-normalised after every step to prevent quaternion drift from accumulated floating-point error.
- Collision resolution using the GJK/MPR narrow-phase detector (provided), implementing the response myself: inverse-mass-weighted interpenetration correction, contact point reconstruction, and impulse-based velocity updates using the full inertia tensor (rotated to world frame each step).
- Distance constraints via Position-Based Dynamics (PBD) — holonomic inequality constraints enforced iteratively with a sequential impulse solver for both position and velocity corrections.
- Scalability extensions: replaced naïve O(n²) broad-phase collision detection with a uniform spatial hash (O(1) expected lookup), reducing candidate pairs from ~500,000 to ~2,000 at n=1000 objects. Added a dirty-queue constraint scheduler that re-enqueues only constraints sharing a displaced mesh, cutting redundant evaluations in large chain scenes.
What I learned the hard way: single-point contact models produce oscillatory micro-rotation on flat resting surfaces; without a friction model, oblique impulses accumulate angular momentum indefinitely; and decoupled positional/velocity corrections mean a body can re-penetrate the floor within a single frame and loop forever. All of these are fundamental limitations of iterative impulse solvers documented in the report.
Coursework 2 — Soft-Body FEM Simulation 95
Built a 3D deformable-body simulator using the finite element method (FEM) on tetrahedral meshes, implementing both a standard linear formulation and a corotational extension.
What I built:
- Linear FEM matrices: per-element stiffness Kₑ = Vol(e) BₑᵀCBₑ assembled into a sparse global stiffness matrix K via Eigen's triplet insertion; lumped mass matrix Mₘ via the Voronoi dual volume approach (¼ρV(e) per vertex); Rayleigh damping D = αM + βK with independently tunable mass- and stiffness-proportional terms.
- Implicit Backward Euler integration: solves (M + ΔtD + Δt²K)v_{t+Δt} = Mv_t − ΔtK(x_t − x₀) each frame. The left-hand side is factorised once at initialisation (A-stable, no blow-ups), so each timestep reduces to a cheap substitution solve.
- Corotational FEM: at every step, each tetrahedron extracts its best-fit rotation R via SVD-based polar decomposition and rotates its local stiffness into the world frame (Kᵉ_corot = Rₑ Kᵉ_lin Rₑᵀ), then reassembles and refactorises the global system. This substantially reduces volume-inflation and sluggish-rotation artefacts caused by the linear model misinterpreting rigid rotation as elastic strain.
What I benchmarked: corotational FEM's per-frame refactorisation costs 46×–103× more per step than the pre-factorised linear solve, depending on mesh density. For the dense Epcot scene (high tet count), this made corotational completely non-interactive — a practical illustration of why production physics engines use reduced-order models or GPU acceleration rather than dense per-frame factorisation.
Key artefacts documented: linear FEM produces visible "puffing" (volume inflation under large rotation), sluggish spin (energy diverted into spurious strain rather than angular momentum), and permanent rest-state divergence. Corotational fixes the first two substantially but can still exhibit mesh inversion at extreme impulses.
Coursework 3 — Debugging Geometry and Simulation 100
Given two fully implemented but intentionally broken algorithms, systematically identified and fixed three bugs in each using structured debugging methodology from the course.
ARAP deformation (As-Rigid-As-Possible surface editing):
- Bug 1 — wrong edge direction and missing transpose in the global Poisson solve. Set numIterations=0 (identity rotations, no deformation expected) and visualised the target edge vectors; found the incidence sign was flipped and the per-vertex rotation R was applied without transposing, violating the column-vector convention of the local step. Fix: reverse edge direction, apply R.transpose().
- Bug 2 — missing cotangent weight matrix W on the right-hand side of the linear system. The left-hand side included W but the right did not, breaking the normal-equation structure and causing the ARAP energy to increase across iterations rather than decrease monotonically.
- Bug 3 — duplicate Polyscope mesh registration from the same vertex/face arrays, causing z-fighting and a flickering interweaving artefact. Removed the redundant input mesh registration.
Multi-body gravitational simulation:
- Bug 1 — Newton's third law violation: both spheres i and j received force in the same direction (+nᵢⱼ) rather than equal and opposite forces. Fixed by negating the force applied to j.
- Bug 2 — collision threshold used 2r² instead of 4r² for a squared-distance check between spheres of radius r, so spheres massively interpenetrated before any collision response triggered.
- Bug 3 — ground bounce condition checked if (vy > 0) (sphere already moving upward), reversing a rising sphere back into the ground rather than reflecting a falling sphere away from it. Fixed by flipping to < 0.
COMN Coursework 2 — Reliable Transport Protocols90% (individual)
Sliding window protocols over UDP 90
Implemented three sliding window protocols for reliable end-to-end file transfer at the application layer over UDP, then empirically characterised their performance under emulated link conditions.
- Part 1 — Basic framing: chunked file transfer over raw UDP with a 3-byte header (2-byte sequence number, 1-byte EoF flag), 1KB payload per packet.
- Part 2 — Stop-and-Wait (rdt3.0): sender FSM with timeout-based retransmission; receiver discards duplicates via sequence numbers and returns 2-byte ACKs. Measured retransmission count and throughput across timeout values under 5% packet loss, 10ms RTT. Identified the optimal timeout as the value that minimises retransmissions without stalling throughput.
- Part 3 — Go-Back-N: extended to sliding window with configurable sender window size; receiver maintains a single expected sequence number and discards out-of-order packets. Characterised throughput scaling with window sizes (powers of 2) across propagation delays of 5ms, 25ms, and 100ms — demonstrating the bandwidth-delay product effect: larger windows are increasingly necessary to keep the pipe full at higher latency.
- Part 4 — Selective Repeat: added per-packet timers and a receiver-side buffer, accepting and storing out-of-order packets within the window. Compared SR vs GBN throughput at 25ms delay / 5% loss across window sizes — SR avoids unnecessary retransmissions of already-received packets, making the gap most visible at larger windows and higher loss rates.
Link conditions emulated throughout using Linux Traffic Control (tc qdisc netem) on the loopback interface.
Informatics Large Practical (ILP) 88% (individual)
CW1 — Core Navigation REST Service
A Spring Boot microservice exposing basic geometric/navigation primitives for a drone system: uid, distanceTo, isCloseTo, nextPosition, isInRegion (ray-casting point-in-polygon). Strict input validation returns 400 on malformed or out-of-range input, and every value is handled with BigDecimal to keep precision-sensitive geometry exact.
CW2 — Advanced Drone Delivery & Dispatch Engine
Builds on CW1 into the ilp_1_2 package: a full delivery-planning system for a fleet of medical-supply drones.
- A* pathfinding with no-fly-zone (restricted area) avoidance, built from scratch (Graph, GraphNode, Scorer, RouteFinder, PositionGraph).
- Multi-day and multi-trip delivery scheduling — deliveries grouped by date, state reset per day, capacity-constrained trip batching per drone.
- Static and dynamic drone queries — filter by capability, cost, and availability, with AND-semantics across multiple attributes.
- calcDeliveryPath / calcDeliveryPathAsGeoJson endpoints returning full route plans, viewable directly on geojson.io.
CW3 — MedSupplyDrones Command Center (Natural Language Interface)
A separate Python layer sitting in front of the CW2 Java service, letting a non-technical dispatcher describe a delivery in plain English instead of hand-building JSON.
- FastAPI backend exposing /plan, /summary/fleet, and /what-if.
- Google Gemini (gemini-2.5-flash) parses natural language into structured dispatch JSON, and answers fleet-capability questions.
- Streamlit UI with four modes: NL planning, fleet queries, what-if strategy comparison, and manual JSON.
- Whole stack (CW2 Java + CW3 Python) shipped as a single Docker image for grading.
CW4 — Demo Video
Walkthrough of the full ILP system in action — navigation primitives, delivery pathfinding around no-fly zones, and the natural-language dispatch layer.
CW5 — Viva
An in-person interview covering concepts from the course and the specific implementation choices made across CW1–CW3 — no separate code deliverable.
Second-year coursework
Shorter recaps of second-year group and pair projects.
-
February – April 2024
Foundations of Data Science
Group project — 3 students- Analysed 7.4M ultramarathon race records to investigate participation and performance trends.
- Cleaned and engineered data features in Python (pandas, NumPy, regex, datetime); built EDA visualisations with matplotlib/seaborn/Plotly.
- Developed regression models (linear, polynomial, multivariate) to predict finishing times (R² ≈ 0.30).
- Presented insights on gender disparities, peak performance ages, and participation growth.
-
January – April 2024
Software Engineering and Professional Practice
Group project — 4 students- Built a Java-based university self-service portal using Agile practices.
- Implemented core features: authentication, FAQ management, keyword search (Lucene), and external service integration.
- Applied MVC architecture, UML design, and ensured quality with JUnit 5 testing and documentation.
-
October – November 2023
Introduction to Computer Systems
Pair project- Implemented a cache, TLB, and linear page-table simulator in C, supporting 32-bit virtual/physical addresses with 4KB pages.
- Added configurable parameters, LRU replacement, page-fault handling, and verbose debugging output; verified correctness with custom trace files.