Help! Very baffling code behavior, simple loop breaks pattern

tl;dr: any simple loop breaks my pattern. The loop doesn’t do anything, is super short, and uses unique variables. Both a for loop and a while loop do this. By all reason they should have no effect whatsoever, but it breaks a specific seemingly unrelated part of the pattern.

Long version: I’m working on this pattern, it’s a modified copy of another pattern I’ve made that works fine. 1D pixel space. It works like this: “spots” of light spawn with random traits - location, hue, speed, size, they float around, despawn, repeat. They new version of the code introduces “gravity”, so the spots are attracted to eachother. This means each spot needs to process it’s relation to every other spot, hence the nested loop. But every time I add even the most benign inner loop, then new spots spawn with a size of 0.

Here’s a video showing the problem: https://drive.google.com/file/d/1BpZOx_3hQQbqXdIMshqzykYVvnHAX_vF/view?pli=1

And here’s the full code:


var decay = 0.98
var G = 0.00001

var numSpots = 2
var fadeTime = 1200

export var position =							array(numSpots)
export var size =									array(numSpots)
export var speed =								array(numSpots)
export var hue =									array(numSpots)
export var deathTimeRemaining =		array(numSpots)
export var fadeIn =								array(numSpots)


baseTime = 2
offsetTime = 0.15

var tVals = array(12)
tVals[0] = baseTime + random(offsetTime)
tVals[1] = baseTime + random(offsetTime)
tVals[2] = baseTime + random(offsetTime)
tVals[3] = baseTime + random(offsetTime)
tVals[4] = baseTime + random(offsetTime)
tVals[5] = baseTime + random(offsetTime)
tVals[6] = baseTime + random(offsetTime)
tVals[7] = baseTime + random(offsetTime)
tVals[8] = baseTime + random(offsetTime)
tVals[9] = baseTime + random(offsetTime)


var RMAX = [1, 0.15,  0.5, 1, 200];
var RMIN = [0, 0.03, -0.5, 0,  100];

function initializeSpot(spot) {
  for (var k = 0; k < 5; k++) {
    var Tw = time(tVals[k]);
    var Tc = time(tVals[2*k]);

    var range = RMAX[k] - RMIN[k];
    var w = wave(Tw) * range;
    var c = wave(Tc) * (range - w) + RMIN[k];
    var v = c + random(w);

    if      (k === 0) position[spot] = v
    else if (k === 1) size[spot]     = v  // <-- THIS IS ZERO WHEN THERE IS A LOOP
    else if (k === 2) speed[spot]    = v
    else if (k === 3) hue[spot]      = v
    else if (k === 4) deathTimeRemaining[spot] = v;
  }

  if (size[spot] <= 1/256) size[spot] = 0.02;

  fadeIn[spot] = 0;
}

// Initialize all spots
for (var i = 0; i < numSpots; i++) {
	initializeSpot(i);
}

export function beforeRender(delta) {

  for (var i = 0; i < numSpots; i++) {
    if (size[i] <= 1/256) {
      // recycle dead spots
      deathTimeRemaining[i] -= delta;
      if (deathTimeRemaining[i] <= 0) initializeSpot(i); 
      continue;
    }
    var xi = position[i];
    var mi = size[i];

    // for (y = 0; y < 3; y++) {} <-- HERE IS THE PROBLEM
    
    // z = 0                  <-- OR HERE
    // while(z < 5){
    //   z++
    // }
    
    
    //   var mj = size[j]
    //   if (mj > 1/256){
  
    //     // signed shortest separation x_j - x_i ∈ [-0.5, 0.5]
    //     var d = position[j] - xi
    //     if (d >  0.5) d -= 1
    //     else if (d < -0.5) d += 1
  
    //     if (d != 0 && d < 0.3 && d > -0.3){
  
      //   // 1/r^2 with sign via s = sign(d); no sqrt needed
      //   var r2    = d * d;
      //   var invr2 = 1 / r2;
      //   var sgn   = d > 0 ? 1 : -1;
  
      //   // a_i =  G * sj * sgn / r^2; a_j = -G * si * sgn / r^2
      //   var fac = delta * G * sgn * invr2;   // common factor for this pair
      //   speed[i] += fac * mj * delta;        // += a_i * delta
      //   speed[j] -= fac * mi * delta;        // += a_j * delta
        
        // rv = speed[j] - speed[i];
        // if(rv != 0 && abs(rv) * delta < 0.5){
        //   tHit = -d/rv
        //   if(tHit > 0 && tHit <= delta){
        //     // COLLISION
        //     speed[i] = (mi * speed[i] + size[j] * speed[j]) / (mi + size[j])
            
        //     mi += size[j]
        //     size[j] = 0
        //   }
        // }
    //     }
    //   }
    // }

    // size decay (your logic)
    mi *= decay
    size[i] = mi

    // integrate position; wrap without modulo
    var x = xi + (speed[i] * delta)/1000;
    if (x >= 1) x -= 1;
    else if (x < 0) x += 1;
    position[i] = x;

    if (fadeIn[i] < fadeTime) fadeIn[i] += delta;
  }
}

