diff --git a/docs/endpoint-map.md b/docs/endpoint-map.md index 78b611c..f390386 100644 --- a/docs/endpoint-map.md +++ b/docs/endpoint-map.md @@ -48,7 +48,7 @@ This document maps confirmed or suspected FIFA 23 FUT API endpoints to their Ope | FUT Endpoint | Core Endpoint | Status | Notes | |---|---|---|---| -| Unknown | `POST /matches/result` | ❌ | Needs capture | +| Unknown | `POST /matches/complete` | ❌ | Needs capture. Body must carry a `match_identity` (exactly-once key). `POST /matches/result` was removed — no transaction, no idempotency key. | ## Objectives diff --git a/src/dashboard.html b/src/dashboard.html index ec95a05..764d199 100644 --- a/src/dashboard.html +++ b/src/dashboard.html @@ -1651,12 +1651,20 @@ async function submitMatch() { const opponentName = currentOpponent?.opponent_name ?? `${diff.replace('_',' ')} Bot`; try { - const r = await api('POST', '/matches/result', { + // Each click is a distinct match, so it mints its own identity: the + // exactly-once route keys idempotency on it, and the old /matches/result + // path (no transaction, no identity) is closed. The dashboard drives Core's + // own match mode, so it opts into Core loan expiry and season progression. + const r = await api('POST', '/matches/complete', { + match_identity: `dashboard-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`, + result: gf > ga ? 'win' : gf === ga ? 'draw' : 'loss', squad_id: 'dashboard', opponent_name: opponentName, goals_for: gf, goals_against: ga, mode: 'squad_battles', + expire_loans: true, + advance_season: true, }); const outcome = gf > ga ? 'Win' : gf === ga ? 'Draw' : 'Loss'; const outcomeColor = gf > ga ? '#3fb950' : gf === ga ? '#8b949e' : '#f85149'; diff --git a/src/mapper.rs b/src/mapper.rs index d9cb0a1..7e25a21 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -157,8 +157,10 @@ const EXACT: &[ExactRoute] = &[ }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/result", - core_method: "POST", core_path: "/matches/result", - notes: "FUT match result submit → Core match result", + core_method: "POST", core_path: "/matches/complete", + notes: "FUT match result submit → Core exactly-once match completion. \ + Core requires a match_identity on the body; /matches/result was \ + removed because it had no transaction and no idempotency key.", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/matches", @@ -301,8 +303,9 @@ const EXACT: &[ExactRoute] = &[ }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/rivals/result", - core_method: "POST", core_path: "/matches/result", - notes: "FUT rivals match result → Core match result", + core_method: "POST", core_path: "/matches/complete", + notes: "FUT rivals match result → Core exactly-once match completion \ + (body must carry a match_identity).", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/rivals/leaderboard", @@ -783,7 +786,7 @@ mod tests { fn test_rivals_result_maps() { let m = map_to_core("POST", "/ut/game/fut/rivals/result"); assert!(m.is_some()); - assert_eq!(m.unwrap().core_path, "/matches/result"); + assert_eq!(m.unwrap().core_path, "/matches/complete"); } #[test]