Cars & engines
How an engine turns fuel into motion. It's the reason I modelled a V8 one part at a time.
Computer Science & Business · Trinity College Dublin
I'm interested in two things: the patterns hidden in data, and ideas turned into things you can hold. So I work on both sides of the line, software and hardware.
Software ⟷ Hardware
I'm a first-year Computer Science & Business student at Trinity College Dublin, with a strong interest in both software and hardware.
On the software side, I'm drawn to data and the patterns within it, from Python scripting to optimising a trading strategy over thousands of backtests. On the hardware side, I like seeing ideas come to life: taking designs from concept sketches to parametric SolidWorks parts and assemblies, then into hands-on mechanical builds.
Right now I'm co-founding Resonance, a web start-up that rebuilds outdated websites for small businesses.
How an engine turns fuel into motion. It's the reason I modelled a V8 one part at a time.
Price action, backtesting, and the statistics behind whether a strategy actually holds up.
Fixing things by hand: drivetrains, brakes, and rebuilding bikes for people who need them.
Taking a product from concept sketch to parametric model and technical drawing, like the soap dispenser I designed for my Leaving Cert DCG project.
Pick one to open
Five projects, from an engine to an app that reads a pool table.
Open a tile to maximise it. Each project has its own space: explore it, then minimise it to choose another.
01
SolidWorks · C# · 3DV8 Engine40-part CAD assembly you can take apart
02
Pine Script · BacktestingNO Candle Strategy11,142 backtests across four FX pairs
03
TikTok · SimpleStudyStudy content31K+ views on my top three videos
04
The Bike Hub · SolidWorksBikes & mechanicsRebuilds for people in need, and an e-bike kit
05
JavaScript · Computer visionGhost BallFinds your easiest pot from one photo of the table
Smaller builds, client work and design projects.
A web start-up I co-founded with a friend. We rebuild outdated websites for small businesses so they’re easier to use and look modern.
Custom timetabling software for Resonance’s first client, VS Driving School, to schedule and manage driving lessons.
A SolidWorks design that turns a standard bicycle into an e-bike, with the motor and battery mounted on a frame someone already owns.
My Leaving Cert DCG project, taken from concept sketches to a parametric model and technical drawings.
On work experience at TCS, my team reimagined food delivery as a drone-based system. I designed the app prototype and user journey in Figma.
Click a role to expand

Contact
I'm always up for a conversation about engines, markets, or something you're building. The quickest way to reach me is email.
Drag to rotate · click a part · click, then scroll to zoom
A 5.7-litre, 90° V8 with a cross-plane crank, based on GM's LS small-block. 18 unique parts, 40 in the assembly, all mated so that turning the crankshaft drives every piston and rod. The model above uses the real geometry exported from SOLIDWORKS.
Parametric from end to end, so the whole engine can be regenerated.
Every dimension lives in a single Spec class: bore, stroke, rod length, deck height, bore spacing and bank stagger.
Each part is built from sketches, extrudes, revolves and cuts, generated by a C# program that drives the SOLIDWORKS API.
Pistons ride on their bore axes, and rods link wrist pins to crank pins. The banks are staggered 23.8 mm so each pair of rods shares one crank journal.
Piston positions match the slider-crank equation to 0.0000 mm through a full turn, and interference detection finds no clashes.
// distance from the crank centreline to the
// piston pin, measured along the bore axis
public static double PistonPinDist(int cyl, double crankDeg)
{
var c = CrankPin(ThrowOf(cyl), crankDeg);
var u = U(Sigma(cyl)); // bank axis, ±45°
double along = V2.Dot(c, u);
double perp = V2.Cross(u, c);
return along + Math.Sqrt(RodLen * RodLen - perp * perp);
}
The 3D model above runs on the same equation, so every piston and rod on this page is where the CAD model says it should be.



