diff --git a/fifa17-recon/docs/ENDPOINT_MAP.md b/fifa17-recon/docs/ENDPOINT_MAP.md index 805397b..739b9cd 100644 --- a/fifa17-recon/docs/ENDPOINT_MAP.md +++ b/fifa17-recon/docs/ENDPOINT_MAP.md @@ -424,6 +424,25 @@ Chemistry/rating/nation/league-count constraints (`teamChemistry 0x307`, `starRa generically as `{eligibilityKey, eligibilityOperation, eligibilityValue}` triples, **not** as named scalar fields on the record. **FREEZE-RISK: elgReq must be a JSON array of objects.** +> **2026-08-19 — `eligibilityKey`/`eligibilityOperation` are LOCALIZATION ORDINALS, not the +> atom hex ids above.** Reversed from the pinned CardsDLL (`4706a881…`). The client's sole +> confirmed consumer of these fields is the requirement-display string builder at +> `~0x1800ef900`: it loads the eligibility int fields (`0x148(rcx)`) and formats them through +> *indexed localization keys* — `ELIGIBILITY_STRING%d` (`0x1802186b8`), `LOC_SBC_ELG_KEY_%d` +> (`0x180226710`), `ELIGIBILITY_OPERATION` (`0x1802186e8`) — appending to a string builder via +> vtable `*0x10`/`*0x20`. There is **no comparison/branch**: the client does not validate on +> these ints, it renders `LOC_SBC_ELG_KEY_` (and an operation string) as +> display text. Therefore `eligibilityKey` is a small ordinal that indexes the **packed FIFA17 +> locale**, NOT `0x307`/`0x22f`/etc. (those hex values are the atom ids of the *named* fields +> the encoding replaces, not the ordinal values). CONSEQUENCE: correct projection needs the +> ordinal→locale-string map, which lives only in the packed locale (absent from CardsDLL and +> every `fifa17-recon/data` file; a game-dir locale probe on the live client found none) or a +> real EA `elgReq` capture (unavailable on a private server). Emitting a *guessed* ordinal +> renders the WRONG requirement text to the player, so `elgReq` stays `[]` until the ordinal +> map is recovered. This is a display-only gap: SBC submission is fully validated server-side +> (Core), and an invalid squad's generic comms modal originates from the server 400, not from +> the empty `elgReq`. + **awards / grantedAwards** — nested array of reward objects (atoms: `rewardType 0x28e`, `rewardValue 0x28f`, `rewardQuantity 0x28d`, `rewardMultiplier 0x28c`, `awardCount 0x40`, `awardSet 0x45`, `awardSetId 0x46`, `prizeSet 0x253`). **FREEZE-RISK: must be array.** diff --git a/openfut-adapter-fifa17/src/fut/sbc.rs b/openfut-adapter-fifa17/src/fut/sbc.rs index 9ca3173..9bd89f6 100644 --- a/openfut-adapter-fifa17/src/fut/sbc.rs +++ b/openfut-adapter-fifa17/src/fut/sbc.rs @@ -116,6 +116,15 @@ pub fn challenges_body(set_id: i64, challenges: &[ChallengeView]) -> Value { "status": "OPEN", "timesCompleted": times_completed, "awards": [], + // `elgReq` stays empty deliberately. Reversed from the pinned CardsDLL + // (2026-08-19, see ENDPOINT_MAP.md "Shared record shapes"): the client + // consumes `eligibilityKey`/`eligibilityOperation` only as ordinals that + // index the packed locale (`LOC_SBC_ELG_KEY_%d`) to render requirement + // text — it does NOT validate on them. The ordinal->string map is not + // recoverable from any asset we have, so any value we emit would show the + // WRONG requirement to the player. Submission is validated server-side by + // Core regardless; leaving this empty is display-only, never a correctness + // gap. Populate ONLY once the locale ordinal map is captured. "elgReq": [] }) }) @@ -360,8 +369,16 @@ mod tests { assert_eq!( parse_wire_item_ids(retail).unwrap(), [ - 100_004_227, 100_004_233, 100_001_317, 100_001_531, 100_000_966, - 100_001_947, 100_000_169, 100_002_017, 100_002_765, 100_000_147, + 100_004_227, + 100_004_233, + 100_001_317, + 100_001_531, + 100_000_966, + 100_001_947, + 100_000_169, + 100_002_017, + 100_002_765, + 100_000_147, 100_000_311 ], "exactly the 11 non-zero players in wire order; manager and empty slots ignored"