fix(lint): float slop in overlapping_clips_same_track compare (#1851)

parseFloat('0.1') + parseFloat('0.2') = 0.30000000000000004, so authored
adjacencies whose sum is exact in decimal drift a few ulps and fire the
overlap rule under the strict compare.

Compare on end - start > 1e-6 instead — 11 orders above the worst observed
drift, 4 below one 60fps frame.
This commit is contained in:
Tzuhany
2026-07-01 19:58:27 -07:00
committed by GitHub
parent a908af11a8
commit 3569293c66
2 changed files with 27 additions and 1 deletions
@@ -384,6 +384,24 @@ describe("composition rules", () => {
window.__timelines = window.__timelines || {}; window.__timelines = window.__timelines || {};
window.__timelines["c1"] = gsap.timeline({ paused: true }); window.__timelines["c1"] = gsap.timeline({ paused: true });
</script> </script>
</body></html>`;
const result = await lintHyperframeHtml(html);
const finding = result.findings.find((f) => f.code === "overlapping_clips_same_track");
expect(finding).toBeUndefined();
});
it("does not flag adjacencies where parseFloat + add drifts by a few ulps", async () => {
// parseFloat("0.1") + parseFloat("0.2") = 0.30000000000000004
const html = `
<html><body>
<div data-composition-id="c1" data-width="1920" data-height="1080">
<div class="clip" data-start="0.1" data-duration="0.2" data-track-index="0">A</div>
<div class="clip" data-start="0.3" data-duration="0.2" data-track-index="0">B</div>
</div>
<script>
window.__timelines = window.__timelines || {};
window.__timelines["c1"] = gsap.timeline({ paused: true });
</script>
</body></html>`; </body></html>`;
const result = await lintHyperframeHtml(html); const result = await lintHyperframeHtml(html);
const finding = result.findings.find((f) => f.code === "overlapping_clips_same_track"); const finding = result.findings.find((f) => f.code === "overlapping_clips_same_track");
+9 -1
View File
@@ -15,6 +15,14 @@ const MAX_COMPOSITION_LINES = 300;
const MAX_TIMED_ELEMENTS_PER_TRACK = 3; const MAX_TIMED_ELEMENTS_PER_TRACK = 3;
const TRACK_DENSITY_EXEMPT_TAGS = new Set(["audio", "script", "style", "video"]); const TRACK_DENSITY_EXEMPT_TAGS = new Set(["audio", "script", "style", "video"]);
// `parseFloat("0.1") + parseFloat("0.2") = 0.30000000000000004`. Sub-second
// authored adjacencies survive parse + add as a value a few ulps above the
// next clip's start; a strict `>` fires the overlap rule on adjacencies that
// are exact in the source HTML. 1μs sits ~11 orders of magnitude above the
// observed drift (worst ~2e-16s across every realistic decimal pair) and 4
// below one 60fps frame (~16.67ms), so this only ever swallows float slop.
const OVERLAP_EPSILON_SECONDS = 1e-6;
function countPhysicalLines(source: string): number { function countPhysicalLines(source: string): number {
if (source.length === 0) return 0; if (source.length === 0) return 0;
@@ -406,7 +414,7 @@ export const compositionRules: Array<(ctx: LintContext) => HyperframeLintFinding
const current = clips[i]; const current = clips[i];
const next = clips[i + 1]; const next = clips[i + 1];
if (!current || !next) continue; if (!current || !next) continue;
if (current.end > next.start) { if (current.end - next.start > OVERLAP_EPSILON_SECONDS) {
findings.push({ findings.push({
code: "overlapping_clips_same_track", code: "overlapping_clips_same_track",
severity: "error", severity: "error",