fix(staging): fill the real club's bench to the client's 18-player minimum
FIFA refuses to kick off with "your squad must have at least 11 players and 7 subs … currently below the minimum number of players (18)". The imported club's squad carries ONLY its starting XI, so a freshly installed real club is unplayable until someone fills the bench by hand in the hub. `fill_bench_to_minimum` tops the squad up with the club's best spare players, writing EMPTY bench slots from index 11 upward. The 23 slots are 0..10 pitch and 11..22 bench/reserves, derived from the index alone, so the starting XI — and any bench the operator has already chosen — is never touched and a re-run is a no-op. Only real PLAYER definitions are eligible: a kit, a manager or a consumable in a squad slot is nonsense the client would drop anyway. Selection is best-rating-first with a stable id tiebreak, so the same bench comes back on a re-run rather than shuffling. Scoped to the REAL club on purpose. The 14-item fixture is a market test bed with a single spare player; demanding 18 there would abort a bring-up that never needed to kick off. Fixture mode therefore does not call this at all. Verified against a copy of the club snapshot (the live staging database was left alone, since it currently holds a squad the operator saved by hand): 11 -> 18 players, slots 0..17, no duplicate instance in two slots, every filled pick a real player definition, and a second run filling nothing. This is NOT a diagnosis of the failure the operator just hit — that squad had already been filled to 23 valid players before the match was created, and the host log shows the client issued no request at all after `match-create` beyond account-sync, so that refusal is decided entirely client-side. It removes the variable: after a clean bring-up the club is now playable without hand-editing.
This commit is contained in:
@@ -748,6 +748,78 @@ def verify_blaze_patch(lay: Layout) -> None:
|
||||
|
||||
|
||||
# --- seeding ------------------------------------------------------------------------
|
||||
# FIFA refuses to kick off unless the squad has 11 starters AND 7 substitutes:
|
||||
# "your squad must have at least 11 players and 7 subs … currently below the
|
||||
# minimum number of players (18)". The imported club's squad has only its 11
|
||||
# starters, so a freshly installed real club is UNPLAYABLE until the bench is
|
||||
# filled. The 23 slots are 0..10 pitch, 11..22 bench/reserves, derived from the
|
||||
# index alone.
|
||||
CLIENT_MIN_SQUAD = 18
|
||||
SQUAD_SLOTS = 23
|
||||
|
||||
|
||||
def fill_bench_to_minimum(conn, club_id: str, squad_id: str, lay: Layout) -> int:
|
||||
"""Top the squad up to the client's minimum with the club's best spare
|
||||
players, filling empty bench slots from index 11 upward.
|
||||
|
||||
Only EMPTY slots are written, so the starting XI — and any bench the
|
||||
operator has already chosen — is never touched, and a re-run is a no-op.
|
||||
Only real PLAYER definitions are eligible: a kit, a manager or a consumable
|
||||
in a squad slot would be nonsense, and the client would drop it anyway.
|
||||
"""
|
||||
taken = {
|
||||
row[0]
|
||||
for row in conn.execute(
|
||||
"SELECT owned_card_id FROM squad_players WHERE squad_id = ?", (squad_id,)
|
||||
)
|
||||
}
|
||||
used_slots = {
|
||||
row[0]
|
||||
for row in conn.execute(
|
||||
"SELECT position_index FROM squad_players WHERE squad_id = ?", (squad_id,)
|
||||
)
|
||||
}
|
||||
if len(taken) >= CLIENT_MIN_SQUAD:
|
||||
return 0
|
||||
|
||||
with open(safe_path(lay.catalog)) as fh:
|
||||
catalog = json.load(fh)["cards"]
|
||||
with open(safe_path(lay.cards)) as fh:
|
||||
overall = {c["id"]: c.get("overall", 0) for c in json.load(fh)}
|
||||
|
||||
# Best first, then a stable id tiebreak so a re-run picks the same bench.
|
||||
candidates = [
|
||||
(overall.get(card_id, 0), owned_id, card_id)
|
||||
for owned_id, card_id in conn.execute(
|
||||
"SELECT id, card_id FROM owned_cards WHERE club_id = ? AND is_loan = 0",
|
||||
(club_id,),
|
||||
)
|
||||
if owned_id not in taken
|
||||
and catalog.get(card_id, {}).get("kind", "player") == "player"
|
||||
]
|
||||
candidates.sort(key=lambda c: (-c[0], c[1]))
|
||||
|
||||
free_slots = [i for i in range(11, SQUAD_SLOTS) if i not in used_slots]
|
||||
need = CLIENT_MIN_SQUAD - len(taken)
|
||||
if need > len(free_slots) or need > len(candidates):
|
||||
raise Fatal(
|
||||
f"cannot reach the client's {CLIENT_MIN_SQUAD}-player minimum: need "
|
||||
f"{need} more, but {len(free_slots)} free bench slots and "
|
||||
f"{len(candidates)} eligible spare players"
|
||||
)
|
||||
|
||||
rows = [
|
||||
(f"sp-bench-{slot}", squad_id, owned_id, slot, 0, 1)
|
||||
for slot, (_ovr, owned_id, _card) in zip(free_slots, candidates[:need])
|
||||
]
|
||||
conn.executemany(
|
||||
"INSERT INTO squad_players (id, squad_id, owned_card_id, position_index, "
|
||||
"is_captain, is_on_bench) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
rows,
|
||||
)
|
||||
return len(rows)
|
||||
|
||||
|
||||
|
||||
|
||||
def seed_core_db(lay: Layout, real_club: dict | None) -> None:
|
||||
@@ -847,6 +919,15 @@ def seed_core_db(lay: Layout, real_club: dict | None) -> None:
|
||||
"updated_at) VALUES (?, ?, ?)",
|
||||
(squad_row[0], STAGING_MANAGER["owned_id"], TS),
|
||||
)
|
||||
|
||||
# Only the REAL club is meant to be played. The 14-item fixture is a
|
||||
# market test bed with a single spare player, so demanding 18 there
|
||||
# would abort a bring-up that never needed to kick off.
|
||||
filled = (
|
||||
fill_bench_to_minimum(conn, seller_club, squad_row[0], lay)
|
||||
if real_club is not None
|
||||
else 0
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
if real_club is None:
|
||||
@@ -863,6 +944,12 @@ def seed_core_db(lay: Layout, real_club: dict | None) -> None:
|
||||
f"{len(STAGING_KITS)} active kits, the manager "
|
||||
f"{STAGING_MANAGER['label']} and Buyer B ({BUYER_COINS} coins)"
|
||||
)
|
||||
if filled:
|
||||
ok(
|
||||
f"filled {filled} empty bench slot(s) with the club's best spare "
|
||||
f"players: FIFA refuses to kick off below {CLIENT_MIN_SQUAD} "
|
||||
"(11 starters + 7 subs), and the imported squad carries only its XI"
|
||||
)
|
||||
|
||||
|
||||
def db_summary(lay: Layout) -> str:
|
||||
|
||||
Reference in New Issue
Block a user