()
| 94 | /** Fallback for runs without a timeline: one browser hop located by the |
| 95 | * narrator line, or the largest output gap in the cast. */ |
| 96 | const actsFromHeuristic = (): Act[] => { |
| 97 | const findHop = (): { start: number; end: number } => { |
| 98 | const markerIndex = events.findIndex((event) => event.text.includes("in the browser")); |
| 99 | if (markerIndex !== -1) { |
| 100 | const start = events[markerIndex]; |
| 101 | const after = events |
| 102 | .slice(markerIndex + 1) |
| 103 | .find((event) => event.at > (start?.at ?? 0) + 1 && event.text.trim().length > 0); |
| 104 | if (start && after) return { start: start.at + 0.8, end: after.at }; |
| 105 | } |
| 106 | let best = { start: 0, end: 0 }; |
| 107 | for (let i = 1; i < events.length; i += 1) { |
| 108 | const previous = events[i - 1]; |
| 109 | const current = events[i]; |
| 110 | if (previous && current && current.at - previous.at > best.end - best.start) { |
| 111 | best = { start: previous.at, end: current.at }; |
| 112 | } |
| 113 | } |
| 114 | return best; |
| 115 | }; |
| 116 | const hop = findHop(); |
| 117 | if (hop.end - hop.start < 2) { |
| 118 | console.error(`film: no browser hop found in the cast (gap ${hop.end - hop.start}s)`); |
| 119 | process.exit(1); |
| 120 | } |
| 121 | return [ |
| 122 | { source: "terminal", from: 0, to: hop.start }, |
| 123 | { source: "browser", from: 0, to: probeSeconds(browserPath) }, |
| 124 | { source: "terminal", from: hop.end, to: castEnd }, |
| 125 | ]; |
| 126 | }; |
| 127 | |
| 128 | const acts = actsFromTimeline() ?? actsFromHeuristic(); |
| 129 |
no test coverage detected