[UN]
personal project

It Girl

Automation for a live Roblox game: rank changes, a queue worker, Flask, and the reality of platform limits.

live status

It Girl taught me that the most annoying systems are often the ones that look simple from far away. Players earn something in-game. Their group rank should update. That sounds small until the audience is real and the admin work starts happening by hand.

Once the game had real activity, manual promotion stopped being a nuisance and started becoming a system problem: missed updates, inconsistent timing, and too much trust in whoever happened to have the admin panel open.

I rebuilt the rank service as a small Python/Flask API used by in-game Lua scripts. It authenticates requests, resolves rank numbers through the Roblox Groups API, and pushes rank changes into a worker queue instead of doing the slow part inline.

Architecture

The core design is a producer/consumer queue. Flask routes validate incoming requests and enqueue rank-change jobs immediately, so game servers get a response without waiting on a slow platform call. A daemonized worker thread drains a thread-safe queue.Queue with a six-second delay between operations, which keeps the service inside Roblox API limits without adding an external scheduler.

The request flow is deliberately plain:

  1. A Roblox game server calls /Promote, /Demote, /SetRank, /group_ranks, or the generic rank endpoint.
  2. The Flask route validates the request and resolves the human-readable rank number to a Roblox role ID through the Groups API.
  3. The route pushes a job tuple onto the queue and returns to the caller.
  4. The worker fetches a fresh CSRF token, issues the Roblox PATCH request, then re-fetches the user's role to verify the change landed.

Reliability

The boring parts are the point here: retry logic with CSRF token refresh on 403s, post-change verification, structured logging with correlation IDs across the async boundary, and a guard against redundant rank changes. The queue exists because Roblox is the bottleneck. The API bends around the platform instead of pretending the platform will behave like a normal internal service.

Ownership

This was not a greenfield weekend build. The useful work was understanding what could not break and replacing the live path without losing behavior people depended on. It runs quietly, which is the point. When it does not, people notice quickly.

Stack

Python, Flask, threading, queue.Queue, Requests, and dotenv. The service runs as a persistent process with a daemonized worker thread.

Access

The codebase stays private because the economy and players are live. Uptime is public on udenna.betteruptime.com. I can walk through the architecture, failure modes, and sanitized samples when the context calls for more detail.

problem

Players were earning in-game milestones faster than the team could safely update Roblox group ranks by hand. The system needed to automate promotions without hammering Roblox APIs or letting one failed request throw the whole path out of sync.

constraints

  • Roblox game servers needed a simple REST surface, but rank changes could not block on slow platform calls.
  • Roblox Groups API rate limits shaped the queue and worker timing more than framework preference did.
  • CSRF token refresh, post-change verification, and redundant-rank guards mattered because the game economy was live.

what it taught me

  • A useful automation system is boring in the right ways: clear boundaries, retries, logs, and failure states people can reason about.
  • When the platform API is the constraint, the queueing and recovery model matter more than the web framework.
  • Private work still needs public proof, so uptime, architecture notes, and walkthrough-ready samples matter.