docs: gravity engine fix design spec
This commit is contained in:
46
docs/superpowers/specs/2026-04-24-gravity-fix-design.md
Normal file
46
docs/superpowers/specs/2026-04-24-gravity-fix-design.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 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. **Lag** — `tick()` calls `el.getBoundingClientRect()` on every body every frame, forcing a layout reflow at 60fps.
|
||||
2. **Container blowout** — `extractLeaves()` 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`, `originY` — `rect.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
|
||||
Reference in New Issue
Block a user