{"record":{"id":"903a925986141c15","repo":"Hmbown/CodeWhale","slug":"invalid-pet-score-checkpoint-native","errorCode":null,"errorMessage":"Invalid pet score checkpoint.","messagePattern":"Invalid pet score checkpoint\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/ios/Resources/pet-native.js","lineNumber":1567,"sourceCode":"factories[\"pet-audio\"]=function(exports,require){\n\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.PetScore = void 0;\nexports.renderPetPCM = renderPetPCM;\nconst pet_sim_js_1 = require(\"./pet-sim.js\");\nconst model_js_1 = require(\"./model.js\");\n/** Core emits score events; WebAudio / AVAudioEngine only present their PCM.\n * Calling at any display cadence produces the same score when all world ticks\n * are supplied. Calling twice for a world tick cannot retrigger a voice. */\nclass PetScore {\n    lastWindow = -1;\n    lastSequence = -1;\n    lastAddress = false;\n    checkpoint() { return [this.lastWindow, this.lastSequence, this.lastAddress]; }\n    restore(value) {\n        if (!Array.isArray(value) || value.length !== 3\n            || !value.slice(0, 2).every(n => Number.isSafeInteger(n) && n >= -1 && n <= pet_sim_js_1.PET_MAX_SECONDS * 2.5) || typeof value[2] !== 'boolean')\n            throw new Error('Invalid pet score checkpoint.');\n        [this.lastWindow, this.lastSequence, this.lastAddress] = value;\n    }\n    voices(frame) {\n        const time = frame.timeMs / 1000, window = Math.floor((frame.timeMs + 1e-7) / 400);\n        const out = [];\n        const add = (id, frequency, duration, gain, pan = 0, delay = 0, kind = 'tone') => out.push({ id, start: time + delay, duration, frequency, gain, pan, kind });\n        const t = frame.telemetry, fresh = t !== undefined && t.sequence !== this.lastSequence;\n        if (fresh) {\n            this.lastSequence = t.sequence;\n            for (let c = 0; c < pet_sim_js_1.CHANNELS.length; c++) {\n                const channel = pet_sim_js_1.CHANNELS[c], n = t.onsets[c];\n                if (!n || channel.sustained || ['human', 'error'].includes(channel.key))\n                    continue;\n                add(`onset:${t.sequence}:${c}`, channel.freq, .24, .035 * Math.min(2, Math.sqrt(n)), (c / 12 - .5) * .7);\n            }\n            if (t.errors)\n                add(`tear:${t.sequence}`, pet_sim_js_1.CHANNELS.find(c => c.key === 'error').freq, .22, .05, 0, 0, 'noise');\n        }","sourceCodeStart":1549,"sourceCodeEnd":1585,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/ios/Resources/pet-native.js#L1549-L1585","documentation":"The pet scorer can resume from a checkpoint array [lastWindow, lastSequence, lastAddress]. restore() validates the shape: an array of exactly 3 elements, the first two safe integers in [-1, PET_MAX_SECONDS * 2.5], and the third a boolean. Anything else throws this error, protecting the incremental scorer from corrupt resume state.","triggerScenarios":"Calling score.restore() with a checkpoint from an older format (different length or ordering), a JSON-serialized checkpoint whose boolean became a string, a null/undefined value, or a checkpoint truncated by storage.","commonSituations":"Version upgrades changing checkpoint shape, storing checkpoints in localStorage/JSON where booleans or numbers degrade, or passing the result of checkpoint() through a transform that wraps it in another array.","solutions":["Regenerate the checkpoint by calling checkpoint() fresh and replaying from the start instead of restoring stale state.","Validate before restoring: Array.isArray(v) && v.length === 3 && Number.isSafeInteger(v[0]) && Number.isSafeInteger(v[1]) && typeof v[2] === 'boolean'.","If migrating from an old format, convert old checkpoints to the current 3-element shape or discard them.","Check that your persistence layer round-trips numbers as numbers and booleans as booleans (e.g. JSON.stringify/parse, not string templates)."],"exampleFix":"// before\nscore.restore(saved.state); // saved.state is a JSON string\n// after\nconst cp = typeof saved.state === 'string' ? JSON.parse(saved.state) : saved.state;\nif (Array.isArray(cp) && cp.length === 3 && typeof cp[2] === 'boolean') score.restore(cp);\nelse score.restore([-1, -1, false]); // restart","handlingStrategy":"type-guard","validationCode":"const isValidCheckpoint = (v) => Array.isArray(v) && v.length === 3\n  && Number.isSafeInteger(v[0]) && Number.isSafeInteger(v[1])\n  && v[0] >= -1 && v[1] >= -1 && v[1] <= 216_000 && typeof v[2] === 'boolean';\nif (isValidCheckpoint(saved)) score.restore(saved); else score.restore([-1, -1, false]);","typeGuard":"const isPetCheckpoint = (v) => Array.isArray(v) && v.length === 3 && typeof v[2] === 'boolean' && v.slice(0, 2).every(n => Number.isSafeInteger(n) && n >= -1);","tryCatchPattern":"try { score.restore(cp) } catch { score.restore([-1, -1, false]); /* restart scoring */ }","preventionTips":["Persist checkpoints with JSON.stringify/parse to preserve types","Version-stamp checkpoints and discard stale formats","Wrap restore() in a guard that falls back to a fresh checkpoint"],"tags":["checkpoint","state","validation"],"backgroundTag":"invalid-argument-format","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}