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:
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}positionwell{Object} - Gravity well with{x, y, mass}G{number} - Gravity constant (typically 50000)
Formula: F = (G × mass) / distance²
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}positionwells{Array} - Array of well objects
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 objectsG{number} - Gravity constantdeltaTime{number} - Time step in seconds (1/60 = 0.01667)
{vx, vy}
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
{x, y}
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)
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}
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)
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 numberwells: []- Placed gravity wellsparticle: null- Active particlesparks: []- Spark particlesattempts: 1- Attempt counterstarsEarned: {1: 0, 2: 0, ...}- Stars per levelaudioMuted: false- Mute toggle
const state = createGameState();
// { mode: 'select', currentLevel: null, wells: [], ... }
loadLevel(state, levelNumber)
Load and start a level.
Parameters:
state{Object} - Current game statelevelNumber{number} - Level to load (1-7)
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 statex{number} - X coordinatey{number} - Y coordinate
Validation:
- Particle cannot be launched
- Max 3 wells per attempt
- Well mass assigned per level configuration
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 statewellId{string} - ID of well to remove
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 statecanvasHeight{number} - Canvas height (800)
Particle starts at:
- Position: (32, canvasHeight/2) = (32, 400)
- Velocity: (400, 0) px/sec
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
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 placedpar{number} - Par value for level
Rating:
- 3 stars: wells ≤ par
- 2 stars: wells = par + 1
- 1 star: wells > par + 1
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 statestarRating{number} - Stars earned (1-3)
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)
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
Spark properties:
- Position: particle center
- Velocity: ±50 px/sec radial
- Radius: 2-4 px random
- Color: #ffffff, #00ddff, or #0088ff
- Lifetime: 0.3 seconds
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 particlescurrentTime{number} - Current time in secondsdeltaTime{number} - Time step (1/60)
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 pointsx{number} - X coordinatey{number} - Y coordinatetime{number} - Time in seconds
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 pointscurrentTime{number} - Current time in secondsmaxTrailAge{number} - Max age (0.2 seconds for 80px trail at 400 px/sec)
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
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 contextstartTime{number} - Start time in context time
Frequency: A3 (110 Hz) Duration: 0.3 seconds Level: -8 dB
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 contextstartTime{number} - Start time
Duration: 0.4 seconds Level: -8 dB
playRisingTone(audioContext, audioContext.currentTime);
playChimeCascade(context, startTime)
Play chime cascade: E6→G6→B6→E7, ~0.6 sec total.
Parameters:
context{AudioContext}startTime{number}
Frequencies:
- E6: 1319 Hz
- G6: 1568 Hz
- B6: 1976 Hz
- E7: 2637 Hz
const notes = playChimeCascade(audioContext, audioContext.currentTime);
playStarPing(context, startTime)
Play star ping: 1568 Hz tone, 0.1 sec.
Parameters:
context{AudioContext}startTime{number}
Frequency: G6 (1568 Hz) Duration: 0.1 seconds
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}
Duration: 0.2 seconds Filter: Low-pass at 200 Hz
playThud(audioContext, audioContext.currentTime);
playVictoryJingle(context, startTime)
Play victory jingle: A4→B4→C5→D5, ~0.8 sec.
Parameters:
context{AudioContext}startTime{number}
Frequencies:
- A4: 440 Hz
- B4: 494 Hz
- C5: 523 Hz
- D5: 587 Hz
playVictoryJingle(audioContext, audioContext.currentTime);
Levels Module (levels.js)
getLevel(levelNumber)
Get complete configuration for a level.
Parameters:
levelNumber{number} - Level (1-7)
Level config contains:
levelNumber{number}targetPosition{Object} - {x, y}par{number} - 1, 2, or 3walls{Array} - Wall obstacleswellMasses{Array} - Preset massesdifficulty{string} - Tutorial/Intermediate/Expert
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
const count = getLevelCount(); // 7
getLevelPar(levelNumber)
Get par (ideal well count) for a level.
Parameters:
levelNumber{number} - Level (1-7)
getLevelPar(1); // 1
getLevelPar(6); // 3
getLevelWellMasses(levelNumber)
Get preset well masses for a level.
Parameters:
levelNumber{number} - Level (1-7)
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)
Walls present on levels 4-7 only.
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)
Hitbox radius: 32 px (fixed)
const target = getLevelTarget(1);
// { x: 1100, y: 400, hitboxRadius: 32 }
Data Structures
Particle
{
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
{
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
{
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
{
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,000EFFECT_RADIUS: 150 pxPARTICLE_RADIUS: 8 pxTARGET_HITBOX_RADIUS: 32 pxCANVAS_WIDTH: 1200 pxCANVAS_HEIGHT: 800 pxINITIAL_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:
npm test
# ✓ 159 tests pass
# ✓ 0 tests failTest 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.