Hmbown/CodeWhale · error · Error

Invalid pet raster size.

Error message

Invalid pet raster size.

What it means

terminal(width, height) rasterizes the pet into a character grid and validates dimensions up front: width and height must be safe integers in [1, 512]. Anything else (0, negative, fractional, >512, NaN, non-number) throws 'Invalid pet raster size.' This bounds the cell buffer at width*height before rendering.

Solutions

  1. Clamp dimensions before calling: Math.max(1, Math.min(512, Math.floor(w)))
  2. Skip the render when width/height are 0/NaN and re-render on the next resize
  3. Guard the resize handler until the terminal reports real integer dimensions
  4. Split oversized views into multiple <=512 rasters if a bigger image is needed

Example fix

// before
const art = pet.terminal(measuredWidth, measuredHeight);
// after
const w = Math.floor(measuredWidth), h = Math.floor(measuredHeight);
const art = (w >= 1 && w <= 512 && h >= 1 && h <= 512) ? pet.terminal(w, h) : null;
Defensive patterns

Strategy: validation

Validate before calling

function canRasterize(w, h) {
  return [w, h].every(n => Number.isSafeInteger(n) && n >= 1 && n <= 512);
}

Try / catch

try {
  art = pet.terminal(w, h);
} catch (err) {
  if (err.message === 'Invalid pet raster size.') art = pet.terminal(clampInt(w,1,512), clampInt(h,1,512));
  else throw err;
}

Prevention

When it happens

Trigger: Calling terminal() with dimensions from unvalidated UI state (0-size pane while hidden, fractional CSS px values, DPI-scaled values like 80.5); passing terminal.cols/rows before they are initialized (NaN/0); requesting a raster larger than 512 in either axis.

Common situations: Layout code measuring a hidden container as 0; passing floats from resize observers; forgetting the 512 hard cap when rendering a very large window.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/d6c6ed13cc170f04. Report an issue: GitHub.

Appendix: source

Thrown at pet/src/core/pet-native.ts:99

    const next = this.engine.clone();
    for (const event of events) next.observe(event, timeMs);
    this.engine = next;
  }
  advanceEngine(timeMs: number, motion: boolean, waiting: boolean): void {
    const target = Math.floor(timeMs * 30 / 1000 + 1e-8);
    if (!Number.isFinite(timeMs) || target < this.engineTick || target - this.engineTick > 300) throw new Error('Engine pet clock jump.');
    this.engine.confirmWaiting(timeMs, waiting);
    const voices: PetVoice[] = [];
    while (this.engineTick < target) {
      if ((this.engineTick + 1) % 12 === 0) this.world.acceptTelemetry(this.engine.bucket(Math.floor(this.engineTick / 12)));
      this.world.step(1 / 30, { motion, sensitivity: 1 });
      voices.push(...this.world.voices);
      this.engineTick++;
    }
    this.world.voices = voices;
  }
  terminal(width: number, height: number): string {
    if (![width, height].every(n => Number.isSafeInteger(n) && n >= 1 && n <= 512)) throw new Error('Invalid pet raster size.');
    const { sim, frame } = this.world, l = layout(width * 2, height * 4, frame.state);
    const cells = Array(width * height).fill(0), bits = [[1, 8], [2, 16], [4, 32], [64, 128]];
    sim.p.forEach((p, i) => {
      if (frame.state.observed < .92 && i % 2 === 1) return;
      const x = Math.round(l.ox + p.x * l.scale * l.flipX), y = Math.round(l.oy + p.y * l.scale);
      if (x >= 0 && x < width * 2 && y >= 0 && y < height * 4) cells[Math.floor(y / 4) * width + Math.floor(x / 2)] |= bits[y % 4][x % 2];
    });
    return JSON.stringify({ width, height, cells, timeMs: frame.timeMs, channel: frame.state.channel, arch: ARCH_OF[frame.state.channel],
      hollow: frame.state.observed < .92, dozing: frame.behaviour === 'doze', lit: frame.state.lit });
  }
  pcmChannels(voicesJSON: string, startSample: number, length: number, rate: number): [Float32Array, Float32Array] {
    const p = renderPetPCM(JSON.parse(voicesJSON) as PetVoice[], startSample, length, rate);
    return [p.left, p.right];
  }
  pcm(voicesJSON: string, startSample: number, length: number, rate: number): string {
    return JSON.stringify(this.pcmChannels(voicesJSON, startSample, length, rate).map(channel => Array.from(channel)));
  }
}

View on GitHub (pinned to 433685b202)