A rule-based price-action strategy I wrote in Pine Script on TradingView, then tuned with 11,142 backtests on four currency pairs (15-minute charts, 30 Nov 2025 to 25 Sep 2026, $100K test account). The original settings lost money on every pair. The tuned versions are profitable on all four, trading both long and short.
Settings combinations run in the TradingView Strategy Tester
Up from 0 / 4 with the original settings
Gross profit ÷ gross loss for the tuned scripts
At 75% of equity per trade, the largest size with no margin calls
Lost money on all four pairs, from −$31 (GBPJPY) to −$280 (GBPUSD).
Move each setting on its own away from the baseline to find which ones matter.
Improve one setting at a time until nothing helps, restarting from 12 different starts to avoid local optima.
Test every combination of the settings that interact most (wick tolerance × stop × reward:risk) around the winners.
Keep only settings with ≥ 100 trades and no worse a drawdown than the original, then test size and leverage.
Ranges tested: trend mode (all 4) · pivot 1–5 · EMA 50–300 · validity 3–20 · pending 1–5 · wick tolerance 0–10 ticks · swing-stop buffer 0–20 ticks, with or without the wick · NO-range stop 0.5–6× · reward:risk 0.5–3 · direction · position size 5–100% · leverage 10×, 20×, 50×.
Everything below is at 10% of equity per trade unless it says otherwise, so the settings compare directly.
The pair script's defaults next to the original. A dot marks each setting the tuning changed.
Net profit when one setting is changed and the rest stay at the tuned values. The coloured bar is the chosen value; faded bars break a guardrail.
The same four scripts with bigger positions. 75% of equity per trade is the largest size with no margin calls on any pair.
$100K account. At 100% every pair hits hundreds of margin calls, and the results collapse.
I documented my Leaving Cert preparation on TikTok as @lil_indian_boi, reaching thousands of students and motivating them to reach their own academic goals. That led to becoming a brand ambassador for SimpleStudy, promoting its study resources to students.
My three most-viewed videos, with over 31,000 views between them.
13.7K viewsRoad to 625: Maths revision, Ep 9
9,017 viewsSpeed-running a whole Maths Paper 1 in about 90 minutes
8,784 viewsLight 5-hour revision
My study videos led to a place on SimpleStudy’s Ambassador Program, promoting its study resources to other students.
In June 2025 I volunteered at The Bike Hub in Dún Laoghaire, building and repairing around ten bikes from scratch under an experienced mechanic. Every one was donated to people in need through the local council. Separately, I designed an e-bike conversion kit in SolidWorks.
To put it simply, I was sick of my friends beating me at pool, so I set out to build a program that would give me an edge.
The more I looked, the more patterns I saw: the same shots kept coming up again and again. Once I’d done the maths, it all made sense.
Hence Ghost Ball: a program that just shows you the shot, instead of boring you with the details.
Take one photo of the table from above. Ghost Ball finds the table, flattens the perspective and picks out every ball, including the white. It works out every pot on the table, straight, off a cushion, or through another ball, ranks them by the chance you’d make them at your level, and draws the best one: where to aim, where to strike the white, and where the white goes next. It all runs in the browser on your phone; the photo never leaves it.
Fifteen balls. Six pockets. So which shot is actually on?
This is Ghost Ball. Take one photo from above. It finds the table, flattens the perspective, and picks out every ball. Missed something? Fix the corners, add a ball, or tap the white yourself.
Then the maths takes over. It finds the ghost ball: exactly where the white must be at contact. It measures how much room the pocket gives, and turns your aim error into a real pot chance. Fifty-nine percent for a beginner. Ninety-nine if you’re strong.
Straight pots. Banks. Kicks. Combinations. Every shot on the table, ranked best first. Plus where to strike the white, and where it goes next.
Ghost Ball. Know your shot before you take it.
From above, with all four corner pockets in frame
Straight, bank, kick and combination pots
Every pot it found on the demo table, ranked by chance
The vision and the maths run in the browser
This is Ghost Ball itself. It opens on a sample table with the best shot already drawn, so you can start straight away.
At a real table, open it on your phone and use Take photo. Your photo never leaves your device.
My friends kept beating me at pool, and I wanted an edge.
The more I played, the more the same shots came round again: the same cuts, the same angles into the same pockets.
Working through the geometry, it all made sense. Every pot comes down to one spot the white has to reach: the ghost ball.
I settled on the simplest version that could work: one photo from straight above in, the best shot drawn on it out. No camera rig, no projector.
Find the table, flatten it, find the balls, solve the pots, draw the answer. I tested it on tables the app draws itself, where it knows where every ball really is, so every miss shows up.
On a phone, the first version looked like a desktop page and choosing a photo did nothing. I rebuilt it phone-first: a camera bar at the bottom, a close-up of the contact, and corners you can drag under a magnifier.
Banks, kicks and combinations, all from the same window maths, every one ranked by the chance of potting it.
The white’s path after contact, a spin guide that says where to strike it and why, and how hard to hit.
I took it to a real table, took one photo, and it found the shot.
A 90-second video and this write-up.
The 80-second video at the top of this page: tighter, with the maths on screen, replacing the first explainer.
The error spreads behind each level are my estimates. Next is logging real shots and fitting them, so the percentages are measured, not modelled.
From one photo to one shot in eight steps. Each step fixes one problem and runs straight into the next. The photos are the app’s own working on its sample table, not mock-ups.


