fifa17: carry the staff rating the client re-rates to, verified live
Staff quick-sell could not be priced correctly: for cardtypes 2/3/4/5/10 the
client overwrites the rating and rare flag we send with values from its own card
database, and a staff wire record carries no rating, no rareflag and no
discardValue at all. The server had no way to know the displayed price from what
it sent, so pricing declined for staff and fell back to the placeholder ladder.
The missing input was read straight out of the running client (pid 6580), no UI
interaction required:
* tools/coach_probe.py grades all four resident staff records HIT, which by
construction requires record +0xb4 == the table's `value` and +0x58 == its
`rare`. That settles `value`-is-the-rating, which was previously an inference
and was deliberately not shipped on that basis.
* tools/discard_probe.py (new) reads both discard slots -- +0x38, the value we
sent, and +0x3c, the value the client computed for itself:
1000509 sub 4 ct 2 rat 88 rare 1 sent 0 calc 282 predicted 282
9000081 sub 6 ct 10 rat 66 rare 0 sent 0 calc 36 predicted 36
3000083 sub 8 ct 4 rat 66 rare 0 sent 0 calc 36 predicted 36
4 of 4 agree, 0 disagree. 36 on the value-66 GK coach was the exact falsifier
written for this last commit.
Entities::enrich_staff now fills rating from `value` and rareflag from `rare` for
the five staff families, and the catalog emits the real rareflag instead of a
hardcoded 0 (it is not cosmetic -- it selects the discard price column, which is
why the rare-1 manager prices at 282 and a rare-0 coach at 36). Players and
consumables are untouched; their wire values are authoritative.
Verified on staging: a GK coach quick-sells for 36, not the 150 floor. The
catalog diff is exactly the two coach entries gaining rating 66; 1710 entries in
and out, nothing else changed.
The same probe shows what production does to PLAYERS today: every resident player
carries sent+38 = 1500, which suppresses the client's own computation, against a
real 688..752 for a gold rare and 72,800 / 74,400 for the two legends.
Still open, and not a discard problem: manager fifa17_1000509 is owned in Core
but has no catalog entry or definition (it reaches the client through the opaque
squad extension), so it declines to the ladder. That is definition coverage.
Importer 41 tests, fmt and clippy clean.
This commit is contained in:
@@ -1068,3 +1068,76 @@ fn deferred_non_player_instances_gate_a_production_apply() {
|
||||
assert!(gate_staging(&plan, false).is_err(), "production blocks");
|
||||
assert!(gate_staging(&plan, true).unwrap(), "staging opt-in allows");
|
||||
}
|
||||
|
||||
/// The staff rating and rare flag come from the client's OWN tables, and these
|
||||
/// exact values were read back out of the RUNNING client's memory:
|
||||
/// `tools/coach_probe.py` graded all four resident staff records HIT (record
|
||||
/// `+0xb4` == `value`, `+0x58` == `rare`), and `tools/discard_probe.py` read the
|
||||
/// discard value the client computed for itself at record `+0x3c` — 36 for both
|
||||
/// `value`-66 coaches and 282 for the `rare`-1, `value`-88 manager.
|
||||
///
|
||||
/// So this is not a table-parsing test. It pins the importer to numbers the live
|
||||
/// client demonstrably uses.
|
||||
#[test]
|
||||
fn staff_stats_are_the_values_the_live_client_re_rates_to() {
|
||||
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../fifa17-recon/data/tables");
|
||||
let ent = Entities::from_tables_dir(dir).expect("committed tables load");
|
||||
|
||||
// (subtype, carddbid) -> (value, rare), verified live.
|
||||
assert_eq!(ent.staff_stats(6, 9000081), Some((66, 0)), "GK coach");
|
||||
assert_eq!(ent.staff_stats(8, 3000083), Some((66, 0)), "fitness coach");
|
||||
assert_eq!(ent.staff_stats(4, 1000509), Some((88, 1)), "manager");
|
||||
|
||||
// Keyed per family: a coach id must not resolve through another's table.
|
||||
assert_eq!(
|
||||
ent.staff_stats(4, 9000081),
|
||||
None,
|
||||
"gkcoach id is not a manager"
|
||||
);
|
||||
assert_eq!(ent.staff_stats(6, 12345678), None, "absent id stays absent");
|
||||
}
|
||||
|
||||
/// `enrich_staff` fills ONLY staff, and only where the wire left a gap.
|
||||
#[test]
|
||||
fn enrich_staff_fills_staff_and_leaves_everything_else_alone() {
|
||||
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../fifa17-recon/data/tables");
|
||||
let ent = Entities::from_tables_dir(dir).expect("committed tables load");
|
||||
|
||||
let items = vec![
|
||||
// A GK coach (subtype 6) and a contract consumable, which carries its own
|
||||
// rating on the wire and must not be touched.
|
||||
staff(100000280, 9000081, 6),
|
||||
consumable(100000300, 5001004, 201),
|
||||
];
|
||||
let mut plan = plan_non_player_definitions(&profile(&items, "[]", 100000500));
|
||||
let before: Vec<Option<i64>> = plan.supported.iter().map(|d| d.rating).collect();
|
||||
ent.enrich_staff(&mut plan);
|
||||
|
||||
let coach = plan
|
||||
.supported
|
||||
.iter()
|
||||
.find(|d| d.resource_id == 9000081)
|
||||
.expect("coach planned");
|
||||
assert_eq!(
|
||||
coach.rating,
|
||||
Some(66),
|
||||
"rating filled from gkcoachcards.value"
|
||||
);
|
||||
assert_eq!(coach.rareflag, Some(0), "rare filled from the same row");
|
||||
|
||||
let cons = plan
|
||||
.supported
|
||||
.iter()
|
||||
.find(|d| d.resource_id == 5001004)
|
||||
.expect("consumable planned");
|
||||
assert_eq!(cons.rareflag, None, "a consumable gets no staff rare flag");
|
||||
let cons_before = before[plan
|
||||
.supported
|
||||
.iter()
|
||||
.position(|d| d.resource_id == 5001004)
|
||||
.unwrap()];
|
||||
assert_eq!(
|
||||
cons.rating, cons_before,
|
||||
"the wire rating is left untouched"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user