Hmbown/CodeWhale · error · Error
Invalid pet score checkpoint.
Error message
Invalid pet score checkpoint.
What it means
restore() validates a deserialized checkpoint tuple before assigning it back to the audio state. A valid checkpoint is exactly [lastWindow, lastSequence, lastAddress]: an array of length 3 whose first two entries are safe integers in [-1, PET_MAX_SECONDS*2.5] and whose third entry is a boolean. Anything else (wrong shape, wrong types, out-of-range values, or a corrupted/truncated payload) is rejected so bad persisted state can never poison render state.
Solutions
- Inspect the value passed to restore(): log or console.log it right before the call and verify it is exactly [number, number, boolean].
- Ensure checkpoints are only created via checkpoint() and stored verbatim — do not reshape, sort, or spread the tuple before saving.
- Clamp/sanitize legacy payloads before restoring: coerce 0/1 to boolean and clamp window/sequence into [-1, PET_MAX_SECONDS*2.5].
- If a versioned persisted format changed, migrate old checkpoints (or drop them and start from a fresh checkpoint()) rather than passing them to restore().
- Wrap restore() in try/catch and fall back to checkpoint() defaults on failure.
Example fix
// before
pet.restore(JSON.parse(saved));
// after
const raw = JSON.parse(saved);
const ck = Array.isArray(raw) && raw.length === 3
? [Number(raw[0]), Number(raw[1]), Boolean(raw[2])]
: pet.checkpoint();
try { pet.restore(ck); } catch { pet.restore(pet.checkpoint()); } Defensive patterns
Strategy: validation
Validate before calling
function isValidCheckpoint(v: unknown): boolean {
return Array.isArray(v) && v.length === 3
&& v.slice(0, 2).every(n => Number.isSafeInteger(n) && n >= -1 && n <= PET_MAX_SECONDS * 2.5)
&& typeof v[2] === 'boolean';
}
if (!isValidCheckpoint(raw)) raw = pet.checkpoint(); Type guard
function isCheckpoint(v: unknown): v is [number, number, boolean] {
return Array.isArray(v) && v.length === 3
&& Number.isSafeInteger(v[0]) && v[0] >= -1 && v[0] <= PET_MAX_SECONDS * 2.5
&& Number.isSafeInteger(v[1]) && v[1] >= -1 && v[1] <= PET_MAX_SECONDS * 2.5
&& typeof v[2] === 'boolean';
} Try / catch
try {
pet.restore(saved);
} catch (err) {
if (err instanceof Error && err.message === 'Invalid pet score checkpoint.') {
pet.restore(pet.checkpoint()); // reset to defaults
} else throw err;
} Prevention
- Always persist the exact array returned by checkpoint(); never reshape it.
- Use a type guard before restoring untrusted (localStorage/JSON) data.
- Booleans survive JSON round-trips, but verify no manual 0/1 encoding is used.
- Add a version field alongside stored checkpoints so schema changes trigger migration instead of restore().
When it happens
Trigger: Calling petAudio.restore() with: a non-array (null, object, string), an array of length != 3, non-integer or negative-below -1 or above PET_MAX_SECONDS*2.5 window/sequence values (e.g. NaN, Infinity, floats), or a non-boolean third element (e.g. 0/1 stored instead of true/false).
Common situations: Loading a checkpoint from localStorage/JSON that was written by an older library version with a different tuple arity; JSON round-trip coercion turning booleans into numbers or numbers into strings; a schema migration truncating or reordering the array; a hand-edited save file.
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
- Codewhale terminal receipt exceeded its total bound
- Duplicate .
- Invalid Engine pet metadata.
- Invalid Engine pet metadata fields.
- Invalid fact key: nonempty bounded text required.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/7db10ea876e91989.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/pet-audio.ts:21
import type { WorldFrame } from './pet-world.js';
export interface PetVoice {
id: string; start: number; duration: number; frequency: number;
gain: number; pan: number; kind: 'tone' | 'noise';
}
/** 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. */
export class PetScore {
private lastWindow = -1;
private lastSequence = -1;
private lastAddress = false;
checkpoint(): [number, number, boolean] { return [this.lastWindow, this.lastSequence, this.lastAddress]; }
restore(value: unknown): void {
if (!Array.isArray(value) || value.length !== 3
|| !value.slice(0, 2).every(n => Number.isSafeInteger(n) && n >= -1 && n <= PET_MAX_SECONDS * 2.5) || typeof value[2] !== 'boolean')
throw new Error('Invalid pet score checkpoint.');
[this.lastWindow, this.lastSequence, this.lastAddress] = value as [number, number, boolean];
}
voices(frame: WorldFrame): PetVoice[] {
const time = frame.timeMs / 1000, window = Math.floor((frame.timeMs + 1e-7) / 400);
const out: PetVoice[] = [];
const add = (id: string, frequency: number, duration: number, gain: number, pan = 0, delay = 0, kind: PetVoice['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 < CHANNELS.length; c++) {
const channel = 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}`, CHANNELS.find(c => c.key === 'error')!.freq, .22, .05, 0, 0, 'noise');
}
const address = frame.state.channel === 'human' && frame.state.attention > .5;View on GitHub (pinned to 433685b202)