export function render3D(index, x, y, attr) {
	if(index >= 215) return
	
		var v = 0
		var h
		for (var i = 0; i < numSpots; i++) {
			if (size[i] > 1/256) {
				dist = abs(index/215 - position[i])
				if(dist > 1 - size[i]){
					dist = 1-dist
				}
				if(dist < size[i]){
					var scale = fadeIn[i] / fadeTime
					prox = ((size[i] - dist) / size[i]) * scale
					if(prox > v){
						v = prox
						h = hue[i]
					}
				}
			}
		}
		
		
		
	ring = false
	invert = false
	if(attr > 0){
		invert = true
		if(attr >= 0.9){
			ring = true
		}
	}
	
	hsv(index > 141? -h : h, 1, v)
}

Somebody please help me find some clue as to WTF is going on here. This installation is getting installed in 48 hours, so I’m freaking out a tiny bit.

There is something strange going on here that I haven’t managed to track down.

The (sort of) good news is, if you get rid of the continue, the pattern goes back to working as expected. The loops work, and you can add as much code as you want. Far as I can tell, the only effect on the pattern of leaving the continue out is that spots restart on the current frame, rather than on the next one.

Here’s what I saw while debugging:

If you put any loop in the code after the

    if (size[i] <= 1/256) {
      // recycle dead spots
      deathTimeRemaining[i] -= delta;
      if (deathTimeRemaining[i] <= 0) initializeSpot(i); 
      continue;
    }

block, (where the commented loop blocks are) the pattern fails as described. Using exported debug variables to track the calculations in initializeSpot() shows that v is being calculated correctly, and it is apparently called properly if k == 1 to set size[spot]. But if a second loop is present, size[spot] remains 0 outside of initializeSpot().

It’s not execution time related. I added a huge block of random trig functions in place of the secondary loops, and everything worked fine.

Maybe there’s math I’m not seeing that’s stomping the size array, but I’m slightly worried that in this case, continue is somehow misbehaving. Would love for someone to find the totally obvious thing that I’m not seeing. Reserved word as variable name? Out-of-bounds write to another array, maybe?

1 Like

Thanks for catching that! I had a suspicion that continue was problematic and pruned it from the area of code I was working in, but I didn’t see that one there.

Still very odd, but now I know not to use continue and I can keep working on this.

Could be a bug with continue I’ll take a look and boil this code down to make a regression test. Thanks for posting it!

I don’t see an obvious issue with stepping on an array but have you tried using an ‘else’ instead of ‘continue’ to see if that fixes the issue?

    if (size[i] <= 1/256) {
      // recycle dead spots
      deathTimeRemaining[i] -= delta;
      if (deathTimeRemaining[i] <= 0) initializeSpot(i); 
    } else {
      var xi = position[i];
      var mi = size[i];
      ...

That’s what I ended up doing, yes. Once I was aware that continue was a problem, that was a pretty clear workaround.