fix(bridge): submit matches to Core's exactly-once completion route
Core removed `POST /matches/result` as an economy path: it had no transaction and no idempotency key, so it re-credited the same match on every call. The two EA result routes and the dashboard now target `POST /matches/complete`. The dashboard mints a fresh `match_identity` per submission — each click is a distinct match — and opts into `expire_loans` / `advance_season`, which the old route used to trigger implicitly. The mapper entries and the endpoint map record that a body must carry `match_identity`; those EA mappings were already marked "Needs capture", so the body shape stays unverified either way.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
+9
-1
@@ -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';
|
||||
|
||||
+8
-5
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user