Problem
Even from above, a phone is never perfectly square to the table. The far end looks smaller, so a line drawn in photo pixels lands in the wrong place on the real table.
Fix
First find the table. The cloth is the most common strong colour in the middle of the photo, so the app keeps the biggest patch of it, fits a straight line to each side (skipping the pocket notches) and intersects the four lines. Then it steps in to the cushion nose, where the brightness changes.
Then flatten it. A homography, a 3 × 3 matrix H, maps the photo onto a flat table 1200 × 600 units long, exactly 2:1. Each corner gives two equations, so four corners give eight: exactly enough for H’s eight unknowns, solved by Gaussian elimination.
The table is flat and measured in real units. But where are the balls?


Problem
Anything on the cloth that isn’t cloth-coloured could be a ball. But balls often touch, and a cluster shows up as one blob.
Fix
A distance transform scores every non-cloth pixel by how far it is from the cloth. Each ball’s centre is a peak, so touching balls still give separate peaks.
Then each ball’s pixels are sorted into white, dark and coloured. The whitest ball is the white and the darkest is the black. Hue splits the rest into reds and yellows, or solids and stripes on a US table.
Every ball has a position and a name. So where should the white go?
Problem
Send the white straight at the red and the red carries straight on, not into the pocket. The red always leaves along the line between the two centres at the moment they touch.
Fix
Work backwards from the pocket. To send the object ball O towards the pocket P, the white has to arrive touching it on the side away from the pocket. That spot is the ghost ball, G. Aim the white’s centre at G, and O sets off towards P.
The cut angle θ is the angle between the white’s path and the object ball’s. The app refuses anything over 80°, because those are too thin to judge.
That gives one exact line. But a pocket is wider than one line.
Problem
A pocket has width, so a whole range of directions drops. Some pots leave lots of room and some leave almost none, and one aim line can’t tell them apart.
Fix
From the object ball, the two jaws mark out an angle. The ball’s centre has to stay one radius clear of each jaw, which trims a little off each side. Every direction left in between drops: that’s the pocket window.
The solver then works that window back through the collision. By bisection, it finds the range of directions for the white that send the object ball inside it, and aims at the middle.
Now the solver knows how much room each pot has. But nobody hits the exact line.
| Level | Aim spread | Cut error at 90° | Cushion spread |
|---|---|---|---|
| Beginner | 0.20° | 2.0° | 2.5° |
| Club | 0.10° | 1.2° | 1.6° |
| Strong | 0.05° | 0.7° | 1.0° |
The error spreads (standard deviations) behind each level.
Problem
Real players miss by fractions of a degree, and the collision magnifies it: a small change in the white’s angle β becomes a bigger change in the object ball’s angle φ, worst on thin cuts. A wide window can still be a bad bet.
Fix
Model the white’s direction as a normal distribution around the aim, with a spread that depends on your level. The app measures the magnification, ∂φ/∂β, numerically, and adds an error for throw and misjudged angles that grows with the sine of the cut.
That P is the pot chance on the shot card, and every pot is ranked by it.
function potDirect(cue, obj, pk, g, sk) {
const C = [cue.x, cue.y], T = [obj.x, obj.y], s = g.r + g.rc;
const w = jawWindow(T, pk.jaws, g.r); // directions that drop
const h = w && hitWindow(C, T, s, w.lo, w.hi); // the white's directions that give them
if (!h) return null;
const k = h.hit(h.aim); // aim at the middle
if (k.cut > MAX_CUT) return null;
const sd = Math.hypot(sk.sigma * DEG * k.slope, throwSd(sk, k.cut));
return { type: 'direct', P: chance(k.phi, h.wlo, h.whi, sd), k };
}
// share of a normal error around phi that still lands inside [lo, hi]
const chance = (phi, lo, hi, sd) => Phi((hi - phi) / sd) - Phi((lo - phi) / sd);
Every straight pot now has a chance. But sometimes there isn’t a straight pot.
Problem
The ball can be blocked, tight on a cushion, or at an angle no pocket allows. Going off a cushion bends the path, and a bent path breaks all the straight-line maths.
Fix
Don’t bend the path; unfold the table. A bounce is a reflection, so for a bank the app mirrors the pocket and its jaws across the cushion, and a straight pot at the mirror image P′ is a bank into the real pocket. For a kick it mirrors the white instead. The mirror line sits one ball radius off the cushion, where the ball’s centre is when it touches. Everything from steps 03 to 05 then works unchanged.
Cushions aren’t perfect, so each one adds its own spread to σφ, weighted by how much of the path comes after the bounce.
Cushions open up new routes. But sometimes another ball is in the way of every one.
Problem
Sometimes the ball near the pocket isn’t the one you’re aiming for, or it sits right on your line.
Fix
Use it. For a combination the window is worked back twice: first the directions ball A must leave in to pot ball B, then the white’s directions that send A that way. The errors compound through both collisions, which is why combinations usually rank below a good straight pot.
Now it can find the most likely pot. But potting the ball isn’t the end of the shot.
Problem
A pot that sends the white into a pocket afterwards, an in-off, is a foul. One that leaves it in a cluster leaves you nothing next.
Fix
At contact the white loses its speed along the line of centres and keeps the part along the tangent line, at 90° to the object ball’s path. Friction then turns its spin into roll. A solid ball has I = ⅖mr², which makes its final velocity five-sevenths of the tangent velocity plus two-sevenths of its spin.
Top bends the white forwards, about 34° off its line near a half-ball hit, which is where the “30° rule” comes from. Screw bends it back. The app follows each option to the first cushion or ball and suggests stun, unless that would go in-off or into another ball; then it picks top or screw, whichever runs clear. Pace comes from how far both balls travel, with the object ball’s share divided by cos²θ, the fraction of the white’s energy a cut passes on.
Put it all together, and one photo gives one shot.
This runs the app’s own solver on a UK 7ft table: straight pots, banks (the red off a cushion) and kicks (the white off a cushion). It starts on the sample table’s best shot. Drag the white or the red, tap a pocket to aim for it, or pick a shot type. The orange wedge is the pocket window, folded at the cushion on a bank; the dashed ring is the ghost ball; the ring on the cushion is where the bounce happens; and the dotted blue line is where a stunned white goes.
Drag a ball, or focus it and use the arrow keys (Shift for bigger steps). Tap a pocket to aim for it, and tap it again to let the app choose.
Each one is the app’s own drawing, on a different sample table.


