A Tale of Two Nyms

Side-by-side screenshots of Trackronym and Cinenym, showing empty boards with the daily word.

The first nym

On July 6th I received a dm from a good friend with a “random digital daily game idea”:

You’re given a codeword and you have to select songs to spell out the word with the first letter of each track’s name. But then you can complicate it by requiring certain types of songs in your selection.

My first questions were about input, how closely song titles should match the daily word, and whether punctuation or spaces should count. By the next day I had started building it.

That game became Trackronym, although for approximately a day it had the working name Tracrostric. 😅

Building Trackronym

The Go-generated site ran in the browser and stored progress locally, with no application server to look after.

The first schedule was selected in JavaScript using the date. It was deterministic, but difficult to inspect ahead of time, so I quickly moved generation into Go. That allowed the build to validate bonus combinations and pin published puzzles so later changes would not rewrite history.

Bonuses soon became the complicated part. 😬

Some combinations contradicted each other, while others were individually reasonable but impossible together. Replacing a growing collection of special cases with reusable rules and a feasibility solver revealed something important: most of my “song game” wasn’t really about songs. The difficult parts were validating that a puzzle could be solved and providing a way to search for the things used to solve it; songs were just the first set of data I applied them to.

An Apple problem

Trackronym originally used iTunes search until an ironic CORS bug meant Apple devices were unable to search Apple’s own music catalog. 🙃

Search is rather fundamental to a game about finding songs, so I replaced it with Deezer for discovery and MusicBrainz for metadata enrichment. I chose them because neither requires credentials and both have open CORS policies, which meant the static site could call their APIs directly without a backend proxy.

The second nym

In September I decided to try the format with movies. Rather than duplicate Trackronym, I split decided to undertake the task of refcatoring it to create sensible seams between the common engine, browser code, and game-specific code.

The common engine handled rules, validation, schedules, difficulty, and puzzle output. Shared browser code handled the board, history, persistence, settings, and sharing. Each game supplied its own items, metadata, search, bonuses, and branding.

The movie game briefly had some alternate aliases flying around, Flickronym and Title Sequence before settling on Cinenym. Unlike Trackronym’s live search, Cinenym uses a generated local movie catalog. There are far fewer movies than songs, which makes shipping the searchable data practical, and I couldn’t find a suitable movie API that allowed credential-free browser access. A maintenance tool fetches TMDB data, cleans it, and commits a reproducible snapshot which is split into static chunks so the browser only downloads the parts needed for a search.

Cinenym also exposed a weakness in the original scheduler. In the scheduler a synthetic movie/song could prove that a bonus was logically compatible, however, didn’t prove that such a movie/song even existed. For Trackronym, this wasn’t such a big deal as the data set is so massive that a synthetic song is almost certainly real, but for Cinenym it was a problem.

The fix for this instead generates boards from a subset of a game’s local corpus and measures how many real solutions each bonus combination leaves behind. This results in a more accurate difficulty rating and prevents impossible puzzles from being published, but means the scheduler has to be run on a machine with a large corpus on it, and can take a long time to run. ⏳

Two nyms are better than one

The useful abstractions came from building Trackronym first, letting the requirements become messy, then testing those assumptions against Cinenym. Had I tried to design a generic engine on day one, I would probably have generalised the wrong things.

I still wrote far more scheduling code than expected, but side projects are half the fun of these sorts of problems.

I wish I could blame this project for why I haven’t written more articles, but I don’t think that would be a fair attribution. 🙈

Play the games