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

  1. Re-create the checkpoint from the current PetWorld state instead of restoring a stale one
  2. After any acceptTelemetry/interact call, discard checkpoints taken before it and take a fresh checkpoint
  3. Verify the checkpoint's bucketIndex and frame.timeMs fall inside the current tapeLog before restoring
  4. 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

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


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)