Skip to content

fix(lint): rotation_pivot_drift — off-pivot rotation check (ring/group scope; draft)#2748

Draft
xuanruli wants to merge 4 commits into
mainfrom
xuanru/rotation-pivot-drift-check
Draft

fix(lint): rotation_pivot_drift — off-pivot rotation check (ring/group scope; draft)#2748
xuanruli wants to merge 4 commits into
mainfrom
xuanru/rotation-pivot-drift-check

Conversation

@xuanruli

Copy link
Copy Markdown
Contributor

What

Adds rotation_pivot_drift, a layout-audit finding for elements that rotate about the wrong pivot — the
common bug where GSAP rotation/scale is applied to an SVG element without svgOrigin, so the pivot
silently resolves to the SVG view-box origin (0,0) instead of the intended center and the figure swings
off-center (often off-canvas). Fix hint points at svgOrigin.

Evidence (geometry-fuzz eval, 81 generated diagrams)

I stress-tested this against 81 freshly-generated animated diagrams (wheels/orbits/gauges/graphs), rendered
each, and pixel-measured the actual rotation pivot (circle-fit of the bbox-center trajectory) for every fire.

What it catches (real, confirmed): ring/group off-pivot — 6 samples drifting 700–1800px off their true
center (concentric HUD rings, radar sweeps, energy-pulse groups). All measured off-canvas or grossly
off-center. This class is complementary to a VLM judge, which under-reports fast/faint rotations.

Hardening in this branch: an 80px absolute drift floor (ROTATION_DRIFT_ABS_FLOOR_PX) — every real
off-pivot case measured >200px, while a 30px status-chip fired spuriously; the floor drops that noise with
zero true-positive loss. Severity stays warning (never auto-fix).

Known limitations (why this is a draft — needs a decision)

  1. Misses thin rotating arms — gauge needles / clock hands (6 real defects in the eval it does NOT catch).
    Two reasons, both structural: (a) isRotationSizeStable excludes elements whose bbox swings >1.6× (a thin
    arm always does); (b) the core invariant "bbox center stays fixed" cannot discriminate a needle pivot — a
    correctly-pivoted needle's bbox center also orbits the hub. Detecting needle pivots needs a different
    invariant (compare the element's effective applied pivot against its authored transformOrigin/hub), which
    is a larger change. Decision needed: ship this as a ring/group-scoped check now, or hold for needle
    coverage.
  2. Intended orbital revolution is indistinguishable from ring off-pivot by geometry (3 eval FPs:
    satellites/cards orbiting a hub). These must carry data-layout-allow-orbit (already honored by the
    sampler). Keep severity at warning until the generator reliably emits that opt-out. A follow-up should
    gate the browser sampler to stable-id selectors (a class-only selector matching N siblings aliases the
    drift measurement).

Tests

checkPipeline.rotationPivotDrift.test.ts — 13 cases (fires on drift past threshold incl. the new floor;
size-stability, small-angle, tiny-area, ±180° wrap negatives). Green.

🤖 Generated with Claude Code

xuanruli and others added 4 commits July 23, 2026 06:58
Catches a rotating element that should spin in place but pivots about the wrong
point (e.g. a wheel whose spokes use a hardcoded/incorrect transformOrigin so
they swing off-center while every existing check still passes).

Cross-sample detection: __hyperframesRotationSample() reports each transformed
element's bbox center + decoded rotation angle per layout sample; the driver
accumulates them and detectRotationPivotDrift() flags any element that (a) is
actually spinning (angle spread > 20deg across >=3 samples), (b) is size-stable
(bbox width ratio <= 1.6, excluding scale/entrance growth), and (c) whose bbox
center drifts > max(10% of its size, 2% of the min viewport dim). Emits a
warning; exempt via data-layout-allow-orbit. Not persistence-tiered, so the
warning is preserved (not demoted to info).

Validated: fires on an off-transformOrigin spoked wheel (109px drift), clean on
non-spinning compositions and on a correctly-centered spinner (drift 0).
tsc/oxlint clean, 112/112 existing tests pass.

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

Review fixes for the size-stability false positive and Fallow CRAP score.

- isRotationSizeStable now bounds the bbox ratio in BOTH width and height. The
  width-only guard let a top-anchored height-scaling element (fixed width,
  growing height) through: its AABB center moves on its own, so it emitted
  rotation_pivot_drift and recommended a destructive pivot rewrite.
- Factor the guard chain into named single-decision predicates
  (hasEnoughRotationSamples / isActuallySpinning / isRotationSizeStable /
  isSizableRotation) so detectRotationPivotDrift reads as a candidate filter +
  drift test. Drops the CRAP score (complexity + coverage).
- Export detectRotationPivotDrift and add direct positive/negative coverage for
  all five filter stages, including the height-scaling regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Run oxfmt on checkPipeline.ts (fixes the red Preflight format:check; oxlint was
  already clean).
- Soften message/fixHint to phrase the finding as the symptom (bbox-center drift,
  "check transformOrigin/svgOrigin") rather than prescribing a specific authoring
  cause the DOM sample can't confirm.
- Move the detector doc-comment above detectRotationPivotDrift and document the
  cross-seek selector-stability invariant (nth-of-type fallback can mis-group a
  classless rotating element).
- Add wrap-around angle-spread tests: a ±180° boundary wobble is not spinning; a
  real spin crossing the seam still fires.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An 80px absolute floor on the pivot-drift threshold. Below this a "drift" is
subpixel/AABB noise on small elements, not an off-pivot bug; a geometry-fuzz
eval across 81 diagrams measured every real off-pivot case at >200px while a
30px status-chip fired spuriously. Floor drops the noise with zero true-positive
loss. Tests bumped to a 100px synthetic drift to stay above the floor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant