Case study · 2026 · In-house product · API + Web
— 10

A prediction engine, server-side and fast.

Tower Predict turns a mobile game save file into exact, deterministic predictions, computed rather than estimated. We built the whole stack: a pure Python prediction library, a FastAPI service over it across 21 routes, and a React front-end on Cloudflare Pages that carries no prediction logic at all. Saves are parsed in memory and discarded when the response ends, never written to disk. Live at towerpredict.com.

ClientTower Predict
Year2026
LocationRemote · EU infrastructure
Tower Predict landing page
Live · towerpredict.comVisit site ↗
~49msFast endpoints
21API routes
In memorySaves never written
Livetowerpredict.com
[ 10 · Overview ]

The brief,
in three beats.

Challenge · Approach · Outcome
— Challenge

What they needed.

A prediction is only worth anything if it is exact and it arrives fast. That means parsing a binary save file, replaying a deterministic sequence forward, and answering in the time it takes to tab back to the app. It also needs a strict honesty rule: an input the engine does not support has to produce an error, never a confident wrong answer. And because the input is somebody's personal save file, writing any part of it to disk was never on the table.

— Approach

How we tackled it.

Three pieces with clean seams. A pure Python library holding the prediction logic, depended on at an exact tag rather than a version floor, because its data tables are derived from one specific build. A FastAPI service over that library, running four uvicorn workers in a container that can do almost nothing. A React front-end on Cloudflare Pages that carries no prediction logic: it collects a save, posts it, and renders the answer.

— Outcome

What happened.

Live at towerpredict.com. Fast endpoints cost about 49ms, the one slow endpoint is bounded so a single search cannot monopolise a worker, and no save file is ever written to disk.

[ 10 · How we got there ]

Inside the
engagement.

7 chapters
01 / 07Architecture

A library, not a pile of endpoints

The prediction logic lives in its own package as pure functions: a save plus parameters in, an answer out, with nothing touching a network or a device. The API depends on it at an exact tag, not a floor, because the catalogs inside are derived from one specific build and "latest" is never the right answer. Both the library and the service carry their own test suites.

02 / 07API

An envelope that refuses to guess

Every prediction response carries the same wrapper: whether the input is supported, how confident the answer is, which game version and catalog produced it, and any warnings. A save from an unsupported build gets a refusal rather than a plausible wrong list. The client never has to work out whether a prediction can be trusted, because the answer says.

  • supported · confidence · game version · catalog · warnings
  • Version gate refuses unsupported inputs outright
  • One shape across the prediction endpoints, so clients need no special cases
03 / 07Performance

One slow search cannot monopolise a worker

Fast endpoints cost about 49ms, almost all of it the save parse. The substat planner is different: it runs an exhaustive search and blocks for seconds. Left alone it would occupy a worker indefinitely, so it gets its own semaphore: two searches per worker, a third waits, and it waits against a deadline rather than forever. Four workers on two CPUs, oversubscribed on purpose to keep the fast paths moving while a search runs.

  • 5s budget on fast endpoints, roughly 100x measured cost
  • 60s budget on the search, deliberately under Cloudflare's proxy limit
  • Concurrency capped per worker, queueing bounded
04 / 07Security

A container that owns nothing

The service parses untrusted input, so it runs as a non-root user with a read-only root filesystem, every Linux capability dropped, no-new-privileges set, and a small tmpfs as the only writable space. CPU, memory and process count are all capped from measurement rather than guesswork. The build is multi-stage: git and SSH exist only in the build stage, so the runtime image carries no toolchain and no key.

05 / 07Privacy

The save never touches disk

A save is parsed in memory and discarded when the response is sent. It is never written to disk and never kept. What the server does keep is a one-line-per-request access log of method, path and status, deliberately, because it is the operator's only usage record: no bodies, no headers, no save contents, and because every request arrives through the tunnel the client address it prints is the tunnel's rather than a visitor's. The published privacy policy documents that log, and the one place anything is retained, rather than claiming nothing happens at all.

  • Prediction path writes nothing and retains nothing
  • Access log is method, path and status only
  • Front-end ships no prediction logic and no source maps
06 / 07Front-end

Pull the save off the phone in the browser

Finding playerInfo.dat by hand means digging through Android's data directory. So the front-end also speaks ADB over WebUSB: on a desktop Chrome or Edge, the phone is plugged in, the player approves the prompt once, and the file goes straight into the same upload path a manual pick uses. It runs entirely in the browser, and nothing else leaves the device.

07 / 07Infrastructure

Deployed without a public port

The service listens inside its container, but the port is published only to the host's loopback, so nothing outside the box can reach it directly. Cloudflare fronts it through a tunnel and the origin's address stays out of DNS. GitLab CI runs the tests, then deploys on the box itself through the host's Docker daemon, with the dependency's deploy key mounted as a build secret so it never lands in an image layer.

  • Port published to 127.0.0.1 only, reached through the tunnel
  • Test then deploy, same pipeline, no manual step
  • Deploy key as a build secret, never baked into a layer
[ 10 · Tech ]

The tools
we shipped on.

Battle-tested choices · not flavour-of-the-month
Python 3.12FastAPIuvicornReact 18ViteWebUSBADBCloudflare PagesCloudflare TunnelPages FunctionsDockerGitLab CI/CDpytest
[ 10 · Related work ]

Other
engagements.

From the studio

— Let's make something —

Tell us what
you're building.

Studio
Port of Spain
Trinidad & Tobago
Hours
Mon — Fri
09:00 — 17:00 AST
Response
Within 24 hours
on business days