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
- Regenerate the checkpoint by calling checkpoint() fresh and replaying from the start instead of restoring stale state.
- Validate before restoring: Array.isArray(v) && v.length === 3 && Number.isSafeInteger(v[0]) && Number.isSafeInteger(v[1]) && typeof v[2] === 'boolean'.
- If migrating from an old format, convert old checkpoints to the current 3-element shape or discard them.
- 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
- Persist checkpoints with JSON.stringify/parse to preserve types
- Version-stamp checkpoints and discard stale formats
- Wrap restore() in a guard that falls back to a fresh checkpoint
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
- Invalid pet score checkpoint.
- Invalid pet state.
- 1
- Invalid coordination ledger
- Invalid pet particle checkpoint.
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)