6c97bc4e2b
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).
59 lines
2.3 KiB
Python
Executable File
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))
|