Hmbown/CodeWhale · error · Error
Invalid pet world checkpoint.
Error message
Invalid pet world checkpoint.
What it means
PetWorld's checkpoint deserializer validates every field of a restored snapshot (frame needs mode, surface/caustic ranges, food coordinates, pod membership, voices array and per-voice id) before rebuilding the world. If any field fails these structural or range checks the constructor throws so a corrupt or hand-edited checkpoint is never loaded. It is a load-time integrity gate for persisted pet-world state.
Solutions
- Compare the failing checkpoint's frame against the validator field-by-field (needs, surface, caustic, food, pod, voices) and fix or regenerate the offending field.
- Re-create the checkpoint by recording a fresh session instead of restoring the corrupt one.
- Confirm the checkpoint and code versions match (petCheckpointVersion) and migrate old-format checkpoints before constructing PetWorld.
- Validate the checkpoint with the same range/shape predicates before passing it to the constructor to get a precise field-level error.
Example fix
// before: frame.pod drifted from members after roster change new PetWorld(points, tape, interactions, c.sim?.expressionVersion ?? 1, true) // throws // after: rebuild checkpoint frame.pod to match current members before restore c.frame.pod = structuredClone(members); new PetWorld(points, tape, interactions, c.sim?.expressionVersion ?? 1, true);
Defensive patterns
Strategy: validation
Validate before calling
function isValidCheckpoint(c, members) {
const range = (v, lo, hi) => typeof v === 'number' && v >= lo && v <= hi;
const f = c?.frame;
if (!f || !['none','orient','approach','call'].includes(f.needs)) return false;
if (!range(f.surface, -0.9, -0.8) || !range(f.caustic, 0, 1)) return false;
if (f.food !== null && (!f.food || !range(f.food.x, -1, 1) || !range(f.food.y, -1, 1.21) || !range(f.food.life, 0, 1))) return false;
if (JSON.stringify(f.pod) !== JSON.stringify(members)) return false;
if (!Array.isArray(c.voices) || c.voices.length > 1024) return false;
return c.voices.every(v => v && typeof v.id === 'string' && v.id.length <= 256);
} Type guard
const isCheckpoint = (c) => !!c && typeof c === 'object' && c.frame !== null && typeof c.frame === 'object' && Array.isArray(c.voices);
Try / catch
try {
const world = new PetWorld(points, tape, interactions, ver, v2);
} catch (e) {
if (e.message === 'Invalid pet world checkpoint.') {
console.error('checkpoint failed validation, re-recording');
world = startFreshWorld();
} else throw e;
} Prevention
- Validate checkpoints at write time with the same predicates the constructor uses
- Never hand-edit checkpoint JSON; regenerate from a live session
- Keep petCheckpointVersion in sync with the code version and migrate old checkpoints
- Write checkpoints atomically so partial frames are never persisted
When it happens
Trigger: Constructing a PetWorld from a checkpoint object `c` whose frame.needs is not one of none/orient/approach/call, whose frame.surface or frame.caustic is outside [-0.9,-0.8] / [0,1], whose food entry exists but has missing/out-of-range x,y or life, whose frame.pod JSON differs from the provided members list, or whose voices array exceeds 1024 entries or contains entries without a string id of length <= 256.
Common situations: Restoring checkpoints saved by an older pet-native version whose frame schema changed; hand-editing or truncating a checkpoint JSON; a storage layer that partially wrote the checkpoint; loading a checkpoint from a different pet whose pod roster does not match the members argument.
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
- invalid persisted user image content kind
- Invalid pet particle checkpoint.
- Invalid pet particle checkpoint.
- Invalid pet particle checkpoint.
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/f7fdd0c48b1e1c5e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/pet_watch/pet-native.js:365
|| !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_sim_js_1.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.');
(0, pet_sim_js_1.validatePetState)(c.frame.state);
(0, pet_audio_js_1.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];View on GitHub (pinned to 73e0f67d83)