{"record":{"id":"05d17fa733711430","repo":"Hmbown/CodeWhale","slug":"invalid-pet-particle-checkpoint-native","errorCode":null,"errorMessage":"Invalid pet particle checkpoint.","messagePattern":"Invalid pet particle checkpoint\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/ios/Resources/pet-native.js","lineNumber":945,"sourceCode":"            phase: this.phase, clock: this.clock, tear: this.tear, previous: this.prev, current: this.cur,\n            color: [...this.col], frame: { ...this.frame } };\n    }\n    /** Restore into a newly constructed sim. Authored body and seeded particle\n     * identity must match exactly; a checkpoint cannot replace the whale. */\n    restore(value) {\n        const c = value;\n        const inRange = (n, low, high) => Number.isFinite(n) && n >= low && n <= high;\n        if (!c || c.version !== 1 || c.expressionVersion !== undefined && ![1, 2].includes(c.expressionVersion) || (c.expressionVersion ?? 1) !== this.expressionVersion || !Array.isArray(c.body) || c.body.length !== this.p.length\n            || c.body.some((v, i) => !Array.isArray(v) || v.length !== 3 || v[0] !== this.p[i].hx || v[1] !== this.p[i].hy || v[2] !== this.p[i].s)\n            || !Array.isArray(c.particles) || c.particles.length !== this.p.length\n            || c.particles.some(v => !Array.isArray(v) || v.length !== 8 || v.some((n, i) => !inRange(n, i === 4 || i === 5 ? 0 : -8, i === 4 || i === 5 ? 2 * exports.PET_MAX_SECONDS : 8)))\n            || !inRange(c.phase, 0, exports.PET_MAX_SECONDS) || !inRange(c.clock, 0, exports.PET_MAX_SECONDS) || !inRange(c.tear, 0, 1)\n            || ![c.previous, c.current].every(n => Number.isInteger(n) && n >= 0 && n < exports.CHANNELS.length)\n            || !Array.isArray(c.color) || c.color.length !== 3 || c.color.some(n => !inRange(n, 0, 255))\n            || !c.frame || ![c.frame.r, c.frame.g, c.frame.b].every(n => inRange(n, 0, 255))\n            || !inRange(c.frame.alpha, 0, 1) || !inRange(c.frame.work, 0, 1) || typeof c.frame.hollow !== 'boolean'\n            || c.frame.channel !== exports.CHANNELS[c.current].key || c.frame.arch !== exports.CHANNELS[c.current].arch)\n            throw new Error('Invalid pet particle checkpoint.');\n        this.phase = c.phase;\n        this.clock = c.clock;\n        this.tear = c.tear;\n        this.prev = c.previous;\n        this.cur = c.current;\n        this.col = [...c.color];\n        this.frame = { ...c.frame };\n        this.p.forEach((p, i) => { [p.x, p.y, p.vx, p.vy, p.jx, p.jy, p.tx, p.ty] = c.particles[i]; });\n    }\n    /** Advance the sim by dt seconds under `state`. Identical math on every port. */\n    step(dt, state, opts) {\n        const S = (v) => lerp(0.5, v, opts.sensitivity);\n        const act = S(state.activity), coh = S(state.coherence), att = S(state.attention);\n        const seen = S(state.observed === undefined ? 1 : state.observed);\n        const motion = opts.motion ? 1 : 0;\n        this.phase += dt * (0.18 + act * 0.55) * motion;\n        this.clock += dt * (opts.motion ? 1 : 0);\n        if (exports.CHANNEL_INDEX[state.channel] !== undefined)","sourceCodeStart":927,"sourceCodeEnd":963,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/ios/Resources/pet-native.js#L927-L963","documentation":"Pet checkpoints restored from storage are fully validated before the engine accepts their state (phase/clock ranges, integer channel indices, RGB color bounds, alpha/work in [0,1], boolean hollow, and frame channel/arch matching the current channel). If any field of the checkpoint object is missing, out of range, or inconsistent with the channel tables, the engine throws rather than animating from corrupt state.","triggerScenarios":"Calling the checkpoint-restore constructor/path with an object that fails any validation: missing frame, color array not length 3, frame.channel not equal to CHANNELS[c.current].key, non-boolean hollow, alpha/work outside [0,1], phase or clock outside [0, PET_MAX_SECONDS], or previous/current indices outside CHANNELS.","commonSituations":"Hand-editing a persisted pet JSON file; a schema change in the checkpoint format between app versions; truncation/corruption during file sync; constructing a checkpoint by hand and forgetting derived fields like frame.channel/arch.","solutions":["Re-serialize the checkpoint from a live pet instance instead of editing the stored JSON by hand.","Fix the specific failing field flagged by validation (ranges are: phase/clock in [0,PET_MAX_SECONDS], color RGB 0-255, alpha/work 0-1, hollow boolean, channel indices valid integers).","Ensure frame.channel and frame.arch match exports.CHANNELS[current] after any channel change.","Migrate old checkpoints through the version's loader rather than feeding them directly to the new code."],"exampleFix":"// before\nnew ParticleState({ phase: 12, clock: 3, tear: 0.1, previous: 0, current: 1, color: [255,0,0] })\n// after\nnew ParticleState({ phase: 12, clock: 3, tear: 0.1, previous: 0, current: 1, color: [255,0,0],\n  frame: { r: 255, g: 0, b: 0, alpha: 0.3, hollow: false, channel: CHANNELS[1].key, arch: CHANNELS[1].arch, work: 0 } })","handlingStrategy":"type-guard","validationCode":"function isValidCheckpoint(c, CHANNELS, PET_MAX_SECONDS) {\n  const inRange = (n, lo, hi) => typeof n === 'number' && Number.isFinite(n) && n >= lo && n <= hi;\n  return !!c && inRange(c.phase, 0, PET_MAX_SECONDS) && inRange(c.clock, 0, PET_MAX_SECONDS)\n    && inRange(c.tear, 0, 1) && [c.previous, c.current].every(n => Number.isInteger(n) && n >= 0 && n < CHANNELS.length)\n    && Array.isArray(c.color) && c.color.length === 3 && c.color.every(n => inRange(n, 0, 255))\n    && !!c.frame && [c.frame.r, c.frame.g, c.frame.b].every(n => inRange(n, 0, 255))\n    && inRange(c.frame.alpha, 0, 1) && inRange(c.frame.work, 0, 1) && typeof c.frame.hollow === 'boolean'\n    && c.frame.channel === CHANNELS[c.current].key && c.frame.arch === CHANNELS[c.current].arch;\n}","typeGuard":"const inRange = (n, lo, hi) => typeof n === 'number' && Number.isFinite(n) && n >= lo && n <= hi;","tryCatchPattern":"try { engine.restore(checkpoint); }\ncatch (e) { if (e.message === 'Invalid pet particle checkpoint.') { engine.reset(); log.warn('discarded corrupt pet checkpoint'); } else throw e; }","preventionTips":["Never hand-edit persisted pet checkpoint JSON.","Re-serialize checkpoints from a live instance after any channel change so frame.channel/arch stay consistent.","Version the checkpoint schema and migrate old files before restoring.","Validate on write, not just on read."],"tags":["validation","checkpoint","schema"],"backgroundTag":"schema-validation-failed","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"}