Back to Gravity Dots

Documentation

API

Gravity Dots - API Reference

Complete reference for all game logic modules exported by src/game-logic.js. These are pure functions, testable in Node.js, and replicated in the browser HTML file.

Module Exports

All modules are re-exported through game-logic.js:

javascript
import * as GravityDots from './src/game-logic.js';


Physics Module (physics.js)

calculateGravityForce(particle, well, G)

Calculate gravitational force between particle and gravity well using inverse-square law.

Parameters:

  • particle {Object} - Particle with {x, y} position
  • well {Object} - Gravity well with {x, y, mass}
  • G {number} - Gravity constant (typically 50000)
Returns: {number} Force magnitude (pixels/sec²)

Formula: F = (G × mass) / distance²

javascript
const force = calculateGravityForce(
  { x: 100, y: 100 },
  { x: 150, y: 100, mass: 1.0 },
  50000
);  // Returns 2000

getEffectiveWells(particle, wells)

Filter wells that are currently affecting the particle (within effect radius).

Parameters:

  • particle {Object} - Particle with {x, y} position
  • wells {Array} - Array of well objects
Returns: {Array} Wells within 150px effect radius

javascript
const effective = getEffectiveWells(
  { x: 100, y: 100 },
  [
    { x: 120, y: 100, effectRadius: 150 },  // Included (20px away)
    { x: 300, y: 100, effectRadius: 150 }   // Excluded (200px away)
  ]
);  // Returns [{x: 120, ...}]

updateParticleVelocity(particle, wells, G, deltaTime)

Update particle velocity based on gravitational forces from all wells.

Parameters:

  • particle {Object} - Particle with {x, y, vx, vy, radius}
  • wells {Array} - Array of well objects
  • G {number} - Gravity constant
  • deltaTime {number} - Time step in seconds (1/60 = 0.01667)
Returns: {Object} Updated particle with new {vx, vy}

javascript
const updated = updateParticleVelocity(
  { x: 100, y: 100, vx: 400, vy: 0, radius: 8 },
  [{ x: 100, y: 150, mass: 1.0, effectRadius: 150 }],
  50000,
  0.01667
);  // vx: 400, vy: ~60 (accelerated downward)

updateParticlePosition(particle, deltaTime)

Update particle position based on velocity.

Parameters:

  • particle {Object} - Particle with {x, y, vx, vy}
  • deltaTime {number} - Time step in seconds
Returns: {Object} Updated particle with new {x, y}

javascript
const updated = updateParticlePosition(
  { x: 100, y: 200, vx: 400, vy: 0 },
  0.01667
);  // x: ~106.67, y: 200

checkTargetCollision(particle, target)

Check if particle has collided with target.

Parameters:

  • particle {Object} - Particle with {x, y, radius}
  • target {Object} - Target with {x, y, hitboxRadius} (32px)
Returns: {boolean} True if distance ≤ hitboxRadius

javascript
const hit = checkTargetCollision(
  { x: 1100, y: 400, radius: 8 },
  { x: 1100, y: 400, hitboxRadius: 32 }
);  // true

checkWallCollision(particle, wall)

Check if particle has collided with axis-aligned rectangular wall.

Parameters:

  • particle {Object} - Particle with {x, y, radius}
  • wall {Object} - Wall with {x, y, width, height}
Returns: {boolean} True if particle overlaps wall

javascript
const hit = checkWallCollision(
  { x: 610, y: 300, radius: 8 },
  { x: 600, y: 250, width: 100, height: 300 }
);  // true

checkBoundaryCollision(particle, canvasWidth)

Check if particle has exited the screen (off-screen miss).

Parameters:

  • particle {Object} - Particle with {x, y}
  • canvasWidth {number} - Canvas width (1200)
Returns: {boolean} True if x > canvasWidth + 100

javascript
const offScreen = checkBoundaryCollision(
  { x: 1301, y: 400 },
  1200
);  // true (1301 > 1300)


Game State Module (game-state.js)

createGameState()

Create initial game state.

Returns: {Object} Game state with defaults:

  • mode: 'select' - Game mode (select/playing/complete)
  • currentLevel: null - Current level number
  • wells: [] - Placed gravity wells
  • particle: null - Active particle
  • sparks: [] - Spark particles
  • attempts: 1 - Attempt counter
  • starsEarned: {1: 0, 2: 0, ...} - Stars per level
  • audioMuted: false - Mute toggle

