# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

> ⚠️ **Stale (FIFA 23).** This file's status and targets predate the FIFA 17 pivot. Prefer [`docs/PROJECT_STATE.md`](./docs/PROJECT_STATE.md) (canonical). The working target is **FIFA 17**; the canonical server is `fifa17-recon/docker/fifa17-python` (`docker compose up -d`). `openfut-bridge` (FIFA 23) is superseded; `openfut-core` remains the shared backend.

## Repository Layout

This is a monorepo containing three independent Rust crates as git submodules:

| Submodule | Role |
|---|---|
| `openfut-core/` | Game-independent offline FUT backend (Axum + SQLite) |
| `openfut-bridge/` | FIFA 23 integration layer and reverse-engineering proxy |
| `openfut-launcher/` | Native GUI launcher (egui/eframe) for starting/stopping services and managing setup |

Each submodule has its own `Cargo.toml`, `src/`, `tests/`, and `target/`. Commands must be run from within the submodule directory or with `--manifest-path`.

## Common Commands

All commands run from within the relevant submodule directory.

### openfut-core

```bash
# Run (dev mode; creates openfut.db in current dir)
cargo run

# Run (release)
cargo build --release && ./target/release/openfut-core

# Test (full integration suite against in-memory SQLite)
cargo test

# Run a single test
cargo test test_health_endpoint

# Lint
cargo clippy -- -D warnings

# Format
cargo fmt
```

### openfut-bridge

```bash
# Run (Core must be running first on port 8080)
cargo run

# Test
cargo test

# Run a single test
cargo test test_bridge_health_endpoint

# Lint / format same as core
cargo clippy -- -D warnings
cargo fmt
```

### openfut-launcher

```bash
# Run (native GUI window)
cargo run

# Build release
cargo build --release

# Build the hook DLL (Windows cross-compile, required for FIFA 23 integration)
cd openfut-hook
cargo build --release --target x86_64-pc-windows-gnu
```

### Full-stack via setup.sh (Linux, from repo root)

```bash
./setup.sh quickstart    # build + start + hosts + NAT + TLS cert (first-time)
./setup.sh start         # start both services in background
./setup.sh stop          # stop both + clean up hosts/NAT
./setup.sh status        # show running state
./setup.sh logs          # tail both logs
./setup.sh watch-domains # find unknown EA domains hitting the bridge
```

### Environment variables

**Core** (`openfut-core/`):

| Variable | Default |
|---|---|
| `LISTEN_ADDR` | `127.0.0.1:8080` |
| `DATABASE_URL` | `sqlite://openfut.db` |
| `DATA_DIR` | `data` |

**Bridge** (`openfut-bridge/`):

| Variable | Default |
|---|---|
| `BRIDGE_LISTEN_ADDR` | `127.0.0.1:8443` |
| `CORE_URL` | `http://127.0.0.1:8080` |
| `CAPTURES_DIR` | `captures` |
| `PLACEHOLDER_MODE` | `true` |
| `TLS_ENABLED` | `true` |

## Architecture

```
FIFA 23 client
      │
      ▼
openfut-bridge  (port 8443)
      │
      ├── Known route  → openfut-core (port 8080)
      └── Unknown route → placeholder JSON + saved to captures/
                                │
                           SQLite database
                                │
                           data/ (JSON)
```

**Core** has zero knowledge of FIFA 23. It exposes a clean REST API and stores everything in SQLite. **Bridge** has zero game logic — it only translates FIFA 23's wire protocol into Core API calls.

## openfut-core internals

**Module map:**

| Path | Purpose |
|---|---|
| `src/main.rs` | Entry point: tracing, config, pool, migrations, seed, serve |
| `src/lib.rs` | `build_app(pool, data_dir)` — used by integration tests |
| `src/app.rs` | Router construction, `AppState` definition |
| `src/config.rs` | `Config` struct from env vars |
| `src/db.rs` | Pool init + migration runner |
| `src/error.rs` | `AppError` enum + `IntoResponse` impl |
| `src/models/` | Pure data types (`Serde` + SQLx `FromRow`) |
| `src/services/` | Business logic; all DB access lives here |
| `src/routes/` | Axum handler functions; one file per domain |
| `src/seed/` | First-run starter pack grant |
| `src/modding/` | Generic JSON directory loader |
| `data/` | Moddable JSON content: cards, packs, objectives, SBCs, events |
| `migrations/` | SQLx SQL migrations (numbered sequentially) |

