funman300 90210702c3
CI / Build, lint & test (push) Successful in 3m25s
feat(core): training replaces, and the rare card boosts all six
Corrects two decisions the previous revision got wrong, both now settled by
the published FIFA 17 training guide -- the same source class and publisher
this project already accepted for the contract matrix, and which
cross-validates 6/6 against fcc_trainingcards on rows we had misclassified.

"You can only boost one attribute or all six... When you apply a new training
card to a player, he loses the improved attributes of previous training
cards. It does not accumulate, it replaces."

So a second card REPLACES rather than being refused, and two slots must never
be boosted at once -- our old composite key permitted exactly that, and
staging demonstrated it by holding a slot-4 and a slot-1 effect together.

Migration 0030 reshapes the table to one row per instance keyed on
owned_card_id alone, with attribute_index nullable for the rare all-six card,
and carries forward the most recent effect where several existed -- the
replaces rule applied retroactively. 0029 is left intact rather than
rewritten: it is already applied to supervised staging, where migration
history matters.

Apply is now delete-then-insert inside the one transaction, so the old effect
cannot survive a failed insert and the two cannot coexist. The outcome
records what it displaced instead of overwriting history silently.

The previous refusal was OUR policy standing in for an unknown, and it was
never evidence about FIFA 17. It is retired now that the behaviour is
recovered, not because the 409 was inconvenient.
2026-08-22 23:33:43 +00:00
2026-06-25 14:54:51 -07:00
2026-06-25 14:54:51 -07:00

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

S
Description
Offline Ultimate Team backend — game-independent core
Readme 635 MiB
Languages
Rust 100%