javascript
const state = createGameState();
// { mode: 'select', currentLevel: null, wells: [], ... }

loadLevel(state, levelNumber)

Load and start a level.

Parameters:

  • state {Object} - Current game state
  • levelNumber {number} - Level to load (1-7)
Returns: {Object} Updated state in 'playing' mode with cleared wells

javascript
const state = loadLevel(gameState, 1);
// state.mode: 'playing'
// state.currentLevel: 1
// state.attempts: 1

placeWell(state, x, y)

Place a gravity well at the given position.

Parameters:

  • state {Object} - Current game state
  • x {number} - X coordinate
  • y {number} - Y coordinate
Returns: {Object} Updated state with new well (if valid)

Validation:

  • Particle cannot be launched
  • Max 3 wells per attempt
  • Well mass assigned per level configuration

javascript
const state = loadLevel(gameState, 1);
const updated = placeWell(state, 400, 300);
// updated.wells: [{ id: 'well-...', x: 400, y: 300, mass: 0.5, ... }]

removeWell(state, wellId)

Remove a gravity well by ID.

Parameters:

  • state {Object} - Current game state
  • wellId {string} - ID of well to remove
Returns: {Object} Updated state without the well

javascript
const wellId = state.wells[0].id;
const updated = removeWell(state, wellId);
// updated.wells.length: 0

launchParticle(state, canvasHeight)

Launch the particle from the left edge.

Parameters:

  • state {Object} - Current game state
  • canvasHeight {number} - Canvas height (800)
Returns: {Object} Updated state with launched particle

Particle starts at:

  • Position: (32, canvasHeight/2) = (32, 400)
  • Velocity: (400, 0) px/sec

javascript
const updated = launchParticle(state, 800);
// updated.particle: { x: 32, y: 400, vx: 400, vy: 0, ... }

resetLevel(state)

Clear all wells and particle, increment attempts.

Parameters:

  • state {Object} - Current game state
Returns: {Object} Updated state ready for new attempt

javascript
const updated = resetLevel(state);
// updated.wells: []
// updated.particle: null
// updated.attempts: 2

calculateStarRating(wellsUsed, par)

Calculate star rating based on wells used vs. par.

Parameters:

  • wellsUsed {number} - Number of wells placed
  • par {number} - Par value for level
Returns: {number} Stars earned (1, 2, or 3)

Rating:

  • 3 stars: wells ≤ par
  • 2 stars: wells = par + 1
  • 1 star: wells > par + 1

javascript
calculateStarRating(1, 1);  // 3
calculateStarRating(2, 1);  // 2
calculateStarRating(3, 1);  // 1

completeLevel(state, starRating)

Mark level as complete and save star rating.

Parameters:

  • state {Object} - Current game state
  • starRating {number} - Stars earned (1-3)
Returns: {Object} Updated state in 'complete' mode

javascript
const updated = completeLevel(state, 3);
// updated.mode: 'complete'
// updated.starsEarned[1]: 3

getWellMass(levelNumber, wellIndex)

Get the mass assigned to a well at a specific index for a level.

Parameters:

  • levelNumber {number} - Level (1-7)
  • wellIndex {number} - Well index (0, 1, 2)
Returns: {number|undefined} Mass (0.5, 1.0, 2.0) or undefined

javascript
getWellMass(1, 0);  // 0.5 (Level 1 has only 1 well)
getWellMass(6, 2);  // 2.0 (Level 6, 3rd well is strong)
getWellMass(1, 1);  // undefined


Particles Module (particles.js)

createSparkParticles(particle, currentTime)

Emit 5-10 spark particles from particle center with random velocities.

Parameters:

  • particle {Object} - Particle with {x, y, vx, vy}
  • currentTime {number} - Current time in seconds
Returns: {Array} Array of spark objects

Spark properties:

  • Position: particle center
  • Velocity: ±50 px/sec radial
  • Radius: 2-4 px random
  • Color: #ffffff, #00ddff, or #0088ff
  • Lifetime: 0.3 seconds

javascript
const sparks = createSparkParticles(
  { x: 300, y: 200, vx: 400, vy: 0 },
  0.5
);
// sparks.length: 5-10
// sparks[0]: { x: 300, y: 200, radius: 3, lifetime: 0.3, ... }

updateSparkParticles(sparks, currentTime, deltaTime)

Update spark positions and remove expired sparks.

Parameters:

  • sparks {Array} - Current spark particles
  • currentTime {number} - Current time in seconds
  • deltaTime {number} - Time step (1/60)
