Skip to content

Settle camera + simulation presets: fix the first-load feel - #6

Merged
aaltshuler merged 2 commits into
mainfrom
settle-camera-presets
Aug 26, 2026
Merged

Settle camera + simulation presets: fix the first-load feel#6
aaltshuler merged 2 commits into
mainfrom
settle-camera-presets

Conversation

@aaltshuler

@aaltshuler aaltshuler commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Problem (measured)

Storybook made it obvious: every fresh force-layout graph renders tiny and jittery. Two mechanisms, quantified on an 800-node clustered protocol:

  1. Distant blobfitViewOnFirstData frames the seed ring; the simulation then contracts the graph to 5–17% viewport fill across the entire parameter space (12-combo sweep; physics cannot fix this, only the camera can).
  2. Endless jitter — the engine's default cooling (decay 5000, friction 0.85) never reaches visible stillness inside 20s. A damped profile (friction 0.6, decay 1000) is visually still in ~5s with a clean exponential cooldown.

Fix

  • Settle camerafitViewOnSettle: 'follow' (default) | 'once' | false. 'follow' keeps the settling graph framed with periodic animated refits riding the engine frame fan-out (frame-counted: no timers, no rAF of its own — it dies with the frames it rides, honoring the quiescence invariant), plus one final fit at first quiescence. Any user camera input (pointer/wheel on the container, or any public camera call) cancels it. Fixed layouts never arm.
  • Simulation presetssimulation accepts 'calm' | 'spread' | 'tight' | 'lively' alongside a full config; SIMULATION_PRESETS and resolveSimulation exported. Values chosen by measured time-to-stillness, not vibes.
  • Default changed — omitted simulation now resolves to 'calm'; the previous engine-default feel is one prop away (simulation="lively").

Result (live, same fixture)

before after
viewport fill at settle 0.07 × 0.12 0.50 × 0.80
visible stillness never (>20s) ~5s

Tests

Nine new pinning tests (settle-camera.test.ts): default-calm config reaches the engine, preset identity no-ops, follow cadence + quiescence finale + post-settle silence, 'once'/false modes, frame cap, user-camera cancellation, fixed-layout never arms. One existing assertion updated (sim-controls): the mount replay now legitimately carries the default simulation config. Full check green: 1,794 tests.

🤖 Generated with Claude Code

https://claude.ai/code/session_018SeFxK217ZrHSERqK6kjcb

Greptile Summary

This PR improves first-load force-layout behavior by adding a settle-aware camera mode and named simulation presets, with calm simulation behavior as the new default.

  • Adds periodic and final settle-camera fitting with cancellation on explicit camera input.
  • Adds and exports simulation presets and preset resolution.
  • Exposes the settle-camera option through the React binding.
  • Adds regression coverage for settle modes, cancellation, and simulation preset propagation.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/core/src/instance.ts Implements settle-camera lifecycle and simulation preset resolution; the previously reported restored-camera overwrite is now addressed on both restore completion paths.
packages/core/src/types.ts Defines the simulation preset names, immutable preset configurations, resolver, and widened host-update input.
packages/react/src/Graph.tsx Exposes the construction-time settle-camera option and forwards widened simulation inputs to the core instance.
packages/core/test/settle-camera.test.ts Pins settle cadence, quiescence behavior, cancellation paths, fixed-layout behavior, and simulation preset propagation.

Reviews (2): Last reviewed commit: "Settle follow yields to a restored view-..." | Re-trigger Greptile

Two measured problems on every fresh force mount (800-node clustered
protocol, max displacement sampled at 500ms): the first-data fit frames
the seed ring while the simulation contracts the graph to 5-17% viewport
fill across the whole parameter space (a distant blob), and the engine's
default cooling never reaches visible stillness inside 20s (reads as
endless jitter).

- fitViewOnSettle construction option ('follow' default | 'once' | false):
  'follow' keeps the settling graph framed with periodic animated refits
  riding the engine frame fan-out (frame-counted — no timers, no rAF of
  its own, dies with the frames it rides), plus a final fit at first
  quiescence; user camera input (pointer/wheel on the container or any
  public camera call) cancels it. Fixed layouts never arm.
- simulation accepts preset names ('calm' | 'spread' | 'tight' |
  'lively'); SIMULATION_PRESETS + resolveSimulation exported; presets are
  frozen singletons so identity comparison keeps working.
- DEFAULT changed: omitted simulation resolves to 'calm' (visually still
  in ~5s) instead of engine defaults; the old feel is simulation="lively".
- Verified live: fill 0.5x0.8 after settle vs 0.07x0.12 before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018SeFxK217ZrHSERqK6kjcb
Comment thread packages/core/src/instance.ts
Review finding: a deep-linked setViewState restore during the first
settle window would have its explicitly restored camera overwritten by
the follow's next periodic or final fit. The restore now cancels the
follow; pinned by a test asserting the restored viewport stands.
@aaltshuler
aaltshuler merged commit 7a4a51e into main Aug 26, 2026
5 checks passed
@aaltshuler
aaltshuler deleted the settle-camera-presets branch August 26, 2026 17:47
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