import { type HarnessMode } from "./regression-harness-distributed.js";
import type { DistributedFormat } from "./services/distributed/shared.js";
type TestMetadata = {
    name: string;
    description: string;
    tags: string[];
    minPsnr: number;
    maxFrameFailures: number;
    minAudioCorrelation: number;
    maxAudioLagWindows: number;
    /**
     * Optional residual-RMS check. Subtracts the rendered audio from the
     * baseline and reads the residual Overall RMS via `astats`. A value
     * of `-50` treats residuals at-or-below -50 dBFS as effectively-
     * silent — i.e. the streams are sample-level equivalent. Omit
     * (undefined) to skip the check; fixtures authored before this field
     * was introduced have implicit `undefined`.
     */
    maxAudioResidualRmsDb?: number;
    renderConfig: {
        /**
         * Frame rate. Stored on disk as a JSON number (integer fps, e.g. `30`)
         * for legacy meta.json files, or a JSON string (`"30000/1001"` for NTSC)
         * for rationals. The metadata validator normalizes both into an `Fps`
         * rational at load time so downstream code only sees the structured form.
         */
        fps: import("@hyperframes/core").Fps;
        /**
         * Output container. Defaults to `"mp4"`. `"png-sequence"` makes the
         * rendered output a directory of zero-padded RGBA PNGs instead of a
         * single video file — the harness branches its comparison logic
         * accordingly (per-frame byte equality instead of PSNR). `"mov"` and
         * `"webm"` are encoded video containers that share the PSNR path with
         * `"mp4"`. Distributed mode supports all four — webm goes through
         * libvpx-vp9 with closed-GOP concat-copy.
         */
        format?: DistributedFormat;
        /**
         * Codec selection for `format: "mp4"`, forwarded to
         * `DistributedRenderConfig.codec`. The in-process renderer doesn't take
         * a codec hint — for the baseline it always picks the format's default
         * (h264 for mp4 SDR), so `codec: "h265"` is exercised exclusively in
         * `--mode=distributed-simulated`. The PSNR comparison against the
         * baseline therefore measures "h265 chunked + concat" ≈ "h264 single-
         * pass" rather than byte equality. Fixtures asserting a tighter
         * contract should explicitly pin a higher `minPsnr`.
         */
        codec?: "h264" | "h265";
        workers?: number;
        /** Force HDR in the harness; omitted/false preserves historical SDR-only test behavior. */
        hdr?: boolean;
        /**
         * Render this suite with the experimental fast-capture path
         * (drawElementImage, `--experimental-fast-capture`). The golden must be
         * regenerated with the flag on. Used by the `fast-capture` regression
         * guard; omit for the default screenshot/BeginFrame capture.
         */
        experimentalFastCapture?: boolean;
        /**
         * Pin the browser capture path for a regression fixture. The producer's
         * software-GPU default normally prefers screenshots, so BeginFrame-only
         * compositor regressions must opt out explicitly to exercise that path.
         */
        captureMode?: "screenshot" | "beginframe";
        /**
         * Render-time variable overrides, equivalent to `hyperframes render
         * --variables '<json>'`. Injected as `window.__hfVariables` before any
         * page script runs so the runtime helper `getVariables()` returns the
         * merged result of declared defaults (`data-composition-variables`)
         * and these overrides. Omit when the test doesn't exercise variables.
         */
        variables?: Record<string, unknown>;
        /**
         * Chunk size in frames for `--mode=distributed-simulated`. Forwarded
         * to `DistributedRenderConfig.chunkSize`. Ignored in `--mode=in-process`.
         * Default is the plan's own default (240 frames).
         */
        chunkSize?: number;
        /**
         * Cap on parallel chunks for `--mode=distributed-simulated`. Forwarded
         * to `DistributedRenderConfig.maxParallelChunks`. Ignored in
         * `--mode=in-process`. Default is the plan's own default (16).
         */
        maxParallelChunks?: number;
    };
};
type TestSuite = {
    id: string;
    dir: string;
    srcDir: string;
    meta: TestMetadata;
};
type CliOptions = {
    testNames: string[];
    excludeTags: string[];
    update: boolean;
    sequential: boolean;
    keepTemp: boolean;
    /**
     * Which render path to exercise. `in-process` (default) calls
     * `executeRenderJob`; `distributed-simulated` calls
     * `plan() → renderChunk() × N → assemble()` from
     * `@hyperframes/producer/distributed`. See
     * `regression-harness-distributed.ts`.
     */
    mode: HarnessMode;
};
export declare function parseArgs(argv: string[]): CliOptions;
export declare function discoverTestSuites(testsDir: string, filterNames: string[], excludeTags?: string[]): TestSuite[];
/** The frame a checkpoint time samples. Shared so selection and lookup agree. */
export declare function frameIndexForCheckpoint(checkpointSec: number, fps: number): number;
/**
 * PSNR for a set of frame indices, in a single ffmpeg pass.
 *
 * The original implementation spawned one ffmpeg per checkpoint, each
 * selecting its frame with `select='eq(n,N)'`. That filter has no index, so
 * ffmpeg decoded from frame 0 every time — checkpoint 99 decoded 99% of both
 * videos to read a single frame. Across 100 checkpoints that is roughly 50
 * full decodes of each video, and it dominated regression runtime (27% of
 * total suite work; 60-80% on short fixtures).
 *
 * This keeps the original `select` semantics exactly and only collapses the
 * spawns: both inputs are filtered to the same frame indices, chosen **by
 * decode index, independently per input**, then compared pairwise.
 *
 * Selecting by index is load-bearing, not incidental. Handing the streams to
 * `psnr` directly (`[0:v][1:v]psnr`) instead makes ffmpeg's framesync align
 * them by presentation timestamp, and rendered output does not carry the same
 * PTS as its golden baseline. That pairs frames which do not correspond: on
 * style-3-prod it moved 80 of 100 checkpoints by more than 2 dB and turned
 * three exactly-identical frames into 82/38/51 dB.
 *
 * `settb=1/1,setpts=N` after each `select` renumbers both selected streams to
 * the same synthetic one-tick-per-frame timeline, so framesync pairs the Nth
 * selected frame of one input with the Nth of the other. The timebase is
 * pinned rather than derived (`setpts=N/FRAME_RATE/TB` is not enough) because
 * `FRAME_RATE` is per-input: if the two videos report different rates, that
 * form hands framesync two different timelines again and it silently emits a
 * different number of rows than frames requested.
 *
 * Returns a 0-based frame index -> PSNR map. `stats_file` reports `psnr_avg`
 * to two decimals where the old stderr parse had full float precision;
 * thresholds are integers and fixtures pass with dB of margin, so the 0.005 dB
 * rounding is not material.
 */
export declare function psnrAtFrames(renderedVideo: string, snapshotVideo: string, frameIndices: number[]): Map<number, number>;
export declare function psnrAtCheckpoint(psnrByFrame: Map<number, number>, checkpointSec: number, fps: number): number;
export declare const MAX_STREAM_DRIFT_SECONDS = 0.5;
export type StreamDurationParity = {
    passed: boolean;
    videoDurationSeconds: number;
    audioDurationSeconds: number;
    driftSeconds: number;
};
export declare function checkStreamDurationParity(videoPath: string): Promise<StreamDurationParity | null>;
export {};
//# sourceMappingURL=regression-harness.d.ts.map