-- Reshape attribute training to AT MOST ONE effect per instance, replaceable. -- -- WHY THIS SUPERSEDES 0029'S SHAPE. 0029 keyed on (owned_card_id, -- attribute_index) and recorded that same-slot behaviour was UNKNOWN, enforcing -- the unknown as a refusal. That was the honest shape while the semantics were -- unrecovered. They are now recovered, and BOTH halves of 0029's shape are -- wrong: -- -- * "You can only boost one attribute or all six. You can not do it with 2, 3, -- 4 or 5 attributes." -- so two effects must never coexist on one instance, -- which the old composite key permitted (and which staging demonstrated by -- holding a slot-4 and a slot-1 effect at once). -- * "When you apply a new training card to a player, he loses the improved -- attributes of previous training cards. It does not accumulate, it -- replaces." -- so a second apply REPLACES, it does not refuse. -- -- Both quotes are from the contemporaneous FIFA 17-specific training guide -- (fifauteam, published 2016-09-08), corroborated by the shipped table: each -- family has exactly 21 rows = 7 card types x 3 levels, and the 7th type in each -- family (subtypes 57 and 67) is the only one flagged `weightrare = 2` with -- amounts 3/6/10, matching the documented RARE "ALL" card at +3/+6/+10. -- DOCUMENTED, corroborated TABLE_PROVEN. It is NOT LIVE_PROVEN against EA. -- -- 0029 is left intact rather than rewritten: it is already applied to the -- supervised staging environment, so migration history matters there. -- -- NEW SHAPE. One row per instance, so "one attribute or all six" is a -- representable invariant instead of a convention: -- attribute_index INTEGER NULL -- a slot in Core's six-attribute model, or -- NULL meaning ALL SIX slots (the rare card). -- The PRIMARY KEY on owned_card_id alone is what makes a second application a -- REPLACE (delete-then-insert inside the one apply transaction) rather than an -- accumulation. -- -- The 1..=15 amount bound is NOT tightened here: 15 is the single-attribute -- ceiling while the all-six card authors at most 10, and which ceiling applies -- depends on the card family -- a per-game rule that belongs at apply time where -- the game's table is in scope, not in the schema. -- -- DATA CARRIED FORWARD: where an instance somehow holds several effects (only -- reachable on staging under 0029's shape), the MOST RECENT survives, which is -- exactly the "replaces" rule applied retroactively. CREATE TABLE owned_card_training_new ( owned_card_id TEXT NOT NULL PRIMARY KEY REFERENCES owned_cards(id) ON DELETE CASCADE, attribute_index INTEGER CHECK (attribute_index IS NULL OR attribute_index BETWEEN 0 AND 5), amount INTEGER NOT NULL CHECK (amount >= 1 AND amount <= 99), source_card_id TEXT NOT NULL, applied_at TEXT NOT NULL ); INSERT INTO owned_card_training_new (owned_card_id, attribute_index, amount, source_card_id, applied_at) SELECT t.owned_card_id, t.attribute_index, t.amount, t.source_card_id, t.applied_at FROM owned_card_training t JOIN ( SELECT owned_card_id, MAX(applied_at) AS newest FROM owned_card_training GROUP BY owned_card_id ) pick ON pick.owned_card_id = t.owned_card_id AND pick.newest = t.applied_at GROUP BY t.owned_card_id; DROP TABLE owned_card_training; ALTER TABLE owned_card_training_new RENAME TO owned_card_training;