Hmbown/CodeWhale · error · Error
Pet checkpoint does not match its recording.
Error message
Pet checkpoint does not match its recording.
What it means
After rebuilding the world from a checkpoint, PetWorld cross-checks the snapshot against its recording: every tape bucket after bucketIndex must be empty of observed activity, all interaction-log entries before interactionIndex must be at or before frame.timeMs, and the next interaction entry must be after it. A mismatch means the checkpoint and its backing recording are inconsistent, so resume would replay or drop events incorrectly. The library throws rather than silently diverging from the recorded timeline.
Solutions
- Restore the checkpoint together with the exact tapeLog and interactionLog it was saved from — do not mix snapshots across recordings.
- Re-take the checkpoint at the current playhead instead of restoring the stale one.
- If the tape was trimmed, advance bucketIndex/interactionIndex in the checkpoint to a consistent point or drop the checkpoint.
- Enable atomic persistence so tape and checkpoint are written together.
Example fix
// before: pairing checkpoint c with a longer/newer tapeLog const world = new PetWorld(points, c.tapeLog, c.interactionLog, ...); // throws // after: use the tapeLog snapshotted inside the same checkpoint const world = new PetWorld(points, c.tape, c.interactionLog, ...);
Defensive patterns
Strategy: validation
Validate before calling
function checkpointMatchesRecording(c, tape, interactionLog) {
const eps = 1e-7;
const stray = tape.some((b, i) => i > c.bucketIndex && b.simTimeMs <= c.frame.timeMs + eps &&
(b.observed !== 0 || b.channel !== 'other' || b.errors || b.waiting || b.agentIds.length || b.onsets.some(Boolean) || b.activeMs.some(Boolean)));
if (stray) return false;
if (interactionLog.slice(0, c.interactionIndex).some(e => e.timeMs > c.frame.timeMs + eps)) return false;
return !(interactionLog[c.interactionIndex]?.timeMs <= c.frame.timeMs + eps);
} Type guard
const isConsistentCheckpoint = (c) => Number.isInteger(c?.bucketIndex) && Number.isInteger(c?.interactionIndex) && typeof c?.frame?.timeMs === 'number';
Try / catch
try {
world = new PetWorld(points, tape, interactions, ver, v2);
// matching checks run during restore
} catch (e) {
if (e.message === 'Pet checkpoint does not match its recording.') {
world = replayFromStart(tape); // fall back to full replay
} else throw e;
} Prevention
- Always persist checkpoint, tapeLog, and interactionLog atomically in one write
- Never pair a checkpoint with a different recording's tape
- Re-take checkpoints after any tape trimming or rotation
- Test restore against the exact recording it was saved from
When it happens
Trigger: Restoring a checkpoint where a non-empty tape bucket exists after c.bucketIndex with simTimeMs <= frame.timeMs, where logged interactions before interactionIndex are later than frame.timeMs, or where the interaction at interactionIndex has timeMs <= frame.timeMs + 1e-7 — i.e. the checkpoint's playhead and interaction cursor disagree with the tape/interaction log.
Common situations: Mixing a checkpoint from one recording with the tape of another; trimming or rotating the tape log after the checkpoint was taken; checkpoint/telemetry writes that are not atomic so the tape advanced past the saved playhead; hand-splicing interaction logs.
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 segment checkpoints do not agree.
- Pet checkpoint does not match its recording.
- Pet checkpoint telemetry does not match its clock.
- Pet checkpoint telemetry does not match its clock.
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7def3d224bf1a539.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/pet_watch/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 73e0f67d83)