/**
 * Frame Capture Service
 *
 * Uses Puppeteer to capture frames from any web page implementing the
 * window.__hf seek protocol. Navigates to a file server URL, waits for
 * the page to expose window.__hf, then captures frames deterministically
 * via Chrome's BeginFrame API or Page.captureScreenshot fallback.
 */
import { type Browser, type Page } from "puppeteer-core";
import { type BrowserLease, type CaptureMode } from "./browserManager.js";
import { type EngineConfig } from "../config.js";
import type { CaptureOptions, CaptureResult, CaptureBufferResult, CapturePerfSummary, CaptureWarning, SubTimelineWaitOutcome } from "../types.js";
export { isMemoryExhaustionError, isTransientBrowserError } from "./captureFailure.js";
export type { CaptureOptions, CaptureResult, CaptureBufferResult, CapturePerfSummary };
/** Called after seeking, before screenshot. Use for video frame injection or other pre-capture work. */
export type BeforeCaptureHook = (page: Page, time: number) => Promise<void>;
export interface CaptureSession {
    browser: Browser;
    /** Exact ownership token for this browser acquisition. */
    browserLease?: BrowserLease;
    page: Page;
    options: CaptureOptions;
    serverUrl: string;
    outputDir: string;
    onBeforeCapture: BeforeCaptureHook | null;
    isInitialized: boolean;
    /**
     * Static-frame dedup (default-on; opt out with `HF_STATIC_DEDUP=false`): indices of frames byte-identical
     * to their predecessor (no GSAP tween / clip cut active in either), predicted from
     * window.__timelines and empirically anchor-verified. These reuse `lastFrameBuffer`
     * instead of re-seeking + re-screenshotting. Undefined when disabled or ineligible.
     */
    staticFrames?: Set<number>;
    /** Last non-deduped frame buffer, reused for every `staticFrames` index in its run. */
    lastFrameBuffer?: Buffer;
    /** Count of frames served from a reused buffer (dedup telemetry). */
    staticDedupCount?: number;
    /** Dedup was enabled for this render (default-on; opt out with `HF_STATIC_DEDUP=false`). */
    staticDedupEnabled?: boolean;
    /**
     * Short machine code for WHY dedup did not arm, for a low-cardinality breakdown.
     * One of: `capture_mode` | `video_injection` | `page_composite` |
     * `ineligible` | `verification_failed` | `verification_budget`. Undefined when armed or disabled.
     */
    staticDedupSkipReason?: string;
    pageReleased?: boolean;
    browserReleased?: boolean;
    browserConsoleBuffer: string[];
    /**
     * Script resources that failed to load (request failure or HTTP >= 400).
     * pollSubCompositionTimelines fail-fasts on these: a comp whose timeline
     * script 404'd can never register window.__timelines[id], so waiting the
     * full playerReadyTimeout (45s) buys nothing (~1% of wild local renders
     * were hitting that wall — a 705-render spike at the 45s setup bucket).
     */
    scriptLoadFailures: string[];
    /** Outcome of the sub-composition timeline wait: ready | timeout | script_failure. */
    subTimelineWaitOutcome?: SubTimelineWaitOutcome;
    /** Structured readiness warnings surfaced to the producer's render policy. */
    warnings: CaptureWarning[];
    initTelemetry?: {
        initDurationMs: number;
        tweenCount: number;
        /** Live DOM element count at end of init; undefined when the measurement itself failed. Observational — see collectSessionInitTelemetry. */
        elementCount?: number;
    };
    capturePerf: {
        frames: number;
        seekMs: number;
        beforeCaptureMs: number;
        screenshotMs: number;
        totalMs: number;
        /** Per-frame capture durations (batch frames get the batch mean). Basis for
         * the warmup-robust p50 in the perf summary. */
        frameMs: number[];
    };
    captureMode: CaptureMode;
    /**
     * Browser LAUNCH mode, immutable after createCaptureSession. `captureMode`
     * is reassigned by initializeSession (e.g. to "drawelement"), so callers
     * that need to know whether this browser actually drives BeginFrame (the
     * SwiftShader liveness probe) read this field instead.
     */
    launchCaptureMode: CaptureMode;
    beginFrameTimeTicks: number;
    beginFrameIntervalMs: number;
    beginFrameHasDamageCount: number;
    beginFrameNoDamageCount: number;
    /** Optional producer config — when set, overrides module-level env var constants. */
    config?: Partial<EngineConfig>;
    /** True if running on SwiftShader (detected at init). Undefined before init. */
    isSwiftShader?: boolean;
    /**
     * Low-cardinality GPU bucket (`<backend>/<vendor>`, e.g. `d3d11/nvidia`)
     * derived from the WebGL renderer at DE session init. Surfaces in
     * CapturePerfSummary → render telemetry so backend-specific drawElement
     * damage (Metal vs D3D11 vs GL) clusters attributably. The raw
     * driver-supplied string is deliberately NOT retained — see
     * classifyGpuRenderer.
     */
    gpuRenderer?: string;
    /** drawElementImage canvas was injected and is ready for capture. */
    drawElementReady?: boolean;
    /**
     * Worker-encode pipeline is active for this session. Set by
     * `initDrawElementOrTransparentBackground` when `enableDrawElementWorkerEncode`
     * is true and capture mode resolved to "drawelement".
     */
    workerEncodeEnabled?: boolean;
    /**
     * Frame indices that must be captured via screenshot rather than drawElement.
     * Populated at init by the clip-cut boundary predictor (Lim 6): frames where
     * the outgoing clip is dropped a frame before the incoming clip's paint record
     * is ready → black frame. Controlled by `HF_FAST_CAPTURE_BOUNDARY_SS=false`.
     * Empty/undefined when the predictor produces no frames.
     */
    clipBoundaryFrames?: Set<number>;
    /** Rolling drawElement frame byte-sizes (last ~60), for silent-blank-drop detection:
     * drawElement intermittently returns an anomalously small (blank) frame with no
     * throw; a frame far below the running median is re-captured via screenshot. */
    deFrameSizes?: number[];
    /** Last non-deduped encode result, reused for a static frame on the drawElement
     * worker-encode path (mirrors `lastFrameBuffer` on the screenshot path). Only set
     * when static-frame dedup is armed on the drawElement path. */
    lastEncodeResult?: Promise<Buffer>;
    /** Frame index lastEncodeResult belongs to — static-dedup reuse must verify
     * every frame in (lastEncodeResultFrame, i] is predicted-static (under the
     * interleaved parallel stride the "previous" produced frame is i−N, and
     * reusing it for frame i is only valid when the whole gap is static). */
    lastEncodeResultFrame?: number;
    /** Per-render self-verification ground truth (ungated-release safety net):
     * K screenshot frames captured at init BEFORE the drawElement canvas is
     * injected (the only window where a page screenshot shows the live DOM, not
     * the capture canvas's stale bitmap). The producer drain compares the DE
     * frame at each index against these; a breach aborts the render with
     * DrawElementVerificationError and the orchestrator re-renders via the
     * screenshot path. */
    deVerifyFrames?: Map<number, Buffer>;
    /** Low-cardinality init-gate reason when drawElement routed to baseline (telemetry). */
    deGateReason?: string;
    /**
     * Full trigger string when drawElement gated off to the screenshot fallback
     * path — preserves the specific CSS effect (`filter:blur`,
     * `filter:drop-shadow`, `backdrop-filter`, `clip-path`) that
     * {@link deGateReason} sanitizes down to a low-cardinality bucket. Populated
     * on the same fallback-gate branches as `deGateReason`; consumed by the
     * `capture_fallback_profile` observability checkpoint gated behind
     * `HF_PROFILE_FALLBACK_CAPTURE=true`. See
     * `packages/producer/src/services/render/fallbackCaptureProfile.ts`.
     */
    deFallbackTrigger?: string;
    /** Wall-clock ms spent capturing self-verification ground truth at init (telemetry). */
    deVerifyInitMs?: number;
    /** Count of per-frame "No cached paint record" screenshot fallbacks (telemetry). */
    deNcprFallbacks?: number;
    /**
     * drawElement init passed every gate but stopped before verification +
     * canvas injection: the session has no video-frame injector yet (probe
     * sessions initialize before extraction) and the comp has <video> elements,
     * so ground-truth screenshots would capture black video boxes. The capture
     * stage completes the init via completeDeferredDrawElementInit once
     * prepareCaptureSessionForReuse attaches the injector.
     */
    deInitDeferred?: boolean;
}
/**
 * drawElement self-verification failure — a captured DE frame diverged from its
 * pre-injection screenshot ground truth (or a blank frame survived a retry).
 * The orchestrator catches this and re-renders the whole job with
 * forceScreenshot. Discriminant-based guard (not instanceof) so it survives
 * duplicated module instances across package boundaries.
 */
