What is this?This is a two-dimensional physics simulator that models gravity and collisions between circular objects (though it doesn't model rotation).How do you use it?You can tweak the following parameters of the simulation while it is running:Number of objects: The specified number of gravitational objects will be added to the scene with random sizes and velocities (replacing any existing objects) upon clicking Reset.Delay: The number of milliseconds to intentionally delay the simulation between steps when it's running on automatic (the better to see what's happening). You can also advance the simulation manually, in which case the delay has no effect.Border Behavior: Determines what happens to objects when they cross the borders of the scene. Options are Annihilate, Unbounded (default), Loop, Ricochet (which is perfectly elastic), and 50% Ricochet (which is less so).Show Fabric of Spacetime: Toggles whether to display visual markers that indicate the strength of gravitational fields by their displacement. Fabric Granularity: A number that determines the number of markers displayed (lower numbers result in higher marker density).Handle Collisions: Toggles whether to perform collision calculations when object velocities would cause them to intersect.Coefficient of Restitution: A number between 0 and 1 that determines the elasticity of collisions. At 1.0, collisions are perfectly elastic. At 0.0, collisions are perfectly inelastic and objects stick to each other upon collision.Even though it's more simulation than game, there are some aspects of user interaction besides fiddling with the parameters:Click to add an object.Click and drag to add an object with an initial velocity.Right click to remove an object.What do I care about?Improving PerformanceAnything that can improve performance when no delay is specified. Based on the massive performance improvements I saw when I refactored my implementation of Conway's Game of Life, I've tried to cache and reuse values in variables wherever possible and limit their recalculation or re-retrieval, to the point of passing retrieved values as parameters to functions that I know will need them, even if they could retrieve or calculate them themselves.I'll be grateful for any mathematical shortcuts that can reduce computation overhead without sacrificing accuracy.Conforming to Best PracticesWhether it's readability, maintainability, testability, or any other -bility, I appreciate any insights and recommendations for improvement.Improving Simulation AccuracySome unrealistic behavior seems inherent to gravity simulations. I'd love to know if there are any routine or standard ways to mitigate such behavior.Specifically: applying linear velocity to move a mass point around means you skip any velocity recalculations that would result from the changes in effective gravity all along the linear path of movement. This results in inaccuracies that are especially apparent when objects are moving at high velocity (and especially when accelerated quickly by a strong gravitational influence).A similar but different inaccuracy involves collisions; my collision code checks for overlapping objects, but if the relative velocity between two objects is high enough, one of the objects might get skipped over by the linear movement of the other object. One solution is to limit the max velocities of objects, but I'd be eager to hear about any other more suitable solutions, like (hypothetically) a way to efficiently calculate the objects' trajectories and check for intersections.What I don't really care aboutSeparation of concerns (unless you convince me otherwise)Being written as a simulation rather than as a game, the calculation and rendering portions of the code are intertwined; since something interesting happens after every calculation, I want those happenings to be redrawn on the screen after every step. I haven't split out rendering to its own virtual thread.Looks a little funky in ChromeChrome seems to handle the globalAlpha of a 2D canvas context differently from Firefox, Edge, and Internet Explorer at the time of this post. The gravity field fabric markers don't fade quickly enough in Chrome, so it looks like there are specks flying around the objects as they float around the screen. If anybody has recomendations for ways to work around that behavior, I'm open to suggestions, but at this point I'm not too concerned.const G = 6.674 * Math.pow(10, -11), // gravitational constant  MAX_RND_VEL = 5,  MAX_RND_MASS = 5 * Math.pow(10, 12),  MIN_RND_MASS = 50,  MAX_RND_SIZE = 8,  PI = Math.PI,  TWOPI = Math.PI * 2,  FADE_RATE = 4,  CANVAS = document.getElementById(canvas),  CURSOR_LINE_COLOR = cyan,  OBJECT_BORDER_COLOR = white,  GRAVITY_FIELD_MARKER_COLOR = rgb(150,200,150),  GRAVITY_FIELD_MARKER_RADIUS = 0.5,  txtNumObjects = document.getElementById(NumObjects),  chkShowFabric = document.getElementById(ShowFabric),  ddlFabricGranularity = document.getElementById(FabricGranularity),  ddlRestitution = document.getElementById(Restitution),  ddlBorderBehavior = document.getElementById(BorderBehavior),  chkHandleCollisions = document.getElementById(chkCollisions),  btnNextStep = document.getElementById(NextStep),  txtDelay = document.getElementById(Delay);const WIDTH = CANVAS.width,  HEIGHT = CANVAS.height,  DOUBLE_SIZE = MAX_RND_SIZE * 2,  CTX = CANVAS.getContext(2d);let numObjects = txtNumObjects.value,  cellSize = ddlFabricGranularity.value,  borderBehavior = ddlBorderBehavior.value,  showFabric = chkShowFabric.checked,  shouldHandleCollisions = chkHandleCollisions.checked,  Cr = ddlRestitution.value, // coefficient of restitution  delay = txtDelay.value,  stellarObjects = [],  running = false,  planting = false,  plantx, planty, mouseX, mouseY, // vars for adding new objects    gField = [],  gHeight, gWidth; // vars for spacetime fabric visualizationlet fadeMultiplier = cellSize / FADE_RATE;reset();ddlRestitution.addEventListener(change, function() {  Cr = this.value;});chkHandleCollisions.addEventListener(change, function() {  shouldHandleCollisions = this.checked;});CANVAS.addEventListener(mousedown, startDragCursor);CANVAS.addEventListener(mousemove, dragCursor);CANVAS.addEventListener(mouseup, releaseCursor);CANVAS.addEventListener(mouseout, function(event) {  planting = false;});ddlBorderBehavior.addEventListener(change, function() {  borderBehavior = this.value;});document.getElementById(Reset).addEventListener(click, function() {  reset();});document.getElementById(NextStep).addEventListener(click, function() {  step();});ddlFabricGranularity.addEventListener(change, function() {  cellSize = this.value;  fadeMultiplier = cellSize / FADE_RATE;  resetGravityField();  recalculateGravityField();  CTX.clearRect(0, 0, WIDTH, HEIGHT);  if (showFabric) {    drawGravityField();  }  drawObjects();});chkShowFabric.addEventListener(change, function() {  showFabric = this.checked;});document.getElementById(AutoStep).addEventListener(click, function() {  if (running) {    clearTimeout(running);    btnNextStep.disabled = false;    running = null;    this.value = Auto;  } else {    btnNextStep.disabled = true;    this.value = Stop;    running = setTimeout(loopStep, delay);  }});txtDelay.addEventListener(change, function() {  delay = +(this.value);});function startDragCursor(event) {  if (event.which === 3 || event.button === 2) {    event.preventDefault();    return false;  } else {    planting = true;    plantx = event.pageX - CANVAS.offsetLeft;    planty = event.pageY - CANVAS.offsetTop;    mouseX = plantx;    mouseY = planty;  }  event.preventDefault();}function dragCursor(event) {  if (planting) {    mouseX = event.pageX - CANVAS.offsetLeft;    mouseY = event.pageY - CANVAS.offsetTop;    if (!running) {      CTX.clearRect(0, 0, WIDTH, HEIGHT);      if (showFabric) {        drawGravityField();      }      drawObjects();    }    drawCursorLine();  }  event.preventDefault();}function releaseCursor(event) {  if (event.which === 3 || event.button === 2) {    removeObjectAt(event.pageX - CANVAS.offsetLeft, event.pageY - CANVAS.offsetTop);    event.preventDefault();    return false;  } else {    if (planting) {      let thing = getRandomStellarObject(),        upX = event.pageX - CANVAS.offsetLeft,        upY = event.pageY - CANVAS.offsetTop;      if (typeof plantx === undefined) {        plantx = upX;        planty = upY;      }      thing.x = upX;      thing.y = upY;      thing.velocity.x = MAX_RND_VEL * MAX_RND_VEL * (plantx - upX) / WIDTH;      thing.velocity.y = MAX_RND_VEL * MAX_RND_VEL * (planty - upY) / HEIGHT;      stellarObjects.push(thing);      numObjects++;      planting = false;      CTX.clearRect(0, 0, WIDTH, HEIGHT);      applyObjectGravityToFabric(upX, upY, thing.mass * G);      if (showFabric) {        drawGravityField();      }      drawObjects();    }  }  event.preventDefault();}// Clears all objects and regenerates them based on the chosen number of objectsfunction reset() {  resetGravityField();  numObjects = document.getElementById(NumObjects).value;  stellarObjects = [];  generateObjects(numObjects);  step();}// Repeatedly advances time based on the chosen delayfunction loopStep() {  let startTime = +new Date,    delta;  step();  if (delay === 0 || (delta = +new Date - startTime) >= delay) {    running = setTimeout(loopStep, 0);  } else {    running = setTimeout(loopStep, delay - delta);  }}// Advances time forward and recalculates all object positions/velocities as necessary, redrawing the canvas as necessaryfunction step() {  CTX.clearRect(0, 0, WIDTH, HEIGHT);  moveObjects();  if (showFabric) {    resetGravityFieldInPlace();    applyObjectsGravity();    drawGravityField();  } else {    applyObjectsGravity();  }  drawObjects();}// adds the specified number of objects to the universefunction generateObjects(num) {  for (let i = 0; i < num; i++) {    stellarObjects.push(getRandomStellarObject());  }}// Removes any stellar objects that overlap at the given xy coordinatesfunction removeObjectAt(x, y) {  for (let i = stellarObjects.length - 1; i >= 0; i--) {    let obj = stellarObjects[i];    let difX = obj.x - x,      difY = obj.y - y;    if (getDistance(difX, difY) <= obj.size) {      stellarObjects.splice(i, 1);    }  }  let newLength = stellarObjects.length;  if (newLength != numObjects) {    numObjects = newLength;    CTX.clearRect(0, 0, WIDTH, HEIGHT);    if (showFabric) {      resetGravityFieldInPlace();      recalculateGravityField();      drawGravityField();    }    drawObjects();  }}// Moves all objects based on their current velocitiesfunction moveObjects() {  if (shouldHandleCollisions) {    for (let i = 0; i < numObjects; i++) {      let o = stellarObjects[i];      let ov = o.velocity;      checkCollision(o, ov.x, ov.y, []);    }  }  for (let i = 0; i < numObjects; i++) {    let o = stellarObjects[i];    let ov = o.velocity;    let ovx = ov.x,      ovy = ov.y;    o.x += ovx;    o.y += ovy;    let ox = o.x,      oy = o.y;    if (borderBehavior !== Unbounded) {      if (borderBehavior === Loop) {        if (ox > WIDTH) {          o.x = 0;        } else if (ox < 0) {          o.x = WIDTH;        }        if (oy > HEIGHT) {          o.y = 0;        } else if (oy < 0) {          o.y = HEIGHT;        }      } else if (borderBehavior === Ricochet) {        if (ox > WIDTH || ox < 0) {          o.velocity.x = ovx * -1;        }        if (oy > HEIGHT || oy < 0) {          o.velocity.y = -1 * ovy;        }      } else if (borderBehavior === HalfRicochet) {        if (ox > WIDTH || ox < 0) {          o.velocity.x = ovx * (-0.5);          if (ox > WIDTH) {            o.x = WIDTH;          } else if (ox < 0) {            o.x = 0;          }        }        if (oy > HEIGHT || oy < 0) {          o.velocity.y = (-0.5) * ovy;          if (oy > HEIGHT) {            o.y = HEIGHT;          } else if (oy < 0) {            o.y = 0;          }        }      } else if (borderBehavior === Annihilate) {        if (ox > WIDTH || oy > HEIGHT || ox < 0 || oy < 0) {          removeObjectAt(ox, oy);        }      }    }  }}// Checks whether two objects are about to collide, and adjusts their velocities if necessaryfunction checkCollision(obj, ovx, ovy, objectsToIgnore) {  for (let i = 0; i < numObjects; i++) {    let test = stellarObjects[i],      shortCircuit = false;    if (test === obj) {      continue;    }    for (let j = 0, len = objectsToIgnore.length; j < len; j++) {      if (test === objectsToIgnore[j]) {        shortCircuit = true;        break;      }    }    if (shortCircuit) {      continue;    }    let ox = obj.x + ovx,      oy = obj.y + ovy,      tv = test.velocity;    let tvx = tv.x,      tvy = tv.y;    let tx = test.x + tvx,      ty = test.y + tvy;    let difx = tx - ox,      dify = ty - oy;    if (difx > DOUBLE_SIZE || dify > DOUBLE_SIZE || (difx === 0 && dify === 0)) {      continue;    }    let aSize = obj.size,      bSize = test.size;    let cumulativeSize = aSize + bSize;    let distance = getDistance(difx, dify);    if (distance <= cumulativeSize) {      handleCollision(obj, test, cumulativeSize, difx, dify, distance, ox, oy, tx, ty);      checkCollision(test, tvx, tvy, [obj].concat(objectsToIgnore)); // objectsToIgnore prevents a Night at the Roxbury collision loop    }  }}// Given two objects, their combined size, differences in their xy coordinates, distance, and their coordinates// updates their coordinates to ensure the objects don't overlap and adjusts their velocitiesfunction handleCollision(first, second, cumulativeSize, difX, difY, distance, x1, y1, x2, y2) {  let mass1 = first.mass,    mass2 = second.mass;  let cumulativeMass = mass1 + mass2;  let v1x = first.velocity.x,    v1y = first.velocity.y,    v2x = second.velocity.x,    v2y = second.velocity.y;  let v1 = Math.sqrt(v1x * v1x + v1y * v1y),    v2 = Math.sqrt(v2x * v2x + v2y * v2y);  let collisionAngle = Math.atan2(difY, difX);  let dir1 = Math.atan2(v1y, v1x),    dir2 = Math.atan2(v2y, v2x);  let d1cA = dir1 - collisionAngle,    d2cA = dir2 - collisionAngle;  let newXv1 = v1 * Math.cos(d1cA),    newYv1 = v1 * Math.sin(d1cA),    newXv2 = v2 * Math.cos(d2cA),    newYv2 = v2 * Math.sin(d2cA);  let massVCalc = mass1 * newXv1 + mass2 * newXv2;  let finalXv1 = (massVCalc + mass2 * Cr * (newXv2 - newXv1)) / cumulativeMass,    finalXv2 = (massVCalc + mass1 * Cr * (newXv1 - newXv2)) / cumulativeMass,    finalYv1 = newYv1,    finalYv2 = newYv2;  let cosAngle = Math.cos(collisionAngle),    sinAngle = Math.sin(collisionAngle);  first.velocity = {    x: cosAngle * finalXv1 - sinAngle * finalYv1,    y: sinAngle * finalXv1 + cosAngle * finalYv1  };  second.velocity = {    x: cosAngle * finalXv2 - sinAngle * finalYv2,    y: sinAngle * finalXv2 + cosAngle * finalYv2  };  // minimum translation difference to prevent overlaps:  let dx = first.x - second.x,    dy = first.y - second.y;  let d = Math.sqrt(dx * dx + dy * dy);  if (d < cumulativeSize) {    let mtd_multiplier = ((first.size + second.size - d) / d);    let mtd_x = mtd_multiplier * dx;    let mtd_y = mtd_multiplier * dy;    let im1 = 1 / mass1,      im2 = 1 / mass2;    let cumIm = im1 + im2;    let imCalc1 = (im1 / (cumIm)),      imCalc2 = (im2 / (cumIm));    first.x += mtd_x * imCalc1;    first.y += mtd_y * imCalc1;    second.x -= mtd_x * imCalc2;    second.y -= mtd_y * imCalc2;  }}// Applies an object's gravity to all other objects and to the gravity field fabric (if displayed)function applyObjectsGravity() {  for (let i = numObjects - 1; i >= 0; i--) {    let o = stellarObjects[i];    let ox = o.x,      oy = o.y,      om = o.massEffect;    applyObjectGravityToObjects(o, ox, oy, om, i);    if (showFabric) {      applyObjectGravityToFabric(ox, oy, om);    }  }}// Given an object, its xy coordinates, and its precalculated mass effect, applies given object's gravity to all other objectsfunction applyObjectGravityToObjects(stellarObject, x, y, massEffect, init) {  let objVel = stellarObject.velocity;  for (let i = init; i >= 0; i--) {    let currentTarget = stellarObjects[i];    if (currentTarget !== stellarObject) {      let targetX = currentTarget.x,        targetY = currentTarget.y,        targetME = currentTarget.massEffect,        targetVel = currentTarget.velocity;      let difY = y - targetY,        difX = x - targetX;      let distance = getDistance(difY, difX);      if (distance !== 0) {        let distSqr = distance * distance;        // F = G*m1*m2 / distance^2... acceleration = F / m... the current object's mass cancels out of Force equation to produce acceleration        let accelTarg = massEffect / distSqr,          accelObj = targetME / distSqr;        let yIsNegative = difY < 0;        let theta = Math.atan(difX / difY);        targetVel.x += difX === 0 ? 0 : ((yIsNegative ? -1 : 1) * accelTarg * Math.sin(theta));        targetVel.y += difY === 0 ? 0 : ((yIsNegative ? -1 : 1) * accelTarg * Math.cos(theta));        objVel.x -= difX === 0 ? 0 : ((yIsNegative ? -1 : 1) * accelObj * Math.sin(theta));        objVel.y -= difY === 0 ? 0 : ((yIsNegative ? -1 : 1) * accelObj * Math.cos(theta));      }    }  }}// For a given object (and its precalculated mass * the gravitational constant) adjusts the gravity field fabric accordinglyfunction applyObjectGravityToFabric(x, y, massEffect) {  let xMeasure = x / cellSize,    yMeasure = y / cellSize;  for (let i = 0; i < gHeight; i++) {    let row = gField[i];    for (let j = 0; j < gWidth; j++) {      let currentVector = row[j];      let oX = currentVector[0],        oY = currentVector[1],        difX = xMeasure - j,        difY = yMeasure - i;      let distance = getDistance(difX, difY);      if (distance !== 0) {        let force = (massEffect) / (distance * distance);        let xIsNegative = difX < 0,          yIsNegative = difY < 0;        let theta = Math.atan(difX / difY);        currentVector[0] += difX === 0 ? 0 : ((yIsNegative ? -1 : 1) * force * Math.sin(theta));        currentVector[1] += difY === 0 ? 0 : ((yIsNegative ? -1 : 1) * force * Math.cos(theta));      }    }  }}// Warps the gravity field fabric based on the mass of each stellar objectfunction recalculateGravityField() {  for (let i = 0; i < numObjects; i++) {    let o = stellarObjects[i];    let ox = o.x,      oy = o.y,      om = o.massEffect;    applyObjectGravityToFabric(ox, oy, om);  }}// Resets all the gravity field fabric markers to their placesfunction resetGravityFieldInPlace() {  for (let i = 0; i < gHeight; i++) {    let row = gField[i];    for (let w = 0; w < gWidth; w++) {      row[w] = [0, 0];    }  }}// Calculates the number of gravity field fabric markers based on granularity and initializes themfunction resetGravityField() {  gField = [];  let maxH = HEIGHT / cellSize,    maxW = WIDTH / cellSize;  for (let h = 0; h <= maxH; h++) {    let row = [];    for (let w = 0; w <= maxW; w++) {      row.push([0, 0]);    }    gField.push(row);  }  gHeight = gField.length;  gWidth = gField[0].length}// draws all stellar objectsfunction drawObjects() {  for (let i = 0; i < numObjects; i++) {    let o = stellarObjects[i];    drawObject(o);  }  if (planting) {    drawCursorLine();  }}// draws a line from the cursor planted point to the cursorfunction drawCursorLine() {  CTX.beginPath();  CTX.strokeStyle = CURSOR_LINE_COLOR;  CTX.moveTo(plantx, planty);  CTX.lineTo(mouseX, mouseY);  CTX.stroke();  CTX.closePath();}// draws a stellar object on the canvasfunction drawObject(o) {  let x = o.x,    y = o.y,    radius = o.size,    color = o.color;  CTX.beginPath();  CTX.arc(x, y, radius, 0, TWOPI);  CTX.fillStyle = color;  CTX.fill();  CTX.strokeStyle = OBJECT_BORDER_COLOR;  CTX.arc(x, y, radius, 0, TWOPI);  CTX.stroke();  CTX.closePath();}// Draws the markers for the gravity field fabricfunction drawGravityField() {  CTX.strokeStyle = GRAVITY_FIELD_MARKER_COLOR;  for (let i = 0; i < gHeight; i++) {    let row = gField[i],      iMeasure = i * cellSize;    for (let j = 0; j < gWidth; j++) {      drawVectorDot(j * cellSize, iMeasure, row[j]);    }  }}// Draws a marker for the gravity field fabric, given the XY coordinates of the dot and a vector representing how much it's been warpedfunction drawVectorDot(x, y, vector) {  let vx = vector[0],    vy = vector[1];  let endX = x + vx,    endY = y + vy;  CTX.globalAlpha = fadeMultiplier / Math.sqrt(vx * vx + vy * vy); // the farther the marker is pulled, the more it fades from view  CTX.beginPath();  CTX.arc(endX, endY, GRAVITY_FIELD_MARKER_RADIUS, 0, TWOPI);  CTX.stroke();  CTX.globalAlpha = 1;}// returns an object with random color, position, velocity, and mass/sizefunction getRandomStellarObject() {  let randomMass = MIN_RND_MASS + Math.random() * (MAX_RND_MASS - MIN_RND_MASS);  let scale = MAX_RND_SIZE / Math.pow(3 * MAX_RND_MASS / 4 / PI, 1 / 3); // size formula based on volume of a sphere  let randomSize = Math.pow(3 * randomMass / 4 / PI, 1 / 3) * scale,    randomColor = getRandomColor();  let velocity = Math.random() * MAX_RND_VEL;  let bsq = Math.random() * velocity;  return {    color: randomColor,    size: randomSize,    mass: randomMass,    x: WIDTH / 6 + (4 * WIDTH / 6 * Math.random()),    y: HEIGHT / 6 + (4 * HEIGHT / 6 * Math.random()),    velocity: {      x: (Math.random() * 2 > 1 ? -1 : 1) * Math.sqrt(bsq),      y: (Math.random() * 2 > 1 ? -1 : 1) * Math.sqrt(velocity - bsq)    },    massEffect: randomMass * G  }}// Returns the distance between two points given the difference between their x and y valuesfunction getDistance(difX, difY) {  return Math.sqrt(difX * difX + difY * difY);}function getRandomColor() {  return rgb( + (Math.random() * 256 >>> 0) + , + (Math.random() * 256 >>> 0) + , + (Math.random() * 256 >>> 0) + );}#AutoStep {  font-weight: bold;}canvas {  cursor: crosshair;  background-color: black;  border: 1px solid black;  vertical-align: text-top;  -ms-touch-action: none;}input,.controls {  font-size: 12pt;  font-family: Calibri;}input {  padding: 2px;}.controls {  text-align: center;  background-color: #bfbfbf;  display: inline-block;  vertical-align: text-top;  border: 1px solid black;}label {  cursor: pointer;}.container {  display: inline-block;  width: 552px;}.controls .inner {  display: inline-block;}.controls .section {  text-align: left;  background-color: #dfdfdf;  border: 1px solid #9f9f9f;  padding: 2px;  margin: 1px;  display: inline-block;  vertical-align: text-top;}input[type=number] {  max-width: 2em;}<div class=container>  <div class=controls>    <div class=section>      <div class=inner>        Objects:        <input id=NumObjects type=number value=3 />      </div>      <input type=button id=Reset value=Reset />    </div>    <div class=section>      <div class=inner>        <input type=button id=NextStep value=Next />        <input type=button id=AutoStep value=Auto />      </div>      <div class=inner>Delay:        <select id=Delay>          <options>            <option value=0>none</option>            <option value=10>10 ms</option>            <option value=30 selected=selected>30 ms</option>            <option value=60>60 ms</option>            <option value=500>500ms</option>            <option value=1000>1 sec</option>          </options>        </select>      </div>    </div>    <div class=section>Border:      <select id=BorderBehavior>        <options>          <option value=Annihilate>Annihilate</option>          <option selected=selected value=Unbounded>Unbounded</option>          <option value=Loop>Loop</option>          <option value=Ricochet>Ricochet</option>          <option value=HalfRicochet>50% Ricochet</option>        </options>      </select>    </div>    <div class=section>      <label>        <input type=checkbox id=ShowFabric checked=checked />Show Fabric</label>      <div>Granularity:        <select id=FabricGranularity>          <options>            <option value=5>5</option>            <option value=7>7</option>            <option value=10>10</option>            <option value=13>13</option>            <option value=15>15</option>            <option value=20 selected=selected>20</option>            <option value=30>30</option>          </options>        </select>      </div>    </div>    <div class=section>      <div class=inner>        <label for=chkCollisions>          <input type=checkbox checked=checked id=chkCollisions />Handle Collisions</label>      </div>      <div>Restitution:        <select id=Restitution>          <options>            <option value=1>1.0</option>            <option value=0.9 selected=selected>0.9</option>            <option value=0.8>0.8</option>            <option value=0.7>0.7</option>            <option value=0.6>0.6</option>            <option value=0.5>0.5</option>            <option value=0.4>0.4</option>            <option value=0.3>0.3</option>            <option value=0.2>0.2</option>            <option value=0.1>0.1</option>            <option value=0.0>0.0</option>          </options>        </select>      </div>    </div>  </div>  <canvas id=canvas height=550 oncontextmenu=return false; width=550>no canvas available</canvas></div>
Do I understand the gravity of the situation?
javascript;simulation;physics