98 lines
5.7 KiB
Python
98 lines
5.7 KiB
Python
"""Check rendered media, matching captions, and frozen benchmark provenance."""
|
||
import argparse
|
||
import hashlib
|
||
import json
|
||
from pathlib import Path
|
||
|
||
import av
|
||
|
||
HERE = Path(__file__).resolve().parent
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument('--quality', default='final', choices=['preview', 'final'])
|
||
parser.add_argument('--scene', default='SlimeFilm')
|
||
args = parser.parse_args()
|
||
name = f'{args.scene}-{args.quality}'
|
||
report = json.loads((HERE / f'output/{name}.render.json').read_text())
|
||
timeline = json.loads((HERE / f'output/{name}.timeline.json').read_text())
|
||
for source, digest in report['source_sha256'].items():
|
||
assert hashlib.sha256((HERE/source).read_bytes()).hexdigest() == digest, f'changed after render: {source}'
|
||
data = json.loads((HERE/'data/benchmarks.json').read_text())
|
||
for record in data['sources']:
|
||
path = HERE/record['path']
|
||
assert hashlib.sha256(path.read_bytes()).hexdigest() == record['sha256'], f'benchmark source changed: {path}'
|
||
# Compare frozen figure values to the original machine-readable measurements.
|
||
def read_source(path):
|
||
return json.loads((HERE/path).read_text())
|
||
mapping = read_source('../../show/results/latest.json')['mapping']
|
||
assert data['A']['before_ms'] == mapping[0]['cpu']['median_ms']
|
||
assert data['A']['after_ms'] == mapping[0]['gpu']['rows'][0]['scan_median_ms']
|
||
for key, item, modes in (('B', mapping[1], (2, 3)), ('C', mapping[2], (3, 4))):
|
||
rows = {r['mode']: r for r in item['gpu']['rows']}
|
||
assert data[key]['before_ms'] == rows[modes[0]]['kernel_median_ms']
|
||
assert data[key]['after_ms'] == rows[modes[1]]['kernel_median_ms']
|
||
f = read_source('../../research/experiments/full_search_feasibility/results/summary.json')
|
||
e = read_source('../../research/experiments/normal_search/results/summary.json')
|
||
g = read_source('../../research/experiments/normal_search/pipeline_results/summary.json')
|
||
for key, before, after in (
|
||
('D', f['known']['kernel_median_ms']['0'], f['known']['kernel_median_ms']['3']),
|
||
('E', e['random65536']['3'], e['random65536']['7']),
|
||
('P', e['known']['3'], e['known']['4']),
|
||
('G', e['known']['4'], e['known']['7']),
|
||
('F', g['mixed512']['1']['host_ms'], g['mixed512']['3']['host_ms']),
|
||
):
|
||
assert data[key]['before_ms'] == before and data[key]['after_ms'] == after, key
|
||
assert data['ROOT']['roots'] == e['known']['info']['jobs']
|
||
assert data['ROOT']['lower35'] == e['known']['info']['lower35']
|
||
assert data['ROOT']['difference_survivors'] == e['metadata']['cpu_validation']['known_filter_survivors']
|
||
assert len(e['known']['info']['matches']) == 24, 'known-window final seed count'
|
||
cli = read_source('../../bench/results/local-r512-n5.json')
|
||
assert data['CLI']['scan_ms'] == cli['median_scan_wall_ms']
|
||
assert data['CLI']['anchors'] == cli['anchors']
|
||
assert all(r['root_jobs'] == data['CLI']['root_jobs'] and r['matches'] == data['CLI']['matches']
|
||
for r in cli['rounds'])
|
||
# Ensure each cue is also present, in order, in the editable exports.
|
||
subtitles = (HERE/f'output/{name}.srt').read_text()
|
||
narration = (HERE/f'output/{name}.narration.md').read_text()
|
||
assert [block.split('\n', 2)[2] for block in subtitles.strip().split('\n\n')] == [c['text'] for c in timeline['cues']]
|
||
for cue in timeline['cues']:
|
||
assert cue['text'] in narration
|
||
if args.scene == 'SlimeFilm':
|
||
assert [c['title'] for c in timeline['chapters']] == [
|
||
'史莱姆区块:先看单个区块如何判定',
|
||
'CPU:先把正确答案算出来',
|
||
'CUDA:每个 thread 负责一个 seed',
|
||
'改变分工:固定坐标,遍历 seed',
|
||
'Outer sum:邻居也可以共享坐标项',
|
||
'Random:状态更新与 48 位等价关系',
|
||
'有效种子:先反推关系,再引入窗口',
|
||
'第一次筛选:只枚举有效种子的低 18 位',
|
||
'复用筛选结果:同一坐标类只生成一次根',
|
||
'每个根预计算一次,避免中间位循环重复计算',
|
||
'中间位:输出差早筛与 16 候选掩码',
|
||
'高位:用 θ 区间求交恢复 h',
|
||
'完整验证:恢复的种子让全部 25 格通过',
|
||
'GPU 执行:按位置与根供给任务,按组搜索',
|
||
'整条流程:同一个窗口,每一步省下什么',
|
||
]
|
||
captions = '\n'.join(c['text'] for c in timeline['cues'])
|
||
for explanation in ('同一段的模输出差完全相同', '末尾统一保留低十八位',
|
||
'没有消失', '三千三百八十三', '二十四个世界种子'):
|
||
assert explanation in captions, f'missing updated explanation: {explanation}'
|
||
displays = '\n'.join(c.get('display', '') for c in timeline['cues'])
|
||
assert r'\theta=3383' in displays and '15959335054446' in displays
|
||
assert captions.index('先只看一个固定区块') < captions.index('再引入窗口')
|
||
assert all(token not in captions for token in ('假设 base 为零', '两组示意输出参数', '一百三十点一三六'))
|
||
for cue in timeline['cues']:
|
||
assert 0 <= cue['start'] < cue['end'] <= timeline['duration']+.1
|
||
for a,b in zip(timeline['cues'],timeline['cues'][1:]):
|
||
assert a['end'] <= b['start']+.01, 'overlapping captions'
|
||
assert len(timeline['cues']) == report['caption_count']
|
||
with av.open(str(HERE/'output'/report['file'])) as container:
|
||
stream = container.streams.video[0]
|
||
expected = (854,480) if args.quality == 'preview' else (1920,1080)
|
||
assert (stream.width,stream.height) == expected
|
||
duration = float(stream.duration*stream.time_base)
|
||
assert abs(duration-timeline['duration']) < .15, 'video and caption timelines differ'
|
||
frames = sum(1 for _ in container.decode(stream))
|
||
assert frames > 0
|
||
print(f'OK: {report["file"]}; {frames} decoded frames; {len(timeline["cues"])} captions; source hashes match')
|