Hmbown/CodeWhale · error · Error

Invalid pet score checkpoint.

Error message

Invalid pet score checkpoint.

What it means

The pet scorer can resume from a checkpoint array [lastWindow, lastSequence, lastAddress]. restore() validates the shape: an array of exactly 3 elements, the first two safe integers in [-1, PET_MAX_SECONDS * 2.5], and the third a boolean. Anything else throws this error, protecting the incremental scorer from corrupt resume state.

Solutions

  1. Regenerate the checkpoint by calling checkpoint() fresh and replaying from the start instead of restoring stale state.
  2. Validate before restoring: Array.isArray(v) && v.length === 3 && Number.isSafeInteger(v[0]) && Number.isSafeInteger(v[1]) && typeof v[2] === 'boolean'.
  3. If migrating from an old format, convert old checkpoints to the current 3-element shape or discard them.
  4. Check that your persistence layer round-trips numbers as numbers and booleans as booleans (e.g. JSON.stringify/parse, not string templates).

Example fix

// before
score.restore(saved.state); // saved.state is a JSON string
// after
const cp = typeof saved.state === 'string' ? JSON.parse(saved.state) : saved.state;
if (Array.isArray(cp) && cp.length === 3 && typeof cp[2] === 'boolean') score.restore(cp);
else score.restore([-1, -1, false]); // restart
Defensive patterns

Strategy: type-guard

Validate before calling

const isValidCheckpoint = (v) => Array.isArray(v) && v.length === 3
  && Number.isSafeInteger(v[0]) && Number.isSafeInteger(v[1])
  && v[0] >= -1 && v[1] >= -1 && v[1] <= 216_000 && typeof v[2] === 'boolean';
if (isValidCheckpoint(saved)) score.restore(saved); else score.restore([-1, -1, false]);

Type guard

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

Try / catch

try { score.restore(cp) } catch { score.restore([-1, -1, false]); /* restart scoring */ }

Prevention

When it happens

Trigger: Calling score.restore() with a checkpoint from an older format (different length or ordering), a JSON-serialized checkpoint whose boolean became a string, a null/undefined value, or a checkpoint truncated by storage.

Common situations: Version upgrades changing checkpoint shape, storing checkpoints in localStorage/JSON where booleans or numbers degrade, or passing the result of checkpoint() through a transform that wraps it in another array.

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@433685b202 (2026-09-15). Data as JSON: /api/errors/903a925986141c15. Report an issue: GitHub.

Appendix: source

Thrown at pet/ios/Resources/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 433685b202)