The all-in-one companion for «Мир танков»: a site, a game modpack and its manager.
Player stats · Marks of excellence · Tank analytics · Clans · Replays · Streamer tools · In-game HUD · Developer API
What's inside · Architecture · Getting started · Commands · CI and releases · Docs
Три отметки is one place for everything a «Мир танков» (Lesta, RU realm) player looks up between battles, plus a modpack that shows the rest right in the game. It is an independent fan project, not affiliated with Lesta Games.
Important
Game data comes from the Lesta API under its terms, and they are hard rules here:
- every page carries the attribution;
- game accounts link only through Lesta ID, never with a password;
- no ads;
- data retention and deletion are honoured.
The mod is fair play: it shows only the player's own data and what the client already shows, never enemy positions, reloads or aim.
🌐 Site — triotmetki.ru
|
|
flowchart LR
subgraph game["Player's PC"]
client["«Мир танков» 1.45"]
mod["Modpack<br/>Python 2.7 + Gameface UI"]
manager["Manager<br/>Tauri 2"]
client --- mod
manager -- installs --> mod
end
subgraph vps["VPS · docker compose"]
caddy["Caddy<br/>TLS · /downloads"]
web["Next.js 16<br/>triotmetki.ru"]
api["NestJS API<br/>api.triotmetki.ru"]
worker["Worker<br/>BullMQ collector"]
db[("PostgreSQL 17<br/>+ TimescaleDB")]
redis[("Redis")]
end
lesta["Lesta API"]
caddy --> web & api
web --> api
api --> db & redis
worker --> db & redis
worker -- rate-limited --> lesta
mod -- signed battle results --> api
manager -- releases --> caddy
- One server image, two entrypoints:
src/main.tsserves the site API, the developer API, auth and mod ingest;src/worker.tsruns the collector that pulls the Lesta API into TimescaleDB. - Everything is stored on the VPS: replays and armor models on a volume, public downloads served by Caddy at
/downloads/. There is no S3 and no CDN.
| Layer | Tech |
|---|---|
| Web | Next.js 16, React 19 + React Compiler, next-intl, TanStack Query/Table/Virtual, visx, Base UI, cmdk, motion, Embla, SCSS modules, FSD |
| API | NestJS 11 on Bun, better-auth (Lesta ID, Telegram, VK Mini App), Zod contracts, OpenAPI |
| Worker | BullMQ jobs, a shared Redis rate limiter, cockatiel circuit breaker |
| Data | PostgreSQL 17 + TimescaleDB, Prisma 7, Redis |
| Game mod | Python 2.7 .mtmod packages (logic tested on Python 3), React Gameface UI |
| Manager | Tauri 2: Rust core + React UI |
| Tooling | Bun workspaces + catalog, ESLint, Prettier, Stylelint, Vitest, Playwright, knip, jscpd, madge, Husky, commitlint |
apps/
web/client/ Next.js site (Feature-Sliced Design)
web/server/ NestJS API + collector worker, Prisma schema, Lesta client, replay parser
game/modpack/ game-client modpack: core, companion, features, catalogue
game/manager/ modpack manager: tauri/ (Rust) + web/ (React)
packages/ only code shared between apps
ratings/ WN8, EFF, Броня-Индекс, MoE math
schemas/ Zod contracts shared by client and server
gamedata/ loadout calculator and the game-data model
design-tokens/ SCSS tokens for the site, the manager and the in-game window
icons/ SVG icon set as React components
sdk/ @otmetki/sdk, the public API client
logger/ pino wrapper
e2e/ Playwright smoke tests
infra/caddy/ Caddyfile for docker-compose.yml
docs/ architecture, guides, ops, product, research, specs
You need Bun ≥ 1.3, Docker and Python 3 (for the modpack's tests). The manager also needs Rust.
bun install
cp .env.example .env # set INTERNAL_API_TOKEN (32+ chars: openssl rand -base64 32)
bun run dev:infra # TimescaleDB :5434, Redis :6380, Mailpit :1025 (inbox :8025)
bun run db:push # extensions + prisma db push + the Timescale layer
bun run gamedata:import # optional: vehicles, modules, equipment and maps from the public client-data repos
bun run dev # server :4000 + client :3000bun run dev:all also starts the worker, and bun run dev:manager starts the manager.
Note
No Lesta key? Everything still runs. There is no mock and no generated data anywhere.
- With
LESTA_APPLICATION_IDempty, the server boots, the worker starts in degraded mode and every page shows its empty state. LESTA_NOTICE=trueadds the site-wide «data not connected yet» banner. It is a runtime switch: restart the client, no rebuild needed.bun run gamedata:importstill fills the tank catalogue, maps and missions.
Dev server troubleshooting
bun run dev and dev:all restart a crashed server or client by themselves (concurrently --restart-tries). Ctrl+C still stops everything.
-
next devslowly eats memory or stops answering. Stop it, deleteapps/web/client/.next/devand start again. The dev-only settings in apps/web/client/config/dev-server.ts keep memory flat:- Turbopack's disk cache stays on;
- memory eviction is
'full'; - the React Compiler runs through its Rust port;
- webpack loaders run in worker threads;
reactDebugChannelis off. With it on, Next 16.3 holds every HTML request that never opens an HMR socket.
dev:clientalso caps the V8 heap at 6 GB, so Next restarts itself before the machine runs out of memory. -
The API disappears.
dev:serveranddev:workerrun undernodemon(apps/web/server/nodemon.json), which watches only the server's own sources, because Bun's--watchcrashes on Windows. After a real crash, save any file or typers+ Enter. -
Need a second
next dev? Start it withNEXT_DIST_DIR=.next/probeand another-p. -
Disk: the Turbopack cache can reach tens of GB. Deleting
.next/devwhile the dev server is stopped is always safe.
| Command | What |
|---|---|
bun run dev · dev:all · dev:client · dev:server · dev:worker · dev:manager |
Dev servers |
bun run dev:infra · dev:infra:down |
Local TimescaleDB, Redis and Mailpit |
bun run db:push · db:reset · db:studio |
Schema sync (no migrations before production), full reset, Prisma Studio |
bun run gamedata:import |
Import the game client's data |
bun run verify |
typecheck + lint + import cycles + UTF-8 + format + styles: what CI runs |
bun run fix |
Auto-fix lint, formatting, styles and the Prisma schema |
bun run test · test:changed |
Vitest across the monorepo · only the tests your uncommitted changes reach |
bun run test:modpack |
The modpack's Python suites |
bun run test:e2e · e2e:screens |
Playwright smoke · the screenshot suite |
bun run lint:unused · lint:dupes |
knip (unused files, exports, dependencies) · jscpd (copy-paste) |
docker compose up -d --build |
Production-like stack: Caddy, client, server, worker, db, redis |
Use bun run test, never bun test.
| Workflow | When | What |
|---|---|---|
| deploy | manual | verify, tests, modpack suites and the e2e smoke, then client and server images to ghcr and a rollout on the VPS. Code that already passed skips the checks. |
| release | manual | Builds the modpack and the manager for versions not yet on the VPS and publishes them there |
| modpack | PRs touching apps/game/modpack, or manual |
pytest, ruff, vermin; a manual release build of the packages and the component catalogue |
| manager | PRs touching the manager, or manual | UI checks, cargo fmt / clippy / test on Windows; a manual NSIS installer build |
To release, bump version in apps/game/modpack/package.json (its otmetki.games lists the supported clients) and in apps/game/manager/package.json, commit, and run release. The first production deploy follows docs/ops/deploy.md.
| Start here | |
|---|---|
| docs/README.md | Index of every doc |
| docs/product/features.md | Product scope and what is done |
| docs/specs/2026-09-24-otmetki-design.md | Architecture |
| docs/guides/ | Code style: client, server, shared |
| docs/architecture/fsd.md | Feature-Sliced Design in the client |
| docs/research/data/lesta-api.md | Lesta API reference and terms |
| apps/game/modpack/README.md | The modpack and its components |
| CLAUDE.md | Guidance for AI agents working in the repo |
- Commits: conventional commits, enforced by commitlint.
- Pre-commit hook: lint-staged, a typecheck of the touched workspaces, and the modpack suites when Python changes.
- Code: every user-facing string goes through next-intl in ru and en. Shared contracts live in
@otmetki/schemas. Ready-made packages are preferred over custom code.
© Три отметки. «Мир танков» and all related game content are the property of Lesta Games.