{"record":{"id":"31ca7d2c99815d89","repo":"Hmbown/CodeWhale","slug":"pet-checkpoint-does-not-match-its-recording-native","errorCode":null,"errorMessage":"Pet checkpoint does not match its recording.","messagePattern":"Pet checkpoint does not match its recording\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/ios/Resources/pet-native.js","lineNumber":382,"sourceCode":"            || c.voices.some(v => !v || typeof v.id !== 'string' || v.id.length > 256))\n            throw new Error('Invalid pet world checkpoint.');\n        (0, pet_sim_js_1.validatePetState)(c.frame.state);\n        (0, pet_audio_js_1.renderPetPCM)(c.voices, 0, 0);\n        const world = new PetWorld(points, tape, interactions, c.sim?.expressionVersion ?? 1, c.petCheckpointVersion === 2);\n        if (c.petCheckpointVersion === 2) {\n            world.hasTelemetry = c.hasTelemetry;\n            world.origin = structuredClone(c);\n        }\n        if (c.history !== world.historyDigest()\n            || c.bucketIndex >= 0 && world.tape[c.bucketIndex].simTimeMs > c.frame.timeMs + 1e-7\n            // acceptTelemetry may fill past gaps after the most recent fixed tick.\n            // Preserve that pending cursor exactly; it may lag only over empty gaps.\n            || world.tape.some((b, i) => i > c.bucketIndex && b.simTimeMs <= c.frame.timeMs + 1e-7\n                && (b.observed !== 0 || b.channel !== 'other' || b.errors || b.waiting || b.agentIds.length\n                    || b.onsets.some(Boolean) || b.activeMs.some(Boolean)))\n            || world.interactionLog.slice(0, c.interactionIndex).some(e => e.timeMs > c.frame.timeMs + 1e-7)\n            || world.interactionLog[c.interactionIndex]?.timeMs <= c.frame.timeMs + 1e-7)\n            throw new Error('Pet checkpoint does not match its recording.');\n        const candidate = world.tape[c.bucketIndex];\n        const telemetry = candidate && c.frame.timeMs < candidate.simTimeMs + candidate.durationMs ? candidate : undefined;\n        if (JSON.stringify(c.frame.telemetry) !== JSON.stringify(telemetry))\n            throw new Error('Pet checkpoint telemetry does not match its clock.');\n        world.sim.restore(c.sim);\n        world.score.restore(c.score);\n        world.random.restore(c.random);\n        world.accumulator = c.accumulator;\n        world.tick = c.tick;\n        world.bucketIndex = c.bucketIndex;\n        world.interactionIndex = c.interactionIndex;\n        world.branchTick = c.branchTick;\n        world.behaviour = c.behaviour;\n        world.until = c.until;\n        world.targetX = c.targetX;\n        world.targetY = c.targetY;\n        world.x = c.x;\n        world.y = c.y;","sourceCodeStart":364,"sourceCodeEnd":400,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/ios/Resources/pet-native.js#L364-L400","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nworld.restoreCheckpoint(oldCheckpoint); // stale after re-record\n// after\nif (oldCheckpoint.bucketIndex < world.tape.length &&\n    world.tape[oldCheckpoint.bucketIndex] &&\n    oldCheckpoint.frame.timeMs < world.tape[oldCheckpoint.bucketIndex].simTimeMs + world.tape[oldCheckpoint.bucketIndex].durationMs) {\n  world.restoreCheckpoint(oldCheckpoint);\n} else {\n  const cp = world.checkpoint(); // re-take from live state\n  world.restoreCheckpoint(cp);\n}","handlingStrategy":"try-catch","validationCode":"function canRestore(world, c) {\n  const b = world.tape[c.bucketIndex];\n  return !!b && c.frame.timeMs >= b.simTimeMs &&\n    c.frame.timeMs < b.simTimeMs + b.durationMs &&\n    !world.interactionLog.slice(0, c.interactionIndex).some(e => e.timeMs > c.frame.timeMs + 1e-7);\n}","typeGuard":"function isConsistentCheckpoint(world, c) {\n  return c != null && typeof c.bucketIndex === 'number' &&\n    world.tape[c.bucketIndex] != null &&\n    JSON.stringify(c.frame.telemetry) === JSON.stringify(world.tape[c.bucketIndex]);\n}","tryCatchPattern":"try {\n  world.restoreCheckpoint(cp);\n} catch (e) {\n  if (e.message.includes('checkpoint does not match')) {\n    cp = world.checkpoint(); // re-take from live state\n    world.restoreCheckpoint(cp);\n  } else throw e;\n}","preventionTips":["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"],"tags":["replay","state-desync","checkpoint"],"backgroundTag":"internal-invariant-violation","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}