{"record":{"id":"1287b8115cdb6a53","repo":"Hmbown/CodeWhale","slug":"invalid-pet-particle-checkpoint-sim","errorCode":null,"errorMessage":"Invalid pet particle checkpoint.","messagePattern":"Invalid pet particle checkpoint\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/pet-sim.ts","lineNumber":324,"sourceCode":"      color: [...this.col], frame: { ...this.frame } };\n  }\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: unknown): void {\n    const c = value as PetSimCheckpoint;\n    const inRange = (n: number, low: number, high: number) => 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 * PET_MAX_SECONDS : 8)))\n      || !inRange(c.phase, 0, PET_MAX_SECONDS) || !inRange(c.clock, 0, PET_MAX_SECONDS) || !inRange(c.tear, 0, 1)\n      || ![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.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 !== CHANNELS[c.current].key || c.frame.arch !== CHANNELS[c.current].arch)\n      throw new Error('Invalid pet particle checkpoint.');\n    this.phase = c.phase; this.clock = c.clock; this.tear = c.tear; this.prev = c.previous; this.cur = c.current;\n    this.col = [...c.color]; 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\n  /** Advance the sim by dt seconds under `state`. Identical math on every port. */\n  step(dt: number, state: PetState, opts: PetOpts): void {\n    const S = (v: number) => 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\n    if (CHANNEL_INDEX[state.channel] !== undefined) this.cur = CHANNEL_INDEX[state.channel];\n    const shown = this.cur;\n    const ch = CHANNELS[shown];\n","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/pet-sim.ts#L306-L342","documentation":"PetSim.restore (the checkpoint loader) validates every field of a serialized checkpoint: phase/clock/tear ranges, previous/current channel indices, RGB color, and frame shape (r/g/b/alpha/work/hollow/channel/arch). Any violation throws this error instead of restoring a corrupt or tampered state.","triggerScenarios":"Calling the PetSim restore/constructor path with a checkpoint whose numeric fields are out of range, whose frame.channel or frame.arch do not match the CHANNELS entry for c.current, whose color array is not length 3 of 0–255 bytes, or whose particles array has the wrong shape.","commonSituations":"Hand-edited or truncated checkpoint files in localStorage/disk; checkpoints written by a different CHANNELS layout after a code change; partially written saves; schema drift between app versions.","solutions":["Re-serialize the checkpoint from a fresh PetSim run instead of restoring the corrupt one","Validate the checkpoint against the same in-range/length/match rules before calling restore","If CHANNELS changed, re-map old checkpoints' channel/arch fields to the new table","Fall back to a default checkpoint and log the invalid one"],"exampleFix":"// before\npet.restore(savedJson); // throws if fields drifted\n// after\nif (isValidCheckpoint(savedJson)) pet.restore(savedJson);\nelse pet = new PetSim(points, seed); // fresh default","handlingStrategy":"validation","validationCode":"const inRange = (n: unknown, lo: number, hi: number) => typeof n === 'number' && Number.isFinite(n) && n >= lo && n <= hi;\nfunction looksLikeValidCheckpoint(c: any): boolean {\n  return !!c && inRange(c.phase, 0, PET_MAX_SECONDS) && inRange(c.clock, 0, PET_MAX_SECONDS)\n    && inRange(c.tear, 0, 1)\n    && Number.isInteger(c.previous) && Number.isInteger(c.current)\n    && Array.isArray(c.color) && c.color.length === 3\n    && !!c.frame && c.frame.channel !== undefined && c.frame.arch !== undefined\n    && inRange(c.frame.alpha, 0, 1) && inRange(c.frame.work, 0, 1)\n    && typeof c.frame.hollow === 'boolean';\n}","typeGuard":"function isValidCheckpoint(c: unknown): c is PetCheckpoint {\n  const ck = c as PetCheckpoint;\n  return !!ck && typeof ck.phase === 'number' && typeof ck.tear === 'number'\n    && Array.isArray(ck.color) && ck.color.length === 3\n    && !!ck.frame && typeof ck.frame.hollow === 'boolean'\n    && Array.isArray(ck.particles);\n}","tryCatchPattern":"try {\n  pet.restore(saved);\n} catch (e) {\n  if (e.message === 'Invalid pet particle checkpoint.') {\n    console.warn('Discarding corrupt pet checkpoint; starting fresh');\n    pet = new PetSim(points, seed);\n  } else throw e;\n}","preventionTips":["Validate checkpoints on write with the same rules restore enforces","Version-stamp checkpoint payloads so schema changes trigger migration, not corruption","Wrap restore in try/catch wherever checkpoints come from user storage","Never hand-edit checkpoint JSON; regenerate from a live sim"],"tags":["validation","deserialization","checkpoint"],"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"}