Files
OpenFUT-Core/migrations/0030_owned_card_training_single.sql
T
funman300 90210702c3
CI / Build, lint & test (push) Successful in 3m25s
feat(core): training replaces, and the rare card boosts all six
Corrects two decisions the previous revision got wrong, both now settled by
the published FIFA 17 training guide -- the same source class and publisher
this project already accepted for the contract matrix, and which
cross-validates 6/6 against fcc_trainingcards on rows we had misclassified.

"You can only boost one attribute or all six... 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 card REPLACES rather than being refused, and two slots must never
be boosted at once -- our old composite key permitted exactly that, and
staging demonstrated it by holding a slot-4 and a slot-1 effect together.

Migration 0030 reshapes the table to one row per instance keyed on
owned_card_id alone, with attribute_index nullable for the rare all-six card,
and carries forward the most recent effect where several existed -- the
replaces rule applied retroactively. 0029 is left intact rather than
rewritten: it is already applied to supervised staging, where migration
history matters.

Apply is now delete-then-insert inside the one transaction, so the old effect
cannot survive a failed insert and the two cannot coexist. The outcome
records what it displaced instead of overwriting history silently.

The previous refusal was OUR policy standing in for an unknown, and it was
never evidence about FIFA 17. It is retired now that the behaviour is
recovered, not because the 409 was inconvenient.
2026-08-22 23:33:43 +00:00

67 lines
3.4 KiB
SQL

-- 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;