**`AppState`** is cloned into every handler via `State<AppState>`. It holds the SQLite pool and Arc-wrapped in-memory registries loaded at startup from `data/` (`CardDb`, `Vec<PackDefinition>`, etc.).

**Layer discipline:** routes extract state and call services; services own all DB access and data-loading logic; models are pure data.

**Single-profile design:** Only one profile per database. All services fetch "the active profile" by selecting the first row — this is intentional.

## openfut-bridge internals

**Module map:**

| Path | Purpose |
|---|---|
| `src/main.rs` | Entry point: config, TLS setup, serve |
| `src/lib.rs` | Library root — re-exports modules |
| `src/config.rs` | `Config` struct |
| `src/proxy.rs` | `catch_all_handler` — the central routing brain |
| `src/mapper.rs` | `map_to_core(method, path)` — FIFA 23 → Core path translation |
| `src/capture.rs` | `CapturedRequest` struct + disk serialization |
| `src/shaper.rs` | Response body transformation (Core → FIFA 23 wire format) |
| `src/tls.rs` | Self-signed cert generation + rustls acceptor |
| `src/routes/` | Bridge admin endpoints (`/_bridge/*`) |
| `captures/` | JSON files written for every intercepted request |

**Request flow:** all traffic hits `catch_all_handler` → `mapper::map_to_core()` → if known, proxy to Core; if unknown, return placeholder JSON and write to `captures/`.

**Adding a new FIFA 23 endpoint:**
1. Capture it in `captures/` or `/_bridge/unknown`
2. Document it in `docs/endpoint-map.md`
3. Add a mapping in `src/mapper.rs` (`EXACT` array or `prefix_routes()`)
4. Implement the handler in Core if needed

## openfut-launcher internals

A native desktop GUI (egui/eframe) that wraps Core and Bridge as managed child processes. It is the recommended way to run OpenFUT on a desktop — no terminal or `setup.sh` required.

**Module map:**

| Path | Purpose |
|---|---|
| `src/main.rs` | Entry point: eframe window setup |
| `src/app.rs` | `LauncherApp` — main UI state + four tabs (Dashboard, Logs, Setup, Config) |
| `src/config.rs` | `LauncherConfig` — persisted settings (binary paths, ports, game dir) |
| `src/process.rs` | `ServiceHandle` — spawns/kills Core and Bridge child processes, tracks status |
| `src/logs.rs` | `LogBuffer` — ring buffer collecting stdout/stderr from child processes |
| `src/setup.rs` | Hook DLL deployment, TLS cert installation into Wine/Proton cert store |
| `openfut-hook/` | Windows DLL (`version.dll`) injected into FIFA 23 via Proton to redirect EA hostnames to the local bridge (cross-compiled with `x86_64-pc-windows-gnu`) |

**Setup flow the launcher automates:**
1. Deploy `openfut_hook.dll` → FIFA 23 game dir as `version.dll` (intercepts network calls inside Wine/Proton, no `/etc/hosts` changes needed)
2. Configure redirect IP written into `openfut.cfg` alongside the DLL
3. Install bridge's self-signed TLS cert into the Wine/Proton cert store
4. Start Core then Bridge (Dashboard tab shows live status)

## Testing approach

**Core** uses a single `tests/integration_test.rs` that spins up a full Axum app against an in-memory SQLite database (`build_app(pool, "data")`). Tests use raw HTTP via `tower::ServiceExt::oneshot`. The `data/` directory is always required at test time (tests run from `openfut-core/`).

**Bridge** tests are in `tests/proxy_test.rs`. They build a bridge app in `placeholder_mode: true` pointing at a dummy Core URL — no live Core required.

## Modding / data files

All game content lives in `openfut-core/data/`. Files are JSON arrays loaded at startup. To add content, drop a JSON file into the right subdirectory and restart Core:

```
data/cards/       ← CardDefinition[]
data/packs/       ← PackDefinition[]
data/objectives/  ← ObjectiveDefinition[]
data/sbcs/        ← SbcDefinition[]
data/events/      ← EventDefinition[]
data/achievements/← AchievementDefinition[]
```

## Version control

All three submodules (`openfut-core`, `openfut-bridge`, `openfut-launcher`) have their own independent git history. Use `tea` (Gitea CLI) for remote operations, not `gh`.
