Skip to content

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

Draft
xuanruli wants to merge 2 commits into
xuanru/needle-pivot-offset-checkfrom
xuanru/content-overlap-dense-resampling
Draft

feat(lint): dense motion re-sampling for content_overlap#2746
xuanruli wants to merge 2 commits into
xuanru/needle-pivot-offset-checkfrom
xuanru/content-overlap-dense-resampling

Conversation

@xuanruli

Copy link
Copy Markdown
Contributor

What it catches

Transient text-on-text collisions during continuous motion — e.g. an orbiting label card crossing the center card — that overlap for a fraction of a second. The content_overlap detector is already correct; the sparse 9-point layout grid simply seeks straight past the crossing moment and never samples it.

Root cause of the miss is sampling density, not detection logic: the baseline grid seeks past t ~ 3.5s, so the fuzz016 collision (a ~0.4s window) is never observed. --samples 30 --at-transitions already catches it; motion-fps (20fps) only runs with a .motion.json, which fuzzed comps don't have.

How it works

  • Adds a dense, overlap-only re-sampling pass that reuses the existing content_overlap detector verbatim — same collectSolidTextBlocks, same 0.2 threshold, zero new detection logic.
  • Runs at 8fps (cap 120 samples), text-only so it's cheap.
  • Gated on the composition actually animating (geometry fingerprint), so static comps pay nothing.
  • Findings feed the existing collapse / persistence tiering unchanged.

Corpus evidence (autonomous geometry-fuzz run, 81 fuzzed diagrams)

  • 0 false positives across all 81 samples.
  • New catch: fuzz016 (orbit) — 28.3% text-on-text at t = 3.53s, the target defect, previously unseen by the sparse grid.
  • Additional real fires surfaced by the denser sampling: fuzz010 / fuzz062 (info-level, real); info→error escalations fuzz004/024/043/067/011 — all inspected and real. No regressions.
  • Correctly drops fuzz031 (VLM over-call: 18.5% < 20% threshold — it grazes but never collides), a 3rd deterministic-verification data point where the VLM over-called.

Validation

  • Autonomous Gemini-3.6 video-judge fuzz run to surface candidates, then a deterministic FP sweep across all 81 rendered compositions.
  • Full check suite passes (2 new cases in check.test.ts); bun run build green.

Reviewer note — sensitivity

At 8fps the occurrences >= 2 persistence shortcut spans ~125ms (vs ~500ms on the sparse ~500ms grid), so brief-but-real collisions now gate at error. Every inspected instance was real, but if softer severity is preferred, the heldMs >= 500 path is the alternative knob.

🤖 Generated with Claude Code

@xuanruli
xuanruli changed the base branch from main to graphite-base/2746 July 24, 2026 07:42
@xuanruli
xuanruli force-pushed the xuanru/content-overlap-dense-resampling branch from e443861 to 1dc7b36 Compare July 24, 2026 07:42
@xuanruli
xuanruli changed the base branch from graphite-base/2746 to xuanru/needle-pivot-offset-check July 24, 2026 07:42

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@xuanruli
xuanruli marked this pull request as ready for review July 24, 2026 07:49
@xuanruli
xuanruli force-pushed the xuanru/needle-pivot-offset-check branch from 7bb8b4f to 399196e Compare July 24, 2026 08:04
@xuanruli
xuanruli force-pushed the xuanru/content-overlap-dense-resampling branch from 1dc7b36 to 938c2cf Compare July 24, 2026 08:04
@xuanruli
xuanruli marked this pull request as draft July 24, 2026 08:05
@xuanruli
xuanruli marked this pull request as ready for review July 24, 2026 08:09

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The narrow implementation is good: this reuses the existing content_overlap detector and its persistence/collapse semantics instead of creating a second overlap rule, and it avoids double-collecting base-grid timestamps.

The animation gate currently defeats the exact transient-motion class this PR is intended to catch:

  • collectMotionOverlapSamples returns unless the sparse base-grid geometry signatures differ (packages/cli/src/utils/checkPipeline.ts:457-468). If an animation starts and ends entirely between sparse samples, every sparse fingerprint can be identical even though the dense grid contains a real overlap. In that case the dense pass never runs, so the new detector misses the motivating defect by construction. The existing static negative test only proves that an actually static mocked fingerprint skips work; it does not cover identical sparse fingerprints with motion/collision between them.

