So I’ve been playing in skeeball events competitively since at least 2016. I’ve run a few skeeball leagues, but have always been frustrated by how other arcade games — especially pinball — are far better organized than skeeball. The reason? Well, it’s two-pronged. Skeeball is trademarked and the sole trademark holders own bars in Austin and Brooklyn and have a tight grip on the usage of the name broadly.
Also, skeeball lanes are kind of annoying to maintain and they’re expensive to acquire. Unlike pinball which has so many different ways you can play despite the same format, skeeball is kind of like the pickleball of bowling. Easy to do, but kind of boring after a while if you play alone.
For pros, I wanted to build an online ranking system so people could log their scores and we could have proper rankings across the world, but I don’t have time to build that right now. What I did have time to throw together in the meantime is a UTR/golf handicap style metric for players to self-rate themselves based on eras of playing skeeball.
I don’t actually think anybody would be able to count how many games they’ve played across the history of their career, but you could estimate that because it’s not super important once you past 500+ or so games.
The Alley Roller Metric (ARM) is a player rating system for 5-game skeeball sessions. It’s inspired by rating systems like UTR (tennis) and adjusts based on how you perform relative to what’s expected at your level.
ARM ratings range from 7.0 (rookie) to 25.0 (elite) and use decimal precision (e.g., 16.3). Ratings go up when you consistently beat expectations and drop if you underperform.
The rest of this is nerdy and it doesn’t matter at all, but…in case you’re curious how it works. I anticipate the format will change over time, and skeeball isn’t dense enough that anybody is gonna be packaging this and selling it.
You can check it out at Skeeboard
Your ARM is calculated based on three inputs:
function expectedScoreForRating(armRating) {
// Maps ARM rating (7.0–25.0) to expected session score (1500–4250)
return ((armRating - 7.0) / 18.0) * 2750 + 1500;
}
function confidenceScale(sessionCount) {
// Volatility decreases as you log more sessions
return Math.max(0.1, 1.0 / (1 + 0.1 * sessionCount));
}
function calculateARM(currentRating, sessionScore, sessionCount) {
const expected = expectedScoreForRating(currentRating);
const delta = sessionScore - expected;
const volatility = confidenceScale(sessionCount);
const ratingChange = (delta / 100) * volatility;
const newRating = currentRating + ratingChange;
return Math.round(Math.min(Math.max(newRating, 7.0), 25.0) * 10) / 10;
}