/** * Load-paced ("clean") flyby driver (docs/design/paced-flyby.md; public * issue #16's flyby pop-in, second front). * * The warm-tiles A/B monitor's verdict on real photogrammetry (Google * P3DT): a two-leg flight needs ~970 fine tiles and route warming covers * ~3% of them — no pre-load fits a real flight in cache, so the cure for a * take with ZERO unrefined frames is pacing, not pre-warming. Wall-clock * stretches; output is clean by construction. (Warming still composes: * warm first and each paced frame waits far less.) * * Why a driver must own the camera: a story fly-to step dispatches ONE * camera action whose animation then runs inside deck/maplibre, independent * of story elapsed time — pausing the story clock does not hold the camera. * So this driver replaces wall-clock play entirely: it steps story time * manually, writes the camera itself each frame (cameraAtTime over the same * fly-to arc the warm sampler uses, via the instant harness setView path), * and gates every advance on all live tilesets reporting isLoaded(). * * Pure module: every effect arrives through `deps`, so tests drive it with * fake tilesets and clocks exactly (warmTileset's now/sleep precedent). */ /** Per-frame tick — `om-paced-tick`'s detail; the story recorder's hook. `waitedMs` > 0 means this frame paused for tiles. */ export interface PacedTick { t: number; waitedMs: number; } export interface PacedFlybyDeps { /** Advance story time by dt, firing due non-camera steps; returns the new clamped story time and whether the story ended. */ advance(dt: number): { t: number; done: boolean; }; /** Instant camera write for story-time t — a set, never a transition. */ setCamera(t: number): void; /** Every live tileset has refined for the current selection. */ tilesetsLoaded(): boolean; /** True once the run must stop mid-frame (paused, superseded, disconnected). Ended is NOT aborted — the final frame still gates. */ aborted(): boolean; /** Per-frame notification once the frame is fully refined. A returned promise HOLDS the next advance until it resolves — the recorder's frame-capture seam (screenshot/snapshot race-free by construction). */ onTick(tick: PacedTick): void | Promise; /** Timing hooks — injectable for tests (warmTileset's now/sleep precedent); production callers omit them for the browser defaults. */ raf?(): Promise; sleep?(ms: number): Promise; now?(): number; /** Story-time step per output frame (default 1000/30). */ frameIntervalMs?: number; /** Cap on one frame's tile wait so a dead tile server degrades to ordinary pop-in instead of freezing playback (default 10s; Infinity = absolute gate). */ maxWaitPerFrameMs?: number; } export declare function runPacedFlyby(deps: PacedFlybyDeps): Promise; /** * One settle pass (issue #37's whenSettled contract, shared with the paced * driver's per-frame gate): resolve once `loaded()` reports true AND one * further animation frame has been drawn — loaded tile data still needs a * draw to reach pixels. Times out to {settled:false} rather than rejecting * (the paced-max-hold degradation posture: a late frame is ordinary pop-in, * not an error). */ export declare function settleOnce(deps: { loaded(): boolean; timeoutMs: number; aborted?(): boolean; raf?(): Promise; now?(): number; }): Promise<{ settled: boolean; waitedMs: number; }>;