mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
1351 lines
105 KiB
Python
1351 lines
105 KiB
Python
#!/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 <alias> # 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 <binary>'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(<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 := <comma,list>` 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 <name>.s in any asm/nonmatchings/<seg>/ 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 `<type> <name>;`
|
||
# (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 <name>` (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 <name>` 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 `<type> <name>;` — 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.<binary>.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 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},
|
||
"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"}],
|
||
"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 = "<!-- progress:begin -->", "<!-- progress: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"_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()
|