Hmbown/CodeWhale · error · Error

Invalid pet random stream.

Error message

Invalid pet random stream.

What it means

The mulberry32 PRNG's restore(state) accepts only a 32-bit unsigned integer: it must be a safe integer in [0, 0xffffffff]. Any other value throws 'Invalid pet random stream.' This keeps the pet's deterministic simulation reproducible — a corrupted RNG state would silently desync frames and scores.

Solutions

  1. Coerce before restore: state = Math.floor(Number(state)) >>> 0, then validate range
  2. Persist the RNG state exactly as state() returns it, without further arithmetic or string conversion
  3. If the saved value fails validation, restart the stream from a known seed instead of restoring a corrupted one
  4. Verify the value's provenance — only feed back output of the stream's own state() accessor

Example fix

// before
rng.restore(saved.state);
// after
const s = Math.floor(Number(saved.state));
if (Number.isSafeInteger(s) && s >= 0 && s <= 0xffffffff) rng.restore(s >>> 0);
else rng = mulberry32(DEFAULT_SEED);
Defensive patterns

Strategy: validation

Validate before calling

function canRestoreRngState(state) {
  return Number.isSafeInteger(state) && state >= 0 && state <= 0xffffffff;
}

Type guard

const isRngState = (v: unknown): v is number =>
  typeof v === 'number' && Number.isSafeInteger(v) && v >= 0 && v <= 0xffffffff;

Try / catch

try {
  rng.restore(saved);
} catch (err) {
  if (err.message === 'Invalid pet random stream.') rng = mulberry32(DEFAULT_SEED);
  else throw err;
}

Prevention

When it happens

Trigger: Calling restore() with a float (e.g. saved after arithmetic that left 0.5), a negative number, a value > 4294967295, or a non-number (string/undefined) — often from a hand-written or truncated checkpoint containing the RNG state.

Common situations: Restoring RNG state from JSON where it was serialized as a string; saving state before the >>>0 normalization; computing state via division instead of bit ops; mixing two PRNG streams' states.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at pet/src/core/pet-sim.ts:97

    throw new Error('Invalid pet state.');
}

const hex2rgb = (h: string) => [parseInt(h.slice(1, 3), 16), parseInt(h.slice(3, 5), 16), parseInt(h.slice(5, 7), 16)];
const RGB = CHANNELS.map(c => hex2rgb(c.color));
const UNKNOWN_RGB = hex2rgb('#738492');
const REST_RGB = [122, 214, 240];

// mulberry32 — a 32-bit seeded PRNG tiny enough to port by hand correctly.
export function mulberry32(seed: number) {
  let a = seed >>> 0;
  return Object.assign(() => {
    a = (a + 0x6D2B79F5) >>> 0;
    let t = a;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  }, { state: () => a, restore: (state: number) => {
    if (!Number.isSafeInteger(state) || state < 0 || state > 0xffffffff) throw new Error('Invalid pet random stream.');
    a = state;
  } });
}

export interface Particle {
  x: number; y: number; vx: number; vy: number;
  s: number; jx: number; jy: number; pod: number;
  hx: number; hy: number; ang: number; rad: number; tail: number;
  tx: number; ty: number;
}

export interface Frame {
  r: number; g: number; b: number;   // particle colour, 0..255
  alpha: number;                     // uniform per-dot alpha
  hollow: boolean;                   // coverage gap: rings, not filled dots
  channel: string;                   // active category
  arch: string;                      // active archetype
  work: number;                      // rest↔work blend actually applied

View on GitHub (pinned to 433685b202)