Writing / Article
From UmaOptimizer to UmaCompanion: The Earlier Architecture
The project began as a Python and PySide6 desktop app before its later Rust and TypeScript rebuild.
This covers the first architecture of what became UMA Companion, when it was still called UmaOptimizer. It was written in July 2026 while the project was active. The project has since been archived, and the retrospective tells the rest of the story.
The goal
Help a player pick the best six support cards for a run in Uma Musume: Pretty Derby. That sounds like a lookup. It is actually an optimisation problem, because the cards interact with each other and with the scenario.
The desktop app
The first prototype used CustomTkinter. It ran into limits quickly: rendering performance, custom styling, and interactions like drag-and-drop deck building. I rewrote the interface in PySide6 (Qt 6) and built a dark design system of my own, called AETHER, from design tokens and factory functions. The painful parts were PySide6 enum changes and the coordinate maths for drag and drop.
By then it had three main features:
- A card library and collection tracker. A catalogue of more than 541 support cards, with ownership and levels kept in a local SQLite database.
- A race calendar and track browser. It cross-references character aptitudes with race schedules and shows track phases and skill activation zones.
- A beam-search optimiser. It evaluates six-card decks against scenario requirements.
An editorial note, because I got this wrong at first: I described the optimiser as finding the “true global maximum”. Beam search is a heuristic and makes no such guarantee.
Two problems worth recording
Wiped save data. Updating the catalogue database could overwrite a user’s local decks and collection. The fix was structural. The app became a slim executable plus a separate Catalogue Data Pack (a zip), and a sync routine, sync_from_seed(), merges new catalogue tables while leaving user tables alone.
Bundling Playwright. Embedding the Chromium scraper in the PyInstaller executable made the download very large and triggered Windows Defender warnings. I moved all scraping to the maintainer side: GitHub Actions runs the scrapers and publishes the Data Pack for users.
The web version
A web edition followed, for people who didn’t want to install anything. The frontend used Next.js and React and matched the desktop design, and added a community hub for sharing decks and viewing tier lists. The backend was FastAPI with SQLAlchemy and Alembic. The optimiser and query logic were pulled out of the PySide6 app, so desktop and web ran the same code and gave identical results.
What it taught me
Local-first design, client-side schema migrations, custom theming in Qt, and porting core logic to a web stack. The Python, PySide6 and Next.js era is the stack described here; the later Rust and TypeScript/TSX rebuild is a separate chapter.