- Svelte 53.2%
- Python 42.6%
- TypeScript 2.4%
- CSS 0.9%
- Shell 0.6%
- Other 0.2%
Users can now hide/show individual dashboard sections via a settings modal (sliders icon next to tab list). Preferences persist to localStorage. Tabs auto-hide when all their sections are toggled off. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| data | ||
| docs | ||
| frontend | ||
| packaging | ||
| scripts | ||
| src/yafst | ||
| tests | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| CLAUDE.md | ||
| IMPROVEMENT_PLAN.md | ||
| IMPROVEMENT_PROGRESS.md | ||
| LICENSE | ||
| Makefile | ||
| PROGRESS.md | ||
| pyproject.toml | ||
| README.md | ||
yafst -- Yet Another Finals Stats Tracker
A Linux-first, open-source ranked stats tracker for THE FINALS. The game has no public API, so yafst captures your screen in real time, detects which game screen is showing, and stores frames for data extraction. Everything is stored in a local SQLite database and viewable through a built-in web dashboard.
Status: Scene detection, capture, and data extraction are working. The pipeline captures and classifies frames, extracts stats via Gemini vision API, and stores results automatically.
Screenshots
Dashboard
Rank score, peak RS, win rate, RS goal tracking, recent form, and rotating tips. Tabbed insight sections: At a Glance, Where You Shine, Your Grind, Your Arsenal, Familiar Faces.
Match History
Filterable match list with search, placement badges, seed-to-place tracking, and RS progression. Filter by map, class, weapon, placement range, date, and session. Export to CSV or JSON.
Match Detail
Placement badge, RS change, bracket reward curve, per-round scoreboards with player stats, loadout display, and match notes.
Sessions
Session list with duration, match count, win record, RS change, VS previous session comparison, RS/hr rate, tilt grade, and sparkline charts. Session RS trend bar chart at the bottom.
Session Detail
Per-session stat cards, VS career average comparison with trend indicators, RS progression chart, and match breakdown table.
Features
Capture and Detection
- Live screen capture via PipeWire ScreenCast (Wayland-native, same as OBS), KDE KWin ScreenShot2, x11grab, or grim -- auto-detects the best backend
- Scene detection using anchor-based pixel spot-checks and color analysis across 6 game screens (bracket, loadout, in-game leaderboard, post-round results, post-game tournament, post-game team performance)
- Resolution profiles -- supports bundled and custom profiles at any resolution via the calibrate page
- Rank tier lookup -- converts RS values to tier names (Bronze through Ruby) based on Season 6 thresholds
- Game data constants -- weapons, gadgets, specializations, maps, and rank tiers bundled in the codebase
Storage and Management
- SQLite storage with sessions, matches, rounds, per-player stats, and loadouts
- Database backup/restore --
yafst backup,yafst restore,yafst backups - TOML config file at
~/.config/yafst/config.tomlfor persistent settings - Manual match creation and editing -- add matches by hand, edit stats
- Match notes -- freeform text notes on any match
Web Dashboard
- Dark THE FINALS-themed UI built on SvelteKit SPA + FastAPI API backend
- RS history charts with color-coded placements and rank tier annotations
- Match filtering and search by map, placement range, date range, and text
- Pagination across all list views
- Session detail pages with per-session RS charts and match tables
- Personal bests and win streaks -- best KD, most eliminations, longest streak
- Rolling performance stats -- sliding-window win rate, placement, and KD
- Loadout performance analytics -- per-weapon and per-class win rates and stats
- CSV/JSON export of full match history with round-level detail
- Keyboard shortcuts for common navigation actions
- Print-friendly CSS for match detail and session pages
- Accessibility -- ARIA labels, skip-to-content links, focus-visible styles
API
- JSON REST API for stats, matches, sessions, RS history, loadout stats, personal bests, and map stats
- API documentation via Swagger UI (
/docs) and ReDoc (/redoc)
Calibration Tools
- Calibrate page (
/calibrate) for setting up detection anchors -- required before first use - Frame extraction from Steam DASH recordings
- Batch scene classification for testing detection accuracy
Quick Start
1. Install system dependencies
All system dependencies are optional but recommended for specific features.
# Arch
sudo pacman -S ffmpeg xdotool gstreamer
# Debian/Ubuntu
sudo apt install ffmpeg xdotool gstreamer1.0-tools
# Fedora
sudo dnf install ffmpeg xdotool gstreamer1-plugins-base-tools
Verify with yafst check after install (see step 3).
2. Install yafst
git clone https://forge.wolfhound.dev/wolfhound/yafst.git
cd yafst
uv venv
uv pip install -e ".[capture,dev]"
The capture extra installs PipeWire/D-Bus dependencies (dbus-fast,
PyGObject). Omit it if you only want to run tests or the web UI.
Or use the Makefile for a full dev setup:
make dev-setup # installs everything + runs setup wizard
3. First-run setup
The first time you run yafst run, a default config is written silently and
the web server starts. Your browser is then redirected to a setup wizard at
/setup where you can enter your player name, choose a capture backend, and
configure your Gemini API key.
yafst run
# Open http://127.0.0.1:8745 — redirects to /setup on first run
You can re-open the setup wizard at any time from the Settings page, or force
it from the CLI with yafst --setup. To generate a default config without
starting the server:
yafst --init-config
4. Verify your setup
yafst check
System dependencies:
✓ ffmpeg 6.1
✗ xdotool (not found -- optional, needed for X11 window detection)
✓ gst-inspect-1.0 1.24.3
5. Calibrate
Before first use, open the calibrate page to set up screen detection anchors for your resolution:
yafst web
# Open http://127.0.0.1:8745/calibrate
Upload or capture screenshots of each game screen and configure the detection anchors. Without calibration, yafst cannot detect game screens.
6. Run
Start the tracker while THE FINALS is running:
yafst run
The web dashboard starts automatically at http://127.0.0.1:8745. Use
--no-web to run capture only, or --port 9000 to change the port.
To view past results without capturing:
yafst web
Seed fake data (for testing the UI)
python scripts/seed_data.py
CLI Tools
| Command | Description |
|---|---|
yafst run |
Main tracker -- captures screen, detects scenes, stores frames, and serves the dashboard. |
yafst web |
Starts only the web dashboard (no screen capture). |
yafst check |
Check system dependencies (ffmpeg, etc.) and print a summary. |
yafst backup |
Back up the database to a timestamped file (or -o PATH for a custom destination). |
yafst restore <file> |
Restore the database from a backup file (prompts for confirmation). |
yafst backups |
List available database backups with dates and sizes. |
yafst calibrate |
Run calibration to capture reference templates. |
yafst reselect |
Clear saved screen capture selection and re-pick the source. |
yafst --setup |
Force the first-run setup wizard (re-configure). |
yafst --init-config |
Generate a default config.toml at ~/.config/yafst/. |
yafst-calibrate classify <image> |
Classify a screenshot into a scene type. |
yafst-calibrate gemini <image> |
Test Gemini extraction on a screenshot (--model, --scene, --no-crop). |
yafst-calibrate extract <dash_dir> <timestamp> |
Pull a frame from a Steam DASH recording. |
yafst-calibrate batch <dash_dir> |
Extract frames at intervals and classify each one. |
yafst-replay <dash_dir> |
Replay a DASH recording through the full pipeline. |
Architecture
src/yafst/
main.py CLI entry point (run / web / backup / restore / check)
config.py TOML config, XDG paths, capture/web settings
setup.py First-run setup wizard (interactive config)
depcheck.py System dependency checker (ffmpeg, xdotool, gstreamer)
game_data.py Known weapons/gadgets/specs, rank tiers, maps
util.py Shared utilities (ISO duration parser, D-Bus checks)
capture/ Screen capture backends (pipewire, kwin, x11grab, grim)
detection/
scene.py Scene enum (6 primary + legacy states) + SceneClassifier
anchors.py Anchor definitions for scene detection
fields.py Field definitions for extraction regions
regions.py Per-scene region definitions (relative coordinates)
profiles.py Resolution profiles (save/load JSON, auto-detect)
pipeline/
processor.py Async capture loop (poll -> classify -> store)
state.py Game state machine (idle -> monitoring -> capturing)
extraction.py Frame extraction via Gemini vision API
debug_state.py Pipeline <-> web bridge for live debug view
models/
schemas.py Data models (Session, Match, Round, PlayerStats, Loadout)
database.py SQLite operations with schema migrations
web/
app.py FastAPI application factory (Swagger UI at /docs, ReDoc at /redoc)
routes.py Dashboard, matches, sessions, calibrate, debug, API endpoints
frontend/ SvelteKit SPA (prerendered, served by FastAPI)
static/ Static assets (favicon, examples)
calibrate.py Calibration CLI
replay.py DASH recording replay
Makefile Dev setup, lint, test, and build targets
scripts/
seed_data.py Generate realistic fake match data
tests/ 316 tests (pytest)
docs/
CONTRIBUTING_FRAMES.md Guide for adding new screens and features
Data Storage
All data lives under ~/.local/share/yafst/ (respects XDG_DATA_HOME):
yafst.db-- SQLite database (sessions, matches, rounds, stats, loadouts)screenshots/-- raw captures saved during tracking
Config at ~/.config/yafst/:
config.toml-- persistent settingsprofiles/-- custom resolution calibration profiles (includes detection anchors)
What's Tracked
Scene detection identifies these screens automatically. Data extraction uses the Gemini vision API to read stats from captured frames.
| Data | Source Screen | Detection | Extraction |
|---|---|---|---|
| Final placement (1st-8th) | POST_GAME_TOURNAMENT | Working | Gemini |
| Rank Score (RS) after match | POST_GAME_TOURNAMENT | Working | Gemini |
| Per-player stats (8 fields) | POST_GAME_TEAM_PERF | Working | Gemini |
| Self-player identification | POST_GAME_TEAM_PERF | Working | Gemini |
| Loadout (class, weapon, spec, gadgets) | LOADOUT | Working | Gemini |
| Map name | BRACKET / LOADOUT | Working | Gemini |
| Skill range | BRACKET | Working | Gemini |
| Round number | LOADOUT | Working | Gemini |
| Rank tier (Bronze through Ruby) | Computed from RS | N/A | Working |
Gemini API Cost
yafst uses Google's Gemini vision API to extract match data from captured screenshots. Each match generates multiple API calls depending on the number of rounds played and leaderboard snapshots captured.
Per-match breakdown
A typical 3-round match triggers 11-13 API calls: bracket, loadout (per round), post-round results, post-game tournament, post-game team performance, and leaderboard snapshots (first + last per round).
| Call type | Calls per match | Input tokens | Output tokens |
|---|---|---|---|
| Bracket | 1 | ~1,360 | ~110 |
| Loadout | 1-3 | ~1,710 | ~130 |
| Post-round results | 1 | ~1,330 | ~270 |
| Post-game tournament | 1 | ~1,270 | ~250 |
| Post-game team perf | 1 | ~1,330 | ~340 |
| Leaderboard snapshot | 2-6 | ~1,500 | ~1,490 |
Monthly cost estimate
Based on 10 games per weekday and 20 on weekends (~380 matches/month), assuming every match goes to 3 rounds with full leaderboard capture (worst case):
| Model | Monthly cost | Notes |
|---|---|---|
| gemini-2.5-flash-lite | ~$2.30 | Cheapest, but unreliable on complex scenes |
| gemini-3.1-flash-lite | ~$7.72 | Best accuracy-to-cost ratio (default) |
| gemini-2.5-flash | ~$12.00 | Higher accuracy, frequent 503s under load |
Most players will land well under these numbers -- early exits, fewer leaderboard captures, and shorter sessions all reduce calls. A casual player doing 5-10 matches a day would see roughly $2-4/month on the default model.
Free tier
Google's free tier allows 1,000 requests per day on lite models. Even a heavy 20-match session produces ~260 API calls, so the free tier covers normal play. You'd need 75+ matches in a single day to exceed it.
Data region cropping
The calibrate page (/calibrate) lets you define crop regions per scene.
Instead of sending the full screenshot, yafst composites just the relevant
areas into a smaller image. On lite models this doesn't reduce token cost
(images are flat-rate), but it reduces visual noise and improves extraction
accuracy.
Token usage is tracked per call and visible on the Settings page.
Roadmap
- AI vision model extraction -- Gemini vision API extracts match data from captured frames; accuracy improvements and edge-case handling are ongoing
- Scoreboard (TAB) extraction -- region definitions needed, scene detection already works (needs screenshots)
- Built-in resolution profiles for common resolutions (1080p, 1440p, 4K, ultrawide) -- needs screenshots from other resolutions
Contributing
See docs/CONTRIBUTING_FRAMES.md for how to help with new screen calibration, feature expansion, and resolution support.
License
MIT