Returns: {Array} Updated sparks, filtered for lifetime < 0.3sec

javascript
const updated = updateSparkParticles(
  sparks,
  0.516,
  0.01667
);
// Sparks older than 0.3 seconds are removed

addTrailPoint(trailPoints, x, y, time)

Add a position point to the particle trail.

Parameters:

  • trailPoints {Array} - Existing trail points
  • x {number} - X coordinate
  • y {number} - Y coordinate
  • time {number} - Time in seconds
Returns: {Array} Updated trail with new point

javascript
const trail = addTrailPoint([], 100, 200, 0);
// trail: [{ x: 100, y: 200, time: 0 }]

pruneTrailPoints(trailPoints, currentTime, maxTrailAge)

Remove trail points older than maxTrailAge.

Parameters:

  • trailPoints {Array} - Current trail points
  • currentTime {number} - Current time in seconds
  • maxTrailAge {number} - Max age (0.2 seconds for 80px trail at 400 px/sec)
Returns: {Array} Pruned trail points

javascript
const pruned = pruneTrailPoints(
  [
    { x: 100, y: 200, time: 0 },
    { x: 105, y: 200, time: 0.1 }
  ],
  0.25,
  0.2
);
// Returns only the second point (first is > 0.2 sec old)


Audio Module (audio.js)

createAudioContext()

Create or get the Web Audio API context.

Returns: {AudioContext|null} Audio context or null if not supported

javascript
const ctx = createAudioContext();
// ctx.sampleRate: 44100
// ctx.destination: AudioDestinationNode

playWhomp(context, startTime)

Play "whomp" sound: 110 Hz tone with 0.3 sec decay.

Parameters:

  • context {AudioContext} - Web Audio context
  • startTime {number} - Start time in context time
Returns: {OscillatorNode} Oscillator node

Frequency: A3 (110 Hz) Duration: 0.3 seconds Level: -8 dB

javascript
const osc = playWhomp(audioContext, audioContext.currentTime);

playRisingTone(context, startTime)

Play "rising tone": 400→600 Hz frequency sweep, 0.4 sec.

Parameters:

  • context {AudioContext} - Web Audio context
  • startTime {number} - Start time
Returns: {OscillatorNode} Oscillator

Duration: 0.4 seconds Level: -8 dB

javascript
playRisingTone(audioContext, audioContext.currentTime);

playChimeCascade(context, startTime)

Play chime cascade: E6→G6→B6→E7, ~0.6 sec total.

Parameters:

  • context {AudioContext}
  • startTime {number}
Returns: {Array} Array of OscillatorNodes

Frequencies:

  • E6: 1319 Hz
  • G6: 1568 Hz
  • B6: 1976 Hz
  • E7: 2637 Hz

javascript
const notes = playChimeCascade(audioContext, audioContext.currentTime);

playStarPing(context, startTime)

Play star ping: 1568 Hz tone, 0.1 sec.

Parameters:

  • context {AudioContext}
  • startTime {number}
Returns: {OscillatorNode}

Frequency: G6 (1568 Hz) Duration: 0.1 seconds

javascript
playStarPing(audioContext, audioContext.currentTime);

playThud(context, startTime)

Play thud: 80 Hz noise burst filtered at 200 Hz, 0.2 sec.

Parameters:

  • context {AudioContext}
  • startTime {number}
Returns: {AudioBufferSourceNode|null}

Duration: 0.2 seconds Filter: Low-pass at 200 Hz

javascript
playThud(audioContext, audioContext.currentTime);

playVictoryJingle(context, startTime)

Play victory jingle: A4→B4→C5→D5, ~0.8 sec.

Parameters:

  • context {AudioContext}
  • startTime {number}
Returns: {Array} OscillatorNodes

Frequencies:

  • A4: 440 Hz
  • B4: 494 Hz
  • C5: 523 Hz
  • D5: 587 Hz

javascript
playVictoryJingle(audioContext, audioContext.currentTime);


Levels Module (levels.js)

getLevel(levelNumber)

Get complete configuration for a level.

Parameters:

  • levelNumber {number} - Level (1-7)
Returns: {Object|null} Level config or null if invalid

Level config contains:

  • levelNumber {number}
  • targetPosition {Object} - {x, y}
  • par {number} - 1, 2, or 3
  • walls {Array} - Wall obstacles
  • wellMasses {Array} - Preset masses
  • difficulty {string} - Tutorial/Intermediate/Expert

