feat(fifa17): economy policy mappers (match reward, pack price)

Pure FIFA17 policy: match_result_coins/match_reward_total (oracle MATCH_COINS
won 400/draw 200/loss 100 + participation 0), result_from_end_reason (endReason
enum -> outcome, draw default), pack_price (catalogue buy-now, None for
unknown/owned-only). 3 unit tests.
This commit is contained in:
OpenFUT Agent
2026-08-13 19:31:23 +00:00
parent 56c364e4c9
commit 09675a9f7f
2 changed files with 88 additions and 0 deletions
@@ -0,0 +1,87 @@
//! FIFA 17 economy POLICY mappers (pure, game-specific).
//!
//! These translate FIFA 17 wire semantics into the generic amounts the host
//! feeds to Core economy authority. They own NO state — Core owns balances and
//! inventory; these are the FIFA-specific numbers/derivations. Values are the
//! current OpenFUT economy (match rewards are the Python oracle's
//! `MATCH_COINS`/`MATCH_PARTICIPATION` at production defaults); pack prices come
//! from the Store catalogue.
use crate::fut::store_catalog::pack_by_id;
/// Normalized match outcome for reward purposes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MatchResult {
Win,
Draw,
Loss,
}
/// Participation award added to every match reward (oracle `MATCH_PARTICIPATION`
/// default = 0).
pub const MATCH_PARTICIPATION: i64 = 0;
/// Per-result match coins (oracle `MATCH_COINS`: won 400 / draw 200 / loss 100).
pub fn match_result_coins(result: MatchResult) -> i64 {
match result {
MatchResult::Win => 400,
MatchResult::Draw => 200,
MatchResult::Loss => 100,
}
}
/// Total match reward = per-result coins + participation.
pub fn match_reward_total(result: MatchResult) -> i64 {
match_result_coins(result) + MATCH_PARTICIPATION
}
/// Derive the outcome from the match `endReason` enum (the oracle's primary
/// signal, `_END_REASON`). Unknown/absent reasons default to `Draw`, matching
/// the oracle's conservative default. Score-based derivation is a fallback the
/// oracle also supports; the enum is authoritative when present.
pub fn result_from_end_reason(end_reason: Option<&str>) -> MatchResult {
match end_reason.unwrap_or("").to_ascii_uppercase().as_str() {
"WIN" | "DNF_WIN" => MatchResult::Win,
"LOSS" | "QUIT" | "DNF" | "DNF_LOSS" => MatchResult::Loss,
// "DRAW", "DNF_DRAW", "NO_CONTEST", unknown -> draw.
_ => MatchResult::Draw,
}
}
/// The Store buy-now price for a pack id (`None` for unknown/owned-only packs,
/// which are never purchasable).
pub fn pack_price(pack_id: u64) -> Option<u64> {
pack_by_id(pack_id)
.filter(|p| !p.owned_only)
.map(|p| p.price)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn match_rewards_match_oracle() {
assert_eq!(match_reward_total(MatchResult::Win), 400);
assert_eq!(match_reward_total(MatchResult::Draw), 200);
assert_eq!(match_reward_total(MatchResult::Loss), 100);
}
#[test]
fn end_reason_maps_to_outcome() {
assert_eq!(result_from_end_reason(Some("WIN")), MatchResult::Win);
assert_eq!(result_from_end_reason(Some("dnf_win")), MatchResult::Win);
assert_eq!(result_from_end_reason(Some("LOSS")), MatchResult::Loss);
assert_eq!(result_from_end_reason(Some("QUIT")), MatchResult::Loss);
assert_eq!(result_from_end_reason(Some("DRAW")), MatchResult::Draw);
assert_eq!(result_from_end_reason(None), MatchResult::Draw);
assert_eq!(result_from_end_reason(Some("weird")), MatchResult::Draw);
}
#[test]
fn pack_price_rejects_unknown_and_owned_only() {
assert!(pack_price(1).is_some());
assert_eq!(pack_price(65534), None); // sentinel absent from catalogue
assert_eq!(pack_price(70), None); // owned-only reward pack, not purchasable
}
}
+1
View File
@@ -7,6 +7,7 @@
pub mod catalog;
pub mod club_response;
pub mod economy;
pub mod economy_policy;
pub mod entities;
pub mod item;
pub mod owned_query;