diff --git a/tools/aot_overlay_spike/coverage_report.py b/tools/aot_overlay_spike/coverage_report.py index a1cb8f53..804a62b3 100644 --- a/tools/aot_overlay_spike/coverage_report.py +++ b/tools/aot_overlay_spike/coverage_report.py @@ -473,6 +473,27 @@ def group_by_region(addrs): for a in addrs: g[region_of(a)].append(a) return {r: sorted(v) for r, v in sorted(g.items())} + +def candidate_miss_records(misses): + """Stable JSON records for strict content-variant coverage gaps.""" + return [ + {'entry': f'0x{entry | 0x80000000:08X}', + 'code_crc': f'{crc:08X}'} + for entry, crc in sorted( + (norm(entry), crc & 0xFFFFFFFF) for entry, crc in misses) + ] + + +def group_candidate_misses_by_region(misses): + """Group strict ``(entry, code_crc)`` gaps without losing variants.""" + grouped = defaultdict(list) + for entry, crc in sorted(misses): + grouped[region_of(entry)].append((entry, crc)) + return { + f'0x{region | 0x80000000:08X}': candidate_miss_records(values) + for region, values in sorted(grouped.items()) + } + def main(): ap = argparse.ArgumentParser() ap.add_argument('--static', required=True, help='static shard cache dir (.ranges)') @@ -548,6 +569,10 @@ def main(): f'0x{x|0x80000000:08X}' for x in r['range_misses']] report['vault_interval_potential_misses'] = list( report['vault_range_misses']) + report['vault_entry_crc_misses'] = candidate_miss_records( + r['entry_crc_misses']) + report['vault_entry_crc_misses_by_region'] = \ + group_candidate_misses_by_region(r['entry_crc_misses']) combined = None if a.bios_dispatch: combined = recall(set(vault_ent), combined_ent, combined_ranges) @@ -575,13 +600,23 @@ def main(): f'{r.get("needed_entry_crc",0)}** ' f'(**{r.get("recall_entry_crc",0)*100:.1f}%**) ' f'_(cg-version differences can lower this vs address-level recall)_') + if r['entry_crc_misses']: + md.append('#### Exact manifest-candidate gaps by region\n') + for region, records in group_candidate_misses_by_region( + r['entry_crc_misses']).items(): + unique_addresses = len({record['entry'] for record in records}) + md.append(f'- `{region}`: **{len(records)}** missing ' + f'candidate variant(s) at **{unique_addresses}** ' + 'entry address(es)') + md.append('') md.append(f'- **MISSED exact entries: {r["missed"]}**') md.append(f'- **OUTSIDE ALL STATIC INTERVALS: ' f'{r["missed_interval_potential"]}** played entry PCs ' '(potential gaps; interval membership is not dispatch proof)\n') if combined: md.append('### Combined with base recompiled BIOS\n') - md.append(f'- Exact native dispatch entries: **{combined["covered_here"]}** ' + md.append(f'- Exact entry addresses present in overlay/BIOS static ' + f'dispatch sets: **{combined["covered_here"]}** ' f'(**{combined["recall_entry"]*100:.1f}%**)') md.append(f'- Contained in an overlay/BIOS static interval ' f'(unverified potential): ' diff --git a/tools/aot_overlay_spike/test_coverage_report.py b/tools/aot_overlay_spike/test_coverage_report.py index 2dd2f125..1400e21f 100644 --- a/tools/aot_overlay_spike/test_coverage_report.py +++ b/tools/aot_overlay_spike/test_coverage_report.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import os import json +import sys import tempfile import unittest import zlib @@ -59,6 +60,58 @@ const PsxNativeStub psx_bios_native_stubs[1] = { self.assertEqual(result['recall_entry_crc'], 0.5) self.assertEqual(result['entry_crc_misses'], [(0x100, 0xAAAA)]) + def test_exact_candidate_misses_persist_variants_grouped_by_region(self): + misses = [(0xA0001234, 0x1AAAAAAAA), (0x1234, 0xBBBBBBBB), + (0x2450, 0xCCCCCCCC)] + self.assertEqual(coverage_report.candidate_miss_records(misses), [ + {'entry': '0x80001234', 'code_crc': 'AAAAAAAA'}, + {'entry': '0x80001234', 'code_crc': 'BBBBBBBB'}, + {'entry': '0x80002450', 'code_crc': 'CCCCCCCC'}, + ]) + self.assertEqual( + coverage_report.group_candidate_misses_by_region(misses), { + '0x80001000': [ + {'entry': '0x80001234', 'code_crc': 'AAAAAAAA'}, + {'entry': '0x80001234', 'code_crc': 'BBBBBBBB'}, + ], + '0x80002000': [ + {'entry': '0x80002450', 'code_crc': 'CCCCCCCC'}, + ], + }) + + def test_main_persists_and_summarizes_exact_candidate_gaps(self): + with tempfile.TemporaryDirectory() as tmp: + static = os.path.join(tmp, 'static') + vault = os.path.join(tmp, 'vault') + os.makedirs(static) + os.makedirs(vault) + with open(os.path.join(static, 'static.ranges'), 'w', + encoding='utf-8') as out: + out.write('F 80001234 BBBBBBBB\nR 80001234 8\n') + with open(os.path.join(vault, 'vault.ranges'), 'w', + encoding='utf-8') as out: + out.write('F 80001234 AAAAAAAA\nR 80001234 8\n') + out.write('F 80001234 BBBBBBBB\nR 80001234 8\n') + out_md = os.path.join(tmp, 'gaps.md') + out_json = os.path.join(tmp, 'gaps.json') + argv = [ + 'coverage_report.py', '--static', static, '--vault', vault, + '--game', 'TEST', '--out-md', out_md, '--out-json', out_json, + ] + with mock.patch.object(sys, 'argv', argv): + coverage_report.main() + with open(out_json, encoding='utf-8') as source: + report = json.load(source) + with open(out_md, encoding='utf-8') as source: + markdown = source.read() + expected = [{'entry': '0x80001234', 'code_crc': 'AAAAAAAA'}] + self.assertEqual(report['vault_entry_crc_misses'], expected) + self.assertEqual(report['vault_entry_crc_misses_by_region'], { + '0x80001000': expected, + }) + self.assertIn('`0x80001000`: **1** missing candidate variant(s)', + markdown) + def test_interval_containment_does_not_count_as_crc_candidate(self): result = coverage_report.recall( {0x100}, {0x100: {0xBBBB}}, [(0x100, 0x108)],