#!/usr/bin/env python3 """Regression test for always-on ray failure diagnostics. Every INCOMPLETE ray endpoint must be reported on stderr with its reason and the affected frame id / camera coordinate time even without ``--verbose``, so a long movie never needs a rerun to be diagnosed. ``--verbose`` (or any Debug build) adds a bounded set of representative samples with film/cost localization. Budget-incomplete ``UNRESOLVED/BUDGET_EXHAUSTED`` frames are reported separately from numerical ``INCOMPLETE`` failures, and the publication refusal must not recommend a larger retry budget for a pure integration error. A deterministic DP54 integration error is produced without a large workload: strict tolerances, ``min == initial == max`` step, and one allowed rejection make the first trial step fail. The rejection quota is reached before any minimum-step check, so the exact detailed reason is ``REJECTION_LIMIT``. Small 16x8 scenes keep every case short. Two-frame movie coverage uses the existing ``test_observer_`` fixture track duplicated to two rows (the Schwarzschild metric is stationary, so the same tetrad is valid at both times); the test then checks that each failing sample is counted once across all time slabs. """ import os import re import struct import subprocess import sys import tempfile from pathlib import Path # Keep scratch data inside the pre-approved OpenCode scratch directory. TMP_ROOT = Path('/tmp/opencode') TMP_ROOT.mkdir(parents=True, exist_ok=True) BUILD = Path(sys.argv[1] if len(sys.argv) > 1 else 'build/Release').resolve() TESTDIR = Path(sys.argv[2]).resolve() if len(sys.argv) > 2 else BUILD ENV = dict(os.environ, OMP_NUM_THREADS='4') # v3 lens-map layout (see src/lens_map.c); only enough to count stored # INCOMPLETE vertices. The first frame header follows the 40..176 v3 # provenance block. VERSION_OFFSET = 8 FRAME_COUNT_OFFSET = 32 FRAME_HEADER_START = 176 VERTEX_SIZE = 108 OUTCOME_INDEX = 10 INCOMPLETE_OUTCOME = 3 COMMON = ['--catalog', 'assets/sky_grid_5deg.csv', '--width', 16, '--height', 8, '--fov-deg', 80, '--exposure', 1e-3, '--coarse-cell-pixels', 8, '--refine-max-level', 0, '--psf-relative-tail', 1e-4] # Deterministic DP54 rejection-quota failure: tight tolerances, a single fixed # step bound, and one allowed rejection. Every ray rejects its first trial step # and reports the exact REJECTION_LIMIT reason instead of a fabricated terminal # category. INJECT = ['--integrator', 'dp54', '--ode-rtol', '1e-15', '--ode-atol-x', '1e-15', '--ode-atol-pi', '1e-15', '--ode-atol-l', '1e-15', '--ode-initial-step', '0.5', '--ode-min-step', '0.5', '--ode-max-step', '0.5', '--ode-max-rejections', '1'] def run(binary, *args, ok=True, env=ENV): result = subprocess.run([str(binary), *map(str, args)], env=env, capture_output=True, text=True) if (result.returncode == 0) != ok: raise AssertionError( f'{binary.name} {args}: rc={result.returncode}\n' f'stdout:\n{result.stdout}\nstderr:\n{result.stderr}') return result def is_debug(result): return 'Debug build:' in result.stdout def incomplete_total(stderr): return sum(int(m) for m in re.findall(r'INCOMPLETE=(\d+)', stderr)) def frame_reported(stderr, frame_id): return re.search(rf'Ray failures: frame {frame_id} camera_t=', stderr) is not None def map_incomplete_vertices(path): data = path.read_bytes() assert data[:8] == b'GRLENS\x01\x00', f'not a lens map: {path}' version = struct.unpack_from(' 0 assert incomplete_total(err) == stored, \ f'refine histogram {incomplete_total(err)} != stored {stored}' # 5) Two-frame movie. (a) A default, non-verbose run must identify both # frames and count each failing sample exactly once across the time # slabs, matching the saved map. track_single = tmp / 'track_single.csv' run(observer_test, track_single) rows = [line for line in track_single.read_text().splitlines() if line.strip() and not line.startswith('#') and not line[0].isalpha()] assert len(rows) == 1, rows fields = rows[0].split(',') fields[0], fields[1] = '1', '1' header = next(line for line in track_single.read_text().splitlines() if line.startswith('t,')) track = tmp / 'track2.csv' track.write_text(header + '\n' + rows[0] + '\n' + ','.join(fields) + '\n') frames_dir = tmp / 'frames' frames_dir.mkdir() movie_map = tmp / 'movie.grlens' movie_run = run(binary, *COMMON, *INJECT, '--allow-incomplete', '--slab-duration', '0.4', '--observer-track', track, '--movie-track-samples', '--frames-dir', frames_dir, '--lens-map-output', movie_map) err = movie_run.stderr assert frame_reported(err, 0) and frame_reported(err, 1), err stored = map_incomplete_vertices(movie_map) assert stored > 0, 'movie map has no INCOMPLETE vertices' assert incomplete_total(err) == stored, \ f'histogram {incomplete_total(err)} != stored {stored}; ' \ 'a sample was re-counted across slabs' if not is_debug(movie_run): assert 'ray failure:' not in err, err # (b) Verbose movie adds request kind, persistent vertex id, film position # and cost, bounded per reason. movie_verbose_dir = tmp / 'frames_verbose' movie_verbose_dir.mkdir() movie_verbose = run(binary, *COMMON, *INJECT, '--allow-incomplete', '--verbose', '--slab-duration', '0.4', '--observer-track', track, '--movie-track-samples', '--frames-dir', movie_verbose_dir) err = movie_verbose.stderr assert 'sample=' in err and 'kind=vertex' in err and 'vertex=' in err, err assert 'film=(' in err, err assert 'accepted=' in err and 'rejected=' in err and 'rhs=' in err, err reps = sum(1 for line in err.splitlines() if 'ray failure:' in line and 'suppressed=' not in line) assert reps <= 6, err # two frames, one reason each, <=3 reps per reason # (c) Render-only replay of the two-frame map without --allow-incomplete # reports both frames' reasons before the publication gate refuses, and # never invents an unpersisted trusted stop state. map_frames_dir = tmp / 'map_frames' map_frames_dir.mkdir() refused = run(binary, '--catalog', 'assets/sky_grid_5deg.csv', '--lens-map-input', movie_map, '--frames-dir', map_frames_dir, '--output', tmp / f'map_refused.{ext}', ok=False) err = refused.stderr assert frame_reported(err, 0) and frame_reported(err, 1), err assert 'REJECTION_LIMIT' in err, err assert 'phase=import' in err, err assert 'Incomplete render refused' in err, err assert err.index('frame 0') < err.index('Incomplete render refused'), err single_map = tmp / 'single.grlens' run(binary, *COMMON, *INJECT, '--allow-incomplete', '--lens-map-output', single_map, '--output', tmp / f'maplive.{ext}') replay = tmp / f'replay.{ext}' replay_run = run(binary, '--catalog', 'assets/sky_grid_5deg.csv', '--lens-map-input', single_map, '--allow-incomplete', '--verbose', '--output', replay) err = replay_run.stderr assert 'Ray failures:' in err and frame_reported(err, 0), err assert 'REJECTION_LIMIT' in err and 'phase=import' in err, err assert 'stop_t=' not in err and 'trusted=1' not in err, err # 6) Budget exhaustion is a distinct, always-on message, and its refusal # still points at the retry budget. A replay of an allowed budget map # must not present the unpersisted continuation time as observed. budget_map = tmp / 'budget.grlens' run(binary, *COMMON, '--integrator', 'dp54', '--trace-lookback-time', '1e-6', '--retry-lookback-increment', '0', '--max-total-lookback-time', '1e-6', '--allow-incomplete', '--lens-map-output', budget_map, '--output', tmp / f'budget_allow.{ext}') replay_budget = run(binary, '--catalog', 'assets/sky_grid_5deg.csv', '--lens-map-input', budget_map, '--allow-incomplete', '--verbose', '--output', tmp / f'budget_replay.{ext}') err = replay_budget.stderr assert 'UNRESOLVED/BUDGET_EXHAUSTED' in err, err assert 'continuation_t=-' in err, err assert 'continuation_t=-1' not in err, \ 'replay invented an unpersisted continuation time' budget = tmp / f'budget.{ext}' budget_run = run(binary, *COMMON, '--integrator', 'dp54', '--trace-lookback-time', '1e-6', '--retry-lookback-increment', '0', '--max-total-lookback-time', '1e-6', '--output', budget, ok=False) err = budget_run.stderr assert 'UNRESOLVED/BUDGET_EXHAUSTED' in err, err assert re.search(r'UNRESOLVED/BUDGET_EXHAUSTED: frame 0 camera_t=', err), err assert 'blocking_triangles=' in err and 'unresolved_samples=' in err, err assert 'Incomplete render refused' in err, err assert 'budget' in err.lower(), err assert not budget.exists() print('ray diagnostics checks passed: always-on reasons + frame/time, ' 'bounded verbose samples, movie slab de-duplication and replay', flush=True)