Build1 distinct publisher3 min readPublished
Ofcom's photosensitive-epilepsy rule is written in seconds, so the rate you extract frames at ends up inside the specification. The same overlooked parameter broke the SQL detector first and the model that adjudicates its output two days later.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The fps value in flashframe is not one setting in one place. It goes to ffmpeg for extraction, to `setup_db_and_ingest` alongside the source rate of 25.0, to `detect_violations`, and into the SQL window itself, sized `ROWS BETWEEN int(fps)-1 PRECEDING AND CURRENT ROW` [6][19]. The window spans fps-1 rows, and it equals one second only when the rate that produced the rows matches the rate that sized the window. The regulation counts seconds; the query counts rows, and everything downstream inherits the difference [3].
Nyquist sets the rest. Sampling at 10 fps means nothing above 5 Hz is representable [10], which is 1.67 times the three-flashes-per-second threshold being certified against [6]. That is thin headroom for a rule a delivery can be rejected on. The adjudication path had less: the API default of 1 fps puts the representable ceiling at 0.5 Hz, six times below the limit [7]. The signal was discarded before the tokens existed, so no prompt could recover it, and the rewrite cost hours and changed nothing [16][20].
The latency table needs the same reading. flashframe's author reports p50 106 ms and p95 163 ms over a 92-minute feature of 138,240 frames [7]. 92 minutes is 5,520 seconds, and 138,240 divided by 5,520 is 25.04, so that frame count is the container's own rate, the 25.0 handed to ingest, not the 10 fps the tool extracted at [1][19]. At 10 fps the table holds about 55,200 rows per tile, roughly 40 percent of the frame count in the header [2], times however many tiles the frame is cut into, which the writeup does not state [6]. For 106 ms to mean anything on your footage you need the same tile count and the same extraction rate, with no escalation, since extraction, adjudication and ingest all sit outside the timed region [7][12]. That timed region measures only the fast part; the real cost lives outside it.
The escalation arithmetic is the good engineering here. Span 1025-1055 is 30 frames, three seconds of video at 10 fps; re-extracting it at 30 fps and then 60 fps costs 90 plus 180 frames, so 270 [4][13]. Re-extracting the whole feature at 60 fps costs 331,200 [5]. A resample tool capped at two iterations buys the escalated answer for about 1,227 times fewer frames [5][12], and the property mattered in maybe 30 frames out of 138,240 to begin with, about 0.02 percent of the file [11][3].
One caution on the false-negative case. The genuine strobe read 5.0 flashes per second at 10 fps and 6.25 after escalation, a 20 percent understatement of the escalated value [15][8]. Both readings exceed 3.00, so on that clip the verdict never moved, only the number [9]. For undersampling to pass a real hazard you need content whose true rate sits just above the limit and aliases below it. The clips described in the writeup sit either side of that case, at five alternations per second and at 6.25 Hz [9][16], and that gap is where I would build the next fixture.
Ranked by verification strength, evidence, and original report placement.
Per-frame luminance goes into ClickHouse and detection is windowed SQL: opposing luminance transitions are paired into flashes and counted over a sliding one-second window.
The detection window is written as sum(is_flash) OVER (PARTITION BY tile ORDER BY frame_idx ROWS BETWEEN {int(fps)-1} PRECEDING AND CURRENT ROW), so window length is fps-1 preceding rows plus the current row, per tile; the number of tiles is not stated.
In 1997 a Pokemon episode aired in Japan with a four-second red-and-blue strobe, and 685 children went to hospital that night.
The regulations that followed, Ofcom Broadcasting Code 2.12 and ITU-R BT.1702, are why UK and Japanese broadcast deliveries have to clear a photosensitive-epilepsy check before transmission.
The regulation says no more than three flashes in any one second; the tool treats 3.00 flashes/sec as the limit.
The author built a screening tool called flashframe, hosted on Railway with code on GitHub, shipping three synthetic test clips that run without an upload or an API key.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 30, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
The duplicate def that ate the trim, and the lint rule nobody was running1 distinct publisher
build
Every viewer hits your HLS key endpoint in the same second, and almost nobody tests it1 distinct publisher
build
A firmness check built on EXTCODESIZE(owner) is forgeable, and six live contracts prove it1 distinct publisher
build
Two timeout numbers, not new code, fixed a nightly job that failed 2-3 mornings a week1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One byline, but the work is on the table
Every number that matters here — the latency percentiles, the 2.08 that became 2.82, the strobe the model could not see — comes from the engineer who wrote the code, published under his own name on dev.to and corroborated by nobody. What lifts it above the usual self-report is that he shows rather than summarises: the window function, the resample_frames body, the request part with fps=24, the agent trace, and a disclosed +12.9% over-report on small-area flashes he refused to paper over. What undercuts it is a wrinkle he does not address — 138,240 frames implies about 25 fps of source, not the 10 fps extraction described a paragraph earlier, so the row basis of the one benchmark is unclear.
Live URL, nobody visible using it
A Railway address and a public repository mean a stranger can click a clip and watch the query run — real, and about as far as it goes. No broadcaster is routing deliveries through this, no second engineer has published a reading, and the only workload it is documented against is a single 92-minute feature the author picked. The finding travels further than the tool: an API that samples video at 1 fps by default will not see a 6 Hz strobe, and that applies to everyone shipping video into a multimodal model, whether or not they ever install flashframe.
Lesson lands; the compliance framing reaches
The thesis is modest and the demonstration matches it — one parameter, two subsystems, both failures shown with numbers. The stretch is in the surrounding register: 'the wrong answer ships in a compliance report' describes a 3×3 tile proxy reading luma code values that the author himself calls screening-grade, not a certified test. And one figure is left dangling. The clip that triggered a violation measured 2.08 and cleared at 2.82, both under the 3.00 limit said to be the rule, so whatever raised that flag is not the threshold the whole story is built on and never gets named.
Portfolio post that indicts its own tool
It ends at a demo the author hosts and a repository he owns, on a platform where engineering writeups double as a résumé — the ordinary pull toward a flattering story. It pulls the other way instead. The narrative is two of his own bugs, one of which he misdiagnosed for hours, and he reports a 12.9% over-report against ground truth while explaining why he would not add a correction constant: the whole claim is that thresholds come from published criteria rather than from whatever made the demo look right. Promotional venue, self-incriminating content.
Checkable mechanics, unverifiable verdicts
We are reading one author on one platform, which caps how far this can be trusted. But the claims are unusually easy to sanity-check from the inside: the Nyquist arithmetic holds, a documented API default is a documented API default, the code is public, and the failure modes are mechanical rather than interpretive. What stays out of reach is anything about the tool's standing — whether these readings would survive a calibrated test, and why 2.08 tripped a 3.00 limit.