Files
OpenFUT/scripts/fifa17-apply-snapshot.py
T
funman300 6c97bc4e2b feat(fifa17): real player-contract consumable apply, replacing the probe
POST /ut/game/fifa17/item/resource/<rid> {"apply":[{"id":N}]} now performs a
durable atomic contract application instead of falling through to Python.

THE RULE. grant = fcc_contractcards[card][tier(TARGET.rating)], then
min(99, contract + grant). The column is keyed on the TARGET's tier, NOT the
card's own -- all 36 cells of EA's shipped table match the published FIFA 17
matrix, and staging discriminates the two readings outright: a bronze-RARE
card on a rating-89 player granted 3 (the gold column), where the card-level
reading predicts 15.

No client binary reads fcc_contractcards -- a string scan of every .exe/.dll
in the install finds it referenced nowhere, and CardsDLL reads only 14 fcc_
tables (fcc_discardcoins among them, which is why quick-sell prices locally).
Consumable effects are server-authoritative, so EA's shipped table is the only
non-invented source and the client renders whatever we persist and re-serve.

The host computes the grant, Core owns the mutation -- the same split
quick-sell already uses (host prices via discard_value, Core performs
sell_item), and what migration 0027 means by "Core defines NO per-category
formula".

FAILS CLOSED, never 200-and-do-nothing: manager contracts 409 because staff
ratings are unimported so the target tier is unknowable; every other family
409 as unproven; batch 400; unresolvable operand 404. Core's deterministic
refusals pass through with their own status instead of collapsing to 503,
which would tell the client to retry a request that can never succeed.

`contract: 7` stops being a hardcode in shape_item/shape_staff_item and
becomes the fallback for an instance Core tracks no contract for. `fitness: 99`
is the same class of hardcode and is deliberately untouched.

CLEAN CUTOVER: Route::ConsumableApplyProbe, its handler, apply_probe_enabled,
the OPENFUT_FIFA17_APPLY_PROBE gate and both probe scripts are deleted. A
handler no classifier can reach is this repo's recurring defect class, and the
new economy arm preempts the probe. fifa17-migration-rehearse.py also drove
the probe (spelled "apply probe", so an apply-probe grep missed it) and would
have eaten a card off the rehearsal profile; retargeted to a non-mutating
assertion.

Not implemented, on purpose: the stored-manager bonus (real mechanic, rule
appears in no shipped table -- guessing it would corrupt the proven part) and
contract decrement per match (nothing spends contracts yet).
2026-08-22 18:23:22 +00:00

59 lines
2.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""Core snapshot around a consumable APPLY: coins, ownership, the source
consumable's copies, and the target's mutable state. Run before and after.
A player-contract apply must move EXACTLY three things: the source consumable
loses one copy, the target's `contract` rises to min(99, before + grant), and
nothing else — coins in particular must not move, because an apply is not an
economy credit. Everything else in this snapshot is here to prove it stayed put.
(Superseded for acceptance by fifa17-contract-apply-validate.py, which asserts
the deltas itself; this remains the raw before/after dump for eyeballing.)"""
import json
import sys
import urllib.request
H = {"X-OpenFUT-Game": "fifa17"}
SOURCE_RES = 5001004
TARGET_WIRE = 100000003
def get(p):
req = urllib.request.Request("http://127.0.0.1:8299" + p, headers=H)
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read())
snap = {}
import sqlite3
con = sqlite3.connect("file:/home/alex/openfut-sold-staging/staging-core.db?mode=ro", uri=True)
snap["coins"] = con.execute("SELECT coins FROM clubs LIMIT 1").fetchone()[0]
snap["owned_rows"] = con.execute("SELECT COUNT(*) FROM owned_cards").fetchone()[0]
snap["by_kind"] = dict(con.execute("SELECT content_kind, COUNT(*) FROM owned_cards GROUP BY 1"))
con.close()
cons = get("/ut/game/fifa17/club/consumables/development")
stacks = cons.get("itemData") or []
snap["development_stacks"] = len(stacks)
snap["development_copies"] = sum(s.get("count", 0) for s in stacks)
snap["source_stack"] = next(
({"count": s.get("count"), "resourceId": s.get("resourceId")}
for s in stacks if s.get("resourceId") == SOURCE_RES), None)
sq = get("/ut/game/fifa17/squad/0")
players = (sq.get("squad") or sq).get("players") or []
for p in players:
it = p.get("itemData") or {}
if it.get("id") == TARGET_WIRE:
snap["target"] = {
"resourceId": it.get("resourceId"), "rating": it.get("rating"),
"contract": it.get("contract"), "fitness": it.get("fitness"),
"injuryType": it.get("injuryType"), "training": it.get("training"),
"playStyle": it.get("playStyle"), "preferredPosition": it.get("preferredPosition"),
}
break
print(json.dumps(snap, indent=2, sort_keys=True))
if len(sys.argv) > 1:
open(sys.argv[1], "w").write(json.dumps(snap, sort_keys=True))