docs: gravity engine fix design spec

This commit is contained in:
2026-04-24 13:18:10 +02:00
parent abb7651e41
commit 571cf35151

View 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