FS - Gravity Toy
Version: 0.2.0 Date: 2026-04-12 Author: Rens Roosloot / Claude collaboration
1. Purpose
This document defines the functional behavior for the current release of the Gravity Toy described in URS.md.
2. Functional Scope
Included in this release:
- 2D n-body gravity simulation
- four named presets (Solar System, Chaos, Binary, Sandbox)
- orb absorption on collision with momentum conservation
- drag-to-launch with hold-for-mass mechanic
- pause/resume
- preset selector and reset
- three-segment gradient trails
- glow hierarchy (background field, outer glow, bright core)
- force lines between close orb pairs
- launch burst ring on spawn
- merge double-ring pulse and white flash on survivor
- hint text on load
- NL/EN i18n
- breadcrumb navigation and pager links
Excluded from this release:
- audio
- save/load or seed sharing
- editable simulation parameters (sliders)
- score or goal mechanics
- animation export
3. User Flow
- User opens the page.
- The simulation starts immediately with the Solar System preset.
- The user watches planets orbit the central star.
- The user drags on the canvas to launch a new orb.
- The user holds longer before releasing to increase the launched orb's mass.
- The user switches preset, which resets the simulation to that configuration.
- The user pauses, observes, and resumes.
- The user resets to restore the current preset cleanly.
4. Page Structure
The page shall contain:
- site header with navigation and language toggle
- breadcrumb: Home / Visuals / Gravity Toy
- section header with title and short description
- full-width canvas area (the simulation)
- minimal control panel (preset selector, reset, pause/resume)
- pager linking to the previous visual (ASCII Star Runner)
- site footer
5. Simulation Behavior
5.1 Physics Model
- every orb exerts a gravitational pull on every other orb
- gravitational force:
F = G * m1 * m2 / (d² + SOFTENING) G = 0.2,SOFTENING = 50- each orb has position, velocity, and mass
- radius is derived from mass via a power-law formula:
r = mass^0.45 * 1.5 - the power law compresses the high-mass end so large bodies do not visually overwhelm small ones
- forces are accumulated each frame and applied as velocity deltas
- fixed orbs (Solar star) contribute gravity to all others but do not update their own position or velocity
5.2 Absorption
- when two orbs overlap (distance < sum of radii), the larger absorbs the smaller
- the surviving orb's mass equals the sum of both masses
- the surviving orb's radius is recalculated from the new mass
- the surviving orb's velocity is updated using conservation of momentum
- a double-ring absorption pulse plays at the collision point
- the surviving orb flashes white for approximately 14 frames
5.3 Drag-to-Launch
- pressing and holding on the canvas begins a drag gesture
- a dashed ghost circle appears at the press point, growing in radius as hold time increases
- when the user drags, a velocity arrow appears from press point to current cursor position
- on release, an orb spawns at the press point with:
- velocity proportional to drag distance and direction (
velocity = drag_vector * 0.05) - mass proportional to hold time:
mass = min(30 + hold_ms * 0.08, 600)
- velocity proportional to drag distance and direction (
- a launch burst ring expands briefly at the spawn point on release
5.4 Boundary Behavior
- orbs that travel beyond 200 px outside the canvas bounds are removed from the simulation
6. Presets
6.1 Solar System
- one central star (mass 2000), marked
fixed— it does not move regardless of gravitational pull from planets - eight planets at orbital radii [60, 90, 118, 150, 182, 215, 248, 278] px from center
- planet masses: [12, 28, 18, 70, 25, 55, 20, 8]
- initial velocities set for near-circular orbits with slight eccentricity (±8% radial perturbation)
- the system feels serene and stable
6.2 Chaos
- 30 orbs distributed across three converging clusters
- clusters start at opposite corners/edges and move toward the center
- orb masses vary widely (8–63) to create chain reaction merges on arrival
- the system feels explosive from the first seconds
6.3 Binary
- two large stars (mass 2000 and 1800), placed 120 px either side of canvas center
- initial velocities set for two-body circular orbit
- 15 debris orbs scattered at radii 80–270 px from center
- debris has partial orbital velocity (0–65% of local circular speed) plus small random component
- the system feels dramatic and gravitationally intense
6.4 Sandbox
- one quiet anchor orb (mass 120) at canvas center, no velocity
- the canvas is otherwise empty
- the user builds their own system by launching orbs
7. Visual Behavior
7.1 Glow Hierarchy (five render passes)
- Background field — for orbs with mass > 800: soft radial haze extending 7× the orb radius, makes gravitational presence visible at a distance
- Force lines — faint teal lines between pairs of orbs within 9× the larger orb's radius; opacity scales with proximity
- Gradient trails — three opacity segments from dim tail to bright head (0.05 / 0.13 / 0.30); trail width scales with orb radius
- Outer glow — soft radial gradient extending 2.5–4.5× the orb radius depending on mass
- Core — bright white centre fading to the orb's velocity/mass color at the edge
7.2 Orb Color
- mass > 800: warm gold (
hsl(45, 90%, 72%)) - mass > 200: medium gold (
hsl(30, 85%, 65%)) - smaller orbs: velocity-mapped hue from blue-violet (slow) to warm yellow (fast)
7.3 Absorption Pulse
- outer ring: expands at 7 px/frame over PULSE_LIFE (35) frames, fades from 0.55 alpha
- inner ring: expands at 2.5 px/frame, lives for first half of PULSE_LIFE, fades from 0.45 alpha
- surviving orb: white radial flash, fades over FLASH_LIFE (14) frames
7.4 Launch Burst
- teal expanding ring at spawn point on release
- expands at 4 px/frame over LAUNCH_LIFE (20) frames, fades from 0.65 alpha
7.5 Drag Preview
- dashed ghost circle at press point, radius grows with hold time
- velocity arrow from press point to current cursor position when drag exceeds 10 px
- arrowhead at cursor tip
7.6 Hint Text
- displayed at the bottom of the canvas on load
- visible for 200 frames (~3.3 s at 60 fps), then fades over 80 frames
- text is language-aware (NL/EN)
- suppressed under
prefers-reduced-motion
8. Controls
| Control | Behavior |
|---|---|
| Preset selector (4 buttons) | Switches active preset and resets simulation |
| Reset | Resets simulation to the current active preset |
| Pause / Resume | Freezes or unfreezes the simulation |
- controls placed in a compact panel adjacent to the canvas
- preset buttons use a 2×2 grid layout
- active preset button is visually highlighted
- pause button label updates to reflect current state
9. i18n Behavior
- all user-facing text strings are available in Dutch (NL) and English (EN)
- language is driven by
?lang=URL parameter first, thenlocalStorage, then defaults to NL - handled by shared
i18n.jsviainitLanguage(strings) languagechangeevent keeps control labels in sync when language is toggled
10. Accessibility
- canvas has
aria-hidden="true" - all interactive controls have visible labels or
aria-label - simulation auto-pauses if
prefers-reduced-motionis set; trails, pulses, and hints are also suppressed
11. Acceptance Criteria
- FS-01: Simulation starts automatically on page load with Solar System preset.
- FS-02: Orbs attract each other via gravity every frame.
- FS-03: Collision causes the smaller orb to be absorbed; surviving orb grows.
- FS-04: Conservation of momentum is applied on absorption.
- FS-05: Dragging on the canvas spawns an orb at the press point with directed velocity.
- FS-06: Hold time during drag increases the spawned orb's mass; ghost circle grows visibly.
- FS-07: A launch burst ring plays at the spawn point on release.
- FS-08: A white flash plays on the surviving orb after every merge.
- FS-09: Preset selector resets to the chosen configuration.
- FS-10: Reset button restores the current preset.
- FS-11: Pause/resume correctly freezes and unfreezes the simulation; all render effects (merge flash, absorption pulse, launch burst) freeze in place during pause and resume cleanly.
- FS-12: The Solar star does not drift off-screen.
- FS-13: Orbs that escape the canvas bounds are removed.
- FS-14: Velocity/mass coloring is visibly applied to all orbs.
- FS-15: Gradient trails are visible on moving orbs.
- FS-16: Force lines appear between close orb pairs.
- FS-17: Absorption double-ring pulse plays on each merge event.
- FS-18: Reduced-motion preference auto-pauses and suppresses trails, pulses, and hints.
- FS-19: NL and EN strings are both complete and applied correctly.
- FS-20:
?lang=enURL parameter boots the page in English.