1df03d4287156fa0629bc662773fbe1f20f0d68c
CI / Build, lint & test (push) Successful in 3m19s
FIFA 17 training is a ONE-MATCH effect and the trigger is the PLAYER PLAYING, not the match completing: a card applied to someone who stays on the bench or in the reserves "will continue to benefit from the training effect until he plays" (DOCUMENTED, fifauteam's contemporaneous FIFA 17 guide, corroborated across two of its pages). So Core expires exactly the instances the caller names, and never a whole club. The participant list is supplied rather than derived here, deliberately: - The FIFA 17 match wire carries NO lineup. Across 36,149 captured requests the 19 match creates carry 5 keys and the 13 ends carry 6; the tokens "lineup" and "substitut" appear ZERO times, while "kitNumber" appears 1311 times and the same extraction recovers 23 instance ids from PUT /squad/0 in that same pcap. The absence is measured against a working positive control, not assumed. - Core must not resolve it from the squad at completion either: the squad at end is provably not the squad that started (a captured match began 20:33:20 and the next squad save landed 12 minutes later with no /match/end between). Empty participants therefore expires nothing, so a caller that cannot identify who played is inert instead of destructive. The mutation sits inside the existing single match transaction, under the same is_economic guard as coins and statistics, so NoContest voids it exactly as it voids everything else, and a rollback leaves boosts intact. Tests: participant scoping (the benched player keeps his boost), empty-participant inertness, club scoping, replay (a resubmitted completion does not consume a freshly reapplied boost), NoContest, and a BeforeCommit fault that fires AFTER the delete to prove the split-brain state "match rejected but training consumed" cannot occur.
OpenFUT Core
Offline Ultimate Team backend — game-independent.
OpenFUT Core is the heart of the OpenFUT project: a fully offline, single-player FUT-style backend written in Rust. It is deliberately decoupled from any specific game, though it is designed to power a FIFA 23 offline experience.
What it does
- Creates and manages local player profiles and clubs
- Manages coins, XP, and progression
- Generates packs from weighted JSON definitions
- Tracks your full card collection
- Squad builder with formations and chemistry (chemistry calculations: WIP)
- Objectives engine (daily, weekly, lifetime, milestone)
- SBC (Squad Building Challenge) engine with JSON-defined challenges
- Match result processing with coin and XP rewards
- NPC transfer market with daily refreshes
- Statistics tracking
- Fully moddable via JSON data files
Tech Stack
- Rust + Axum (HTTP framework)
- Tokio (async runtime)
- SQLite + SQLx (database + migrations)
- Serde (JSON data layer)
- tower-http (middleware: CORS, tracing)
Quick Start
# Build
cargo build --release
# Run (creates openfut.db in current directory)
./target/release/openfut-core
# Or with custom config
DATABASE_URL=sqlite://./myclub.db LISTEN_ADDR=127.0.0.1:8080 ./target/release/openfut-core
Environment Variables
| Variable | Default | Description |
|---|---|---|
LISTEN_ADDR |
127.0.0.1:8080 |
Address to listen on |
DATABASE_URL |
sqlite://openfut.db |
SQLite database path |
DATA_DIR |
data |
Path to JSON data files |
API Routes
| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check |
POST |
/auth/local |
Create first-run profile + club |
GET |
/profile |
Get active profile |
GET |
/club |
Get active club with coins |
GET |
/cards |
Browse all card definitions |
GET |
/collection |
Get owned cards |
GET |
/packs |
List unopened packs |
POST |
/packs/open/:pack_id |
Open a pack |
GET |
/squad |
Get active squad |
POST |
/squad |
Save squad |
GET |
/objectives |
List objectives with progress |
POST |
/matches/complete |
Complete a match exactly once + receive rewards |
GET |
/sbc |
List SBC definitions |
POST |
/sbc/submit |
Submit SBC solution |
GET |
/market |
Browse NPC transfer market |
POST |
/market/buy |
Buy listing |
POST |
/market/sell |
Quick-sell card |
POST |
/market/refresh |
Refresh NPC listings |
GET |
/statistics |
Get match/pack/SBC stats |
First Run
# Create your profile
curl -X POST http://localhost:8080/auth/local \
-H 'Content-Type: application/json' \
-d '{"username": "Player 1"}'
# Check your club (5000 coins + a gold pack waiting)
curl http://localhost:8080/club
# Open your starter pack
curl -X POST http://localhost:8080/packs/open/<pack_id>
# Submit a match win. `match_identity` keys exactly-once economy: resubmitting the
# same identity echoes the first result and grants nothing twice.
curl -X POST http://localhost:8080/matches/complete \
-H 'Content-Type: application/json' \
-d '{"match_identity":"match-1","result":"win","squad_id":"any","opponent_name":"Beginner AI","goals_for":3,"goals_against":0,"mode":"squad_battles"}'
Modding
All game content lives in data/. Drop JSON files into the appropriate folder and restart.
data/
cards/ ← CardDefinition[]
packs/ ← PackDefinition[]
objectives/ ← ObjectiveDefinition[]
sbcs/ ← SbcDefinition[]
events/ ← (future)
See docs/modding.md for schema reference.
Development
cargo fmt
cargo clippy -- -D warnings
cargo test
License
MIT — see LICENSE
Description
Languages
Rust
100%