docs: add FLE bridge direction pivot and squad-injection test tooling
Adds the app-centric FLE bridge direction (direction.md) replacing the Blaze-backend route as primary plan, plus a corrected foundational test procedure and snapshot/diff/apply tooling for reverse-engineering FLE's Freeze Lineup write mechanism. Also picks up prior untracked research docs (status-review, fut-integration-options, fifa23-startup-flow, track-c) and existing capture/export tooling that hadn't been committed yet.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
-- squad-injector/snapshot_lineup_tables.lua
|
||||
-- FIFA Live Editor Lua script (v2 API).
|
||||
--
|
||||
-- Dumps every DB table whose name looks lineup/formation/squad-related,
|
||||
-- full rows + fields, to a timestamped JSON file. Run it once BEFORE using
|
||||
-- FLE's GUI "Freeze Lineup" feature (Formation Editor -> arrange players on
|
||||
-- pitch -> tick "Freeze Lineup" -> Data -> Save) and once AFTER, then diff
|
||||
-- the two snapshots with diff_snapshots.py to find which table/field(s) the
|
||||
-- GUI feature actually writes.
|
||||
--
|
||||
-- This exists because "Freeze Lineup" is a confirmed, documented mechanism
|
||||
-- for forcing a starting XI (FLE wiki: Formation-Editor.md) but it's GUI-only
|
||||
-- — its underlying DB write is not documented anywhere. Diffing before/after
|
||||
-- snapshots is how we find it, the same way export_squad.lua's exhaustive
|
||||
-- table dump was the oracle for Track C.
|
||||
--
|
||||
-- Output: C:\FIFA 23 Live Editor\openfut_snapshot_<unix-ish timestamp>.json
|
||||
|
||||
local OUT_DIR = "C:\\FIFA 23 Live Editor\\"
|
||||
|
||||
-- Keyword filter, broader than squad-exporter's FUT-table filter — Freeze
|
||||
-- Lineup is a formation/tactics feature so the write could land in any of
|
||||
-- these families. "players" is included because position fields on the
|
||||
-- player row itself may also change.
|
||||
local KEYWORDS = { "squad", "lineup", "formation", "tactic", "teamsheet", "selection", "players", "teams" }
|
||||
|
||||
local function json_val(v)
|
||||
local t = type(v)
|
||||
if t == "nil" then return "null"
|
||||
elseif t == "boolean" then return tostring(v)
|
||||
elseif t == "number" then
|
||||
if v ~= v then return "null" end
|
||||
return string.format("%.6g", v)
|
||||
elseif t == "string" then
|
||||
return '"' .. v:gsub('\\', '\\\\'):gsub('"', '\\"'):gsub('\n', '\\n') .. '"'
|
||||
elseif t == "table" then
|
||||
local n = #v
|
||||
if n > 0 then
|
||||
local parts = {}
|
||||
for i = 1, n do parts[i] = json_val(v[i]) end
|
||||
return "[" .. table.concat(parts, ",") .. "]"
|
||||
else
|
||||
local parts = {}
|
||||
for k, vv in pairs(v) do
|
||||
parts[#parts + 1] = json_val(tostring(k)) .. ":" .. json_val(vv)
|
||||
end
|
||||
return "{" .. table.concat(parts, ",") .. "}"
|
||||
end
|
||||
end
|
||||
return "null"
|
||||
end
|
||||
|
||||
local function export_table(tbl_name)
|
||||
local fields = GetDBTableFields(tbl_name) or {}
|
||||
local field_names = {}
|
||||
for _, f in ipairs(fields) do field_names[#field_names + 1] = f.name end
|
||||
|
||||
local rows = GetDBTableRows(tbl_name)
|
||||
if not rows or #rows == 0 then
|
||||
Log("[snapshot] table '" .. tbl_name .. "': empty or not found")
|
||||
return { fields = field_names, rows = {} }
|
||||
end
|
||||
|
||||
local out_rows = {}
|
||||
for _, row in ipairs(rows) do
|
||||
local rec = {}
|
||||
for _, fname in ipairs(field_names) do
|
||||
local cell = row[fname]
|
||||
if cell then rec[fname] = cell.value end
|
||||
end
|
||||
out_rows[#out_rows + 1] = rec
|
||||
end
|
||||
Log("[snapshot] table '" .. tbl_name .. "': " .. #out_rows .. " rows, " .. #field_names .. " fields")
|
||||
return { fields = field_names, rows = out_rows }
|
||||
end
|
||||
|
||||
local all_table_names = GetDBTablesNames()
|
||||
local matched = {}
|
||||
for _, tname in ipairs(all_table_names) do
|
||||
local lower = tname:lower()
|
||||
for _, kw in ipairs(KEYWORDS) do
|
||||
if lower:find(kw) then
|
||||
matched[#matched + 1] = tname
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local snapshot = {
|
||||
captured_at = os.date("!%Y-%m-%dT%H:%M:%SZ"),
|
||||
is_career_mode = IsInCM(),
|
||||
matched_table_names = matched,
|
||||
tables = {},
|
||||
}
|
||||
|
||||
for _, tname in ipairs(matched) do
|
||||
snapshot.tables[tname] = export_table(tname)
|
||||
end
|
||||
|
||||
local out_path = OUT_DIR .. "openfut_snapshot_" .. os.date("!%Y%m%dT%H%M%SZ") .. ".json"
|
||||
local f = io.open(out_path, "w")
|
||||
if f then
|
||||
f:write(json_val(snapshot))
|
||||
f:close()
|
||||
Log("[snapshot] written to " .. out_path)
|
||||
MessageBox("OpenFUT Snapshot", "Snapshotted " .. #matched .. " tables.\n" .. out_path ..
|
||||
"\n\nTake one snapshot BEFORE Freeze Lineup, one AFTER, then run diff_snapshots.py on the two files.")
|
||||
else
|
||||
Log("[snapshot] ERROR: could not open " .. out_path)
|
||||
MessageBox("OpenFUT Snapshot", "ERROR writing to " .. out_path)
|
||||
end
|
||||
Reference in New Issue
Block a user