{"record":{"id":"b2b6a2d6408fca6f","repo":"Hmbown/CodeWhale","slug":"invalid-pet-state-native","errorCode":null,"errorMessage":"Invalid pet state.","messagePattern":"Invalid pet state\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/ios/Resources/pet-native.js","lineNumber":711,"sourceCode":"    { key: 'memory', label: 'Memory / RAG', color: '#b6a77f', freq: 195.99, sustained: true, arch: 'gyre', form: 'gyre · scanning' },\n    { key: 'code', label: 'Code execution', color: '#9b9ed7', freq: 164.81, sustained: false, arch: 'strike', form: 'strike · along the body' },\n    { key: 'filesystem', label: 'Filesystem', color: '#92b9c9', freq: 440.00, sustained: false, arch: 'strike', form: 'strike · fanning' },\n    { key: 'network', label: 'Network / API', color: '#d3ac74', freq: 523.25, sustained: false, arch: 'cross', form: 'crossing · one way' },\n    { key: 'browser', label: 'Browser / computer', color: '#9ea9df', freq: 349.23, sustained: false, arch: 'cross', form: 'crossing · a sweep' },\n    { key: 'communication', label: 'Agent messages', color: '#83c5c9', freq: 293.66, sustained: false, arch: 'cross', form: 'crossing · two ways' },\n    { key: 'agent', label: 'Subagent activity', color: '#b09acb', freq: 220.00, sustained: true, arch: 'pod', form: 'pod · peers' },\n    { key: 'orchestration', label: 'Orchestration', color: '#6c8798', freq: 98.00, sustained: true, arch: 'pod', form: 'pod · hub' },\n    { key: 'error', label: 'Errors / exceptions', color: '#e79186', freq: 185.00, sustained: false, arch: 'tear', form: 'torn · irregular' },\n    { key: 'human', label: 'Human interaction', color: '#c2b787', freq: 391.99, sustained: false, arch: 'address', form: 'decision · junction' },\n    { key: 'other', label: 'Unclassified', color: '#738492', freq: 146.83, sustained: false, arch: 'drift', form: 'drifting · unformed' },\n];\nexports.CHANNEL_INDEX = Object.fromEntries(exports.CHANNELS.map((c, i) => [c.key, i]));\nfunction validatePetState(value) {\n    const s = value;\n    if (!s || typeof s !== 'object' || !Object.hasOwn(exports.CHANNEL_INDEX, s.channel)\n        || ![s.activity, s.coherence, s.attention, s.observed, s.lit].every(n => Number.isFinite(n) && n >= 0 && n <= 1)\n        || ![s.roamX, s.roamY, s.flip].every(n => Number.isFinite(n) && Math.abs(n) <= 1))\n        throw new Error('Invalid pet state.');\n}\nconst hex2rgb = (h) => [parseInt(h.slice(1, 3), 16), parseInt(h.slice(3, 5), 16), parseInt(h.slice(5, 7), 16)];\nconst RGB = exports.CHANNELS.map(c => hex2rgb(c.color));\nconst UNKNOWN_RGB = hex2rgb('#738492');\nconst REST_RGB = [122, 214, 240];\n// mulberry32 — a 32-bit seeded PRNG tiny enough to port by hand correctly.\nfunction mulberry32(seed) {\n    let a = seed >>> 0;\n    return Object.assign(() => {\n        a = (a + 0x6D2B79F5) >>> 0;\n        let t = a;\n        t = Math.imul(t ^ (t >>> 15), t | 1);\n        t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n        return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n    }, { state: () => a, restore: (state) => {\n            if (!Number.isSafeInteger(state) || state < 0 || state > 0xffffffff)\n                throw new Error('Invalid pet random stream.');\n            a = state;","sourceCodeStart":693,"sourceCodeEnd":729,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/ios/Resources/pet-native.js#L693-L729","documentation":"`validatePetState(obj)` is the guard for pet telemetry frame objects. It checks that the channel is a known key in `CHANNEL_INDEX` and that every numeric field (activity, coherence, attention, observed, roamX, roamY, flip, lit, onsets entries, activeMs entries, etc.) is a finite number. Any missing field, wrong type, or non-finite value (NaN/Infinity) makes the frame invalid and the library throws.","triggerScenarios":"Submitting a frame object to PetWorld's record/append API that is missing a required field, has a channel key not present in `CHANNELS`, contains NaN/Infinity values (e.g. from a division by zero upstream), or has onsets/activeMs arrays with non-numeric entries.","commonSituations":"Deserializing frames from JSON where numbers came back as strings or null; adding a new channel without registering it in `CHANNELS`/`CHANNEL_INDEX`; sensor code emitting NaN when a reading is unavailable; renaming a field and breaking the frame shape.","solutions":["Validate/sanitize the frame before submitting: check the channel key against `CHANNEL_INDEX` and coerce every numeric field with `Number()` plus a `Number.isFinite` check.","Replace unavailable sensor readings with the documented defaults (e.g. activity .12, coherence .25, attention 0) instead of null/NaN.","If a field or channel was renamed/added, update the producer and `CHANNELS` registration together so the frame matches the schema."],"exampleFix":"// before\nworld.record(seq, { channel: 'purr', activity: NaN, coherence: .25 }); // missing fields, NaN -> throws\n// after\nconst frame = normalizeFrame(raw); // fills defaults, coerces numbers, rejects NaN\nif (CHANNEL_INDEX[frame.channel] === undefined) throw new TypeError('unknown channel: ' + frame.channel);\nworld.record(seq, frame);","handlingStrategy":"type-guard","validationCode":"function isValidPetStateFrame(f) { return f != null && typeof f === 'object' && f.channel in CHANNEL_INDEX && [f.activity, f.coherence, f.attention, f.observed, f.roamX, f.roamY, f.flip, f.lit, ...f.onsets, ...f.activeMs].every(Number.isFinite); }","typeGuard":"function isPetState(v) { return v != null && typeof v === 'object' && typeof v.channel === 'string' && v.channel in CHANNEL_INDEX && [v.activity, v.coherence, v.attention, v.observed, v.roamX, v.roamY, v.flip, v.lit].every(n => Number.isFinite(n)); }","tryCatchPattern":"try { world.record(seq, frame); } catch (e) { if (e.message === 'Invalid pet state.') { logCorruptFrame(seq, frame); frame = defaultFrame(seq); world.record(seq, frame); } else throw e; }","preventionTips":["Sanitize producer output: coerce numeric fields and replace missing readings with documented defaults, never NaN/null.","Register new channels in CHANNELS/CHANNEL_INDEX before emitting frames with their key.","Validate frames at the ingestion boundary (before record) so corruption is caught at the source.","Check JSON round-trips keep numbers as numbers, not strings."],"tags":["validation","schema","telemetry","javascript"],"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"}