javascript
const level = getLevel(1);
// {
//   levelNumber: 1,
//   targetPosition: { x: 1100, y: 400 },
//   par: 1,
//   walls: [],
//   wellMasses: [0.5],
//   difficulty: 'Tutorial'
// }

getLevelCount()

Get total number of levels.

Returns: {number} 7

javascript
const count = getLevelCount();  // 7

getLevelPar(levelNumber)

Get par (ideal well count) for a level.

Parameters:

  • levelNumber {number} - Level (1-7)
Returns: {number} Par value (1, 2, or 3)

javascript
getLevelPar(1);  // 1
getLevelPar(6);  // 3

getLevelWellMasses(levelNumber)

Get preset well masses for a level.

Parameters:

  • levelNumber {number} - Level (1-7)
Returns: {Array} Array of masses [0.5, 1.0, 2.0]

javascript
getLevelWellMasses(3);  // [0.5, 1.0]
getLevelWellMasses(6);  // [0.5, 1.0, 2.0]

getLevelWalls(levelNumber)

Get wall obstacles for a level.

Parameters:

  • levelNumber {number} - Level (1-7)
Returns: {Array} Wall objects {x, y, width, height}

Walls present on levels 4-7 only.

javascript
const walls = getLevelWalls(4);
// [{ x: 600, y: 250, width: 100, height: 300 }, ...]

getLevelTarget(levelNumber)

Get target position for a level.

Parameters:

  • levelNumber {number} - Level (1-7)
Returns: {Object} Target {x, y, hitboxRadius}

Hitbox radius: 32 px (fixed)

javascript
const target = getLevelTarget(1);
// { x: 1100, y: 400, hitboxRadius: 32 }


Data Structures

Particle

javascript
{
  x: number,              // Center x-coordinate
  y: number,              // Center y-coordinate
  vx: number,             // Velocity x (px/sec)
  vy: number,             // Velocity y (px/sec)
  radius: 8,              // Particle radius (constant)
  isLaunched: boolean,    // Has launch been triggered?
  trailPoints: Array      // [{x, y, time}, ...]
}

Gravity Well

javascript
{
  id: string,              // Unique ID
  x: number,              // Center x
  y: number,              // Center y
  mass: number,           // 0.5, 1.0, or 2.0
  effectRadius: 150,      // Gravity effect radius (constant)
  createdTime: number     // Creation timestamp
}

Spark Particle

javascript
{
  x: number,              // Center x
  y: number,              // Center y
  vx: number,             // Velocity x (px/sec)
  vy: number,             // Velocity y (px/sec)
  radius: number,         // 2-4 px
  createdTime: number,    // Creation timestamp
  lifetime: 0.3,          // Duration in seconds (constant)
  color: string           // #ffffff, #00ddff, or #0088ff
}

Game State

javascript
{
  mode: string,                    // 'select', 'playing', or 'complete'
  currentLevel: number|null,       // 1-7 or null
  wells: Array,                    // Gravity well objects
  particle: Object|null,           // Particle object or null
  sparks: Array,                   // Spark particle objects
  attempts: number,                // Attempt counter (starts at 1)
  starsEarned: Object,             // {1: 0-3, 2: 0-3, ...}
  audioMuted: boolean              // Mute toggle state
}


Constants

Physics

  • GRAVITY_CONSTANT: 50,000
  • EFFECT_RADIUS: 150 px
  • PARTICLE_RADIUS: 8 px
  • TARGET_HITBOX_RADIUS: 32 px
  • CANVAS_WIDTH: 1200 px
  • CANVAS_HEIGHT: 800 px
  • INITIAL_PARTICLE_VELOCITY: 400 px/sec

Audio

  • Master gain: -3 dB
  • SFX level: -8 dB
  • Sample rate: 44,100 Hz

Particles

  • Trail length: 80 px
  • Spark lifetime: 0.3 seconds
  • Spark radius: 2-4 px
  • Spark emission rate: 5-10 per frame

Testing

All modules are fully tested with 159 unit tests using Node.js node:test:

bash
npm test
# ✓ 159 tests pass
# ✓ 0 tests fail

Test categories:

  • Physics calculations
  • Game state transitions
  • Particle effects
  • Audio constants
  • Level data
  • Star ratings
  • Edge cases

Version

Gravity Dots 1.0.0 API Reference - February 2026

All functions are pure and deterministic (same input → same output). Safe for browser and server environments.

© 2026 Jonathan Leahy · v1.0.1