Hmbown/CodeWhale · error · Error
Pet checkpoint does not match its recording.
Error message
Pet checkpoint does not match its recording.
What it means
PetWorld validates every saved checkpoint against its own recording before restoring from it. The checkpoint's cursor (bucketIndex, frame timeMs, interactionIndex) must line up exactly with the tape buckets and interaction log — e.g. no interaction log entry past the checkpoint time and no tape bucket that already observed the frame. If the checkpoint cursor no longer matches what was actually recorded, the world throws this instead of silently restoring a desynced state.
Solutions
- Re-create the checkpoint from the current PetWorld state instead of restoring a stale one
- After any acceptTelemetry/interact call, discard checkpoints taken before it and take a fresh checkpoint
- Verify the checkpoint's bucketIndex and frame.timeMs fall inside the current tapeLog before restoring
- Ensure the checkpoint was produced by the same PetWorld instance that owns the tape
Example fix
// before
world.restoreCheckpoint(oldCheckpoint); // stale after re-record
// after
if (oldCheckpoint.bucketIndex < world.tape.length &&
world.tape[oldCheckpoint.bucketIndex] &&
oldCheckpoint.frame.timeMs < world.tape[oldCheckpoint.bucketIndex].simTimeMs + world.tape[oldCheckpoint.bucketIndex].durationMs) {
world.restoreCheckpoint(oldCheckpoint);
} else {
const cp = world.checkpoint(); // re-take from live state
world.restoreCheckpoint(cp);
} Defensive patterns
Strategy: try-catch
Validate before calling
function canRestore(world, c) {
const b = world.tape[c.bucketIndex];
return !!b && c.frame.timeMs >= b.simTimeMs &&
c.frame.timeMs < b.simTimeMs + b.durationMs &&
!world.interactionLog.slice(0, c.interactionIndex).some(e => e.timeMs > c.frame.timeMs + 1e-7);
} Type guard
function isConsistentCheckpoint(world, c) {
return c != null && typeof c.bucketIndex === 'number' &&
world.tape[c.bucketIndex] != null &&
JSON.stringify(c.frame.telemetry) === JSON.stringify(world.tape[c.bucketIndex]);
} Try / catch
try {
world.restoreCheckpoint(cp);
} catch (e) {
if (e.message.includes('checkpoint does not match')) {
cp = world.checkpoint(); // re-take from live state
world.restoreCheckpoint(cp);
} else throw e;
} Prevention
- Re-take checkpoints after every acceptTelemetry or interact call
- Persist the checkpoint and its tapeLog snapshot together
- Never share checkpoints between PetWorld instances
- Version checkpoints with the recording id and reject mismatches before restoring
When it happens
Trigger: Calling the checkpoint restore path with a checkpoint object whose frame.timeMs, bucketIndex, or interactionIndex were produced by a different tape — e.g. after the tape was spliced/re-recorded (segmented acceptTelemetry overwriting buckets), after the interaction log was truncated by interact() at a new branch tick, or a checkpoint deserialized from a host that recorded a different session.
Common situations: Host app keeps old checkpoints across an acceptTelemetry overwrite; replaying a checkpoint saved before a branch; loading a checkpoint JSON from a prior app session whose recording was archived or reset; mixing checkpoints between two PetWorld instances.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Pet checkpoint does not match its recording.
- Pet checkpoint does not match its recording.
- Pet checkpoint telemetry does not match its clock.
- Pet segment checkpoints do not agree.
- The recording segment is missing its starting checkpoint.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/31ca7d2c99815d89.
Report an issue: GitHub.
Appendix: source
Thrown at pet/ios/Resources/pet-native.js:382
|| 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];
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);
world.accumulator = c.accumulator;
world.tick = c.tick;
world.bucketIndex = c.bucketIndex;
world.interactionIndex = c.interactionIndex;
world.branchTick = c.branchTick;
world.behaviour = c.behaviour;
world.until = c.until;
world.targetX = c.targetX;
world.targetY = c.targetY;
world.x = c.x;
world.y = c.y;View on GitHub (pinned to 433685b202)