"""Project FFGO's labelled Bonfire reference tables without copying a run. The target identity is a compatibility gate for the existing server catalog, not a claim that these rows were captured from that Client assembly. """ import argparse import hashlib import json from pathlib import Path def main(): p = argparse.ArgumentParser(description=__doc__) p.add_argument('--excel', type=Path, required=True) p.add_argument('--custom', type=Path, required=True) p.add_argument('--catalog', type=Path, required=True) p.add_argument('--source-binary-sha256', required=True) p.add_argument('--output', type=Path, required=True) a = p.parse_args() if len(bytes.fromhex(a.source_binary_sha256)) != 32: raise ValueError('Expected SHA256 identity') excel_raw, custom_raw = a.excel.read_bytes(), a.custom.read_bytes() excel, custom = json.loads(excel_raw), json.loads(custom_raw) catalog = json.loads(a.catalog.read_bytes()) def rows(name, fields): result = [] for row in excel[name]: copy = dict(row) for field in fields: if field in copy: copy[field] = int(copy[field]) result.append(copy) return result configs = {k:v for k,v in custom['config_files'].items() if v.get('Engine') == 'TeaBreakOption'} result = { 'format_version': 1, 'target_client_version': catalog['client_version'], 'target_assembly_sha256': catalog['provenance']['game_assembly_sha256'], 'source_binary_sha256': a.source_binary_sha256, 'source_kind': 'reference-implementation', 'source': 'FFGO embedded GridFight Excel rows plus FFGO declarative TeaBreakOption recipes; not original Client scripts or a native Client capture.', 'compatibility_scope': 'Event IDs must also be present on the active Client node; no reference route or saved run is imported. Only explicitly supported effects execute.', 'source_excel_sha256': hashlib.sha256(excel_raw).hexdigest(), 'source_custom_sha256': hashlib.sha256(custom_raw).hexdigest(), 'tables': { 'events': rows('grid_fight_bonfire_event', ['ID','PetPalID','DialogID']), 'options': rows('grid_fight_bonfire_event_option', ['EventID','OptionID','DialogID']), 'heal': rows('grid_fight_bonfire_heal', ['ChapterID','HealNum']), }, 'configs': configs, } a.output.write_text(json.dumps(result, ensure_ascii=False, indent=2)+'\n', encoding='utf8') print(json.dumps({k:len(v) for k,v in result['tables'].items()} | {'recipes':len(configs)})) if __name__ == '__main__': main()