/**
 * Structured detail carried alongside the human-readable message — lets
 * telemetry report the actual failure kind / failing dB / frame index
 * instead of the orchestrator having to regex them back out of formatted
 * text (a message-text dependency is exactly the failure mode this shape
 * exists to close — review finding: message wording, translation, or a
 * cross-module/serialized error must never be able to flip the reported
 * kind). All fields but `kind` are optional: a blank-frame trip has no PSNR
 * score, so `failedDb`/`verifyThresholdDb` are omitted for that throw site.
 */
export interface DrawElementVerificationDetails {
    kind: "blank" | "psnr";
    frameIndex?: number;
    failedDb?: number;
    verifyThresholdDb?: number;
}
export declare class DrawElementVerificationError extends Error {
    readonly kind: "blank" | "psnr";
    readonly frameIndex?: number;
    readonly failedDb?: number;
    readonly verifyThresholdDb?: number;
    constructor(message: string, details: DrawElementVerificationDetails);
}
export declare function isDrawElementVerificationError(err: unknown): boolean;
/**
 * Extracts the structured details off a (possibly cause-wrapped) verification
 * error — same chain-walk as isDrawElementVerificationError, structural
 * (not instanceof) for the same duplicated-module-instance reason. Returns
 * undefined when the error isn't a verification failure at all.
 */
export declare function getDrawElementVerificationDetails(err: unknown): DrawElementVerificationDetails | undefined;
/** Wait for inline CSS background images introduced by the latest seek. */
export declare function decodeDynamicCssBackgroundImages(page: Page): Promise<void>;
export declare function sanitizeDiagnosticUrl(input: string): string;
export declare function formatNavigationFailureDiagnostic(input: {
    captureMode: CaptureMode;
    url: string;
    timeoutMs: number;
    elapsedMs: number;
    error: unknown;
}): string;
export declare function formatNavigationStartDiagnostic(input: {
    captureMode: CaptureMode;
    url: string;
    timeoutMs: number;
}): string;
export declare function formatRequestFailureDiagnostic(input: {
    method: string;
    resourceType: string;
    url: string;
    failureText: string;
}): string;
/**
 * Chromium reports media loads that it intentionally cancels during probing as
 * request failures. They are expected when the probe discovers or seeks local
 * audio/video and do not indicate a missing asset.
 */
export declare function shouldIgnoreRequestFailureDiagnostic(input: {
    resourceType: string;
    url: string;
    failureText: string;
}): boolean;
export declare function formatHttpErrorDiagnostic(input: {
    method: string;
    resourceType: string;
    url: string;
    status: number;
    statusText: string;
}): string;
/**
 * Fixed warmup-loop iteration count used when `CaptureOptions.lockWarmupTicks`
 * is `true`. Picked to roughly match the median tick count observed by the
 * unlocked wall-clock loop during a typical 2s page load at 30fps — so
 * `beginFrameTimeTicks` lands in a similar range regardless of host speed.
 */
export declare const LOCKED_WARMUP_TICKS = 60;
/**
 * Internal driver for the BeginFrame warmup loop.
 *
 *   - Unlocked: exits as soon as `state.running` flips to `false`. Tick count
 *     varies with wall-clock page-load time.
 *   - Locked: ignores `state.running` entirely and exits once it has driven
 *     exactly `LOCKED_WARMUP_TICKS` iterations. Caller awaits this promise
 *     after page-readiness so `session.beginFrameTimeTicks` is identical
 *     across hosts.
 *   - `tick` errors are swallowed (Chrome's `beginFrame` is best-effort
 *     during page load — the page hasn't installed CDP listeners yet). When
 *     `tick` throws, the iteration count does NOT advance.
 *
 * `intervalMs` is the BeginFrame interval (≈33ms at 30fps).
 *
 * `frameTimeTicks` is derived as `ticks * intervalMs` and exposed via
 * {@link warmupFrameTimeTicks} — not stored on the state, to keep `ticks`
 * the single source of truth.
 */
export interface WarmupTickState {
    running: boolean;
    ticks: number;
}
export interface WarmupTickOptions {
    intervalMs: number;
    lockWarmupTicks: boolean;
    tick: (frameTimeTicks: number, intervalMs: number) => Promise<void>;
    /** Injectable so tests can advance "time" without real setTimeout. */
    sleep?: (ms: number) => Promise<void>;
}
/**
 * Derive the current simulated frame time from a warmup state. Single source
 * of truth so tests and callers stay in sync.
 */
export declare function warmupFrameTimeTicks(state: WarmupTickState, intervalMs: number): number;
export interface BeginFrameTimelineTicks {
    capture: number;
    commit: number;
    probe: number;
}
export interface PreparedBeginFrameTimeline {
    commitParams: {
        frameTimeTicks: number;
        interval: number;
        noDisplayUpdates: false;
    };
    timeline: BeginFrameTimelineTicks;
}
/**
 * Place frame zero after the warmup clock while retaining capture-rate-sized
 * headroom for the visual commit and liveness probe ticks that precede it.
 *
 * The warmup and capture intervals can differ (warmup currently runs at a
 * fixed 33ms). Basing both clocks on the capture interval would move time
 * backwards whenever the output frame rate is faster than the warmup rate.
 */
export declare function deriveBeginFrameTimeTicks(state: WarmupTickState, warmupIntervalMs: number, captureIntervalMs: number): number;
export declare function deriveBeginFrameProbeTimeTicks(captureTimeTicks: number, captureIntervalMs: number): number;
export declare function deriveBeginFrameTimelineTicks(state: WarmupTickState, warmupIntervalMs: number, captureIntervalMs: number): BeginFrameTimelineTicks;
export declare function prepareBeginFrameTimeline(session: Pick<CaptureSession, "beginFrameIntervalMs" | "beginFrameTimeTicks">, state: WarmupTickState, warmupIntervalMs: number): PreparedBeginFrameTimeline;
export declare function driveWarmupTicks(options: WarmupTickOptions, state: WarmupTickState): Promise<void>;
export declare function resolveCaptureSessionOptions(options: CaptureOptions, browserVersion: string, platform?: NodeJS.Platform): CaptureOptions;
/**
 * Complete a deferred drawElement init (see CaptureSession.deInitDeferred).
 * Call after prepareCaptureSessionForReuse has attached the video-frame
 * injector; no-op when the session is not deferred or still has no injector.
 */
export declare function completeDeferredDrawElementInit(session: CaptureSession): Promise<void>;
export declare function createCaptureSession(serverUrl: string, outputDir: string, options: CaptureOptions, onBeforeCapture?: BeforeCaptureHook | null, config?: Partial<EngineConfig>): Promise<CaptureSession>;
/**
 * Classify a console "Failed to load resource" error as a font-load failure.
 *
 * These are expected when deterministic font injection replaces Google Fonts
 * @import URLs with embedded base64 — or when the render environment has no
 * network access to Google Fonts. Suppressing them reduces noise in render
 * output without hiding real asset failures (images, videos, scripts, etc.).
 *
 * Chrome's `msg.text()` for a failed resource is typically just
 * `"Failed to load resource: net::ERR_FAILED"` — the URL is only on
 * `msg.location().url`. We match against both so the filter works regardless
 * of which form Chrome emits.
 */
export declare function isFontResourceError(type: string, text: string, locationUrl: string): boolean;
export declare function formatConsoleDiagnostic(type: string, text: string, locationUrl: string): {
    text: string;
    suppressHostLog: boolean;
};
export declare function pollSubCompositionTimelines(page: Page, timeoutMs: number, intervalMs?: number, getScriptLoadFailures?: () => readonly string[], scriptFailureGraceMs?: number): Promise<SubTimelineWaitOutcome>;
/** @internal exported for unit testing only */
export declare function pollImagesReady(page: Page, timeoutMs: number, intervalMs?: number): Promise<boolean>;
/** @internal exported for contract testing. */
export declare function collectMediaReadinessWarnings(page: Page, skipIds: readonly string[], timeoutMs: number): Promise<CaptureWarning[]>;
/**
 * Build the `live_map_detected` warning for the detected map libraries.
 * A live tile map violates the deterministic-render contract (tiles are
 * render-time network fetches): none of the readiness polls cover late-added
 * tile images or map canvases, so frames captured before tiles arrive ship a
 * blank/partial map with no error — the render exits success. Wild signature:
 * PRINFRA-300 (blank hook scene, zero diagnostics, "fixed" by re-render).
 */
