"""Extend a CW catalog with identity-checked native star rows (no guessed rows).""" import argparse import copy import hashlib import json from pathlib import Path def main(): p = argparse.ArgumentParser(description=__doc__) p.add_argument('--capture', type=Path, required=True) p.add_argument('--catalog', type=Path, required=True) p.add_argument('--output', type=Path, required=True) a = p.parse_args() raw = a.capture.read_bytes() lines = raw.decode('utf-8-sig').splitlines() if any('[GAC_CW_ROLE_STARS] ERROR' in line for line in lines): raise ValueError('capture reports an error') rows = [json.loads(line.split(' ROW ', 1)[1]) for line in lines if '[GAC_CW_ROLE_STARS] ROW ' in line] c = json.loads(a.catalog.read_text(encoding='utf-8-sig')) bases = {} for r in c['captured_role_variants']: identity = r['Config']['ID'] if identity not in bases or r['Star'] < bases[identity]['Star']: bases[identity] = r variants = {(r['Config']['ID'], r['Star']): r for r in c['captured_role_variants']} seen = set() for row in rows: key = row['ID'], row['Star'] star = row['StarConfig'] if key in seen or (star['ID'], star['Star']) != key: raise ValueError(f'duplicate or clamped star row: {key}') seen.add(key) base = bases.get(key[0]) if base is None or not 0 < key[1] <= base['MaxStar']: raise ValueError(f'missing role configuration: {key}') if key in variants: if variants[key]['StarConfig'] != star or variants[key]['ServantStarConfig'] != row['ServantStarConfig']: raise ValueError(f'row differs from earlier capture: {key}') continue v = copy.deepcopy(base) v.update(Star=key[1], StarConfig=star, ServantStarConfig=row['ServantStarConfig'], SellPrice=0, SaleQuoteKnown=False) # Only ordinary roles whose default quote matches the current rarity # table can use that table at a higher star. Special quotes stay unknown. constants = c['constants']['ConstValueCommonExcelTable'] default = constants.get(f"GridFight_AvatarSnippetSellGoldStar{base['Star']}", {}).get('ArrayValue', []) target = constants.get(f'GridFight_AvatarSnippetSellGoldStar{key[1]}', {}).get('ArrayValue', []) rarity = base['Config']['Rarity'] - 1 if rarity < len(default) and rarity < len(target) and default[rarity]['IntValue'] == base['SellPrice']: v.update(SellPrice=target[rarity]['IntValue'], SaleQuoteKnown=True) variants[key] = v if not rows: raise ValueError('no native star rows') c['captured_role_variants'] = [variants[k] for k in sorted(variants)] c['provenance']['star_rows_capture_sha256'] = hashlib.sha256(raw).hexdigest() c['provenance']['star_rows_capture_complete'] = any(line.startswith('[GAC_CW_ROLE_STARS] END ') for line in lines) c['provenance']['star_rows_source'] = 'Initialized RoleConfig.GetStarConfig(star), identity checked; four rows per heartbeat, paused in Battle. Uncaptured variants remain unavailable.' a.output.write_text(json.dumps(c, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') print(f'Imported {len(rows)} native rows; catalog contains {len(variants)} variants') if __name__ == '__main__': main()