#!/usr/bin/env python3 from __future__ import annotations import argparse, hashlib, json, re, struct from pathlib import Path HERE = Path(__file__).resolve().parents[1] PROJECT_ROOT = HERE.parent SRC = PROJECT_ROOT / 'vendor/kam_remake' ASSETS = PROJECT_ROOT / 'private-assets' MANIFEST = HERE / 'config/fixture_manifest.json' OUT = PROJECT_ROOT / 'reports/open-questions/current' INCLUDE_FALLBACKS: set[str] = set() def sha(p: Path) -> str: return hashlib.sha256(p.read_bytes()).hexdigest() def text(p: Path) -> str: return p.read_text(encoding='utf-8-sig', errors='replace') def emit(name: str, data: dict) -> dict: OUT.mkdir(parents=True, exist_ok=True) (OUT / (name + '.json')).write_text(json.dumps(data, indent=2, ensure_ascii=False) + '\n', encoding='utf-8') return data def load_manifest() -> dict: return json.loads(MANIFEST.read_text(encoding='utf-8')) def selected_specs() -> list[dict]: m = load_manifest() specs = [dict(x, selection='primary') for x in m.get('primary', [])] known = {x['id'] for x in m.get('conditional_fallbacks', [])} unknown = INCLUDE_FALLBACKS - known if unknown: raise SystemExit('Unknown fallback id(s): ' + ', '.join(sorted(unknown))) for group in m.get('conditional_fallbacks', []): if group['id'] in INCLUDE_FALLBACKS: specs.extend(dict(x, selection='fallback:' + group['id']) for x in group.get('files', [])) return specs def resolve_spec(spec: dict) -> Path: rel = Path(spec['path']) if rel.is_absolute() or '..' in rel.parts: raise SystemExit(f"Unsafe fixture path in manifest: {spec['path']}") root = ASSETS.resolve() p = (root / rel).resolve() try: p.relative_to(root) except ValueError: raise SystemExit(f"Fixture escaped asset root: {spec['path']}") return p def selected_files(exts: set[str]) -> list[tuple[dict, Path]]: rows = [] for spec in selected_specs(): p = resolve_spec(spec) if p.suffix.lower() in exts: rows.append((spec, p)) return rows def fixture_manifest_status() -> dict: rows = [] good = True for spec in selected_specs(): p = resolve_spec(spec) row = { 'id': spec['id'], 'role': spec['role'], 'path': spec['path'], 'selection': spec['selection'], 'expected_size': spec.get('expected_size'), 'exists': p.is_file(), } if p.is_file(): row['actual_size'] = p.stat().st_size row['size_matches'] = spec.get('expected_size') in (None, p.stat().st_size) if not row['size_matches']: good = False else: row['actual_size'] = None row['size_matches'] = False good = False rows.append(row) return emit('fixture_manifest_status', { 'test': 'fixture_manifest_status', 'selection_policy': load_manifest().get('selection_policy'), 'assets_root': str(ASSETS), 'manifest': str(MANIFEST), 'included_fallbacks': sorted(INCLUDE_FALLBACKS), 'fixtures': rows, 'status': 'PASS' if good else 'MISSING_OR_MISMATCH' }) def fixture_inventory(): rows = [] status = 'PASS' for spec, p in selected_files({'.map', '.dat', '.rxx', '.rx', '.rxa', '.txt', '.libx', '.script'}): if not p.is_file(): rows.append({'id': spec['id'], 'path': spec['path'], 'role': spec['role'], 'status': 'MISSING'}) status = 'MISSING_OR_MISMATCH' continue size = p.stat().st_size size_ok = spec.get('expected_size') in (None, size) if not size_ok: status = 'MISSING_OR_MISMATCH' rows.append({ 'id': spec['id'], 'path': spec['path'], 'role': spec['role'], 'logical_fixture': spec.get('logical_fixture'), 'selection': spec['selection'], 'size': size, 'expected_size': spec.get('expected_size'), 'size_matches': size_ok, 'sha256': sha(p), 'ext': p.suffix.lower(), 'resource_category': spec.get('resource_category') }) return emit('fixture_inventory', { 'test': 'fixture_inventory', 'count': len(rows), 'fixtures': rows, 'status': status if rows else 'NEEDS_FIXTURES', 'selection_policy': 'exact manifest whitelist; no directory scan' }) def source_patterns(): pats = { 'desktop_units': r'\b(?:LCLIntf|LCLType|Graphics|FileUtil|LazUTF8|LazFileUtils|lconvencoding)\b', 'read_calls': r'\.Read(?:Buffer)?\s*\(', 'terrain_generation': r'GenerateTerrainTransitions|GetGenTerrainInfoLegacy|GetGenTerrainInfo', 'tokenizer': r'TokenizeScript\s*\(', 'render_globals': r'gGFXData|SKIP_RENDER|NO_OGL' } out = {k: [] for k in pats} if SRC.exists(): for p in SRC.rglob('*.pas'): s = text(p) for k, pat in pats.items(): for m in re.finditer(pat, s, re.I): out[k].append({'file': p.relative_to(SRC).as_posix(), 'line': s.count('\n', 0, m.start()) + 1, 'match': m.group(0)}) return emit('source_inventory', {'test': 'portable_closure_inventory', 'status': 'PASS' if (SRC/'SOURCE_RECEIPT.json').exists() else 'BLOCKED', 'patterns': out}) def dat_scan(): p = SRC/'src/mission/KM_MissionScript.pas' cmd = re.findall(r"'([A-Z][A-Z0-9_]*)'", text(p)) if p.exists() else [] rows = [] for spec, f in selected_files({'.dat'}): if not f.is_file(): continue b = f.read_bytes() printable = sum(c in b'\t\n\r 0123456789!' for c in b) / max(1, len(b)) rows.append({'id': spec['id'], 'path': spec['path'], 'size': len(b), 'likely_encoded': printable < .20, 'bang_count_raw': b.count(b'!')}) return emit('dat_inventory', {'test': 'dat_command_inventory', 'recognized_source_tokens': sorted(set(cmd)), 'fixtures': rows, 'status': 'PASS' if p.exists() and rows else 'BLOCKED'}) def map_scan(): rows = [] for spec, f in selected_files({'.map'}): if not f.is_file(): continue b = f.read_bytes() r = {'id': spec['id'], 'path': spec['path'], 'size': len(b), 'sha256': hashlib.sha256(b).hexdigest()} if len(b) >= 8: x = struct.unpack_from(' int: global SRC, ASSETS, MANIFEST, OUT, INCLUDE_FALLBACKS ap = argparse.ArgumentParser(description='Task 1 open-question evidence selector with an exact binary-fixture whitelist.') ap.add_argument('action', choices=['verify', 'all', 'source']) ap.add_argument('--source', type=Path, default=SRC) ap.add_argument('--assets-root', type=Path, default=ASSETS) ap.add_argument('--fixture-manifest', type=Path, default=MANIFEST) ap.add_argument('--include-fallback', action='append', default=[]) ap.add_argument('--out', type=Path, default=OUT) a = ap.parse_args() SRC = a.source.resolve(); ASSETS = a.assets_root.resolve(); MANIFEST = a.fixture_manifest.resolve(); OUT = a.out.resolve() INCLUDE_FALLBACKS = set(a.include_fallback) if a.action == 'verify': result = fixture_manifest_status() return 0 if result['status'] == 'PASS' else 2 if a.action == 'all': allrun() else: source_patterns() return 0 if __name__ == '__main__': raise SystemExit(main())