feat(lint): dense motion re-sampling for content_overlap (#2746)

* feat(lint): dense motion re-sampling for content_overlap

Transient text-on-text collisions during continuous motion (e.g. an
orbiting label card crossing the center card) overlap for a fraction of
a second that the sparse 9-point layout grid seeks straight past. The
content_overlap detector is correct; it just never gets a sample at the
crossing moment. Rerun ONLY content_overlap on an 8fps grid (text-only,
cheap) when the composition animates; findings feed the existing
persistence tiering unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(lint): unconditional dense content_overlap pass + honor 500ms floor

Round-1 blocker: the dense motion-overlap re-pass was gated on sparse-grid
geometry fingerprints changing, so an animation aliased to the sparse grid
(identical fingerprints, yet colliding between samples) bypassed the pass —
exactly the transient false-negative it was built to catch. Remove the gate:
the dense pass now runs unconditionally (bounded, text-only), driven by the
composition timeline rather than a fingerprint heuristic.

Round-2 follow-ups:
- Persistence-tier drift: at 8fps, occurrences>=2 spans only ~125ms, not the
  ~500ms the design intends, and it short-circuited before the ms floor.
  content_overlap promotion now requires BOTH occurrences>=2 AND a literal
  firstSeen..lastSeen span >= 500ms, so the wall-clock floor is honored at any
  sampling density. Comment block updated to match.
- Sample cap scales to hold a true 8fps grid up to ~75s (raised 120 -> 600)
  with an explicit note that longer comps degrade below 8fps to stay bounded.

Tests:
- Replaced the trivial "warning at every sample" test with a real between-grid
  regression: a collision living only inside (3.5,4.5) — a gap the sparse grid
  seeks past — is detected and, held ~750ms, promoted to error.
- Replaced the now-invalid "skips when static" test with one asserting the
  dense pass runs even when sparse fingerprints are identical (aliased motion).
- Added a tiering regression: two dense occurrences spanning ~125ms stay a
  warning (not error). Both new guards verified red before the fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(lint): settle-free geometry seek for dense content_overlap pass

The dense overlap re-pass did up to OVERLAP_MAX_SAMPLES full-settle seeks
(120ms paint settle each, ~72s of pure sleep at the ceiling) even though
collectOverlap only reads getBoundingClientRect geometry, valid
synchronously after the timeline setTime. Add a settle-free
DENSE_GEOMETRY_SEEK_OPTIONS + driver.seekGeometry used only by the dense
loop; the base grid keeps full-settle driver.seek.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(lint): document + cover content_overlap 500ms boundary for sparse callers

The occurrences>=2 AND heldMs>=500 promotion rule is a semantics change for
sparse callers (--samples 20, --at, short comps) whose two samples can land
<500ms apart. Document the change in the tiering comment and add boundary
tests: 499ms span stays warning, 500ms span promotes to error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(lint): make dense content_overlap seek genuinely geometry-only

Per review: DENSE_GEOMETRY_SEEK_OPTIONS only overrode settleMs, still
inheriting animationFrameSettle:double + waitForFontsMs:500 → ~3 frame
waits + font wait per seek → ~30s at the 600-sample cap. Geometry
(getBoundingClientRect) is valid synchronously post-setTime, so drop all
post-seek waits (animationFrameSettle:none, waitForFontsMs:0, settleMs:0).
Add options-level regression locking the geometry-only contract. Also fix
a stale comment name (detectMotionTextOverlap → collectMotionOverlapSamples).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style: collapse multi-line comments to single lines

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Xuanru Li
2026-07-25 11:29:24 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 3b3d4f559c
commit 72e2f08f15
9 changed files with 174 additions and 19 deletions
+41
View File
@@ -147,7 +147,9 @@ function fakeDriver(overrides: Partial<CheckAuditDriver> = {}): CheckAuditDriver
getCanvas: vi.fn(async () => ({ width: 1920, height: 1080 })),
findAmbiguousSelectors: vi.fn(async (_selectors: string[]) => []),
seek: vi.fn(async (_time: number) => undefined),
seekGeometry: vi.fn(async (_time: number) => undefined),
collectLayout: vi.fn(async (_time: number, _tolerance: number) => []),
collectOverlap: vi.fn(async (_time: number) => []),
collectLayoutGeometry: vi.fn(async () => `geometry-${geometryCallCount++}`),
collectRotationSample: vi.fn(async (_time: number) => []),
collectOffPivotRotationSample: vi.fn(async (time: number) => ({ time, samples: [] })),
@@ -1343,3 +1345,42 @@ describe("contrast candidate round-trip", () => {
expect(source).not.toMatch(/prepared\.map\(\(entry\) => entry\.candidate\)/);
});
});
describe("dense motion-overlap re-sampling", () => {
// Collision lives inside (3.5, 4.5), a gap the sparse base grid seeks past; only the 8fps dense pass observes it.
const inBetweenGridWindow = (time: number): boolean => time >= 3.6 && time <= 4.4;
it("detects a content_overlap that occurs ONLY between two sparse grid samples", async () => {
const driver = fakeDriver({
// Sparse base grid sees nothing at any base sample time.
collectLayout: vi.fn(async (_time: number) => []),
// The transient exists only strictly between base samples 3.5 and 4.5.
collectOverlap: vi.fn(async (time: number) =>
inBetweenGridWindow(time)
? [layoutIssue("warning", { time, code: "content_overlap" })]
: [],
),
});
const { report } = await runScenario(driver);
expect(driver.collectOverlap).toHaveBeenCalled();
expect(report.layout.findings.some((f) => f.code === "content_overlap")).toBe(true);
// Held ~750ms across the dense grid (>= the 500ms floor) -> promoted.
expect(report.layout.errorCount).toBeGreaterThan(0);
});
it("runs the dense pass even when sparse fingerprints are identical (aliased motion)", async () => {
// Aliased motion has identical fingerprints yet still collides between samples — the false-negative the removed gate caused.
const driver = fakeDriver({
collectLayoutGeometry: vi.fn(async () => "static"),
collectLayout: vi.fn(async (_time: number) => []),
collectOverlap: vi.fn(async (time: number) =>
inBetweenGridWindow(time)
? [layoutIssue("warning", { time, code: "content_overlap" })]
: [],
),
});
const { report } = await runScenario(driver);
expect(driver.collectOverlap).toHaveBeenCalled();
expect(report.layout.findings.some((f) => f.code === "content_overlap")).toBe(true);
});
});
@@ -1425,6 +1425,16 @@
return issues;
};
// Reruns only the overlap detector (same threshold, no new surface) on a fine grid for the dense motion re-sampling pass.
window.__hyperframesOverlapAudit = function auditOverlap(options) {
const time = options && typeof options.time === "number" ? options.time : 0;
const root =
document.querySelector("[data-composition-id][data-width][data-height]") ||
document.querySelector("[data-composition-id]") ||
document.body;
return contentOverlapIssues(root, time);
};
// Frozen-sweep guard (#U10, checkPipeline.ts): a compact per-sample
// fingerprint of every visible element's box + opacity, in DOM order. Node
// calls this once per seeked grid point and compares the strings across the