{"record":{"id":"1d73c5938c88792b","repo":"Hmbown/CodeWhale","slug":"pet-checkpoint-telemetry-does-not-match-its-clock","errorCode":null,"errorMessage":"Pet checkpoint telemetry does not match its clock.","messagePattern":"Pet checkpoint telemetry does not match its clock\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tui/pet_watch/pet-native.js","lineNumber":386,"sourceCode":"        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;\n        world.flip = c.flip;\n        world.lit = c.lit;\n        world.lastActivity = c.lastActivity;\n        world.addressedAt = c.addressedAt ?? -Infinity;","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/pet_watch/pet-native.js#L368-L404","documentation":"PetWorld independently verifies that the telemetry snapshot stored in the checkpoint equals the tape bucket the checkpoint's clock currently falls inside (candidate bucket where frame.timeMs < simTimeMs + durationMs, or undefined in a gap). If the serialized telemetry does not match the reconstructed bucket, the checkpoint's clock and telemetry are out of sync. Throwing prevents rendering telemetry that contradicts the restored timeline.","triggerScenarios":"Restoring a checkpoint whose frame.telemetry was captured against a different bucket than c.bucketIndex implies, or whose frame.timeMs sits in a gap but frame.telemetry is non-null (or vice versa) — a strict JSON deep-equality failure between c.frame.telemetry and the recomputed bucket.","commonSituations":"Older checkpoint format without telemetry field restored under new code; partial checkpoint writes; manually editing frame.timeMs or telemetry in a saved checkpoint; checkpoints exported before a telemetry bucket was replaced by acceptTelemetry.","solutions":["Re-capture the checkpoint so frame.telemetry and frame.timeMs are written atomically from the same tick.","Update the checkpoint's frame.telemetry to match the tape bucket at bucketIndex (or null when in a gap).","Migrate checkpoints saved by older versions to the current telemetry format before restore.","Verify the tape used at restore is the same one the checkpoint was recorded against."],"exampleFix":"// before: stale telemetry from a replaced bucket\nc.frame.telemetry = oldBucket; // throws on restore\n// after: recompute telemetry from the restored tape\nconst cand = world.tape[c.bucketIndex];\nc.frame.telemetry = cand && c.frame.timeMs < cand.simTimeMs + cand.durationMs ? cand : undefined;","handlingStrategy":"validation","validationCode":"function telemetryMatchesClock(c, tape) {\n  const cand = tape[c.bucketIndex];\n  const expected = cand && c.frame.timeMs < cand.simTimeMs + cand.durationMs ? cand : undefined;\n  return JSON.stringify(c.frame.telemetry) === JSON.stringify(expected);\n}","typeGuard":"const hasCoherentTelemetry = (c) => (c?.frame?.telemetry ?? undefined) !== undefined ? typeof c.frame.telemetry === 'object' : true;","tryCatchPattern":"try {\n  world.resumeFromCheckpoint(c);\n} catch (e) {\n  if (e.message === 'Pet checkpoint telemetry does not match its clock.') {\n    c.frame.telemetry = recomputeTelemetry(c, tape);\n    world.resumeFromCheckpoint(c);\n  } else throw e;\n}","preventionTips":["Write frame.timeMs and frame.telemetry in the same atomic snapshot","Recompute telemetry from the tape instead of trusting a stale serialized copy","Version the checkpoint format and migrate telemetry fields on upgrade","Never edit frame.timeMs without recomputing telemetry"],"tags":["checkpoint","telemetry","consistency"],"backgroundTag":"internal-invariant-violation","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}