Please gate on an authoritative animation/timeline signal, or run the cheap dense overlap sampling independently of the sparse fingerprints, and add a regression where all sparse signatures are equal but a mid-grid overlap is detected.

Requesting changes for this false-negative path.

— Magi

@xuanruli

Copy link
Copy Markdown
Contributor Author

Thanks Magi — you're right, the sparse-fingerprint gate defeats exactly the transient-motion class this PR targets: if an animation starts and ends between two sparse samples, every sparse fingerprint is identical and the dense pass never runs. Fix in progress:

  • Drop the "skip unless sparse signatures differ" gate; instead drive the dense overlap pass from an authoritative timeline/motion signal (sample the window when the GSAP timeline has duration/tweens on the elements), falling back to a cheap unconditional dense pass over the held/animated window (bounded sample count).
  • Regression: all sparse signatures equal but a real overlap occurs mid-grid → content_overlap is detected (the between-grid case the current static negative test doesn't cover).

@james-russo-rames-d-jusso james-russo-rames-d-jusso left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 938c2cf37a7e69288403280c2820fb69043c00bb.

Genuinely tidy small-footprint PR — the "zero new detection logic" claim holds up (grep confirms __hyperframesOverlapAudit at layout-audit.browser.js:1433-1440 calls contentOverlapIssues(root, time) directly, same function invoked by the main layout audit path at :1420; no reimplementation of collectSolidTextBlocks, overlapIssue, or the 0.2 threshold). The static-composition gate is intact (the wiring test at check.test.ts:1364-1373 correctly asserts collectOverlap is never called for collectLayoutGeometry: () => 'static'), root-element discovery matches the main audit's 3-tier chain, baseTimes.has(time) de-dup works despite float risk (both grids round to 3 decimals via roundTime), the geometry fingerprint captures a broad set of animation modes (getBoundingClientRect() + opacity per element + canvas/video pixel hash), and there's no leaky dependency between this PR and #2744's rotation scaffolding (collectMotionOverlapSamples uses only driver.seek, driver.collectOverlap, and pre-existing collected.geometrySignatures).

Reuse claim: verified. Boundary with sparse pass: verified. Not-a-leaky-stack: verified.

Where the review lands hardest is on persistence-tier semantics under the new grid density — your PR-body reviewer note flags the sensitivity honestly, but the comment at layoutAudit.ts:186-198 documents an explicit design contract (≥500ms → error, ~250ms → ignore) that the code silently violates in dense-pass mode. Inline for the specific ask. Not a blocker — but the design choice is worth making explicit either in code or in that comment before merge.

Individual findings — one 🟠 with the design ask, five smaller 🟠 concerns on cliff-effects, one 🟡 doc-string nit — all inline.

Cross-cutting themes

1. Persistence-tier math wasn't updated for the new grid density. The occurrences >= 2 shortcut in isContentOverlapHeldLongEnough (layoutAudit.ts:324) was designed assuming a grid where 2 occurrences ≥ ~1s wall-clock. At 8fps dense sampling, 2 occurrences can span ~125ms — contradicting the design comment (~500ms) and the stated ignore floor (~250ms). Your reviewer note acknowledges this in prose; the code doesn't reflect it. Two lines of code make the choice explicit either way; that's the primary ask.

2. Silent cliff-effects at cap + duration boundaries. The 120-sample cap silently degrades effective fps for 15s+ compositions (~5.94fps at 20s, ~3.95fps at 30s — the "8fps" contract is only guaranteed under ~14.8s). The fingerprint gate has an aliasing failure mode on periodic motion whose period aligns with the base-grid step. The dense loop has no early-terminate — once occurrences clears the threshold there's no signal upside from more samples. All fail-silent — the design's stated invariants get erased by boundary conditions without the code announcing it.

3. Tests validate the mechanism, not the target defect. The new "surfaces a held content_overlap" test at check.test.ts:1345-1362 has collectOverlap return a warning at EVERY dense sample time (~72 samples for the 9s fake duration). That trivializes the persistence promotion — the test would pass equally well against a broken dense pass that promoted every finding. The scenario the PR was built for — a real 125ms crossing seen only by 2 adjacent dense samples — is not reproduced anywhere. Also, buildOverlapSampleTimes(duration) is a pure function (checkPipeline.ts:441) and would take five lines to unit-test for cap/bounds/quantization; that catches the density-math regressions the corpus can't.

🟢 Verified-safe

  • "Zero new detection logic" reuse claim verified (contentOverlapIssues shared between both paths).
  • Root-element discovery matches the main layout audit (3-tier [data-composition-id][data-width][data-height] → [data-composition-id] → document.body).
  • baseTimes.has(time) de-dup works despite float risk — both grids round to 3 decimals via uniqueSortedTimes / roundTime.
  • Static-composition gate is intact — test 2 at check.test.ts:1364-1373 correctly drives collectLayoutGeometry: () => 'static' and asserts collectOverlap is not called.
  • Fingerprint (getBoundingClientRect() + opacity per element + canvas/video pixel hash) captures CSS keyframes, SVG child anims, GSAP tweens, canvas/video content changes.
  • No leaky dependency on #2744's rotation collectors — collectMotionOverlapSamples only calls driver.seek, driver.collectOverlap (new), and reads pre-existing collected.geometrySignatures.
  • collectSolidTextBlocks has no other callers in the codebase — no state that could break other consumers.
  • Perf claim "text-only so it's cheap" defensible per-sample (dense pass runs on text elements filtered pre-loop).

Small, focused PR; the ask is mainly to close the persistence-tier contract question before the dense pass ships as a default.

Review by Rames D Jusso

): Promise<void> {
if (new Set(collected.geometrySignatures).size <= 1) return;
const baseTimes = new Set(grid.layoutSamples);
for (const time of buildOverlapSampleTimes(grid.duration)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Design-contract question — these dense-pass findings enter the persistence tier via a shortcut that assumed sparse-grid timing.

for (const time of buildOverlapSampleTimes(grid.duration)) {
  if (baseTimes.has(time)) continue;
  await driver.seek(time);
  collected.layoutIssues.push(...(await driver.collectOverlap(time)));
}

The findings pushed here flow into collapseStaticLayoutIssuesisContentOverlapHeldLongEnough (layoutAudit.ts:323-330, unmodified by this PR):

function isContentOverlapHeldLongEnough(issue: LayoutIssue, occurrences: number): boolean {
  if (occurrences >= HELD_ACROSS_SAMPLES_MIN_OCCURRENCES) return true;   // ← short-circuits
  const firstSeen = issue.firstSeen ?? issue.time;
  const lastSeen = issue.lastSeen ?? issue.time;
  const heldMs = (lastSeen - firstSeen) * 1000;
  return heldMs >= CONTENT_OVERLAP_HELD_ERROR_MS;
}

Design comment at layoutAudit.ts:186-198 frames the mapping: "At the default 9-sample grid over a multi-second composition ... two collapsed occurrences are already >= one sample-to-sample gap, which is well past 500ms — so 'held under 250ms' reduces to occurrences <= 1 and 'held >= 500ms' reduces to occurrences >= 2." That mapping holds for the sparse grid (~1s between adjacent samples).

At the new 8fps dense grid, two adjacent samples span ~125ms. So occurrences === 2 → promoted straight to error — well under the 500ms design floor and even under the 250ms ignore floor.

The design comment DOES anticipate this — it says "with the literal ms span (CONTENT_OVERLAP_HELD_ERROR_MS) kept as a fallback for callers whose samples really are spaced close enough together for the ms floor to matter on its own (dense --at/--at-transitions runs)". But the code short-circuits on occurrences FIRST — the ms-fallback branch only runs when occurrences < 2, so it never gates the dense-pass 2-occurrence case.

Your PR-body reviewer note acknowledges this: "At 8fps the occurrences >= 2 persistence shortcut spans ~125ms (vs ~500ms on the sparse grid), so brief-but-real collisions now gate at error. Every inspected instance was real, but if softer severity is preferred, the heldMs >= 500 path is the alternative knob."

The request is: make the choice explicit either in code or in the comment. Three options:

  • (a) Tighten isContentOverlapHeldLongEnough to ANDreturn occurrences >= 2 && (lastSeen - firstSeen) * 1000 >= CONTENT_OVERLAP_HELD_ERROR_MS. Still passes the sparse-grid case (samples ≥1s apart → heldMs ≥1000 ≥ 500) and correctly gates the dense case. Cleanest — one source of truth.
  • (b) Tag dense-pass findings with a source: 'dense' marker HERE and skip the occurrences shortcut only for that source in the persistence-tier code.
  • (c) Update the design comment at layoutAudit.ts:186-198 to say the ms floor is intentionally relaxed to ~125ms for dense-pass findings, with a rationale from the corpus ("81/81 samples of the corpus showed 125ms crossings were real defects").

Any of the three closes the contract question. Right now the comment and the code disagree; the disagreement widens by 375ms every time the dense pass fires.

Review by Rames D Jusso

const OVERLAP_SAMPLE_FPS = 8;
const OVERLAP_MAX_SAMPLES = 120;

function buildOverlapSampleTimes(duration: number): number[] {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 20s+ compositions silently degrade to sub-8fps sampling under the 120-cap.

const count = Math.min(
  OVERLAP_MAX_SAMPLES,
  Math.max(2, Math.ceil(duration * OVERLAP_SAMPLE_FPS) + 1),
);
const step = duration / (count - 1);

For duration = 20s, Math.ceil(20*8)+1 = 161 → capped to 120 → step = 20/119 ≈ 0.168s → effective ~5.94fps. For duration = 30s, step ≈ 0.253s → ~3.95fps. The comment at :422-430 sells the design as "8fps (~0.125s spacing) lands >= 2 samples inside a window that narrow", but that guarantee holds only for duration <= 14.875s.

Failure mode: long-form compositions (explainer clips, marketing walk-throughs) fall back to a sparser grid than the design claims. Density guarantee erodes silently — the occurrences >= 2 promotion machinery loses its sub-250ms transient coverage past ~15s.

HF's fuzz corpus is short so this may not bite in CI, but production comp durations trend longer.

Fix options: (a) preserve density and drop the tail past 15s: const step = 1 / OVERLAP_SAMPLE_FPS; const count = Math.min(OVERLAP_MAX_SAMPLES, Math.floor(duration * OVERLAP_SAMPLE_FPS) + 1); (b) accept the degradation but return a truncatedAtSeconds signal so downstream consumers can flag long comps. Minimum: document the effective-fps table in the const block so future authors don't assume 8fps everywhere.

Review by Rames D Jusso

Comment thread packages/cli/src/utils/checkPipeline.ts Outdated
grid: SampleGrid,
collected: GridSamples,
): Promise<void> {
if (new Set(collected.geometrySignatures).size <= 1) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 The fingerprint gate can be fooled by animations whose period aligns with the base-grid step.

async function collectMotionOverlapSamples(
  driver: CheckAuditDriver,
  grid: SampleGrid,
  collected: GridSamples,
): Promise<void> {
  if (new Set(collected.geometrySignatures).size <= 1) return;

geometrySignatures is populated once per base-grid sample. A composition whose animation period aligns with the base-grid step (e.g. a 1s-period rotation sampled at 1s spacings) can land on the same rotational configuration at every base sample → same bounding rects → same fingerprint → Set.size === 1 → dense pass skipped. This is EXACTLY when it's most needed (fast periodic motion).

Rare in practice because mergeSampleTimes breaks alignment for most real comps (transition/caption/frame-check offsets), but it's asymmetric — false negatives on the gate skip the dense pass ENTIRELY, giving zero coverage where the design promises the most.

Fix (cheap): OR the current signal with motion.times.length > 0 (a comp that had detected motion in the first place trivially animates). Or use a two-fingerprint check across the base grid AND a mid-point sample. Minimum: document the aliasing limitation in the gate comment at :446-455.

Review by Rames D Jusso

expect(source).not.toMatch(/prepared\.map\(\(entry\) => entry\.candidate\)/);
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 The "surfaces a held content_overlap" test doesn't exercise the dense-vs-sparse distinction it claims to demonstrate.

collectOverlap: vi.fn(async (time: number) => [
  layoutIssue("warning", { time, code: "content_overlap" }),
]),

This returns a content_overlap warning at EVERY dense sample time (~72 samples for a 9s fake duration at 8fps). The collapse step sees ~72 occurrences → persistence tier trivially promotes to error via the occurrences >= 2 shortcut (see the sibling comment on layoutAudit.ts:324). The test would pass equally well against a broken dense pass that simply promoted every finding — it never asserts occurrences count or firstSeen/lastSeen span.

More importantly: the test doesn't reproduce the target defect (transient crossing seen only by 2 adjacent dense samples that the sparse grid missed). Test 2 (:1364-1373) correctly validates the static-composition gate — that one is fine.

Fix: add a third test where collectOverlap returns a finding at exactly two adjacent dense sample times and nothing anywhere else. Assert (a) driver.collectOverlap.mock.calls.length matches the dense grid size, (b) the surviving finding has occurrences === 2, (c) severity resolves to what the intended semantic is (once the sibling comment's design question is decided).

Also: buildOverlapSampleTimes(duration) at checkPipeline.ts:441 is a pure function and would take ~5 lines to unit-test for the count/bounds/cap-at-120/quantization behavior. Catches density-math regressions the corpus can't.

Review by Rames D Jusso

): Promise<void> {
if (new Set(collected.geometrySignatures).size <= 1) return;
const baseTimes = new Set(grid.layoutSamples);
for (const time of buildOverlapSampleTimes(grid.duration)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Dense loop has neither early-terminate nor try/catch — cost + flake risk.

for (const time of buildOverlapSampleTimes(grid.duration)) {
  if (baseTimes.has(time)) continue;
  await driver.seek(time);
  collected.layoutIssues.push(...(await driver.collectOverlap(time)));
}

Cost: serial loop of up to ~120 seek + evaluate roundtrips per composition. Each seek waits for GSAP/CSS timeline settle. Once persistence tier is going to promote a finding at occurrences >= 2, additional samples of the same key add zero signal but still pay the cost. The perf claim text-only so it's cheap (comment at :428-430) is defensible per-sample but not per-run.

Flake: neither driver.seek(time) nor driver.collectOverlap(time) is guarded. If either throws mid-loop (transient page-evaluate error, browser blip, timeout on a specific frame), the entire dense pass — and by inheritance the surrounding collectGridSamples await — rejects, aborting the whole check run. Under the sparse grid the same shape existed with 9 chances; now there are ~120, so the throw budget multiplies with the sample count.

Fix: (a) wrap the loop body in try/catch, record the throw as a soft dense_overlap_probe_failed info-level finding rather than aborting. (b) early-terminate when every observed key has already crossed a threshold (say 5 occurrences). (c) at minimum, add a per-sample timeout so a hung seek doesn't stall the whole pass. Neither is a merge blocker — but the const block should carry a worst-case cost note.

Review by Rames D Jusso

return issues;
};

// content_overlap only, for the dense motion re-sampling grid (checkPipeline

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Comment references a function name that doesn't exist in the codebase.

// content_overlap only, for the dense motion re-sampling grid (checkPipeline
// detectMotionTextOverlap). Two free-positioned text blocks crossing mid-orbit

detectMotionTextOverlap doesn't exist — the actual function is collectMotionOverlapSamples at checkPipeline.ts:457. Reader searching for the reference finds nothing.

Fix: rename to collectMotionOverlapSamples in the comment.

Review by Rames D Jusso

xuanruli and others added 2 commits July 24, 2026 08:52
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>
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>
@xuanruli
xuanruli force-pushed the xuanru/content-overlap-dense-resampling branch from 938c2cf to 3e0c480 Compare July 24, 2026 10:23
@xuanruli
xuanruli marked this pull request as draft July 24, 2026 10:23
@xuanruli

Copy link
Copy Markdown
Contributor Author

@miguel-heygen pushed the fix in 3e0c480d7 — ready for another look.

False-negative path (animation gate):

  • Dropped the "skip unless sparse geometry signatures differ" gate. The dense overlap pass now runs unconditionally over the animated/held window (bounded sample count), so an overlap that starts and ends entirely between two sparse samples is caught. Added the between-grid regression: all sparse signatures equal, real overlap mid-grid → content_overlap is detected.

Round-2 items folded into the same commit:

  • Persistence-tier contract: tightened to AND (occurrences AND the ms floor) so the ~500ms floor is actually honored — occurrences >= 2 alone can no longer promote at ~125ms. Reconciled the layoutAudit.ts:186-198 comment to match.
  • Sample cap now scales with duration to hold ~8fps instead of silently degrading on 15s+ comps.
  • Replaced the trivial "warning at every sample" test with the real between-grid transient case above.

CI: the stack's Test/Fallow run on #2744's SHA is green; only the Windows re-run is finishing.

@xuanruli
xuanruli requested a review from miguel-heygen July 24, 2026 10:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants