Hmbown/CodeWhale · error · Error
Invalid pet world checkpoint.
Error message
Invalid pet world checkpoint.
What it means
PetWorld.restore performs deep validation of a checkpoint before rebuilding a world: frame field ranges and enums, food bounds, pod matching the member list, voice limits/ids, pet state, and more. If any structural invariant of the checkpoint blob fails, restore refuses to construct a world from corrupted or foreign data.
Solutions
- Obtain checkpoints only from world.checkpoint()/recording() output of a matching library version.
- Verify the checkpoint blob was stored/loaded losslessly (no truncation, no manual edits).
- If migrating across versions, run the checkpoint through the library's migration/validation path before restore.
Example fix
// before
const c = JSON.parse(partialString); // truncated
PetWorld.restore(points, tape, interactions, c); // throws
// after
if (!isPetCheckpoint(c)) throw new Error('not a checkpoint');
const world = PetWorld.restore(points, tape, interactions, c); Defensive patterns
Strategy: type-guard
Validate before calling
// Structural pre-check before restore:
if (!c?.frame || !['none','orient','approach','call'].includes(c.frame.needs) ||
!Array.isArray(c.voices) || c.voices.length > 1024 ||
JSON.stringify(c.frame.pod) !== JSON.stringify(c.members))
throw new Error('checkpoint fails structural pre-check'); Type guard
function isPetCheckpoint(v: unknown): v is PetCheckpoint {
const c = v as PetCheckpoint;
return !!c && !!c.frame && typeof c.frame.timeMs === 'number' &&
Array.isArray(c.voices) && c.voices.length <= 1024 &&
(!c.petCheckpointVersion || [1, 2].includes(c.petCheckpointVersion));
} Try / catch
try {
const world = PetWorld.restore(points, tape, interactions, c);
} catch (e) {
if (e.message === 'Invalid pet world checkpoint.') {
// discard corrupted blob or re-export from the source world
} else throw e;
} Prevention
- Only feed library-produced checkpoint blobs into restore.
- Guard storage against truncation (checksum the blob).
- Validate checkpoints at load time before restore.
When it happens
Trigger: Calling PetWorld.restore(points, tape, interactions, c) with a checkpoint whose frame fields are out of range, frame.pod differs from c.members, voices exceed 1024 entries or have invalid ids, or which was produced by a different/older schema.
Common situations: Hand-edited or partially serialized checkpoints; truncation during storage; loading a checkpoint saved by another app version whose schema drifted; JSON round-trips that drop undefined fields.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- 1
- Agent has no continuable checkpoint to resume from…
- Checkpoint schema v is newer than supported v
- Codewhale metadata event was not a terminal receipt
- Invalid pet particle checkpoint.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/80ab0bb9ce25f8f1.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/pet-world.ts:237
|| !integer(c.branchTick, -1, c.tick) || !integer(c.random, 0, 0xffffffff)
|| !['swim', 'dive', 'roll', 'breathe', 'drift', 'doze', 'wake'].includes(c.behaviour)
|| !range(c.until, 0, PET_MAX_SECONDS + 10) || ![c.targetX, c.targetY, c.x, c.y, c.flip].every(n => range(n, -1, 1))
|| !range(c.lit, 0, 1) || !range(c.lastActivity, 0, c.tick / HZ)
|| c.addressedAt !== null && !range(c.addressedAt, 0, c.tick / HZ)
|| !range(c.waitSince, -1, c.tick / HZ) || typeof c.lastStill !== 'string' || c.lastStill.length > 2048
|| !Array.isArray(c.members) || c.members.length > 6
|| c.members.some(m => !m || typeof m.id !== 'string' || !m.id || m.id.length > 4096
|| !integer(m.slot, 0, 5) || !range(m.phase, 0, Math.PI * 2) || typeof m.present !== 'boolean')
|| new Set(c.members.map(m => m.id)).size !== c.members.length || new Set(c.members.map(m => m.slot)).size !== c.members.length
|| c.food !== null && (!c.food || !range(c.food.time, 0, c.tick / HZ) || ![c.food.x, c.food.y].every(n => range(n, -1, 1)))
|| !c.frame || c.frame.timeMs !== c.tick * 1000 / HZ || c.frame.behaviour !== c.behaviour
|| !['none', 'orient', 'approach', 'call'].includes(c.frame.needs)
|| !range(c.frame.surface, -.9, -.8) || !range(c.frame.caustic, 0, 1)
|| c.frame.food !== null && (!c.frame.food || !range(c.frame.food.x, -1, 1) || !range(c.frame.food.y, -1, 1.21) || !range(c.frame.food.life, 0, 1))
|| JSON.stringify(c.frame.pod) !== JSON.stringify(c.members)
|| !Array.isArray(c.voices) || c.voices.length > 1024
|| c.voices.some(v => !v || typeof v.id !== 'string' || v.id.length > 256))
throw new Error('Invalid pet world checkpoint.');
validatePetState(c.frame.state);
renderPetPCM(c.voices, 0, 0);
const world = new PetWorld(points, tape, interactions, c.sim?.expressionVersion ?? 1, c.petCheckpointVersion === 2);
if (c.petCheckpointVersion === 2) { world.hasTelemetry = c.hasTelemetry!; world.origin = structuredClone(c); }
if (c.history !== world.historyDigest()
|| c.bucketIndex >= 0 && world.tape[c.bucketIndex].simTimeMs > c.frame.timeMs + 1e-7
// acceptTelemetry may fill past gaps after the most recent fixed tick.
// Preserve that pending cursor exactly; it may lag only over empty gaps.
|| world.tape.some((b, i) => i > c.bucketIndex && b.simTimeMs <= c.frame.timeMs + 1e-7
&& (b.observed !== 0 || b.channel !== 'other' || b.errors || b.waiting || b.agentIds.length
|| b.onsets.some(Boolean) || b.activeMs.some(Boolean)))
|| world.interactionLog.slice(0, c.interactionIndex).some(e => e.timeMs > c.frame.timeMs + 1e-7)
|| world.interactionLog[c.interactionIndex]?.timeMs <= c.frame.timeMs + 1e-7)
throw new Error('Pet checkpoint does not match its recording.');
const candidate = world.tape[c.bucketIndex];
const telemetry = candidate && c.frame.timeMs < candidate.simTimeMs + candidate.durationMs ? candidate : undefined;
if (JSON.stringify(c.frame.telemetry) !== JSON.stringify(telemetry)) throw new Error('Pet checkpoint telemetry does not match its clock.');
world.sim.restore(c.sim); world.score.restore(c.score); world.random.restore(c.random);View on GitHub (pinned to 433685b202)