feat(fifa17): route match completion to Core exactly-once economy

Adapter: new fut/match_wire.rs owns the FIFA17 match wire — endReason
enum -> canonical result token (win/draw/loss/dnf/no_contest), match-end
payload parse (goals from myMatchStats[0], omitted on DNF/QUIT), and the
reward-response projection (only reversed fields; never bidTokens/
qualifiedChampionEventId). Match logic removed from economy_policy.rs
(kept pack/fee); registered match_wire in fut/mod.rs.

Host: handle_match_end now applies the match to Core's authoritative
complete_match (POST /matches/complete) fail-closed — any Core error is a
503, never a Python fallback — and renders Core's authoritative coins.
Per-match identity from matchReportId or a body fingerprint keys Core's
durable idempotency. New CoreEconomy::complete_match transport +
CoreMatchCompletion/CoreMatchReceipt.

Tests: adapter endReason/parse/projection; host shaping, fail-closed,
malformed, identity dedupe; integration replay + rebased balance chains
(match now also grants XP/level-up/achievement coins).
This commit is contained in:
funman300
2026-08-20 17:30:17 +00:00
parent 25f4ad12bc
commit 9ddd80993c
9 changed files with 524 additions and 132 deletions
+9 -1
View File
@@ -805,7 +805,8 @@ pub async fn handle_move_items(
mod tests {
use super::*;
use crate::{
EconomyEntitlement, EconomyGrantItem, EconomyPurchase, EconomySale, EconomySaleReceipt,
CoreMatchCompletion, CoreMatchReceipt, EconomyEntitlement, EconomyGrantItem,
EconomyPurchase, EconomySale, EconomySaleReceipt,
};
use std::collections::HashMap;
use std::sync::atomic::{AtomicI64, AtomicU64, AtomicUsize, Ordering};
@@ -959,6 +960,13 @@ mod tests {
squad_slots_freed: 0,
})
}
fn complete_match(
&self,
_m: &CoreMatchCompletion<'_>,
) -> Result<CoreMatchReceipt, CoreError> {
// Match completion is not exercised through the market double.
Err(CoreError::Status(501))
}
}
// ---- SquadWireResolver double -----------------------------------------