Hmbown/CodeWhale · error · Error

Invalid pet score checkpoint.

Error message

Invalid pet score checkpoint.

What it means

This score-voice scheduler keeps a checkpoint of its playback position as a 3-element array [lastWindow, lastSequence, lastAddress] and validates it on restore(). The stored window and sequence must be safe integers in [-1, PET_MAX_SECONDS * 2.5] and the address flag a boolean; otherwise it throws 'Invalid pet score checkpoint.' to avoid resuming playback from a corrupt position.

Solutions

  1. Guard the restore call: only call restore() with a value previously returned by checkpoint(), else skip and start from the beginning.
  2. Validate before restoring — same predicate as the library (Array length 3, safe integers in range, boolean) — and discard invalid checkpoints.
  3. Clear the stale persisted checkpoint after a version upgrade that changes PET_MAX_SECONDS or the checkpoint shape.
  4. Check the storage layer for JSON serialization of NaN/Infinity becoming null, and store sanitized values.

Example fix

// before
petScore.restore(saved.checkpoint); // may be null/corrupt
// after
const cp = saved.checkpoint;
const ok = Array.isArray(cp) && cp.length === 3
  && cp.slice(0, 2).every(n => Number.isSafeInteger(n) && n >= -1 && n <= PET_MAX_SECONDS * 2.5)
  && typeof cp[2] === 'boolean';
if (ok) petScore.restore(cp);
Defensive patterns

Strategy: try-catch

Validate before calling

const isValidCheckpoint = (v, maxSeconds) =>
  Array.isArray(v) && v.length === 3 &&
  v.slice(0, 2).every(n => Number.isSafeInteger(n) && n >= -1 && n <= maxSeconds * 2.5) &&
  typeof v[2] === 'boolean';

Type guard

const isScoreCheckpoint = (v) =>
  Array.isArray(v) && v.length === 3 &&
  Number.isSafeInteger(v[0]) && Number.isSafeInteger(v[1]) &&
  v[0] >= -1 && v[1] >= -1 && typeof v[2] === 'boolean';

Try / catch

try {
  score.restore(saved);
} catch (e) {
  if (e.message === 'Invalid pet score checkpoint.') {
    // fall back to a fresh scheduler instead of a corrupt position
    score = createPetScore();
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling restore() with null/undefined (no checkpoint saved yet), an array of the wrong length, a JSON-round-tripped value where numbers became strings, NaN from a corrupted store, or an out-of-range sequence from an older build with a different PET_MAX_SECONDS.

Common situations: Persisting the checkpoint to disk/config and loading it after an upgrade that changed PET_MAX_SECONDS; a JSON serializer turning Infinity/NaN into null; restoring before the first checkpoint() call; handing restore() the wrong object (e.g. the whole state blob instead of the 3-tuple).

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/572c2b88f5dcf94d. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/pet-native.js:1567

factories["pet-audio"]=function(exports,require){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PetScore = void 0;
exports.renderPetPCM = renderPetPCM;
const pet_sim_js_1 = require("./pet-sim.js");
const model_js_1 = require("./model.js");
/** Core emits score events; WebAudio / AVAudioEngine only present their PCM.
 * Calling at any display cadence produces the same score when all world ticks
 * are supplied. Calling twice for a world tick cannot retrigger a voice. */
class PetScore {
    lastWindow = -1;
    lastSequence = -1;
    lastAddress = false;
    checkpoint() { return [this.lastWindow, this.lastSequence, this.lastAddress]; }
    restore(value) {
        if (!Array.isArray(value) || value.length !== 3
            || !value.slice(0, 2).every(n => Number.isSafeInteger(n) && n >= -1 && n <= pet_sim_js_1.PET_MAX_SECONDS * 2.5) || typeof value[2] !== 'boolean')
            throw new Error('Invalid pet score checkpoint.');
        [this.lastWindow, this.lastSequence, this.lastAddress] = value;
    }
    voices(frame) {
        const time = frame.timeMs / 1000, window = Math.floor((frame.timeMs + 1e-7) / 400);
        const out = [];
        const add = (id, frequency, duration, gain, pan = 0, delay = 0, kind = 'tone') => out.push({ id, start: time + delay, duration, frequency, gain, pan, kind });
        const t = frame.telemetry, fresh = t !== undefined && t.sequence !== this.lastSequence;
        if (fresh) {
            this.lastSequence = t.sequence;
            for (let c = 0; c < pet_sim_js_1.CHANNELS.length; c++) {
                const channel = pet_sim_js_1.CHANNELS[c], n = t.onsets[c];
                if (!n || channel.sustained || ['human', 'error'].includes(channel.key))
                    continue;
                add(`onset:${t.sequence}:${c}`, channel.freq, .24, .035 * Math.min(2, Math.sqrt(n)), (c / 12 - .5) * .7);
            }
            if (t.errors)
                add(`tear:${t.sequence}`, pet_sim_js_1.CHANNELS.find(c => c.key === 'error').freq, .22, .05, 0, 0, 'noise');
        }

View on GitHub (pinned to 73e0f67d83)