#!/usr/bin/env python3 """BFM matching-progress report (authoritative; supersedes the Phase-6 PhaseEnd estimate). Classifies every matchable function across all src/*.c and prints a deterministic summary (also written to docs/progress.md). Ghidra-free. The REAL count is the number the Gen1-exit "≥25 matched functions" bar counts (splat-auto empties do NOT count). Usage: tools/progress.py # print summary + write docs/progress.md tools/progress.py --audit # also verify every empty no-op's asm is exactly {jr,nop} tools/progress.py --check # also hash build/us/SLUS_007.26 vs config/check.us.sha tools/progress.py --binary # report a non-default binary (default: main = the EXE) """ import os, re, sys, hashlib, pathlib sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import cdecl # §134/R33: the ONE comment/string masking oracle ROOT = pathlib.Path(__file__).resolve().parent.parent # Per-binary report config (Phase 9). `main` = the retail EXE; its paths are the originals # (no-op default). A second binary (Phase 10) adds an entry — its build/check + src/asm tree; # the overlay src/asm subtree LAYOUT is a Phase-10 decision, not invented here. BINARIES = { "main": dict(build="build/us/SLUS_007.26", check="config/check.us.sha", src="src", asm="asm/nonmatchings", out="docs/progress.md"), "resident": dict(build="build/resident/resident", check="config/check.resident.sha", src="src/resident", asm="asm/resident/nonmatchings", out="docs/progress.resident.md"), "ov_SC01_077": dict(build="build/ov_SC01_077/ov_SC01_077", check="config/check.ov_SC01_077.sha", src="src/ov_SC01_077", asm="asm/ov_SC01_077/nonmatchings", out="docs/progress.ov_SC01_077.md"), "ov_SC01_005": dict(build="build/ov_SC01_005/ov_SC01_005", check="config/check.ov_SC01_005.sha", src="src/ov_SC01_005", asm="asm/ov_SC01_005/nonmatchings", out="docs/progress.ov_SC01_005.md"), "ov_SC01_006": dict(build="build/ov_SC01_006/ov_SC01_006", check="config/check.ov_SC01_006.sha", src="src/ov_SC01_006", asm="asm/ov_SC01_006/nonmatchings", out="docs/progress.ov_SC01_006.md"), "ov_SC03_001": dict(build="build/ov_SC03_001/ov_SC03_001", check="config/check.ov_SC03_001.sha", src="src/ov_SC03_001", asm="asm/ov_SC03_001/nonmatchings", out="docs/progress.ov_SC03_001.md"), "ov_SC01_000": dict(build="build/ov_SC01_000/ov_SC01_000", check="config/check.ov_SC01_000.sha", src="src/ov_SC01_000", asm="asm/ov_SC01_000/nonmatchings", out="docs/progress.ov_SC01_000.md"), "ov_SC01_001": dict(build="build/ov_SC01_001/ov_SC01_001", check="config/check.ov_SC01_001.sha", src="src/ov_SC01_001", asm="asm/ov_SC01_001/nonmatchings", out="docs/progress.ov_SC01_001.md"), "ov_SC02_000": dict(build="build/ov_SC02_000/ov_SC02_000", check="config/check.ov_SC02_000.sha", src="src/ov_SC02_000", asm="asm/ov_SC02_000/nonmatchings", out="docs/progress.ov_SC02_000.md"), "ov_SC02_003": dict(build="build/ov_SC02_003/ov_SC02_003", check="config/check.ov_SC02_003.sha", src="src/ov_SC02_003", asm="asm/ov_SC02_003/nonmatchings", out="docs/progress.ov_SC02_003.md"), "ov_SC03_002": dict(build="build/ov_SC03_002/ov_SC03_002", check="config/check.ov_SC03_002.sha", src="src/ov_SC03_002", asm="asm/ov_SC03_002/nonmatchings", out="docs/progress.ov_SC03_002.md"), "ov_SC03_006": dict(build="build/ov_SC03_006/ov_SC03_006", check="config/check.ov_SC03_006.sha", src="src/ov_SC03_006", asm="asm/ov_SC03_006/nonmatchings", out="docs/progress.ov_SC03_006.md"), "ov_SC04_000": dict(build="build/ov_SC04_000/ov_SC04_000", check="config/check.ov_SC04_000.sha", src="src/ov_SC04_000", asm="asm/ov_SC04_000/nonmatchings", out="docs/progress.ov_SC04_000.md"), "ov_SC04_018": dict(build="build/ov_SC04_018/ov_SC04_018", check="config/check.ov_SC04_018.sha", src="src/ov_SC04_018", asm="asm/ov_SC04_018/nonmatchings", out="docs/progress.ov_SC04_018.md"), "ov_SC04_019": dict(build="build/ov_SC04_019/ov_SC04_019", check="config/check.ov_SC04_019.sha", src="src/ov_SC04_019", asm="asm/ov_SC04_019/nonmatchings", out="docs/progress.ov_SC04_019.md"), "ov_SC05_000": dict(build="build/ov_SC05_000/ov_SC05_000", check="config/check.ov_SC05_000.sha", src="src/ov_SC05_000", asm="asm/ov_SC05_000/nonmatchings", out="docs/progress.ov_SC05_000.md"), "ov_SC06_000": dict(build="build/ov_SC06_000/ov_SC06_000", check="config/check.ov_SC06_000.sha", src="src/ov_SC06_000", asm="asm/ov_SC06_000/nonmatchings", out="docs/progress.ov_SC06_000.md"), "ov_SC07_000": dict(build="build/ov_SC07_000/ov_SC07_000", check="config/check.ov_SC07_000.sha", src="src/ov_SC07_000", asm="asm/ov_SC07_000/nonmatchings", out="docs/progress.ov_SC07_000.md"), "ov_SC01_004": dict(build="build/ov_SC01_004/ov_SC01_004", check="config/check.ov_SC01_004.sha", src="src/ov_SC01_004", asm="asm/ov_SC01_004/nonmatchings", out="docs/progress.ov_SC01_004.md"), "ov_SC01_008": dict(build="build/ov_SC01_008/ov_SC01_008", check="config/check.ov_SC01_008.sha", src="src/ov_SC01_008", asm="asm/ov_SC01_008/nonmatchings", out="docs/progress.ov_SC01_008.md"), "ov_SC01_009": dict(build="build/ov_SC01_009/ov_SC01_009", check="config/check.ov_SC01_009.sha", src="src/ov_SC01_009", asm="asm/ov_SC01_009/nonmatchings", out="docs/progress.ov_SC01_009.md"), "ov_SC01_074": dict(build="build/ov_SC01_074/ov_SC01_074", check="config/check.ov_SC01_074.sha", src="src/ov_SC01_074", asm="asm/ov_SC01_074/nonmatchings", out="docs/progress.ov_SC01_074.md"), "ov_SC01_080": dict(build="build/ov_SC01_080/ov_SC01_080", check="config/check.ov_SC01_080.sha", src="src/ov_SC01_080", asm="asm/ov_SC01_080/nonmatchings", out="docs/progress.ov_SC01_080.md"), "ov_SC01_084": dict(build="build/ov_SC01_084/ov_SC01_084", check="config/check.ov_SC01_084.sha", src="src/ov_SC01_084", asm="asm/ov_SC01_084/nonmatchings", out="docs/progress.ov_SC01_084.md"), "ov_SC02_004": dict(build="build/ov_SC02_004/ov_SC02_004", check="config/check.ov_SC02_004.sha", src="src/ov_SC02_004", asm="asm/ov_SC02_004/nonmatchings", out="docs/progress.ov_SC02_004.md"), "ov_SC02_005": dict(build="build/ov_SC02_005/ov_SC02_005", check="config/check.ov_SC02_005.sha", src="src/ov_SC02_005", asm="asm/ov_SC02_005/nonmatchings", out="docs/progress.ov_SC02_005.md"), "ov_SC02_011": dict(build="build/ov_SC02_011/ov_SC02_011", check="config/check.ov_SC02_011.sha", src="src/ov_SC02_011", asm="asm/ov_SC02_011/nonmatchings", out="docs/progress.ov_SC02_011.md"), "ov_SC02_015": dict(build="build/ov_SC02_015/ov_SC02_015", check="config/check.ov_SC02_015.sha", src="src/ov_SC02_015", asm="asm/ov_SC02_015/nonmatchings", out="docs/progress.ov_SC02_015.md"), "ov_SC02_016": dict(build="build/ov_SC02_016/ov_SC02_016", check="config/check.ov_SC02_016.sha", src="src/ov_SC02_016", asm="asm/ov_SC02_016/nonmatchings", out="docs/progress.ov_SC02_016.md"), "ov_SC02_017": dict(build="build/ov_SC02_017/ov_SC02_017", check="config/check.ov_SC02_017.sha", src="src/ov_SC02_017", asm="asm/ov_SC02_017/nonmatchings", out="docs/progress.ov_SC02_017.md"), "ov_SC02_021": dict(build="build/ov_SC02_021/ov_SC02_021", check="config/check.ov_SC02_021.sha", src="src/ov_SC02_021", asm="asm/ov_SC02_021/nonmatchings", out="docs/progress.ov_SC02_021.md"), "ov_SC02_026": dict(build="build/ov_SC02_026/ov_SC02_026", check="config/check.ov_SC02_026.sha", src="src/ov_SC02_026", asm="asm/ov_SC02_026/nonmatchings", out="docs/progress.ov_SC02_026.md"), "ov_SC02_027": dict(build="build/ov_SC02_027/ov_SC02_027", check="config/check.ov_SC02_027.sha", src="src/ov_SC02_027", asm="asm/ov_SC02_027/nonmatchings", out="docs/progress.ov_SC02_027.md"), "ov_SC02_028": dict(build="build/ov_SC02_028/ov_SC02_028", check="config/check.ov_SC02_028.sha", src="src/ov_SC02_028", asm="asm/ov_SC02_028/nonmatchings", out="docs/progress.ov_SC02_028.md"), "ov_SC02_031": dict(build="build/ov_SC02_031/ov_SC02_031", check="config/check.ov_SC02_031.sha", src="src/ov_SC02_031", asm="asm/ov_SC02_031/nonmatchings", out="docs/progress.ov_SC02_031.md"), "ov_SC02_035": dict(build="build/ov_SC02_035/ov_SC02_035", check="config/check.ov_SC02_035.sha", src="src/ov_SC02_035", asm="asm/ov_SC02_035/nonmatchings", out="docs/progress.ov_SC02_035.md"), "ov_SC02_039": dict(build="build/ov_SC02_039/ov_SC02_039", check="config/check.ov_SC02_039.sha", src="src/ov_SC02_039", asm="asm/ov_SC02_039/nonmatchings", out="docs/progress.ov_SC02_039.md"), "ov_SC02_041": dict(build="build/ov_SC02_041/ov_SC02_041", check="config/check.ov_SC02_041.sha", src="src/ov_SC02_041", asm="asm/ov_SC02_041/nonmatchings", out="docs/progress.ov_SC02_041.md"), "ov_SC03_003": dict(build="build/ov_SC03_003/ov_SC03_003", check="config/check.ov_SC03_003.sha", src="src/ov_SC03_003", asm="asm/ov_SC03_003/nonmatchings", out="docs/progress.ov_SC03_003.md"), "ov_SC03_007": dict(build="build/ov_SC03_007/ov_SC03_007", check="config/check.ov_SC03_007.sha", src="src/ov_SC03_007", asm="asm/ov_SC03_007/nonmatchings", out="docs/progress.ov_SC03_007.md"), "ov_SC03_010": dict(build="build/ov_SC03_010/ov_SC03_010", check="config/check.ov_SC03_010.sha", src="src/ov_SC03_010", asm="asm/ov_SC03_010/nonmatchings", out="docs/progress.ov_SC03_010.md"), "ov_SC03_011": dict(build="build/ov_SC03_011/ov_SC03_011", check="config/check.ov_SC03_011.sha", src="src/ov_SC03_011", asm="asm/ov_SC03_011/nonmatchings", out="docs/progress.ov_SC03_011.md"), "ov_SC03_012": dict(build="build/ov_SC03_012/ov_SC03_012", check="config/check.ov_SC03_012.sha", src="src/ov_SC03_012", asm="asm/ov_SC03_012/nonmatchings", out="docs/progress.ov_SC03_012.md"), "ov_SC03_013": dict(build="build/ov_SC03_013/ov_SC03_013", check="config/check.ov_SC03_013.sha", src="src/ov_SC03_013", asm="asm/ov_SC03_013/nonmatchings", out="docs/progress.ov_SC03_013.md"), "ov_SC03_014": dict(build="build/ov_SC03_014/ov_SC03_014", check="config/check.ov_SC03_014.sha", src="src/ov_SC03_014", asm="asm/ov_SC03_014/nonmatchings", out="docs/progress.ov_SC03_014.md"), "ov_SC03_015": dict(build="build/ov_SC03_015/ov_SC03_015", check="config/check.ov_SC03_015.sha", src="src/ov_SC03_015", asm="asm/ov_SC03_015/nonmatchings", out="docs/progress.ov_SC03_015.md"), "ov_SC03_023": dict(build="build/ov_SC03_023/ov_SC03_023", check="config/check.ov_SC03_023.sha", src="src/ov_SC03_023", asm="asm/ov_SC03_023/nonmatchings", out="docs/progress.ov_SC03_023.md"), "ov_SC03_024": dict(build="build/ov_SC03_024/ov_SC03_024", check="config/check.ov_SC03_024.sha", src="src/ov_SC03_024", asm="asm/ov_SC03_024/nonmatchings", out="docs/progress.ov_SC03_024.md"), "ov_SC03_028": dict(build="build/ov_SC03_028/ov_SC03_028", check="config/check.ov_SC03_028.sha", src="src/ov_SC03_028", asm="asm/ov_SC03_028/nonmatchings", out="docs/progress.ov_SC03_028.md"), "ov_SC03_029": dict(build="build/ov_SC03_029/ov_SC03_029", check="config/check.ov_SC03_029.sha", src="src/ov_SC03_029", asm="asm/ov_SC03_029/nonmatchings", out="docs/progress.ov_SC03_029.md"), "ov_SC03_030": dict(build="build/ov_SC03_030/ov_SC03_030", check="config/check.ov_SC03_030.sha", src="src/ov_SC03_030", asm="asm/ov_SC03_030/nonmatchings", out="docs/progress.ov_SC03_030.md"), "ov_SC03_031": dict(build="build/ov_SC03_031/ov_SC03_031", check="config/check.ov_SC03_031.sha", src="src/ov_SC03_031", asm="asm/ov_SC03_031/nonmatchings", out="docs/progress.ov_SC03_031.md"), "ov_SC03_089": dict(build="build/ov_SC03_089/ov_SC03_089", check="config/check.ov_SC03_089.sha", src="src/ov_SC03_089", asm="asm/ov_SC03_089/nonmatchings", out="docs/progress.ov_SC03_089.md"), "ov_SC03_090": dict(build="build/ov_SC03_090/ov_SC03_090", check="config/check.ov_SC03_090.sha", src="src/ov_SC03_090", asm="asm/ov_SC03_090/nonmatchings", out="docs/progress.ov_SC03_090.md"), "ov_SC03_091": dict(build="build/ov_SC03_091/ov_SC03_091", check="config/check.ov_SC03_091.sha", src="src/ov_SC03_091", asm="asm/ov_SC03_091/nonmatchings", out="docs/progress.ov_SC03_091.md"), "ov_SC03_092": dict(build="build/ov_SC03_092/ov_SC03_092", check="config/check.ov_SC03_092.sha", src="src/ov_SC03_092", asm="asm/ov_SC03_092/nonmatchings", out="docs/progress.ov_SC03_092.md"), "ov_SC03_093": dict(build="build/ov_SC03_093/ov_SC03_093", check="config/check.ov_SC03_093.sha", src="src/ov_SC03_093", asm="asm/ov_SC03_093/nonmatchings", out="docs/progress.ov_SC03_093.md"), "ov_SC03_094": dict(build="build/ov_SC03_094/ov_SC03_094", check="config/check.ov_SC03_094.sha", src="src/ov_SC03_094", asm="asm/ov_SC03_094/nonmatchings", out="docs/progress.ov_SC03_094.md"), "ov_SC03_095": dict(build="build/ov_SC03_095/ov_SC03_095", check="config/check.ov_SC03_095.sha", src="src/ov_SC03_095", asm="asm/ov_SC03_095/nonmatchings", out="docs/progress.ov_SC03_095.md"), "ov_SC03_096": dict(build="build/ov_SC03_096/ov_SC03_096", check="config/check.ov_SC03_096.sha", src="src/ov_SC03_096", asm="asm/ov_SC03_096/nonmatchings", out="docs/progress.ov_SC03_096.md"), "ov_SC03_097": dict(build="build/ov_SC03_097/ov_SC03_097", check="config/check.ov_SC03_097.sha", src="src/ov_SC03_097", asm="asm/ov_SC03_097/nonmatchings", out="docs/progress.ov_SC03_097.md"), "ov_SC03_098": dict(build="build/ov_SC03_098/ov_SC03_098", check="config/check.ov_SC03_098.sha", src="src/ov_SC03_098", asm="asm/ov_SC03_098/nonmatchings", out="docs/progress.ov_SC03_098.md"), "ov_SC03_099": dict(build="build/ov_SC03_099/ov_SC03_099", check="config/check.ov_SC03_099.sha", src="src/ov_SC03_099", asm="asm/ov_SC03_099/nonmatchings", out="docs/progress.ov_SC03_099.md"), "ov_SC03_100": dict(build="build/ov_SC03_100/ov_SC03_100", check="config/check.ov_SC03_100.sha", src="src/ov_SC03_100", asm="asm/ov_SC03_100/nonmatchings", out="docs/progress.ov_SC03_100.md"), "ov_SC03_101": dict(build="build/ov_SC03_101/ov_SC03_101", check="config/check.ov_SC03_101.sha", src="src/ov_SC03_101", asm="asm/ov_SC03_101/nonmatchings", out="docs/progress.ov_SC03_101.md"), "ov_SC03_102": dict(build="build/ov_SC03_102/ov_SC03_102", check="config/check.ov_SC03_102.sha", src="src/ov_SC03_102", asm="asm/ov_SC03_102/nonmatchings", out="docs/progress.ov_SC03_102.md"), "ov_SC03_103": dict(build="build/ov_SC03_103/ov_SC03_103", check="config/check.ov_SC03_103.sha", src="src/ov_SC03_103", asm="asm/ov_SC03_103/nonmatchings", out="docs/progress.ov_SC03_103.md"), "ov_SC03_104": dict(build="build/ov_SC03_104/ov_SC03_104", check="config/check.ov_SC03_104.sha", src="src/ov_SC03_104", asm="asm/ov_SC03_104/nonmatchings", out="docs/progress.ov_SC03_104.md"), "ov_SC03_105": dict(build="build/ov_SC03_105/ov_SC03_105", check="config/check.ov_SC03_105.sha", src="src/ov_SC03_105", asm="asm/ov_SC03_105/nonmatchings", out="docs/progress.ov_SC03_105.md"), "ov_SC03_108": dict(build="build/ov_SC03_108/ov_SC03_108", check="config/check.ov_SC03_108.sha", src="src/ov_SC03_108", asm="asm/ov_SC03_108/nonmatchings", out="docs/progress.ov_SC03_108.md"), "ov_SC03_109": dict(build="build/ov_SC03_109/ov_SC03_109", check="config/check.ov_SC03_109.sha", src="src/ov_SC03_109", asm="asm/ov_SC03_109/nonmatchings", out="docs/progress.ov_SC03_109.md"), "ov_SC03_110": dict(build="build/ov_SC03_110/ov_SC03_110", check="config/check.ov_SC03_110.sha", src="src/ov_SC03_110", asm="asm/ov_SC03_110/nonmatchings", out="docs/progress.ov_SC03_110.md"), "ov_SC03_111": dict(build="build/ov_SC03_111/ov_SC03_111", check="config/check.ov_SC03_111.sha", src="src/ov_SC03_111", asm="asm/ov_SC03_111/nonmatchings", out="docs/progress.ov_SC03_111.md"), "ov_SC03_112": dict(build="build/ov_SC03_112/ov_SC03_112", check="config/check.ov_SC03_112.sha", src="src/ov_SC03_112", asm="asm/ov_SC03_112/nonmatchings", out="docs/progress.ov_SC03_112.md"), "ov_SC03_113": dict(build="build/ov_SC03_113/ov_SC03_113", check="config/check.ov_SC03_113.sha", src="src/ov_SC03_113", asm="asm/ov_SC03_113/nonmatchings", out="docs/progress.ov_SC03_113.md"), "ov_SC03_114": dict(build="build/ov_SC03_114/ov_SC03_114", check="config/check.ov_SC03_114.sha", src="src/ov_SC03_114", asm="asm/ov_SC03_114/nonmatchings", out="docs/progress.ov_SC03_114.md"), "ov_SC03_115": dict(build="build/ov_SC03_115/ov_SC03_115", check="config/check.ov_SC03_115.sha", src="src/ov_SC03_115", asm="asm/ov_SC03_115/nonmatchings", out="docs/progress.ov_SC03_115.md"), "ov_SC03_116": dict(build="build/ov_SC03_116/ov_SC03_116", check="config/check.ov_SC03_116.sha", src="src/ov_SC03_116", asm="asm/ov_SC03_116/nonmatchings", out="docs/progress.ov_SC03_116.md"), "ov_SC03_117": dict(build="build/ov_SC03_117/ov_SC03_117", check="config/check.ov_SC03_117.sha", src="src/ov_SC03_117", asm="asm/ov_SC03_117/nonmatchings", out="docs/progress.ov_SC03_117.md"), "ov_SC03_118": dict(build="build/ov_SC03_118/ov_SC03_118", check="config/check.ov_SC03_118.sha", src="src/ov_SC03_118", asm="asm/ov_SC03_118/nonmatchings", out="docs/progress.ov_SC03_118.md"), "ov_SC03_119": dict(build="build/ov_SC03_119/ov_SC03_119", check="config/check.ov_SC03_119.sha", src="src/ov_SC03_119", asm="asm/ov_SC03_119/nonmatchings", out="docs/progress.ov_SC03_119.md"), "ov_SC03_121": dict(build="build/ov_SC03_121/ov_SC03_121", check="config/check.ov_SC03_121.sha", src="src/ov_SC03_121", asm="asm/ov_SC03_121/nonmatchings", out="docs/progress.ov_SC03_121.md"), "ov_SC03_124": dict(build="build/ov_SC03_124/ov_SC03_124", check="config/check.ov_SC03_124.sha", src="src/ov_SC03_124", asm="asm/ov_SC03_124/nonmatchings", out="docs/progress.ov_SC03_124.md"), "ov_SC03_125": dict(build="build/ov_SC03_125/ov_SC03_125", check="config/check.ov_SC03_125.sha", src="src/ov_SC03_125", asm="asm/ov_SC03_125/nonmatchings", out="docs/progress.ov_SC03_125.md"), "ov_SC03_126": dict(build="build/ov_SC03_126/ov_SC03_126", check="config/check.ov_SC03_126.sha", src="src/ov_SC03_126", asm="asm/ov_SC03_126/nonmatchings", out="docs/progress.ov_SC03_126.md"), "ov_SC04_002": dict(build="build/ov_SC04_002/ov_SC04_002", check="config/check.ov_SC04_002.sha", src="src/ov_SC04_002", asm="asm/ov_SC04_002/nonmatchings", out="docs/progress.ov_SC04_002.md"), "ov_SC04_003": dict(build="build/ov_SC04_003/ov_SC04_003", check="config/check.ov_SC04_003.sha", src="src/ov_SC04_003", asm="asm/ov_SC04_003/nonmatchings", out="docs/progress.ov_SC04_003.md"), "ov_SC04_004": dict(build="build/ov_SC04_004/ov_SC04_004", check="config/check.ov_SC04_004.sha", src="src/ov_SC04_004", asm="asm/ov_SC04_004/nonmatchings", out="docs/progress.ov_SC04_004.md"), "ov_SC04_005": dict(build="build/ov_SC04_005/ov_SC04_005", check="config/check.ov_SC04_005.sha", src="src/ov_SC04_005", asm="asm/ov_SC04_005/nonmatchings", out="docs/progress.ov_SC04_005.md"), "ov_SC04_006": dict(build="build/ov_SC04_006/ov_SC04_006", check="config/check.ov_SC04_006.sha", src="src/ov_SC04_006", asm="asm/ov_SC04_006/nonmatchings", out="docs/progress.ov_SC04_006.md"), "ov_SC04_007": dict(build="build/ov_SC04_007/ov_SC04_007", check="config/check.ov_SC04_007.sha", src="src/ov_SC04_007", asm="asm/ov_SC04_007/nonmatchings", out="docs/progress.ov_SC04_007.md"), "ov_SC04_008": dict(build="build/ov_SC04_008/ov_SC04_008", check="config/check.ov_SC04_008.sha", src="src/ov_SC04_008", asm="asm/ov_SC04_008/nonmatchings", out="docs/progress.ov_SC04_008.md"), "ov_SC04_009": dict(build="build/ov_SC04_009/ov_SC04_009", check="config/check.ov_SC04_009.sha", src="src/ov_SC04_009", asm="asm/ov_SC04_009/nonmatchings", out="docs/progress.ov_SC04_009.md"), "ov_SC04_010": dict(build="build/ov_SC04_010/ov_SC04_010", check="config/check.ov_SC04_010.sha", src="src/ov_SC04_010", asm="asm/ov_SC04_010/nonmatchings", out="docs/progress.ov_SC04_010.md"), "ov_SC04_011": dict(build="build/ov_SC04_011/ov_SC04_011", check="config/check.ov_SC04_011.sha", src="src/ov_SC04_011", asm="asm/ov_SC04_011/nonmatchings", out="docs/progress.ov_SC04_011.md"), "ov_SC04_012": dict(build="build/ov_SC04_012/ov_SC04_012", check="config/check.ov_SC04_012.sha", src="src/ov_SC04_012", asm="asm/ov_SC04_012/nonmatchings", out="docs/progress.ov_SC04_012.md"), "ov_SC04_015": dict(build="build/ov_SC04_015/ov_SC04_015", check="config/check.ov_SC04_015.sha", src="src/ov_SC04_015", asm="asm/ov_SC04_015/nonmatchings", out="docs/progress.ov_SC04_015.md"), "ov_SC04_016": dict(build="build/ov_SC04_016/ov_SC04_016", check="config/check.ov_SC04_016.sha", src="src/ov_SC04_016", asm="asm/ov_SC04_016/nonmatchings", out="docs/progress.ov_SC04_016.md"), "ov_SC04_020": dict(build="build/ov_SC04_020/ov_SC04_020", check="config/check.ov_SC04_020.sha", src="src/ov_SC04_020", asm="asm/ov_SC04_020/nonmatchings", out="docs/progress.ov_SC04_020.md"), "ov_SC04_021": dict(build="build/ov_SC04_021/ov_SC04_021", check="config/check.ov_SC04_021.sha", src="src/ov_SC04_021", asm="asm/ov_SC04_021/nonmatchings", out="docs/progress.ov_SC04_021.md"), "ov_SC05_001": dict(build="build/ov_SC05_001/ov_SC05_001", check="config/check.ov_SC05_001.sha", src="src/ov_SC05_001", asm="asm/ov_SC05_001/nonmatchings", out="docs/progress.ov_SC05_001.md"), "ov_SC05_002": dict(build="build/ov_SC05_002/ov_SC05_002", check="config/check.ov_SC05_002.sha", src="src/ov_SC05_002", asm="asm/ov_SC05_002/nonmatchings", out="docs/progress.ov_SC05_002.md"), "ov_SC05_003": dict(build="build/ov_SC05_003/ov_SC05_003", check="config/check.ov_SC05_003.sha", src="src/ov_SC05_003", asm="asm/ov_SC05_003/nonmatchings", out="docs/progress.ov_SC05_003.md"), "ov_SC05_004": dict(build="build/ov_SC05_004/ov_SC05_004", check="config/check.ov_SC05_004.sha", src="src/ov_SC05_004", asm="asm/ov_SC05_004/nonmatchings", out="docs/progress.ov_SC05_004.md"), "ov_SC05_005": dict(build="build/ov_SC05_005/ov_SC05_005", check="config/check.ov_SC05_005.sha", src="src/ov_SC05_005", asm="asm/ov_SC05_005/nonmatchings", out="docs/progress.ov_SC05_005.md"), "ov_SC05_006": dict(build="build/ov_SC05_006/ov_SC05_006", check="config/check.ov_SC05_006.sha", src="src/ov_SC05_006", asm="asm/ov_SC05_006/nonmatchings", out="docs/progress.ov_SC05_006.md"), "ov_SC05_007": dict(build="build/ov_SC05_007/ov_SC05_007", check="config/check.ov_SC05_007.sha", src="src/ov_SC05_007", asm="asm/ov_SC05_007/nonmatchings", out="docs/progress.ov_SC05_007.md"), "ov_SC05_008": dict(build="build/ov_SC05_008/ov_SC05_008", check="config/check.ov_SC05_008.sha", src="src/ov_SC05_008", asm="asm/ov_SC05_008/nonmatchings", out="docs/progress.ov_SC05_008.md"), "ov_SC05_009": dict(build="build/ov_SC05_009/ov_SC05_009", check="config/check.ov_SC05_009.sha", src="src/ov_SC05_009", asm="asm/ov_SC05_009/nonmatchings", out="docs/progress.ov_SC05_009.md"), "ov_SC05_010": dict(build="build/ov_SC05_010/ov_SC05_010", check="config/check.ov_SC05_010.sha", src="src/ov_SC05_010", asm="asm/ov_SC05_010/nonmatchings", out="docs/progress.ov_SC05_010.md"), "ov_SC05_011": dict(build="build/ov_SC05_011/ov_SC05_011", check="config/check.ov_SC05_011.sha", src="src/ov_SC05_011", asm="asm/ov_SC05_011/nonmatchings", out="docs/progress.ov_SC05_011.md"), "ov_SC05_017": dict(build="build/ov_SC05_017/ov_SC05_017", check="config/check.ov_SC05_017.sha", src="src/ov_SC05_017", asm="asm/ov_SC05_017/nonmatchings", out="docs/progress.ov_SC05_017.md"), "ov_SC05_018": dict(build="build/ov_SC05_018/ov_SC05_018", check="config/check.ov_SC05_018.sha", src="src/ov_SC05_018", asm="asm/ov_SC05_018/nonmatchings", out="docs/progress.ov_SC05_018.md"), "ov_SC05_019": dict(build="build/ov_SC05_019/ov_SC05_019", check="config/check.ov_SC05_019.sha", src="src/ov_SC05_019", asm="asm/ov_SC05_019/nonmatchings", out="docs/progress.ov_SC05_019.md"), "ov_SC06_006": dict(build="build/ov_SC06_006/ov_SC06_006", check="config/check.ov_SC06_006.sha", src="src/ov_SC06_006", asm="asm/ov_SC06_006/nonmatchings", out="docs/progress.ov_SC06_006.md"), "ov_SC06_008": dict(build="build/ov_SC06_008/ov_SC06_008", check="config/check.ov_SC06_008.sha", src="src/ov_SC06_008", asm="asm/ov_SC06_008/nonmatchings", out="docs/progress.ov_SC06_008.md"), "ov_SC06_010": dict(build="build/ov_SC06_010/ov_SC06_010", check="config/check.ov_SC06_010.sha", src="src/ov_SC06_010", asm="asm/ov_SC06_010/nonmatchings", out="docs/progress.ov_SC06_010.md"), "ov_SC06_011": dict(build="build/ov_SC06_011/ov_SC06_011", check="config/check.ov_SC06_011.sha", src="src/ov_SC06_011", asm="asm/ov_SC06_011/nonmatchings", out="docs/progress.ov_SC06_011.md"), "ov_SC06_013": dict(build="build/ov_SC06_013/ov_SC06_013", check="config/check.ov_SC06_013.sha", src="src/ov_SC06_013", asm="asm/ov_SC06_013/nonmatchings", out="docs/progress.ov_SC06_013.md"), "ov_SC06_014": dict(build="build/ov_SC06_014/ov_SC06_014", check="config/check.ov_SC06_014.sha", src="src/ov_SC06_014", asm="asm/ov_SC06_014/nonmatchings", out="docs/progress.ov_SC06_014.md"), "ov_SC06_015": dict(build="build/ov_SC06_015/ov_SC06_015", check="config/check.ov_SC06_015.sha", src="src/ov_SC06_015", asm="asm/ov_SC06_015/nonmatchings", out="docs/progress.ov_SC06_015.md"), "ov_SC06_016": dict(build="build/ov_SC06_016/ov_SC06_016", check="config/check.ov_SC06_016.sha", src="src/ov_SC06_016", asm="asm/ov_SC06_016/nonmatchings", out="docs/progress.ov_SC06_016.md"), "ov_SC06_018": dict(build="build/ov_SC06_018/ov_SC06_018", check="config/check.ov_SC06_018.sha", src="src/ov_SC06_018", asm="asm/ov_SC06_018/nonmatchings", out="docs/progress.ov_SC06_018.md"), "ov_SC06_020": dict(build="build/ov_SC06_020/ov_SC06_020", check="config/check.ov_SC06_020.sha", src="src/ov_SC06_020", asm="asm/ov_SC06_020/nonmatchings", out="docs/progress.ov_SC06_020.md"), "ov_SC06_022": dict(build="build/ov_SC06_022/ov_SC06_022", check="config/check.ov_SC06_022.sha", src="src/ov_SC06_022", asm="asm/ov_SC06_022/nonmatchings", out="docs/progress.ov_SC06_022.md"), "ov_SC06_024": dict(build="build/ov_SC06_024/ov_SC06_024", check="config/check.ov_SC06_024.sha", src="src/ov_SC06_024", asm="asm/ov_SC06_024/nonmatchings", out="docs/progress.ov_SC06_024.md"), "ov_SC06_025": dict(build="build/ov_SC06_025/ov_SC06_025", check="config/check.ov_SC06_025.sha", src="src/ov_SC06_025", asm="asm/ov_SC06_025/nonmatchings", out="docs/progress.ov_SC06_025.md"), "ov_SC06_027": dict(build="build/ov_SC06_027/ov_SC06_027", check="config/check.ov_SC06_027.sha", src="src/ov_SC06_027", asm="asm/ov_SC06_027/nonmatchings", out="docs/progress.ov_SC06_027.md"), "ov_SC06_029": dict(build="build/ov_SC06_029/ov_SC06_029", check="config/check.ov_SC06_029.sha", src="src/ov_SC06_029", asm="asm/ov_SC06_029/nonmatchings", out="docs/progress.ov_SC06_029.md"), "ov_SC06_030": dict(build="build/ov_SC06_030/ov_SC06_030", check="config/check.ov_SC06_030.sha", src="src/ov_SC06_030", asm="asm/ov_SC06_030/nonmatchings", out="docs/progress.ov_SC06_030.md"), "ov_SC06_032": dict(build="build/ov_SC06_032/ov_SC06_032", check="config/check.ov_SC06_032.sha", src="src/ov_SC06_032", asm="asm/ov_SC06_032/nonmatchings", out="docs/progress.ov_SC06_032.md"), "ov_SC06_033": dict(build="build/ov_SC06_033/ov_SC06_033", check="config/check.ov_SC06_033.sha", src="src/ov_SC06_033", asm="asm/ov_SC06_033/nonmatchings", out="docs/progress.ov_SC06_033.md"), "ov_SC07_001": dict(build="build/ov_SC07_001/ov_SC07_001", check="config/check.ov_SC07_001.sha", src="src/ov_SC07_001", asm="asm/ov_SC07_001/nonmatchings", out="docs/progress.ov_SC07_001.md"), "ov_SC07_002": dict(build="build/ov_SC07_002/ov_SC07_002", check="config/check.ov_SC07_002.sha", src="src/ov_SC07_002", asm="asm/ov_SC07_002/nonmatchings", out="docs/progress.ov_SC07_002.md"), "ov_SC07_008": dict(build="build/ov_SC07_008/ov_SC07_008", check="config/check.ov_SC07_008.sha", src="src/ov_SC07_008", asm="asm/ov_SC07_008/nonmatchings", out="docs/progress.ov_SC07_008.md"), "ov_SC07_009": dict(build="build/ov_SC07_009/ov_SC07_009", check="config/check.ov_SC07_009.sha", src="src/ov_SC07_009", asm="asm/ov_SC07_009/nonmatchings", out="docs/progress.ov_SC07_009.md"), "ov_SC07_006": dict(build="build/ov_SC07_006/ov_SC07_006", check="config/check.ov_SC07_006.sha", src="src/ov_SC07_006", asm="asm/ov_SC07_006/nonmatchings", out="docs/progress.ov_SC07_006.md"), "ov_SC07_007": dict(build="build/ov_SC07_007/ov_SC07_007", check="config/check.ov_SC07_007.sha", src="src/ov_SC07_007", asm="asm/ov_SC07_007/nonmatchings", out="docs/progress.ov_SC07_007.md"), "ov_SC07_010": dict(build="build/ov_SC07_010/ov_SC07_010", check="config/check.ov_SC07_010.sha", src="src/ov_SC07_010", asm="asm/ov_SC07_010/nonmatchings", out="docs/progress.ov_SC07_010.md"), "ov_SC07_011": dict(build="build/ov_SC07_011/ov_SC07_011", check="config/check.ov_SC07_011.sha", src="src/ov_SC07_011", asm="asm/ov_SC07_011/nonmatchings", out="docs/progress.ov_SC07_011.md"), "ov_MAIN_012": dict(build="build/ov_MAIN_012/ov_MAIN_012", check="config/check.ov_MAIN_012.sha", src="src/ov_MAIN_012", asm="asm/ov_MAIN_012/nonmatchings", out="docs/progress.ov_MAIN_012.md"), "ov_SC02_037": dict(build="build/ov_SC02_037/ov_SC02_037", check="config/check.ov_SC02_037.sha", src="src/ov_SC02_037", asm="asm/ov_SC02_037/nonmatchings", out="docs/progress.ov_SC02_037.md"), "ov_SC03_107": dict(build="build/ov_SC03_107/ov_SC03_107", check="config/check.ov_SC03_107.sha", src="src/ov_SC03_107", asm="asm/ov_SC03_107/nonmatchings", out="docs/progress.ov_SC03_107.md"), "md_MAIN_013": dict(build="build/md_MAIN_013/md_MAIN_013", check="config/check.md_MAIN_013.sha", src="src/md_MAIN_013", asm="asm/md_MAIN_013/nonmatchings", out="docs/progress.md_MAIN_013.md"), "md_MAIN_014": dict(build="build/md_MAIN_014/md_MAIN_014", check="config/check.md_MAIN_014.sha", src="src/md_MAIN_014", asm="asm/md_MAIN_014/nonmatchings", out="docs/progress.md_MAIN_014.md"), "md_MAIN_015": dict(build="build/md_MAIN_015/md_MAIN_015", check="config/check.md_MAIN_015.sha", src="src/md_MAIN_015", asm="asm/md_MAIN_015/nonmatchings", out="docs/progress.md_MAIN_015.md"), "md_MAIN_016": dict(build="build/md_MAIN_016/md_MAIN_016", check="config/check.md_MAIN_016.sha", src="src/md_MAIN_016", asm="asm/md_MAIN_016/nonmatchings", out="docs/progress.md_MAIN_016.md"), "md_MAIN_017": dict(build="build/md_MAIN_017/md_MAIN_017", check="config/check.md_MAIN_017.sha", src="src/md_MAIN_017", asm="asm/md_MAIN_017/nonmatchings", out="docs/progress.md_MAIN_017.md"), "md_MAIN_018": dict(build="build/md_MAIN_018/md_MAIN_018", check="config/check.md_MAIN_018.sha", src="src/md_MAIN_018", asm="asm/md_MAIN_018/nonmatchings", out="docs/progress.md_MAIN_018.md"), "md_MAIN_019": dict(build="build/md_MAIN_019/md_MAIN_019", check="config/check.md_MAIN_019.sha", src="src/md_MAIN_019", asm="asm/md_MAIN_019/nonmatchings", out="docs/progress.md_MAIN_019.md"), "md_MAIN_020": dict(build="build/md_MAIN_020/md_MAIN_020", check="config/check.md_MAIN_020.sha", src="src/md_MAIN_020", asm="asm/md_MAIN_020/nonmatchings", out="docs/progress.md_MAIN_020.md"), "md_MAIN_021": dict(build="build/md_MAIN_021/md_MAIN_021", check="config/check.md_MAIN_021.sha", src="src/md_MAIN_021", asm="asm/md_MAIN_021/nonmatchings", out="docs/progress.md_MAIN_021.md"), "md_MAIN_022": dict(build="build/md_MAIN_022/md_MAIN_022", check="config/check.md_MAIN_022.sha", src="src/md_MAIN_022", asm="asm/md_MAIN_022/nonmatchings", out="docs/progress.md_MAIN_022.md"), "md_MAIN_023": dict(build="build/md_MAIN_023/md_MAIN_023", check="config/check.md_MAIN_023.sha", src="src/md_MAIN_023", asm="asm/md_MAIN_023/nonmatchings", out="docs/progress.md_MAIN_023.md"), "md_MAIN_024": dict(build="build/md_MAIN_024/md_MAIN_024", check="config/check.md_MAIN_024.sha", src="src/md_MAIN_024", asm="asm/md_MAIN_024/nonmatchings", out="docs/progress.md_MAIN_024.md"), "md_MAIN_025": dict(build="build/md_MAIN_025/md_MAIN_025", check="config/check.md_MAIN_025.sha", src="src/md_MAIN_025", asm="asm/md_MAIN_025/nonmatchings", out="docs/progress.md_MAIN_025.md"), "md_MAIN_026": dict(build="build/md_MAIN_026/md_MAIN_026", check="config/check.md_MAIN_026.sha", src="src/md_MAIN_026", asm="asm/md_MAIN_026/nonmatchings", out="docs/progress.md_MAIN_026.md"), "md_MAIN_027": dict(build="build/md_MAIN_027/md_MAIN_027", check="config/check.md_MAIN_027.sha", src="src/md_MAIN_027", asm="asm/md_MAIN_027/nonmatchings", out="docs/progress.md_MAIN_027.md"), "md_MAIN_028": dict(build="build/md_MAIN_028/md_MAIN_028", check="config/check.md_MAIN_028.sha", src="src/md_MAIN_028", asm="asm/md_MAIN_028/nonmatchings", out="docs/progress.md_MAIN_028.md"), "md_MAIN_029": dict(build="build/md_MAIN_029/md_MAIN_029", check="config/check.md_MAIN_029.sha", src="src/md_MAIN_029", asm="asm/md_MAIN_029/nonmatchings", out="docs/progress.md_MAIN_029.md"), "md_MAIN_030": dict(build="build/md_MAIN_030/md_MAIN_030", check="config/check.md_MAIN_030.sha", src="src/md_MAIN_030", asm="asm/md_MAIN_030/nonmatchings", out="docs/progress.md_MAIN_030.md"), "md_MAIN_031": dict(build="build/md_MAIN_031/md_MAIN_031", check="config/check.md_MAIN_031.sha", src="src/md_MAIN_031", asm="asm/md_MAIN_031/nonmatchings", out="docs/progress.md_MAIN_031.md"), "md_MAIN_032": dict(build="build/md_MAIN_032/md_MAIN_032", check="config/check.md_MAIN_032.sha", src="src/md_MAIN_032", asm="asm/md_MAIN_032/nonmatchings", out="docs/progress.md_MAIN_032.md"), "md_MAIN_033": dict(build="build/md_MAIN_033/md_MAIN_033", check="config/check.md_MAIN_033.sha", src="src/md_MAIN_033", asm="asm/md_MAIN_033/nonmatchings", out="docs/progress.md_MAIN_033.md"), "md_MAIN_034": dict(build="build/md_MAIN_034/md_MAIN_034", check="config/check.md_MAIN_034.sha", src="src/md_MAIN_034", asm="asm/md_MAIN_034/nonmatchings", out="docs/progress.md_MAIN_034.md"), "md_MAIN_035": dict(build="build/md_MAIN_035/md_MAIN_035", check="config/check.md_MAIN_035.sha", src="src/md_MAIN_035", asm="asm/md_MAIN_035/nonmatchings", out="docs/progress.md_MAIN_035.md"), "md_MAIN_036": dict(build="build/md_MAIN_036/md_MAIN_036", check="config/check.md_MAIN_036.sha", src="src/md_MAIN_036", asm="asm/md_MAIN_036/nonmatchings", out="docs/progress.md_MAIN_036.md"), "md_MAIN_037": dict(build="build/md_MAIN_037/md_MAIN_037", check="config/check.md_MAIN_037.sha", src="src/md_MAIN_037", asm="asm/md_MAIN_037/nonmatchings", out="docs/progress.md_MAIN_037.md"), "md_MAIN_038": dict(build="build/md_MAIN_038/md_MAIN_038", check="config/check.md_MAIN_038.sha", src="src/md_MAIN_038", asm="asm/md_MAIN_038/nonmatchings", out="docs/progress.md_MAIN_038.md"), "md_MAIN_039": dict(build="build/md_MAIN_039/md_MAIN_039", check="config/check.md_MAIN_039.sha", src="src/md_MAIN_039", asm="asm/md_MAIN_039/nonmatchings", out="docs/progress.md_MAIN_039.md"), "md_MAIN_040": dict(build="build/md_MAIN_040/md_MAIN_040", check="config/check.md_MAIN_040.sha", src="src/md_MAIN_040", asm="asm/md_MAIN_040/nonmatchings", out="docs/progress.md_MAIN_040.md"), "md_MAIN_041": dict(build="build/md_MAIN_041/md_MAIN_041", check="config/check.md_MAIN_041.sha", src="src/md_MAIN_041", asm="asm/md_MAIN_041/nonmatchings", out="docs/progress.md_MAIN_041.md"), "md_MAIN_042": dict(build="build/md_MAIN_042/md_MAIN_042", check="config/check.md_MAIN_042.sha", src="src/md_MAIN_042", asm="asm/md_MAIN_042/nonmatchings", out="docs/progress.md_MAIN_042.md"), "md_MAIN_043": dict(build="build/md_MAIN_043/md_MAIN_043", check="config/check.md_MAIN_043.sha", src="src/md_MAIN_043", asm="asm/md_MAIN_043/nonmatchings", out="docs/progress.md_MAIN_043.md"), "md_MAIN_044": dict(build="build/md_MAIN_044/md_MAIN_044", check="config/check.md_MAIN_044.sha", src="src/md_MAIN_044", asm="asm/md_MAIN_044/nonmatchings", out="docs/progress.md_MAIN_044.md"), "md_MAIN_045": dict(build="build/md_MAIN_045/md_MAIN_045", check="config/check.md_MAIN_045.sha", src="src/md_MAIN_045", asm="asm/md_MAIN_045/nonmatchings", out="docs/progress.md_MAIN_045.md"), "md_MAIN_046": dict(build="build/md_MAIN_046/md_MAIN_046", check="config/check.md_MAIN_046.sha", src="src/md_MAIN_046", asm="asm/md_MAIN_046/nonmatchings", out="docs/progress.md_MAIN_046.md"), "md_MAIN_047": dict(build="build/md_MAIN_047/md_MAIN_047", check="config/check.md_MAIN_047.sha", src="src/md_MAIN_047", asm="asm/md_MAIN_047/nonmatchings", out="docs/progress.md_MAIN_047.md"), "md_MAIN_001": dict(build="build/md_MAIN_001/md_MAIN_001", check="config/check.md_MAIN_001.sha", src="src/md_MAIN_001", asm="asm/md_MAIN_001/nonmatchings", out="docs/progress.md_MAIN_001.md"), "md_MAIN_008": dict(build="build/md_MAIN_008/md_MAIN_008", check="config/check.md_MAIN_008.sha", src="src/md_MAIN_008", asm="asm/md_MAIN_008/nonmatchings", out="docs/progress.md_MAIN_008.md"), "md_MAIN_011": dict(build="build/md_MAIN_011/md_MAIN_011", check="config/check.md_MAIN_011.sha", src="src/md_MAIN_011", asm="asm/md_MAIN_011/nonmatchings", out="docs/progress.md_MAIN_011.md"), "md_SC07_003": dict(build="build/md_SC07_003/md_SC07_003", check="config/check.md_SC07_003.sha", src="src/md_SC07_003", asm="asm/md_SC07_003/nonmatchings", out="docs/progress.md_SC07_003.md"), "md_SC07_004": dict(build="build/md_SC07_004/md_SC07_004", check="config/check.md_SC07_004.sha", src="src/md_SC07_004", asm="asm/md_SC07_004/nonmatchings", out="docs/progress.md_SC07_004.md"), "md_MAIN_003": dict(build="build/md_MAIN_003/md_MAIN_003", check="config/check.md_MAIN_003.sha", src="src/md_MAIN_003", asm="asm/md_MAIN_003/nonmatchings", out="docs/progress.md_MAIN_003.md"), "md_SC03_073": dict(build="build/md_SC03_073/md_SC03_073", check="config/check.md_SC03_073.sha", src="src/md_SC03_073", asm="asm/md_SC03_073/nonmatchings", out="docs/progress.md_SC03_073.md"), "md_SC03_074": dict(build="build/md_SC03_074/md_SC03_074", check="config/check.md_SC03_074.sha", src="src/md_SC03_074", asm="asm/md_SC03_074/nonmatchings", out="docs/progress.md_SC03_074.md"), "md_SC03_075": dict(build="build/md_SC03_075/md_SC03_075", check="config/check.md_SC03_075.sha", src="src/md_SC03_075", asm="asm/md_SC03_075/nonmatchings", out="docs/progress.md_SC03_075.md"), "md_SC03_076": dict(build="build/md_SC03_076/md_SC03_076", check="config/check.md_SC03_076.sha", src="src/md_SC03_076", asm="asm/md_SC03_076/nonmatchings", out="docs/progress.md_SC03_076.md"), "md_SC03_077": dict(build="build/md_SC03_077/md_SC03_077", check="config/check.md_SC03_077.sha", src="src/md_SC03_077", asm="asm/md_SC03_077/nonmatchings", out="docs/progress.md_SC03_077.md"), "md_SC03_078": dict(build="build/md_SC03_078/md_SC03_078", check="config/check.md_SC03_078.sha", src="src/md_SC03_078", asm="asm/md_SC03_078/nonmatchings", out="docs/progress.md_SC03_078.md"), "md_SC03_079": dict(build="build/md_SC03_079/md_SC03_079", check="config/check.md_SC03_079.sha", src="src/md_SC03_079", asm="asm/md_SC03_079/nonmatchings", out="docs/progress.md_SC03_079.md"), "md_SC03_132": dict(build="build/md_SC03_132/md_SC03_132", check="config/check.md_SC03_132.sha", src="src/md_SC03_132", asm="asm/md_SC03_132/nonmatchings", out="docs/progress.md_SC03_132.md"), "md_SC03_133": dict(build="build/md_SC03_133/md_SC03_133", check="config/check.md_SC03_133.sha", src="src/md_SC03_133", asm="asm/md_SC03_133/nonmatchings", out="docs/progress.md_SC03_133.md"), "md_SC03_134": dict(build="build/md_SC03_134/md_SC03_134", check="config/check.md_SC03_134.sha", src="src/md_SC03_134", asm="asm/md_SC03_134/nonmatchings", out="docs/progress.md_SC03_134.md"), "md_SC03_135": dict(build="build/md_SC03_135/md_SC03_135", check="config/check.md_SC03_135.sha", src="src/md_SC03_135", asm="asm/md_SC03_135/nonmatchings", out="docs/progress.md_SC03_135.md"), "md_SC03_136": dict(build="build/md_SC03_136/md_SC03_136", check="config/check.md_SC03_136.sha", src="src/md_SC03_136", asm="asm/md_SC03_136/nonmatchings", out="docs/progress.md_SC03_136.md"), "md_SC03_137": dict(build="build/md_SC03_137/md_SC03_137", check="config/check.md_SC03_137.sha", src="src/md_SC03_137", asm="asm/md_SC03_137/nonmatchings", out="docs/progress.md_SC03_137.md"), "md_SC03_138": dict(build="build/md_SC03_138/md_SC03_138", check="config/check.md_SC03_138.sha", src="src/md_SC03_138", asm="asm/md_SC03_138/nonmatchings", out="docs/progress.md_SC03_138.md"), "md_SC04_024": dict(build="build/md_SC04_024/md_SC04_024", check="config/check.md_SC04_024.sha", src="src/md_SC04_024", asm="asm/md_SC04_024/nonmatchings", out="docs/progress.md_SC04_024.md"), "md_SC04_025": dict(build="build/md_SC04_025/md_SC04_025", check="config/check.md_SC04_025.sha", src="src/md_SC04_025", asm="asm/md_SC04_025/nonmatchings", out="docs/progress.md_SC04_025.md"), "md_SC04_026": dict(build="build/md_SC04_026/md_SC04_026", check="config/check.md_SC04_026.sha", src="src/md_SC04_026", asm="asm/md_SC04_026/nonmatchings", out="docs/progress.md_SC04_026.md"), "md_SC04_027": dict(build="build/md_SC04_027/md_SC04_027", check="config/check.md_SC04_027.sha", src="src/md_SC04_027", asm="asm/md_SC04_027/nonmatchings", out="docs/progress.md_SC04_027.md"), "md_SC04_028": dict(build="build/md_SC04_028/md_SC04_028", check="config/check.md_SC04_028.sha", src="src/md_SC04_028", asm="asm/md_SC04_028/nonmatchings", out="docs/progress.md_SC04_028.md"), "md_SC04_029": dict(build="build/md_SC04_029/md_SC04_029", check="config/check.md_SC04_029.sha", src="src/md_SC04_029", asm="asm/md_SC04_029/nonmatchings", out="docs/progress.md_SC04_029.md"), "md_SC04_030": dict(build="build/md_SC04_030/md_SC04_030", check="config/check.md_SC04_030.sha", src="src/md_SC04_030", asm="asm/md_SC04_030/nonmatchings", out="docs/progress.md_SC04_030.md"), "md_SC05_023": dict(build="build/md_SC05_023/md_SC05_023", check="config/check.md_SC05_023.sha", src="src/md_SC05_023", asm="asm/md_SC05_023/nonmatchings", out="docs/progress.md_SC05_023.md"), "md_SC05_024": dict(build="build/md_SC05_024/md_SC05_024", check="config/check.md_SC05_024.sha", src="src/md_SC05_024", asm="asm/md_SC05_024/nonmatchings", out="docs/progress.md_SC05_024.md"), "md_SC05_025": dict(build="build/md_SC05_025/md_SC05_025", check="config/check.md_SC05_025.sha", src="src/md_SC05_025", asm="asm/md_SC05_025/nonmatchings", out="docs/progress.md_SC05_025.md"), "md_SC05_026": dict(build="build/md_SC05_026/md_SC05_026", check="config/check.md_SC05_026.sha", src="src/md_SC05_026", asm="asm/md_SC05_026/nonmatchings", out="docs/progress.md_SC05_026.md"), "md_SC05_027": dict(build="build/md_SC05_027/md_SC05_027", check="config/check.md_SC05_027.sha", src="src/md_SC05_027", asm="asm/md_SC05_027/nonmatchings", out="docs/progress.md_SC05_027.md"), "md_SC05_028": dict(build="build/md_SC05_028/md_SC05_028", check="config/check.md_SC05_028.sha", src="src/md_SC05_028", asm="asm/md_SC05_028/nonmatchings", out="docs/progress.md_SC05_028.md"), "md_SC05_029": dict(build="build/md_SC05_029/md_SC05_029", check="config/check.md_SC05_029.sha", src="src/md_SC05_029", asm="asm/md_SC05_029/nonmatchings", out="docs/progress.md_SC05_029.md"), "md_SC02_009": dict(build="build/md_SC02_009/md_SC02_009", check="config/check.md_SC02_009.sha", src="src/md_SC02_009", asm="asm/md_SC02_009/nonmatchings", out="docs/progress.md_SC02_009.md"), "md_MAIN_007": dict(build="build/md_MAIN_007/md_MAIN_007", check="config/check.md_MAIN_007.sha", src="src/md_MAIN_007", asm="asm/md_MAIN_007/nonmatchings", out="docs/progress.md_MAIN_007.md"), "md_MAIN_009": dict(build="build/md_MAIN_009/md_MAIN_009", check="config/check.md_MAIN_009.sha", src="src/md_MAIN_009", asm="asm/md_MAIN_009/nonmatchings", out="docs/progress.md_MAIN_009.md"), "md_SC03_053": dict(build="build/md_SC03_053/md_SC03_053", check="config/check.md_SC03_053.sha", src="src/md_SC03_053", asm="asm/md_SC03_053/nonmatchings", out="docs/progress.md_SC03_053.md"), "md_SC03_054": dict(build="build/md_SC03_054/md_SC03_054", check="config/check.md_SC03_054.sha", src="src/md_SC03_054", asm="asm/md_SC03_054/nonmatchings", out="docs/progress.md_SC03_054.md"), "md_SC03_056": dict(build="build/md_SC03_056/md_SC03_056", check="config/check.md_SC03_056.sha", src="src/md_SC03_056", asm="asm/md_SC03_056/nonmatchings", out="docs/progress.md_SC03_056.md"), # <<< overlays: tools/new_overlay.sh inserts ov_* entries above this line (Phase 13) >>> } MAKEFILE = ROOT / "Makefile" # Per-binary state (BINARY/_cfg/SRCS/ASM_ROOT/OUT/BUILD/CHECK/LINKED_SEGS) is set by # set_binary() so report() can run over MANY binaries in one process (--fleet, Phase 15). BINARY = _cfg = SRCS = ASM_ROOT = OUT = BUILD = CHECK = LINKED_SEGS = None def set_binary(binary): """Point the module globals at 's src/asm/build/check tree + recompute LINKED_SEGS.""" global BINARY, _cfg, SRCS, ASM_ROOT, OUT, BUILD, CHECK, LINKED_SEGS if binary not in BINARIES: sys.exit(f"progress.py: unknown --binary '{binary}' (known: {', '.join(BINARIES)})") BINARY = binary _cfg = BINARIES[binary] # Phase 35 T2: the source dir comes from the Makefile oracle (corpus.src_dir — a twin's is its primary's), the table's # `src` is only a cross-check against it (R33). import corpus as _corpus _src = _corpus.src_dir(binary) if _src != _cfg["src"] and _corpus.twin_of(binary) is None: sys.exit(f"progress.py: {binary}: the Makefile says src dir {_src!r}, this table says {_cfg['src']!r} (R32)") SRCS = sorted((ROOT / _src).glob("*.c")) # every c-segment (src/boot.c, src/800.c, ...) ASM_ROOT = ROOT / _cfg["asm"] # per-segment subdirs (boot/, 800/, ...) OUT, BUILD, CHECK = ROOT / _cfg["out"], ROOT / _cfg["build"], ROOT / _cfg["check"] LINKED_SEGS = linked_subsegs() global _S_INDEX # one directory walk per binary (not a glob per function — 340k globs over the fleet) _S_INDEX = {p.stem: p for p in ASM_ROOT.glob("*/*.s")} if ASM_ROOT.exists() else {} def linked_subsegs(): """Library subsegments swapped to real PsyQ objects at build time = the 5th positional arg of each `psyq_integrate.py` call in the Makefile (e.g. `libcd1,libcd2`). Parsing the Makefile keeps it the SINGLE source of truth: a new library integration adds its psyq_integrate call and its stubs are auto-counted LINKED (no separate manifest to drift). Stubs in these subsegs build byte-identically from the real SDK objects when present, and from the committed asm fallback otherwise (fresh clone) — so they are LINKED regardless of local .run/obj40 state (a property of the project, not the machine). Scoped to the active binary: every psyq_integrate call lives inside the Makefile's `ifeq ($(BINARY),main)` block (PsyQ library linking is the EXE's layout — Phase 8), so only `main` has LINKED subsegs. A second binary (e.g. resident) has none. When a future binary gains its own gated integration, parse that gate here instead of the main-only shortcut.""" # FAIL-CLOSED on an unconfigured module (R32). This function is gated on the module global # BINARY, which set_binary() assigns; imported as a LIBRARY without that call it used to # return an empty set — i.e. "this binary has no linked library subsegs", which for `main` is a # confidently WRONG answer (there are 49). An importer then classifies ~1,300 already- # byte-identical LINKED library stubs as outstanding game-code work. Silent, and exactly the # shape R32 exists for; caught 2026-07-27 by an importer (mine) that hit it. # set_binary() assigns BINARY before calling this, so the CLI path is unaffected. if BINARY is None: raise RuntimeError( "progress.linked_subsegs(): module not configured — call progress.set_binary() " "first. Returning an empty set here would silently claim 'no linked subsegs'.") if BINARY != "main": return set() return _main_linked_segs_from_makefile() def _main_linked_segs_from_makefile(): """The EXE's LINKED subseg names, parsed from the Makefile's psyq_integrate calls — BINARY-independent (P31 S78: the fleet's weighted metric needs main's link state while iterating other binaries).""" if not MAKEFILE.exists(): return set() txt = MAKEFILE.read_text() # stub lists may be passed inline (`libcd1,libcd2`) or via a make var (`$(LIBGTE_STUBS)` for the # long multi-block ones) — collect `NAME := ` defs so either form resolves. mvars = dict(re.findall(r'^(\w+)\s*:=\s*([A-Za-z0-9_,]+)\s*$', txt, re.M)) segs = set() # leading --flag value pairs (Phase 9: --vram-base/--exe/--symbols) precede the 4 positionals # (elf_dir ld_path objdir syms_ld); the 5th positional is the stub list captured below. for m in re.finditer(r'psyq_integrate\.py(?:\s+--\S+\s+\S+)*\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)', txt): arg = m.group(1) vm = re.fullmatch(r'\$\((\w+)\)', arg) if vm: arg = mvars.get(vm.group(1), '') segs.update(s for s in arg.split(',') if re.fullmatch(r'[A-Za-z0-9_]+', s)) return segs _DEDUP_CACHE = None # binary -> set(shared fn names); parsed once (the registry is large at fleet scale) def dedup_members(binary): """Function names matched-once-and-shared via config/dedup.us.yaml for this binary. They are hand-matched byte-identical C, but instantiated from a shared body (a macro in src/shared/), so classify()'s function-definition scan does NOT recognize the macro form — count them REAL via the registry instead (the source of truth for code shares, Phase 11). Returns a set.""" p = ROOT / "config/dedup.us.yaml" if not p.exists(): return set() global _DEDUP_CACHE if _DEDUP_CACHE is None: # parse the registry ONCE, not per --fleet binary (136x) _DEDUP_CACHE = {} try: import yaml sys.path.insert(0, str(ROOT / "tools")) from dedup_integrate import group_members # one parser for both verbose + shorthand forms data = yaml.safe_load(p.read_text()) or {} for g in (data.get("groups") or []): for (b, _v, name) in group_members(g): _DEDUP_CACHE.setdefault(b, set()).add(name) except Exception: _DEDUP_CACHE = {} return _DEDUP_CACHE.get(binary, set()) _S_INDEX = {} # name -> .s path, rebuilt per binary in set_binary() (avoids a glob per function) def find_s(name): """Locate .s in any asm/nonmatchings// subdir (segments: boot, 800, ...).""" return _S_INDEX.get(name) INSTR = re.compile(r'^\s*/\*\s*[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s*\*/\s+[a-z]') def strip_comments(s): """Blank comments AND string/char-literal contents, via the ONE masking oracle (`cdecl._mask`). §134 / R33 (P30 S39). This was a private two-line regex that removed `/*…*/` and `//…` and was NOT string-aware — so a brace or a semicolon inside a C string literal (`printf("}")`, a path like `"a;b"`) was seen as real syntax by the callers below, every one of which is a LINE-SHAPE decision on this function's output: the `{`-vs-`;` scan that separates a definition from a declaration, the `count('{') - count('}')` body-depth walk, and the empty-vs-real body test. A miscount there mis-buckets a function in the fn-count metric. That is the §134 class, which had by this point appeared in SIX tools; the fix is routing every line-shape decision through `cdecl._mask` rather than writing a seventh regex. `_mask` is length-preserving (it blanks rather than deletes), which is strictly better here: the callers compare offsets (`br < sm`) and count characters, and blanking keeps those offsets valid against the ORIGINAL line while removing the false tokens.""" return cdecl._mask(s) def is_data_blob(name): """A .s with a code label (glabel/jlabel) is a function; data-only (dlabel, no code) is a blob.""" p = find_s(name) if p is None: return False txt = p.read_text() return ('glabel' not in txt and 'jlabel' not in txt and 'dlabel' in txt) def asm_is_trivial(name): """True iff the function's asm is exactly {jr, nop} (the empty-no-op shape splat emits void{} for).""" p = find_s(name) if p is None: return None mnem = [] for ln in p.read_text().splitlines(): m = re.match(r'^\s*/\*\s*[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s*\*/\s+([a-z0-9.]+)', ln) if m: mnem.append(m.group(1)) return set(mnem) <= {'jr', 'nop'} and len(mnem) <= 2 SIG = re.compile(r'^\s*[A-Za-z_][\w \t\*]*\b([A-Za-z_]\w*)\s*\(') # A K&R parameter declaration sitting between the signature and the `{`: a bare ` ;` # (optionally an array/pointer). It carries NO parens — that is what distinguishes it from the # continuation line of a wrapped ANSI prototype, which always carries the closing `)`. KR_PARAM = re.compile(r'^\s*[A-Za-z_][\w \t\*]*\s+\**\w+\s*(?:\[[^\]]*\])?\s*;\s*$') _INCLUDED = set() # names defined through a shared-header include site (Phase 35 form), for the dedup fold _SHARED_INC = re.compile(r'#\s*include\s+"((?:\.\./)*shared/[^"]+\.h)"') _SHARED_FN_DEF = re.compile(r'#\s*define\s+SHARED_FN\s+(\w+)') _HDR_DEFS = {} def _shared_header_defs(path): """[(name, empty)] a shared header defines — through share_census.header_defs, the one reader of the include form (R33).""" key = str(path) if key not in _HDR_DEFS: sys.path.insert(0, str(ROOT / "tools")) import share_census _HDR_DEFS[key] = share_census.header_defs(path) return _HDR_DEFS[key] def classify(): real, empty, nonmatching, stubs, blobs, linked, verbatim = [], [], [], [], [], [], [] _INCLUDED.clear() for src in SRCS: lines = src.read_text().split('\n') n = len(lines); i = 0 shared_fn = None # the active `#define SHARED_FN ` (the parameterized include form) while i < n: s = lines[i].strip() # Phase 35 T2 — a shared body INCLUDED at its site is that function's definition in this TU (sotn's shape): # #include "../shared/ov/func_80128EA8.h" -> defines func_80128EA8 (REAL, or EMPTY if the body is empty) # #define SHARED_FN func_80037334 / #include ... / #undef SHARED_FN -> defines the SHARED_FN name # The header's definitions are read by share_census.header_defs (masked: a macro-only header defines nothing). fd = _SHARED_FN_DEF.match(s) if fd: shared_fn = fd.group(1); i += 1; continue if s.startswith('#') and 'undef' in s and 'SHARED_FN' in s: shared_fn = None; i += 1; continue im = _SHARED_INC.match(s) if im: hp = (src.parent / im.group(1)).resolve() for nm, is_empty in _shared_header_defs(hp): if nm == 'SHARED_FN': if not shared_fn: sys.exit(f"progress.py: {src.relative_to(ROOT)}:{i+1}: a SHARED_FN header included with no " f"`#define SHARED_FN ` above it (R43)") nm = shared_fn (empty if is_empty else real).append(nm) _INCLUDED.add(nm) i += 1; continue if s.startswith('#ifdef NON_MATCHING'): # THE `#else` HALF IS LIVE CODE, AND SWALLOWING IT UNDERCOUNTS REAL (P31 S73). # The old form consumed from `#ifdef NON_MATCHING` all the way to `#endif`, so a # function BANKED into the `#else` branch landed in NO bucket at all — not real, # not a stub, invisible, exactly like the K&R and `#if 0` defects documented below # and above. Measured: CdReadStateMachine, CdReadSectorReadyCB and # StreamLoadStateMachine were all byte-identical in the shipped build and counted # as zero, because banking replaces the `#else` INCLUDE_ASM with the real body and # leaves the old attempt in the dead half. # So: consume only the DEAD half, then decide from the LIVE half. If it still holds # an INCLUDE_ASM the function is a NON_MATCHING stub (unchanged accounting); if it # holds anything else, rewind and let the normal scan classify it. dead = [] while i < n and not lines[i].strip().startswith(('#else', '#endif')): dead.append(lines[i]); i += 1 had_else = i < n and lines[i].strip().startswith('#else') i += 1 # step over the `#else` / `#endif` live_start = i live = [] if had_else: while i < n and not lines[i].strip().startswith('#endif'): live.append(lines[i]); i += 1 i += 1 # step over the `#endif` m = re.search(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', '\n'.join(dead + live)) if m and (not had_else or re.search(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', '\n'.join(live))): nonmatching.append(m.group(1)) # still a stub: accounting unchanged continue if had_else and live: i = live_start # a banked body: classify it normally continue # `#if 0` — DEAD CODE, not a match (Phase-28 T5). The scanner knew `#ifdef NON_MATCHING` # but not this, so a `#if 0`-preserved analysis body was read as a live definition AND its # real INCLUDE_ASM stub was counted separately => the SAME function landed in both `real` # and `stubs`. Live case: src/resident/resident.c:868-925 wraps a full # `void func_800D00E4(s32){...}` in `#if 0` (its jtbl dossier) and re-declares the stub at # :926 — inflating resident REAL by 1 and the denominator by 1 (reported 123/146 = 85.62%; # honest 122/145 = 85.5%). Skip the block entirely: dead code is neither matched nor stubbed. if re.match(r'#\s*if\s+0\b', s): depth = 1 i += 1 while i < n and depth: t = lines[i].strip() if re.match(r'#\s*if', t): depth += 1 elif t.startswith('#endif'): depth -= 1 i += 1 continue m = re.match(r'INCLUDE_ASM\("([^"]+)",\s*(\w+)\)', s) if m: seg, fn = m.group(1).rstrip('/').rsplit('/', 1)[-1], m.group(2) if is_data_blob(fn): blobs.append(fn) elif seg in LINKED_SEGS: # subseg swapped to a real PsyQ object (byte-identical) linked.append(fn) else: stubs.append(fn) i += 1; continue if s.startswith('INCLUDE_RODATA'): # Bucket the symbol as a data BLOB rather than skipping it unbucketed: the module # class (S45) emits header rodata (the §154 id word, orphan jtbl carriers) as # per-symbol .s files in nonmatchings/, so they enter _S_INDEX and an unbucketed # skip reads as an R32 COVERAGE DEFECT. Rodata is data by construction. m = re.match(r'INCLUDE_RODATA\("[^"]+",\s*(\w+)\)', s) if m: blobs.append(m.group(1)) i += 1; continue # §265 VERBATIM-ASM BODY — bytes, NOT a decompile, and it needs its OWN bucket (P31 S75). # # A file-scope `__asm__("...")` block that DEFINES a function (`.ent NAME` / `.globl NAME` # + `NAME:`) matches none of the patterns above, so it landed in NO bucket and surfaced as # `UNPLACED (parse hole)` — which is R32 doing its job, but leaves the operator with an # error instead of a number. Counting it REAL would be worse: it is the assembly pasted # into a C string, byte-identical BY CONSTRUCTION and completely unexplained. # # Measured fleet-wide at introduction: 116 such bodies (114 main, 1 md_MAIN_003, # 1 ov_SC05_005), the largest being SaveLoadRoutine at 1,165 instructions. Several main # ones are PsyQ library routines where verbatim is defensible; the point of the bucket is # that the GAP between "byte-identical binary" and "decompiled source" is now visible in # the headline instead of hiding inside REAL or inside a parse hole. if s.startswith('__asm__') and '(' in s: blk, j, depth, opened = [], i, 0, False while j < n: c = lines[j] blk.append(c) depth += c.count('(') - c.count(')') if '(' in c: opened = True j += 1 if opened and depth <= 0: break txt = '\n'.join(blk) # BOTH SPELLINGS, AND REQUIRE THE `\n` TERMINATOR (P31 S75, second cut). # The sources use `".ent\tNAME\n"` AND `".ent NAME\n"`, and a bare fragment # `".ent\t"` also occurs. A pattern with an OPTIONAL `\t` and no terminator matched # the fragment and captured the literal name `t`, while a pattern requiring `\t` # missed every space-spelled block: three of my own counts (116 / 112 / 108) were # all wrong before this was validated against one known-true case of EACH spelling # (SaveLoadRoutine = tab, func_80047D3C = space). True count: 178. names = set(re.findall(r'\.ent(?:\\t|\s)+(\w+)\\n', txt)) if not names: g = set(re.findall(r'\.globl(?:\\t|\s)+(\w+)\\n', txt)) names = g & set(re.findall(r'"(\w+):\\n"', txt)) verbatim.extend(sorted(names)) i = j; continue fm = SIG.match(lines[i]) if fm and '(' in lines[i]: # Definition ({ ... }) vs forward declaration (ends ;)? Scan to the first { or ;. # extern/prototype lines (e.g. `extern s32 CdQueueBusy(void);`) are NOT functions. # # K&R DEFINITIONS (Phase 26 session 8 — this silently erased ~190k banked instructions): # s32 func_8015AE2C(arg0) # s32 arg0; <- a `;` BEFORE the `{` # { ... } # The old scan hit that `;` and classified the function as a forward declaration, so it # landed in NO bucket at all — not REAL, not a stub, invisible. And a K&R def is MANDATORY # whenever a zero-arg engine_core.h thunk calls the function (it must be unprototyped), so # the metric was blind to exactly the shape our own banking recipe requires: the heavy-jr # cores. func_8015AE2C (562x134), func_8015A3C8 (493x132), func_80166994 (369x134) were all # compiled, linked and BYTE-IDENTICAL in the shipped build while counting as zero. # A K&R parameter declaration is a bare ` ;` — no parens (a wrapped ANSI # prototype's continuation carries the `)`), so it is unambiguous to skip over. j = i; kind = None while j < n: c = strip_comments(lines[j]) br = c.find('{'); sm = c.find(';') if br != -1 and (sm == -1 or br < sm): kind = 'def'; break if sm != -1: if j > i and '(' not in c and ')' not in c and KR_PARAM.match(c): j += 1; continue # K&R parameter declaration -> keep scanning for the `{` kind = 'decl'; break j += 1 if kind != 'def': i = j + 1; continue # skip the declaration start = i; depth = 0; opened = False while i < n: c = strip_comments(lines[i]); depth += c.count('{') - c.count('}') if '{' in c: opened = True i += 1 if opened and depth <= 0: break body = '\n'.join(lines[start:i]) a, b = body.index('{'), body.rindex('}') (real if strip_comments(body[a+1:b]).strip() else empty).append(fm.group(1)) continue i += 1 return real, empty, nonmatching, stubs, blobs, linked, verbatim def report(binary, audit=False, write=True): """Classify one binary; write docs/progress..md (if write) + print; return a stats dict (for --fleet aggregation). Single-binary output is byte-for-byte the legacy format.""" set_binary(binary) real, empty, nonmatching, stubs, blobs, linked, verbatim = classify() # Code-shared functions (dedup.us.yaml) are REAL byte-identical matches whose macro-instantiated # form classify() doesn't parse — fold them in (dedup-safe) so the count stays honest (P9). # # ...but ONLY if they are actually instantiated. A member that is STILL an INCLUDE_ASM stub in this # binary is not banked, whatever the registry says — the registry can go stale (a group whose # DEFINE_ macro no longer exists in src/). Counting it REAL while it also sits in `stubs` # double-counts it into `matchable` AND inflates `byteident`. Measured: 532 phantom instances # (Phase 26 session 8 scanner audit). Subtracting `stubs` makes the registry advisory, and the # source tree authoritative — which is the right precedence (P9: only what is in the build counts). # Phase 35 T2: a member instantiated through an INCLUDE site is parsed by classify() itself (it is in `real`/`empty` and in # _INCLUDED); it is still a shared body, so it counts in `shared`. A member parsed as a PRIVATE definition is not shared # (a demacroized copy) — the registry is advisory, the source authoritative, as before. members = dedup_members(BINARY) # Phase 35 T6: an included member whose header body is EMPTY is classified `empty` by the include rule above and must NOT be # folded into `real` as "shared" (pre-T4 the macro site was invisible and an empty shared body counted as real through this # fold; the T6 health run put func_801EF790/func_801EF85C in BOTH buckets — the R32 assertion below caught it). shared = sorted(((members - set(real) - set(stubs)) | (_INCLUDED & members)) - set(empty)) real = sorted(set(real) | set(shared)) # ---- COVERAGE ASSERTION (the rule ratified 2026-07-14: a scanner over the corpus must assert its # own coverage; a silent skip is a DEFECT, not a no-op). Ground truth = every function splat emitted # a .s for. Anything classify() could not place in ANY bucket is a parsing hole, and it is exactly # how the K&R blindness above hid ~190k banked instructions for 26 phases while the byte-gate stayed # green (the gate compiles; this tool only reads text — they share no code, so the gate can never # catch a miscount). Report it LOUDLY rather than silently under-reporting progress. placed = (set(real) | set(empty) | set(nonmatching) | set(stubs) | set(blobs) | set(linked) | set(verbatim)) unplaced = sorted(set(_S_INDEX) - placed) # OVER-coverage is the DUAL defect, and the assertion above is blind to it (Phase-28 T5). `placed` # is a set union, so it catches a function in NO bucket — but `matchable` below summed len()s, so a # function in TWO buckets was counted twice and nothing complained. Not hypothetical: the `#if 0` # hole (fixed above) put func_800D00E4 in BOTH `real` and `stubs`, reporting the resident as # 123/146 = 85.62% when the truth is 122/145 = 85.5% — and the flag-plant target is that very # denominator. R32 means assert coverage in BOTH directions: nothing missing, nothing counted twice. _buckets = {"real": real, "empty": empty, "nonmatching": nonmatching, "stubs": stubs, "linked": linked, "verbatim": verbatim} _seen = {} for _name, _b in _buckets.items(): for _fn in _b: _seen.setdefault(_fn, []).append(_name) _multi = {f: bs for f, bs in _seen.items() if len(bs) > 1} assert not _multi, ("progress.py: %d function(s) landed in MULTIPLE buckets — a MISCOUNT, not a " "no-op (R32): %s" % (len(_multi), dict(list(_multi.items())[:5]))) matchable = len(set(real) | set(empty) | set(nonmatching) | set(stubs) | set(linked) | set(verbatim)) # SET, not a len() sum # VERBATIM counts as byte-identical (it is, by construction) but NEVER as REAL. byteident = len(set(real) | set(linked) | set(empty) | set(verbatim)) out = [] out.append("# BFM matching progress (generated by tools/progress.py — authoritative)") out.append("") out.append(f"REAL substantive matches : {len(real):5d} <- the Gen1-exit >=25 bar counts THIS") if shared: out.append(f" (of which dedup-shared : {len(shared):5d} one body -> N sites, config/dedup.us.yaml)") out.append(f"LINKED real PsyQ objects : {len(linked):5d} <- byte-identical via linked SDK objects") if verbatim: out.append(f"VERBATIM __asm__ bodies : {len(verbatim):5d} <- §265: BYTES, NOT A DECOMPILE " f"(byte-identical by construction, unexplained; NOT counted in REAL)") out.append(f"NON_MATCHING (near-miss) : {len(nonmatching):5d}") out.append(f"splat-auto empty no-ops : {len(empty):5d}") out.append(f"INCLUDE_ASM stubs : {len(stubs):5d}") out.append(f"data blobs (excluded) : {len(blobs):5d}") if unplaced: out.append(f"!! UNPLACED (parse hole) : {len(unplaced):5d} <- classify() could not bucket these; " f"a SILENT SKIP is a defect, not a no-op. e.g. {', '.join(unplaced[:4])}") print(f"[progress] COVERAGE DEFECT in {binary}: {len(unplaced)} function(s) splat emitted a .s for " f"are in NO bucket — the count is WRONG. e.g. {', '.join(unplaced[:6])}", file=sys.stderr) out.append("-" * 40) out.append(f"matchable functions : {matchable:5d}") out.append(f"REAL / matchable : {len(real)} / {matchable} = {100*len(real)/matchable:.2f}%") out.append(f"byte-identical/ matchable: {byteident} / {matchable} = {100*byteident/matchable:.2f}% (REAL+LINKED+empties+VERBATIM)") out.append("") out.append("LINKED subsegs: " + " ".join(sorted(LINKED_SEGS)) + f" ({len(linked)} fns)") out.append("REAL matches: " + " ".join(sorted(real))) out.append("NON_MATCHING: " + " ".join(sorted(nonmatching))) if BUILD.exists(): h = hashlib.sha1(BUILD.read_bytes()).hexdigest() want = CHECK.read_text().split()[0] if CHECK.exists() else "" out.append("") out.append(f"build SHA1: {h} ({'byte-identical' if h == want else 'MISMATCH'})") if audit: bad = [n for n in empty if asm_is_trivial(n) is False] out.append("") out.append(f"empties audit: {len(empty)-len(bad)}/{len(empty)} genuine jr;nop" + (f" !! SUSPICIOUS: {bad}" if bad else " (all clean)")) if write: text = "\n".join(out) + "\n" print(text, end="") OUT.write_text(text) return dict(binary=binary, real=len(real), shared=len(shared), linked=len(linked), nonmatching=len(nonmatching), empty=len(empty), stubs=len(stubs), blobs=len(blobs), matchable=matchable, byteident=byteident) def _sig_binary(sigpath): """Map a sig filename to its corpus/build binary name. main's sig is historically named after the EXE (sig.SLUS_007.26.jsonl); overlays/resident already match.""" b = os.path.basename(sigpath).split("sig.")[1][:-6] return "main" if b == "SLUS_007.26" else b def _main_linked_ranges(): """[(vram_lo, vram_hi)] of main's LINKED subsegs, derived from the Makefile's psyq_integrate stub lists (LINKED_SEGS) and the splat yaml's subseg rows (vram = fileoff + (segment vram - segment start)). P31 S78 (R33): the game-code denominator must follow the LIVE link state, not a sig's snapshot.""" import yaml as _yaml y = _yaml.safe_load(open(ROOT / "config/splat.us.exe.yaml")) out, rows, vb = [], [], None for seg in y["segments"]: if isinstance(seg, dict) and "subsegments" in seg: vb = int(seg["vram"]) - int(seg["start"]) for r in seg["subsegments"]: if isinstance(r, list) and len(r) >= 3: rows.append((int(r[0]), str(r[2]))) elif isinstance(r, list) and len(r) == 1: rows.append((int(r[0]), None)) rows.sort() linked = _main_linked_segs_from_makefile() for i, (off, name) in enumerate(rows): if name in linked and i + 1 < len(rows): out.append((off + vb, rows[i + 1][0] + vb)) return out def weighted_metrics(): """Instruction/byte-weighted matching % from the committed sigs (.run/sig.*.jsonl) + the DERIVED stub oracle (corpus.stubs, R33 — NOT a func_-only regex, which missed curated-named stubs and, for main, would have globbed the nonexistent src/SLUS_007.26/ and reported main 100% matched). Against EXECUTABLE CODE only; MIPS instrs are 4 bytes so instruction% == byte%: - fleet : sum(matched nins) / sum(total nins) over resident + every overlay (shared engine counted PER-OVERLAY) — the decomp.dev/frogress per-binary aggregate DISPLAY number. - dedup : each distinct h_exact class ONCE, nins-weighted — the 'distinct RE' number. - main : the main EXE's GAME-CODE weighted %, reported SEPARATELY (Phase-27 T10). main is not folded into `fleet` because its only sig today is a Ghidra sig (different provenance from the overlays' sig_image sigs) that EXCLUDES the LINKED PsyQ objects (Ghidra never analysed them) — so it is a clean game-code-only number, but a stale one (see main_sig_date), and folding a stale/incomplete value into the decomp.dev-comparable headline would mislead. A fresh/complete main sig (sig-refresh, or the deferred sig_image-on-main oracle — docs/second-oracle.md) makes it authoritative and foldable. Returns a dict, or None if no sigs.""" import os, glob, json sys.path.insert(0, str(ROOT / "tools")) import corpus paths = sorted(glob.glob(str(ROOT / ".run/sig.ov_*.jsonl")))\ + sorted(glob.glob(str(ROOT / ".run/sig.md_*.jsonl"))) # S44: module class rp = ROOT / ".run/sig.resident.jsonl" if rp.exists(): paths.append(str(rp)) if not paths: return None def stub_addrs(binary): # FAIL LOUD (R32/R35). This was `except Exception: return set()` — and an empty stub set does # not mean "no stubs", it means "the oracle could not answer": `matched = sig − stubs` then # credits EVERY function in that binary as banked. Byte-witnessed during P30 S1e: running # this metric in a tree with no `asm/` made corpus.stubs raise its (correct, coverage-asserted) # CorpusError for all 140 binaries, the swallow turned each into an empty set, and the tool # cheerfully reported **instr-weighted 100.00% / distinct-code 100.00%** — a 100%-complete # decomp, from a swallowed error. corpus.stubs is deliberately fail-closed ("it refuses to # answer rather than guess 'banked'"); wrapping it in a bare except reinstated exactly the # guess it refuses to make. A loud failure nobody counts is as invisible as a silent one # (R32) — so here it is neither swallowed nor merely logged: it aborts the metric. return set(corpus.stubs(binary)) # {addr:int -> Stub}; the derived INCLUDE_ASM set (R33) fm = ft = 0 cls_nins, matched_cls = {}, set() per_bin = {} # P33 D3: binary -> [matched ins, total ins] (the objdiff units) for p in paths: b = _sig_binary(p) st = stub_addrs(b) bm = bt = 0 for line in open(p): r = json.loads(line) a, n, hx = int(r["addr"], 16), r["nins"], r["h_exact"] ft += n; bt += n cls_nins[hx] = n # h_exact-identical -> identical nins if a not in st: # non-stub == matched (fleet is byte-identical) fm += n; bm += n matched_cls.add(hx) per_bin[b] = [bm, bt] ut = sum(cls_nins.values()) um = sum(n for hx, n in cls_nins.items() if hx in matched_cls) # main — game-code only. P33 A2: the sig is the BUILD-DERIVED one (`make sig-main` → # .run/sig.main.jsonl: every game-code object's functions at build-true lengths, from the link map # + the objects' own symbols; Ghidra-free, splat-free, regenerable on a public clone). The # 2026-08-05 Ghidra sig (.run/sig.SLUS_007.26.jsonl) is the legacy fallback. NO sig at all is an # ERROR (R32): this block used to set mt=0 and carry on, which made the digest read "217 binaries" # and MAIN 0/0 on any machine without the Ghidra file — a public clone would have published a # headline that quietly omitted the EXE. mm = mt = 0 mp = ROOT / ".run/sig.main.jsonl" main_prov = "build-derived sig (make sig-main)" if not mp.exists(): mp = ROOT / ".run/sig.SLUS_007.26.jsonl" main_prov = "LEGACY Ghidra sig" if not mp.exists(): raise SystemExit("progress: no main sig — run `make check BINARY=main && make sig-main` " "(.run/sig.main.jsonl); the fleet digest is not written without the EXE (R32)") import datetime main_date = datetime.date.fromtimestamp(mp.stat().st_mtime).isoformat() mst = stub_addrs("main") # P31 S78: exclude LINKED subsegs LIVE (R33 — derive from the Makefile stub lists + the yaml # ranges), not by trusting the sig's generation-time exclusion. When 13 "game code" subsegs # became libgte23..30/libgs7/snd10/snd11 the 2026-08-05 sig still carried their 2,907 ins, # which then read as unmatched game code (59.8% -> 56.1% with no game-code change). lr = _main_linked_ranges() for line in open(mp): r = json.loads(line) a, n = int(r["addr"], 16), r["nins"] if n == 0: continue # GTE thunks / no-body if any(lo <= a < hi for lo, hi in lr): continue # LINKED PsyQ object — not game code mt += n if a not in mst: mm += n # MAIN IS NOW IN THE WEIGHTED DENOMINATORS (roadmap §1 metrics contract, 2026-07-22). # The contract requires all three headline metrics to include the main EXE; it had been reported # as a separate provisional line since Phase-27 T10, which meant the headline silently measured # 139 of 140 binaries and flattered itself by omitting the least-decompiled one. # # What the caveat actually is (restated precisely, because "stale" was misleading): main's sig # comes from Ghidra (2026-06-14), and function BOUNDARIES derive from the original bytes, which # do not change — matched-vs-stub comes from the LIVE corpus.stubs. So the numbers do not drift. # The real limitation is R34: `sig_image` cannot independently validate a PS-X EXE's boundaries, # so main has no SECOND, DISAGREEING oracle for the PHANTOM/TRUNCATED class (closed P31 S77: # `make sig-main-oracle`). # # CORRECTION (P31 S78): this block used to claim "the sig also EXCLUDES the LINKED PsyQ objects". # It did not. The 2026-08-05 sig carried every Ghidra function including the ~31,000 ins of linked # SDK objects, whose INCLUDE_ASM stub records read as UNMATCHED game code — so main's game-code # weighted % was reported as 59.8% (47,521 / 79,510) when the game-code denominator is 48,537 ins # and the honest figure was 91.8%. The LINKED exclusion is now applied LIVE above # (_main_linked_ranges, R33/R35); the remainder 48,537 − 44,562 = 3,975 ins equals the sum of # main's open stubs in `frontier_classify` (a known-true-case check). fleet_m_all, fleet_t_all = fm + mm, ft + mt dedup_m_all, dedup_t_all = um + mm, ut + mt # main's fns are unique — no h_exact sharing per_bin["main"] = [mm, mt] return dict(fleet_m=fleet_m_all, fleet_t=fleet_t_all, per_bin=per_bin, fleet_pct=(100 * fleet_m_all / fleet_t_all if fleet_t_all else 0.0), dedup_m=dedup_m_all, dedup_t=dedup_t_all, dedup_pct=(100 * dedup_m_all / dedup_t_all if dedup_t_all else 0.0), nbins=len(paths) + (1 if mt else 0), dedup_fns=len(matched_cls), dedup_total_fns=len(cls_nins), fleet_m_exmain=fm, fleet_t_exmain=ft, fleet_pct_exmain=(100 * fm / ft if ft else 0.0), main_m=mm, main_t=mt, main_pct=(100 * mm / mt if mt else 0.0), main_sig_date=main_date, main_sig_prov=main_prov) def main_oracle_line(): """main's independent-oracle verdict, DERIVED at render time (P33 A2). The fleet digest used to carry the literal "0 phantom, 0 truncated, 1 explained pad-tail", which went stale the moment the last pad-tail function was banked (S85) — a published number nobody was computing (R51).""" sys.path.insert(0, str(ROOT / "tools")) import corpus if not corpus.sig_is_independent("main"): return "oracle sig absent — run `make sig-main-oracle`" r = corpus.audit("main") return f"{len(r['phantom'])} phantom, {len(r['truncated'])} truncated, {len(r['pad_tail'])} pad-tail" def fleet(): """Aggregate every binary into docs/progress.fleet.md — DETERMINISTIC (source-derived from the committed src/*.c + config/dedup.us.yaml). The live byte-identity gate is `make check-all`; collapsible-byte leverage lives in docs/duplicates.cross.md. Rows: main, resident, overlays sorted.""" order = [b for b in ("main", "resident") if b in BINARIES] + \ sorted(b for b in BINARIES if b not in ("main", "resident")) rows = [report(b, write=False) for b in order] tot = lambda k: sum(r[k] for r in rows) REAL, SHARED, LINKED, EMPTY, NM, STUBS, MATCH, BYTE = (tot('real'), tot('shared'), tot('linked'), tot('empty'), tot('nonmatching'), tot('stubs'), tot('matchable'), tot('byteident')) # dedup registry totals (deterministic from the committed yaml) ngroups = nmembers = 0 gs = [] dp = ROOT / "config/dedup.us.yaml" if dp.exists(): try: import yaml from dedup_integrate import group_members # expands verbose + shorthand alike gs = (yaml.safe_load(dp.read_text()) or {}).get("groups") or [] ngroups = len(gs); nmembers = sum(1 for g in gs for _ in group_members(g)) except Exception: pass wm = weighted_metrics() head = ["# BFM FLEET matching progress (generated by tools/progress.py --fleet — authoritative)", f"# {len(rows)} binaries: main + resident + {len(rows)-2} location overlays. DETERMINISTIC,", "# source-derived (committed src/*.c + config/dedup.us.yaml). Live byte gate: `make check-all`;", "# cross-binary collapsible-byte leverage: docs/duplicates.cross.md.", "", "# THREE progress metrics (all matter — see the labels):", f"FLEET fn-count byte-ident: {BYTE:6d} / {MATCH} = {100*BYTE/MATCH:.2f}% (REAL+LINKED+empties; FUNCTION-count, ×134-inflated — one crack counts per overlay)"] if wm: head += [ f"FLEET instr-weighted : {wm['fleet_m']:7d} / {wm['fleet_t']} = {wm['fleet_pct']:.1f}% (shipped .text across main + resident + {wm['nbins']-2} overlays; the decomp.dev-DISPLAY number)", f"FLEET distinct-code(uniq): {wm['dedup_m']:7d} / {wm['dedup_t']} = {wm['dedup_pct']:.1f}% ({wm['dedup_fns']}/{wm['dedup_total_fns']} unique fns; the DISTINCT-RE number)"] if wm.get('main_t'): head += [ f"MAIN game-code weighted : {wm['main_m']:7d} / {wm['main_t']} = {wm['main_pct']:.1f}% (INCLUDED in the fleet numbers above since 2026-07-22 — roadmap §1 metrics contract; {wm['main_sig_prov']} dated {wm['main_sig_date']}; boundaries INDEPENDENTLY VERIFIED since P31 S77 — `make sig-main-oracle` + `make audit-corpus`: {main_oracle_line()})", f" (fleet EXCLUDING main, for continuity with pre-2026-07-22 readings: {wm['fleet_m_exmain']} / {wm['fleet_t_exmain']} = {wm['fleet_pct_exmain']:.1f}%)"] else: head += ["# (instr-weighted + distinct-code metrics need .run/sig.*.jsonl — run `make sig-overlays`)"] head += ["", f"FLEET REAL substantive : {REAL:6d} (of which dedup-shared {SHARED} via {ngroups} groups / {nmembers} instances)", f"FLEET LINKED PsyQ objs : {LINKED:6d}", f"FLEET NON_MATCHING : {NM:6d} (0 in any default build — G4)", f"FLEET INCLUDE_ASM stubs : {STUBS:6d}", f"FLEET matchable : {MATCH:6d}", ""] table = ["| binary | REAL | shared | LINKED | byte-ident | matchable | byte-ident % |", "|---|---:|---:|---:|---:|---:|---:|"] for r in rows: pct = (100 * r['byteident'] / r['matchable']) if r['matchable'] else 0.0 table.append(f"| {r['binary']} | {r['real']} | {r['shared']} | {r['linked']} | " f"{r['byteident']} | {r['matchable']} | {pct:.1f}% |") text = "\n".join(head + table) + "\n" print("\n".join(head[:-1])) # the summary block (drop the trailing blank) (ROOT / "docs/progress.fleet.md").write_text(text) # P33 D1/D3: the same numbers as DATA (docs/progress.json, README block, badges, frogress/objdiff feeds) nverb = 0 vp = ROOT / "config/verbatim_manifest.json" if vp.exists(): try: import json as _json vm = _json.loads(vp.read_text()) _vrows = vm.get("rows", vm) if isinstance(vm, dict) else vm # the hand-asm bodies kept verbatim = the PERMANENT rows (P36 T1b: a DECOMPILE-* row is work, not a kept body); Sony's # routines carried as asm where no SDK object matched are counted apart (SDK-VERBATIM) nverb = sum(1 for r in _vrows if r.get("disposition", "PERMANENT-VERBATIM") == "PERMANENT-VERBATIM") nsdkverb = sum(1 for r in _vrows if r.get("disposition") == "SDK-VERBATIM") except Exception: nverb = 0 nsdkverb = 0 _census = {} try: import json as _j _cj = ROOT / ".run/P35/census/share_census.json" if _cj.exists(): _cjd = _j.loads(_cj.read_text()) _census = _cjd.get("same_vram_unregistered", {}) _census["deferred_texts"] = _cjd.get("same_address_text_duplicates_deferred", {}) except Exception: _census = {} # Phase 36 T1: the lever census (tools/lever_census.py) — compiler-forcing constructs left in the C; published until 0 (R75) _lev = None try: import json as _jl _lj = ROOT / ".run/P36/census/lever_census.json" if _lj.exists(): _ld = _jl.loads(_lj.read_text()) _ab, _cl, _vb = _ld["levers_AB"], _ld["classes"], _ld.get("verbatim_in_function", {}) _lev = {"date": _ld.get("generated"), "what": "compiler-forcing constructs left in the C: register pins + asm statements (the classes Phase 36 drives to 0; " "GTE coprocessor ops and whole-body hand-asm routines excluded), with the volatile/register qualifiers censused", "sites": _ab["sites"], "pins": _ab["pins"], "asm_statements": _ab["asm"], "bodies": _ab["bodies"], "distinct_bodies": _ab["distinct_bodies"], "marked_fake": _ab["marked"], "unmarked": _ab["unmarked"], "volatile_sites": _cl["C"]["sites"], "register_keyword_sites": _cl["D"]["sites"], "gte_sites": _ld["asm"]["kinds"].get("gte/direct", 0) + _ld["asm"]["kinds"].get("gte/via-macro", 0), "hand_asm_routines_in_c_shells": _vb.get("routines"), "hand_asm_routine_sites": _vb.get("sites")} except Exception: _lev = None # Phase 37 T1: the type census (tools/type_census.py) — the struct debt, the declaration debt and the struct map, published until 0 (R75) _typ = None try: import json as _jt _tj = ROOT / ".run/P37/census/type_census.json" if _tj.exists(): _ts = _jt.loads(_tj.read_text())["summary"] _d, _c, _dc, _sm = _ts["definitions"], _ts["casts"], _ts["decls"], _ts["struct_map"] _typ = {"date": _ts.get("when", "")[:10], "head": _ts.get("head"), "what": "the structs phase's counters: raw pointer-cast dereferences in four forms (P *(T*)(…), I *(T*)ident, X ((T*)e)[i], M M2C_FIELD), " "struct definitions and their duplicate classes, lying call declarations, and the struct map's types (Phase 37 drives the first three to 0)", "raw_cast_sites": _c["deref_total"], "raw_cast_forms": _c["by_form"], "raw_cast_bodies": _c["bodies"], "address_of_sites": _c["addr_form"], "typed_cast_member_sites": _c["typed_cast_member"], "absolute_address_casts": _c["abs_casts"], "struct_definitions": _d["total"], "canonical_definitions": _d["canon"], "definitions_in_c_files": _d["in_c_file_scope"] + _d["in_c_block_scope"], "distinct_layouts": _d["distinct_layouts"], "duplicate_classes": _d["dup_layout_classes"], "duplicate_names": _d["dup_layout_names"], "variant_names": _d["variant_names"], "dead_canonical_names": _d["dead_canon_names"], "lying_declarations": _dc["lying"], "lying_callees": _dc["lying_callees"], "asm_label_aliases": _dc["asm_label_aliases"], "multi_spelled_callees": _dc["multi_spelled_callees"], "struct_map_types": _sm["types"], "struct_map_explained_pct": _sm["explained_pct"]} except Exception: _typ = None # Phase 35 T5b: the TEXT tier — groups keyed by one normalized text at one address whose bytes vary per binary (verbose members) _tg = [g for g in gs if g.get("tier") == "h_text"] return { "schema": 1, "note": "generated by tools/progress.py --fleet/--json from the committed sources — every number carries its denominator; regenerate, never edit", "binaries": len(rows), "fleet": { "fn_count": {"matched": BYTE, "total": MATCH, "pct": round(100 * BYTE / MATCH, 2) if MATCH else 0.0, "what": "functions byte-identical / matchable, every binary (one shared body counts once per overlay it is instantiated in)"}, "instr_weighted": ({"matched": wm['fleet_m'], "total": wm['fleet_t'], "pct": round(wm['fleet_pct'], 1), "what": "shipped .text instructions across all binaries (the decomp.dev-style display number)"} if wm else None), "distinct_code": ({"matched": wm['dedup_m'], "total": wm['dedup_t'], "pct": round(wm['dedup_pct'], 1), "unique_fns_matched": wm['dedup_fns'], "unique_fns_total": wm['dedup_total_fns'], "what": "each distinct function body counted once, instruction-weighted (the distinct reverse-engineering number)"} if wm else None), "main_game_code": ({"matched": wm['main_m'], "total": wm['main_t'], "pct": round(wm['main_pct'], 1), "what": "SLUS_007.26 game-code instructions (excludes the linked Sony PsyQ objects); build-derived boundaries"} if wm and wm.get('main_t') else None), }, "counts": {"real_c_functions": REAL, "dedup_shared_of_real": SHARED, "linked_psyq_objects": LINKED, "verbatim_asm_bodies": nverb, "sdk_verbatim_bodies": nsdkverb, "include_asm_stubs": STUBS, "non_matching": NM, "matchable": MATCH, "dedup_groups": ngroups, "dedup_instances": nmembers, # Phase 35 T7 — "one source per unique function": bodies written ONCE in C = every instantiated function (REAL + EMPTY) # minus the shared instances plus their one header each (derived from this run's own totals, R33); the duplicate # copies still in source come from the census (.run/P35/census/share_census.json — every one is ledgered, S1) "unique_function_bodies": REAL + EMPTY - nmembers + ngroups, "duplicate_source_copies": _census.get("copies"), "duplicate_source_classes": _census.get("classes"), "text_tier_groups": len(_tg), "text_tier_instances": sum(len(g.get("members") or []) for g in _tg), "deferred_same_address_texts": (_census.get("deferred_texts") or {}).get("texts"), "deferred_same_address_copies": (_census.get("deferred_texts") or {}).get("sites"), "levers": _lev, "types": _typ}, "corrections": [ # dated snapshots of published-number moves with their cause and the command that shows them (R75) {"date": "2026-09-08", "phase": "P35 T2", "metric": "fn_count.total", "delta": 7, "cause": "seven overlays including the whale header but never registered were counted by neither classify nor the registry fold", "command": "tools/progress.py --fleet"}, {"date": "2026-09-08", "phase": "P35 T6", "metric": "fn_count.total", "delta": 459, "cause": "macro sites invisible to the classifier became includes at T4: ov_SC03_015's 219 single-site macros, ov_SC03_118's 3, and the never-extended members of five under-listed groups", "command": "tools/progress.py --fleet"}, {"date": "2026-09-08", "phase": "P35 T6", "metric": "counts.real_c_functions", "delta": -10211, "cause": "empty-bodied shared functions were folded into REAL under the macro form; the include form classifies them EMPTY (the instruction-weighted metrics are unchanged)", "command": "tools/progress.py --fleet"}, {"date": "2026-09-09", "phase": "P36 T1b", "metric": "counts.verbatim_asm_bodies", "delta": 22, "cause": "the §265 lane's IN-FUNCTION form (a whole routine as one __asm__ statement inside a C shell) was invisible to the file-scope detector behind the manifest: 22 scratchpad stack-switch trampolines (one private copy per overlay, 2,682 sites) are hand asm and are listed PERMANENT-VERBATIM; a 23rd asm-body is -O0 compiler output and is listed DECOMPILE-NOW", "command": "tools/lever_census.py; tools/verbatim_check.py"}, {"date": "2026-09-12", "phase": "P37 T1", "metric": "counts.types.raw_cast_sites", "delta": 91166, "cause": "the readability series' regex counted one cast form (`*(T *)(`: 411,850 over raw text); the type census counts four dereference forms over comment-masked text — P 409,007 + I 60,666 (`*(T *)ident`, `*(T *)&D_x`) + X 13,800 (`((T *)e)[i]`) + M 19,543 (`M2C_FIELD`) = 503,016 — with a coverage assertion per form; 18,912 address-of casts and 36,681 typed cast-member accesses are counted apart", "command": "tools/type_census.py"}, {"date": "2026-09-12", "phase": "P37 T1", "metric": "counts.levers (--strict)", "delta": 6717, "cause": "lever_census --strict now reads its own words: 6,717 direct GTE coprocessor asm statements in function bodies (Sony's idiom spelled inline instead of as the header's macro call; kinds gte/gte-unsigned, incl. func_8013D9B0's 268 unsigned respellings) are asm statements outside the GTE header — the P36 headline (--check, 4,010) keeps its definition for the series' continuity", "command": "tools/lever_census.py --check --strict"}], "per_binary": [{"binary": r['binary'], "real": r['real'], "shared": r['shared'], "linked": r['linked'], "byte_identical": r['byteident'], "matchable": r['matchable'], "instr_matched": (wm['per_bin'].get(r['binary'], [None, None])[0] if wm else None), "instr_total": (wm['per_bin'].get(r['binary'], [None, None])[1] if wm else None)} for r in rows], } def badge(label, message, pct): color = "brightgreen" if pct >= 99.95 else "green" if pct >= 75 else "yellow" if pct >= 50 else "orange" return {"schemaVersion": 1, "label": label, "message": message, "color": color} def badge_files(d): """{name: json text} for docs/badges/*.json in the shields.io endpoint format (README: img.shields.io/endpoint?url=…). Names are fixed: the README references exactly these files.""" import json as _json f, c = d["fleet"], d["counts"] ok_bins = sum(1 for r in d["per_binary"] if r["matchable"] and r["byte_identical"] == r["matchable"]) files = { "fleet_instr.json": badge("matched (instructions)", f"{f['instr_weighted']['pct']:.1f}%", f['instr_weighted']['pct']) if f['instr_weighted'] else None, "fleet_fn.json": badge("matched (functions)", f"{f['fn_count']['pct']:.2f}%", f['fn_count']['pct']), "distinct.json": badge("distinct code matched", f"{f['distinct_code']['pct']:.1f}%", f['distinct_code']['pct']) if f['distinct_code'] else None, "binaries.json": badge("byte-identical binaries", f"{ok_bins}/{d['binaries']}", 100.0 * ok_bins / d['binaries']), } return {n: _json.dumps(v) + "\n" for n, v in files.items() if v is not None} def write_badges(d): bd = ROOT / "docs/badges"; bd.mkdir(exist_ok=True) files = badge_files(d) for name, text in files.items(): (bd / name).write_text(text) return sorted(files) README_BEGIN, README_END = "", "" def readme_block(d): """The README's generated numbers block, from progress.json (D1): a table WITH denominators (R41).""" f, c = d["fleet"], d["counts"] rows = [("Functions byte-identical (all binaries)", f["fn_count"]), ("Instructions byte-identical (instruction-weighted)", f["instr_weighted"]), ("Distinct code byte-identical (each body once)", f["distinct_code"]), ("Main executable game code (excl. Sony objects)", f["main_game_code"])] out = [README_BEGIN, "| Metric | Matched | Of | % |", "|---|---:|---:|---:|"] for name, m in rows: if m: out.append(f"| {name} | {m['matched']:,} | {m['total']:,} | {m['pct']:.1f}% |" if name.startswith("Functions") is False else f"| {name} | {m['matched']:,} | {m['total']:,} | {m['pct']:.2f}% |") out += ["", f"{d['binaries']} binaries rebuild byte-identical from source · {c['real_c_functions']:,} functions in C " f"({c['dedup_shared_of_real']:,} of them shared bodies via {c['dedup_groups']:,} dedup groups) · " f"{c['linked_psyq_objects']:,} Sony PsyQ library functions linked from the SDK objects, not our C · " f"{c['verbatim_asm_bodies']} hand-written-assembly bodies kept verbatim" + (f" · {c['sdk_verbatim_bodies']} Sony library routines carried as assembly where no SDK object matched" if c.get('sdk_verbatim_bodies') else "") + f" · {c['include_asm_stubs']} assembly stubs left · " f"{c['non_matching']} non-matching functions.", "", (f"One source per unique function (Phase 35): {c['unique_function_bodies']:,} function bodies written once in C " f"({c['dedup_groups']:,} of them shared headers instantiated {c['dedup_instances']:,} times); " + (f"{c['duplicate_source_copies']:,} duplicate copies remain in {c['duplicate_source_classes']:,} ledgered classes " f"(declaration conflicts left for the types phase)" if c.get('duplicate_source_copies') is not None else "the duplicate-copy census was not available in this run") + (f"; {c['text_tier_groups']:,} functions whose bytes vary per binary share one text through the text tier ({c['text_tier_instances']:,} sites)" if c.get('text_tier_groups') else "") + (f"; {c['deferred_same_address_copies']:,} same-address copies of {c['deferred_same_address_texts']:,} tiny bodies inside the cross-address classes are deferred to the names phase." if c.get('deferred_same_address_texts') is not None else ".")), ] + ([f"", f"Levers off (Phase 36, snapshot {c['levers']['date']}): {c['levers']['pins']:,} register pins and {c['levers']['asm_statements']:,} " f"asm statements remain in {c['levers']['bodies']:,} functions ({c['levers']['distinct_bodies']:,} distinct bodies), " f"{c['levers']['marked_fake']:,} of them marked `// !FAKE:`; {c['levers']['volatile_sites']:,} volatile qualifiers and " f"{c['levers']['register_keyword_sites']:,} bare `register` keywords are censused; {c['levers']['gte_sites']:,} GTE coprocessor " f"operations and {c['levers']['hand_asm_routines_in_c_shells']} whole-body assembly routines kept inside C shells " f"({c['levers']['hand_asm_routine_sites']:,} sites; the original's scratchpad stack-switch trampolines, one copy per " f"overlay, listed in the verbatim manifest) are not levers and are excluded."] if c.get("levers") else []) + ([f"", f"Types (Phase 37, snapshot {c['types']['date']}): {c['types']['raw_cast_sites']:,} raw pointer-cast dereferences remain in " f"{c['types']['raw_cast_bodies']:,} functions (four forms; {c['types']['typed_cast_member_sites']:,} typed cast-member accesses and " f"{c['types']['address_of_sites']:,} address-of casts counted apart); {c['types']['struct_definitions']:,} struct definitions " f"({c['types']['canonical_definitions']:,} in the canonical header, {c['types']['definitions_in_c_files']:,} inside `.c` files) over " f"{c['types']['distinct_layouts']:,} distinct layouts, {c['types']['duplicate_classes']:,} duplicate classes covering " f"{c['types']['duplicate_names']:,} names; {c['types']['lying_declarations']:,} call declarations narrower than their callee's " f"definition over {c['types']['lying_callees']:,} callees; the struct map clusters the cast sites into {c['types']['struct_map_types']:,} " f"types by evidence ({c['types']['struct_map_explained_pct']} % of sites assigned)."] if c.get("types") else []) + [ "", f"_Generated by `tools/progress.py --readme` from `docs/progress.json` — numbers are never typed by hand._", README_END] return "\n".join(out) def write_json_and_readme(check=False): import json as _json d = fleet() jp = ROOT / "docs/progress.json" text = _json.dumps(d, indent=1) + "\n" if check: if not jp.exists() or jp.read_text() != text: sys.exit("progress.py --json --check: docs/progress.json is STALE — run `make report BINARY=main`") for name, want in badge_files(d).items(): bp = ROOT / "docs/badges" / name if not bp.exists() or bp.read_text() != want: sys.exit(f"progress.py --check: docs/badges/{name} is STALE or missing — run `make report BINARY=main`") else: jp.write_text(text) write_badges(d) rp = ROOT / "README.md" if not rp.exists(): sys.exit("progress.py --readme: README.md missing") rt = rp.read_text() if README_BEGIN not in rt or README_END not in rt: sys.exit(f"progress.py --readme: README.md lacks the {README_BEGIN} … {README_END} markers — refusing (R43)") a, b = rt.index(README_BEGIN), rt.index(README_END) + len(README_END) new = rt[:a] + readme_block(d) + rt[b:] if check: if new != rt: sys.exit("progress.py --readme --check: README.md's progress block is STALE — run `make report BINARY=main`") print("progress.py --check: docs/progress.json + README block are fresh") return if new != rt: rp.write_text(new) print(f"progress.py: docs/progress.json written; README block {'updated' if new != rt else 'unchanged'}") def main(): if '--json' in sys.argv or '--readme' in sys.argv: write_json_and_readme(check='--check' in sys.argv); return if '--fleet' in sys.argv: fleet(); return if '--weighted' in sys.argv: wm = weighted_metrics() if not wm: sys.exit("progress.py --weighted: no .run/sig.*.jsonl — run `make sig-overlays` first.") print("BFM byte-weighted matching progress (executable code only; resident + %d overlays; main EXE excluded):" % (wm['nbins'] - 1)) print(" instr-weighted (fleet, decomp.dev-display): %7d / %d = %.1f%%" % (wm['fleet_m'], wm['fleet_t'], wm['fleet_pct'])) print(" distinct-code (dedup, distinct-RE) : %7d / %d = %.1f%% (%d/%d unique fns)" % (wm['dedup_m'], wm['dedup_t'], wm['dedup_pct'], wm['dedup_fns'], wm['dedup_total_fns'])) return audit = '--audit' in sys.argv check = '--check' in sys.argv binary = next((sys.argv[i + 1] for i, x in enumerate(sys.argv) if x == "--binary" and i + 1 < len(sys.argv)), "main") report(binary, audit=audit, write=True) if check: import subprocess sys.exit(subprocess.run(["make", "-C", str(ROOT), "check"]).returncode) if __name__ == "__main__": main()