Files
gabrielkaszewski-next/docs/superpowers/specs/2026-04-24-gravity-fix-design.md

2.8 KiB

Gravity Engine Fix — Design Spec

Date: 2026-04-24
Branch: gravity
Files in scope: lib/gravity-engine.ts

Problem

Two bugs in the current GravityEngine:

  1. Lagtick() calls el.getBoundingClientRect() on every body every frame, forcing a layout reflow at 60fps.
  2. Container blowoutextractLeaves() sets width/height on intermediate nodes, but stop() only clears styles on leaf bodies and .gravity-body roots. Intermediate nodes keep their locked dimensions, breaking layout on deactivation.

Approach — Option A: Cache + dirty-set + CSS transition glide-back

Data structures

PhysicsBody gains four new fields:

  • width, height — snapshotted from getBoundingClientRect() at start(), used in tick() for floor collision instead of live DOM queries
  • originX, originYrect.left/rect.top at activation time, used as the glide-back transform target

GravityEngine gains:

  • dirtyElements: HTMLElement[] — every element whose inline styles are touched (leaves, intermediate containers, .gravity-body roots). stop() iterates this single list to fully wipe styles.

start() — three strictly ordered phases

  1. Size-lock phase — walk .gravity-body containers and all descendants via extractLeaves. For each intermediate node (non-solid, has children), write width/height from offsetWidth/offsetHeight and push to dirtyElements. Push .gravity-body roots too.
  2. Read phase — batch all getBoundingClientRect() across collected leaf elements before touching any positions. One layout flush, no interleaving.
  3. Write phase — using snapshotted rects, set each leaf to position: fixed, zero left/top, apply translate(rect.left, rect.top). Cache width/height/originX/originY. Push leaf to dirtyElements. Attach pointer events.

tick() — one change

Replace el.getBoundingClientRect().height with body.height. Remove redundant position/left/top re-sets inside the dragging branch (set once at start, never change).

stop() — glide-back then full cleanup

  1. Cancel animation frame, set isRunning = false.
  2. For each body: apply transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1) and set transform: translate(originX, originY).
  3. After 600ms (setTimeout): iterate dirtyElements, clear all inline styles (transform, position, left, top, width, height, margin, transition). Document flow fully restored.
  4. Clear bodies[] and dirtyElements[].

Success criteria

  • No layout reflow during animation loop
  • Toggling gravity off glides all elements back to their original positions with a spring curve
  • After glide completes, all intermediate container styles are cleared and layout is indistinguishable from the initial page load