export declare function buildLiveMapWarning(libraries: readonly string[]): CaptureWarning;
export declare function initializeSession(session: CaptureSession): Promise<void>;
/**
 * Internal helper: seek timeline and inject video frames.
 * Shared by captureFrame (disk) and captureFrameToBuffer (buffer).
 * Returns timing breakdown for perf tracking.
 */
export declare function waitForPendingSeekCompletion(page: Pick<Page, "evaluate">): Promise<void>;
export declare const MAX_STATIC_DEDUP_ANALYSIS_FRAMES = 1000000;
export declare function isStaticDedupFrameAnalysisSafe(totalFrames: number): boolean;
/**
 * Predict the dedupable (static) frame set from window.__timelines. A frame f (f>0) is
 * static iff NEITHER f NOR f-1 falls inside any GSAP tween interval — content didn't
 * change f-1→f, so f can reuse f-1's buffer. Requiring BOTH neighbours static under-
 * claims by one frame at each tween edge (the SAFE direction). Disqualifies the whole
 * comp on any signal the tween-walker can't see: video / canvas / webgl (redraw without
 * a tween), zero tweens (non-GSAP animation), or a running CSS/WAAPI animation.
 */
export declare function computeStaticFrameSet(page: Page, fps: number): Promise<{
    totalFrames: number;
    staticFrameSet: Set<number>;
    hasVideo: boolean;
    hasCanvas: boolean;
    hasNonGsapAnim: boolean;
    tweenCount: number;
    eligible: boolean;
    reason: string;
}>;
/**
 * Interior verification points for a run [a..b], plus the always-included end `b`.
 * Density used to be a flat point-count cap (min(sampleCount, 8)), so a run's
 * stride grew with its span — on a long run (many merged static frames), two
 * checks could land hundreds of frames apart. A genuine content change in
 * between (e.g. text swapped by a mechanism computeStaticFrameSet's GSAP-only
 * tween walk can't see) then hides between samples and the whole run gets
 * wrongly trusted as static.
 *
 * `sampleCount` (HF_STATIC_DEDUP_SAMPLES) is a per-run point-count FLOOR, not a
 * stride cap — raising it always increases density, never decreases it. (An
 * earlier revision of this fix bounded the stride BY sampleCount directly, which
 * inverted that: raising sampleCount widened the allowed gap instead of shrinking
 * it, and the "raise HF_STATIC_DEDUP_SAMPLES to verify more" log guidance became
 * backwards for exactly the long runs it's meant to help.) The length-scaling
 * fix itself comes from STATIC_VERIFY_REFERENCE_STRIDE, which is independent of
 * sampleCount, so density scales with run length regardless of how that knob is
 * set; sampleCount only ever raises density further above that floor.
 *
 * Pure and exported so its scaling behavior is unit-testable without a real
 * page/browser.
 */
export declare function computeStaticVerificationPoints(a: number, b: number, sampleCount: number): number[];
/**
 * Empirically verify the predicted-static set before trusting it. Group static frames
 * into runs; each run [a..b] reuses anchor a-1. CRITICAL: compare against the ANCHOR,
 * not the predecessor — a slow drift with sub-quantization per-frame deltas is byte-
 * identical frame-to-frame yet drifts far from the anchor by the run's end (the real
 * frozen error). Capture each run's anchor once, compare END + a midpoint to it; any
 * mismatch ⇒ the run isn't truly static ⇒ disable dedup whole-comp. Capture-mode-
 * independent (seeks + screenshots in normal DOM). Returns the first bad frame, or null.
 */
export declare function verifyStaticFramesSafe(session: CaptureSession, page: Page, staticFrames: Set<number>, fps: number, sampleCount: number): Promise<{
    badFrame: number;
    budgetExhausted: boolean;
} | null>;
export declare function captureFrame(session: CaptureSession, frameIndex: number, time: number): Promise<CaptureResult>;
/**
 * Write an already-captured frame buffer to the session's output dir using the
 * canonical `frame_NNNNNN.{jpg,png}` naming. `fileIndex` is the ENCODER-facing
 * index (0-based within the captured range), which may differ from the absolute
 * composition frame index used for seeking/boundary lookups. Extracted so the
 * disk worker-encode pipeline can write a buffer produced by
 * `captureFrameToBufferPipelined` without duplicating the naming convention.
 */
export declare function writeCapturedFrame(session: CaptureSession, fileIndex: number, buffer: Buffer): string;
/**
 * Capture a frame and return the screenshot as a Buffer instead of writing to disk.
 * Used by the streaming encode pipeline to pipe frames directly to FFmpeg stdin.
 */
export declare function captureFrameToBuffer(session: CaptureSession, frameIndex: number, time: number): Promise<CaptureBufferResult>;
/**
 * Pipelined drawElement frame capture for the worker-encode path.
 *
 * Performs seek prep + paint-wait + drawElementImage + composite +
 * `createImageBitmap` + transfers the bitmap to the in-page encode worker.
 * Returns `encodeResult` immediately (before the worker finishes encoding).
 * The caller overlaps frame N's encode with frame N+1's produce phase.
 *
 * Requirements:
 *  - `session.workerEncodeEnabled` must be true (set by initializeSession when
 *    `config.enableDrawElementWorkerEncode` is true and mode resolved to drawelement).
 *  - JPEG format only. PNG falls back to `captureFrameToBuffer`.
 *  - macOS hardware GPU path (syncToPaintEvent=true, beginFrameTimeTicks=0).
 *    BeginFrame (Linux) uses the standard synchronous path unchanged.
 */
export declare function captureFrameToBufferPipelined(session: CaptureSession, frameIndex: number, time: number): Promise<{
    encodeResult: Promise<Buffer>;
    captureTimeMs: number;
}>;
/**
 * Verification-grade single-frame recapture for the producer's blank-frame
 * guard. Unlike {@link captureFrameToBufferPipelined} it takes NO shortcuts
 * and has NO fallbacks, both of which can return the WRONG FRAME's pixels at
 * drain time:
 *  - the static-dedup fast path returns session.lastEncodeResult, which by
 *    drain time can hold a frame several indices AHEAD of the suspect frame;
 *  - the per-frame "No cached paint record" screenshot fallback captures the
 *    injected canvas — i.e. the LAST drawn drawElement frame, not this one.
 * Any failure here throws; the caller treats that as verification failure and
 * falls back the whole render (correct, never wrong-frame).
 */
export declare function recaptureDrawElementFrameForVerify(session: CaptureSession, frameIndex: number, time: number): Promise<Buffer>;
/**
 * P6 prototype (HF_DE_BATCH): capture N consecutive frames in one CDP
 * round-trip via {@link produceDrawElementFrameBatch}. The caller pre-plans the
 * batch (consecutive frame indices, none static-dedup'd, none opt-in
 * boundary-screenshot). On a mid-batch in-page failure the remaining frames are
 * re-captured through {@link captureFrameToBufferPipelined}, which owns the
 * per-frame screenshot-fallback semantics — so failure behavior is identical to
 * the unbatched path, just discovered at batch granularity.
 */
export declare function captureFramesBatchPipelined(session: CaptureSession, frameIndices: number[], times: number[]): Promise<Array<{
    frameIndex: number;
    encodeResult: Promise<Buffer>;
}>>;
/**
 * Type of the "inner capture" function consumed by
 * {@link discardWarmupCapture}. Matches the real `captureFrameCore` signature
 * with the buffer-bearing result trimmed to what the caller actually uses
 * (the wrapper never inspects the buffer). Exposed so unit tests can inject
 * a stub instead of driving Chrome end-to-end.
 */
export type DiscardWarmupInnerCapture = (session: CaptureSession, frameIndex: number, time: number) => Promise<{
    buffer: Buffer;
    quantizedTime: number;
    captureTimeMs: number;
}>;
/**
 * Perform one capture, throw away the buffer, and restore any session
 * side-effects (perf counters, BeginFrame damage tallies) so downstream
 * captures see state identical to a fresh session.
 *
 * Distributed chunk workers need this because Chrome's BeginFrame screenshot
 * pipeline maintains a per-process `lastFrameCache`: when a captured frame's
 * `hasDamage` reports `false`, the screenshot path returns the previously
 * captured buffer. For chunk N (N > 0) the worker has no prior frame in its
 * cache, so the very first capture's `hasDamage` reporting diverges from
 * what an in-process render at the same absolute frame index would see (the
 * in-process renderer always has frame N-1 cached). One discard capture
 * before the first real capture primes the cache.
 *
 * The function intentionally restores perf state so the warmup capture does
 * NOT bias `getCapturePerfSummary()`'s per-frame averages.
 *
 * No file is written; the buffer is discarded.
 *
 * @param session — initialized capture session
 * @param frameIndex — frame index to warm up with (default 0). Chunk
 *   workers typically pass their chunk's first absolute frame index.
 * @param time — time in seconds (default 0). Chunk workers typically pass
 *   the corresponding `frameIndex / fps`.
 * @param innerCapture — injectable for tests; defaults to the real
 *   `captureFrameCore`.
 */
export declare function discardWarmupCapture(session: CaptureSession, frameIndex?: number, time?: number, innerCapture?: DiscardWarmupInnerCapture): Promise<void>;
export declare function closeCaptureSession(session: CaptureSession): Promise<void>;
export declare function prepareCaptureSessionForReuse(session: CaptureSession, outputDir: string, onBeforeCapture: BeforeCaptureHook | null): void;
export declare function getCompositionDuration(session: CaptureSession): Promise<number>;
/**
 * Ungated-release safety net, part 1: capture K screenshot ground-truth frames
 * BEFORE the drawElement canvas is injected (the only window where a page
 * screenshot shows the live DOM). The producer's drain compares each DE frame
 * at these indices against its screenshot; a breach (or a blank frame that
 * survives one retry) throws DrawElementVerificationError, and the orchestrator
 * re-renders the whole job via the screenshot path.
 *
 * Env: HF_DE_VERIFY = sample count (default 4, clamp 0..8; 0 disables).
 * Deterministic index selection: fixed fractions of the timeline, nudged off
 * clip-cut boundaries (screenshot-vs-DE is legitimately ±1-frame desynced
 * there — Lim 6). Skipped for tiny comps (<10 frames) and under
 * HF_FORCE_DRAWELEMENT (debug escape hatch).
 *
 * False-positive bias is intentional: a nondeterministic comp may mismatch its
 * init-time screenshot → the render falls back to the screenshot path (slower,
 * never wrong). Cost when passing: ~K×(seek+screenshot) ≈ 150–300ms at init.
 */
/**
 * Timeline fractions the self-verify samples. First k-1 evenly spaced, last
 * pinned at 95%: late-onset damage (an end-of-comp reveal exposing pixels DE
 * paints wrong) was invisible to the old (i+1)/(k+1) grid, whose final sample
 * sat at 80% — measured miss: a body-gradient drop starting at ~79% of the
 * timeline passed verification while the drained output bottomed at 30.9 dB.
 */
export declare function computeDeVerifySampleFractions(k: number): number[];
/**
 * Percentile of a positive-real sample set (nearest-rank; matches how the
 * existing {@link medianOf} p50 helper picks the middle index). `p` is a
 * fraction in [0, 1]; the sample at `floor(p * n)` (clamped to `[0, n-1]`)
 * is returned. Sample set is not mutated. Returns 0 for empty input, mirroring
 * the p50 helper.
 *
 * Used for the `capture_fallback_profile` observability checkpoint added in
 * the fast-capture fallback profiling PR: we already collect `capturePerf.frameMs`
 * per session, so computing p95/p99 is one sort + two lookups — cheap enough
 * to always compute alongside the existing p50, no separate opt-in path
 * needed for the math. The env gate lives at the emission site.
 */
export declare function percentileOf(samples: number[], p: number): number;
export declare function getCapturePerfSummary(session: CaptureSession): CapturePerfSummary;
//# sourceMappingURL=frameCapture.d.ts.map