← back to Wild Orbs Opus
chore: lint, refactor, v1.0.1 (session close)
40f3e992178e677b00fff394d821284de5614ba8 · 2026-09-09 09:16:20 -0700 · Steve Abrams
Refactor (behavior-preserving): extracted the duplicated wrapped-delta ->
force -> velocity step from six gravity-well / black-hole pull sites into one
applyPull() helper. Each site keeps its own constants and falloff formula; only
the shared 'divide by distance, add to velocity' step is centralized. An
independent reviewer had scored maintainability 6/10 citing exactly this.
(One site regroups (dx/d)*f*1.1 as (dx/d)*(f*1.1) — a last-ULP float
associativity difference, not a semantic one.)
Fix found by the close-out gate: the adaptive-resolution controller could not
shed pixels when it most needed to. Downscale and upscale shared one cooldown
counter, and a recovery step sets it to 300 frames — so a recent upscale locked
out an urgently-needed downscale for ~10 seconds, leaving the game pinned at
29fps while refusing to lower resolution. A clearly-dropping framerate now
bypasses the cooldown, and a downscale reseeds frameAvg just under the trigger
instead of down at a healthy value (which had forced the average to re-climb
from scratch after every step). Measured on a loaded machine: convergence to
the floor went 9s -> 6s and the settled framerate 49fps -> 66fps.
This was surfacing as an intermittent 56/57 'holds framerate under load'
failure that I had been attributing to machine load. Sampling renderScale once
a second showed it stuck at 0.95, which is unreachable by downscale steps and
therefore proved an upscale cooldown was blocking it. Suite is back to 57/57
with cross-engine + touch green.
Also removed four stray *-dtd.mjs probe files left in the repo by panel
subagents.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XP13P5ZKs7oWJQ3pjbvpnG
Files touched
M index.htmlM package.json
Diff
commit 40f3e992178e677b00fff394d821284de5614ba8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 9 09:16:20 2026 -0700
chore: lint, refactor, v1.0.1 (session close)
Refactor (behavior-preserving): extracted the duplicated wrapped-delta ->
force -> velocity step from six gravity-well / black-hole pull sites into one
applyPull() helper. Each site keeps its own constants and falloff formula; only
the shared 'divide by distance, add to velocity' step is centralized. An
independent reviewer had scored maintainability 6/10 citing exactly this.
(One site regroups (dx/d)*f*1.1 as (dx/d)*(f*1.1) — a last-ULP float
associativity difference, not a semantic one.)
Fix found by the close-out gate: the adaptive-resolution controller could not
shed pixels when it most needed to. Downscale and upscale shared one cooldown
counter, and a recovery step sets it to 300 frames — so a recent upscale locked
out an urgently-needed downscale for ~10 seconds, leaving the game pinned at
29fps while refusing to lower resolution. A clearly-dropping framerate now
bypasses the cooldown, and a downscale reseeds frameAvg just under the trigger
instead of down at a healthy value (which had forced the average to re-climb
from scratch after every step). Measured on a loaded machine: convergence to
the floor went 9s -> 6s and the settled framerate 49fps -> 66fps.
This was surfacing as an intermittent 56/57 'holds framerate under load'
failure that I had been attributing to machine load. Sampling renderScale once
a second showed it stuck at 0.95, which is unreachable by downscale steps and
therefore proved an upscale cooldown was blocking it. Suite is back to 57/57
with cross-engine + touch green.
Also removed four stray *-dtd.mjs probe files left in the repo by panel
subagents.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XP13P5ZKs7oWJQ3pjbvpnG
---
index.html | 37 ++++++++++++++++++++++++++-----------
package.json | 2 +-
2 files changed, 27 insertions(+), 12 deletions(-)
diff --git a/index.html b/index.html
index 40bda2a..7e5d727 100644
--- a/index.html
+++ b/index.html
@@ -232,10 +232,19 @@ function resize(){
/** nudge the backing-store scale toward whatever holds ~60fps (with hysteresis) */
function adaptResolution(){
- if(scaleCooldown>0){ scaleCooldown--; return; }
+ // A recovery step sets a deliberately long cooldown so the resolution can't
+ // oscillate. But the cooldown is shared, so a recent upscale used to lock out
+ // an urgently-needed DOWNSCALE for ~10s — the game sat at 29fps refusing to
+ // shed pixels. A clearly-dropping framerate now bypasses the cooldown.
+ const urgent = frameAvg > 30;
+ if(scaleCooldown>0 && !urgent){ scaleCooldown--; return; }
if(frameAvg > 23 && renderScale > 0.55){
renderScale = Math.max(0.55, renderScale - 0.15);
- scaleCooldown = 40; frameAvg = 16.7; applyBackingStore();
+ // Seed just UNDER the trigger, not down at a healthy 16.7: reseeding to a
+ // good value made the average re-climb from scratch after every step, so
+ // converging took ~9s of visible jank. Now a couple of still-slow frames
+ // step again immediately. Recovery stays deliberately slow (below).
+ scaleCooldown = 24; frameAvg = 21; applyBackingStore();
} else if(frameAvg < 13.5 && renderScale < 1){
renderScale = Math.min(1, renderScale + 0.1);
scaleCooldown = 300; frameAvg = 16.7; applyBackingStore();
@@ -267,6 +276,15 @@ function wrapDelta(x1,y1,x2,y2){
return [dx,dy];
}
function wrapDist(a,b){ const [dx,dy]=wrapDelta(a.x,a.y,b.x,b.y); return Math.hypot(dx,dy); }
+/** turn a wrapped delta + force magnitude into a velocity nudge on entity.
+ * dx,dy,d must already be the wrapDelta/hypot to the field; f is the
+ * caller's own (already strength+falloff scaled) force for this pull site —
+ * every well/hole/entity pairing keeps its own formula, this just centralizes
+ * the shared "divide by distance, scale by force, add to velocity" step. */
+function applyPull(entity, dx, dy, d, f){
+ entity.vx += (dx/d)*f;
+ entity.vy += (dy/d)*f;
+}
/** Draw fn once per screen-wrap copy so entities never pop at the edges. */
function wrapDraw(x,y,r,fn){
@@ -1555,8 +1573,7 @@ function update(){
const [dx,dy] = wrapDelta(ship.x,ship.y,w.x,w.y);
const d = Math.hypot(dx,dy);
if(d < w.r && d>1){
- const f = w.str * (1 - d/w.r) * 0.9;
- ship.vx += (dx/d)*f; ship.vy += (dy/d)*f;
+ applyPull(ship, dx, dy, d, w.str * (1 - d/w.r) * 0.9);
}
}
if(G.time%2===0){
@@ -1578,8 +1595,7 @@ function update(){
const [dx,dy] = wrapDelta(ship.x,ship.y,h.x,h.y);
const d = Math.hypot(dx,dy);
if(d < h.pull && d>1){
- const f = 0.85*(1 - d/h.pull);
- ship.vx += (dx/d)*f*1.1; ship.vy += (dy/d)*f*1.1;
+ applyPull(ship, dx, dy, d, 0.85*(1 - d/h.pull)*1.1);
if(d < h.r + ship.r*0.4) killShip();
}
}
@@ -1587,8 +1603,7 @@ function update(){
const o=orbs[j];
const [dx,dy]=wrapDelta(o.x,o.y,h.x,h.y); const d=Math.hypot(dx,dy)||1;
if(d < h.pull){
- const f = 0.7*(1-d/h.pull);
- o.vx += (dx/d)*f; o.vy += (dy/d)*f;
+ applyPull(o, dx, dy, d, 0.7*(1-d/h.pull));
if(d < h.r*0.9){
h.eaten++;
addScore(40,o.x,o.y);
@@ -1684,7 +1699,7 @@ function update(){
for(const w of wells){
const [dx,dy]=wrapDelta(o.x,o.y,w.x,w.y); const d=Math.hypot(dx,dy);
- if(d<w.r && d>1){ const f=w.str*0.4*(1-d/w.r); o.vx+=(dx/d)*f; o.vy+=(dy/d)*f; }
+ if(d<w.r && d>1){ applyPull(o, dx, dy, d, w.str*0.4*(1-d/w.r)); }
}
// clamp runaway speed (wells + shockwaves + rubber boosts can stack)
@@ -1719,12 +1734,12 @@ function update(){
for(const w of wells){
const [dx,dy]=wrapDelta(b.x,b.y,w.x,w.y); const d=Math.hypot(dx,dy);
- if(d<w.r && d>1){ const f=w.str*1.5*(1-d/w.r); b.vx+=(dx/d)*f; b.vy+=(dy/d)*f; }
+ if(d<w.r && d>1){ applyPull(b, dx, dy, d, w.str*1.5*(1-d/w.r)); }
}
for(const h of holes){
const [dx,dy]=wrapDelta(b.x,b.y,h.x,h.y); const d=Math.hypot(dx,dy);
if(d<h.pull && d>1){
- const f=1.5*(1-d/h.pull); b.vx+=(dx/d)*f; b.vy+=(dy/d)*f;
+ applyPull(b, dx, dy, d, 1.5*(1-d/h.pull));
if(d<h.r*0.8){ burst(h.x,h.y,4,'#c07bff',1.6,{life:16}); dead = true; break; }
}
}
diff --git a/package.json b/package.json
index 1682d33..639520b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "wild-orbs",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Asteroids, except every orb detonates into a different disaster. One self-contained HTML file.",
"private": true,
"scripts": {
← 4e055c9 Keep the neutral build comparator; leave the Qwen3 repo pris
·
back to Wild Orbs Opus
·
(newest)