{"record":{"id":"994393a88522ad84","repo":"Hmbown/CodeWhale","slug":"invalid-pet-interaction","errorCode":null,"errorMessage":"Invalid pet interaction.","messagePattern":"Invalid pet interaction\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tui/pet_watch/pet-native.js","lineNumber":217,"sourceCode":"        if (tape.length > 216_000 || interactions.length > 100_000)\n            throw new Error('Pet recording exceeds its input limit.');\n        this.sim = new pet_sim_js_1.PetSim(points, 0xC0FFEE, expressionVersion);\n        this.segmented = segmented;\n        this.hasTelemetry = tape.length > 0;\n        this.tapeLog = structuredClone([...tape]);\n        this.interactionLog = structuredClone([...interactions]);\n        for (let i = 0; i < this.tape.length; i++) {\n            const b = this.tape[i];\n            (0, pet_telemetry_js_1.validatePetBucket)(b);\n            if (segmented ? i > 0 && b.sequence <= this.tape[i - 1].sequence : b.sequence !== i)\n                throw new Error('World requires contiguous version 1 pet buckets.');\n        }\n        for (let i = 0; i < this.interactionLog.length; i++) {\n            const e = this.interactionLog[i];\n            if (!Number.isFinite(e.timeMs) || e.timeMs < 0 || i > 0 && e.timeMs < this.interactionLog[i - 1].timeMs\n                || !['attention', 'food'].includes(e.kind) || !Number.isFinite(e.x) || !Number.isFinite(e.y)\n                || Math.abs(e.x) > 1 || Math.abs(e.y) > 1)\n                throw new Error('Invalid pet interaction.');\n        }\n        this.hashTape(0);\n        this.frame = this.makeFrame(0);\n        this.voices = this.score.voices(this.frame);\n        if (segmented)\n            this.origin = this.checkpoint();\n    }\n    checkpoint() {\n        return structuredClone({ petCheckpointVersion: this.segmented ? 2 : 1,\n            ...(this.segmented ? { historyStart: (this.origin?.tick ?? this.tick), hasTelemetry: this.hasTelemetry } : {}), history: this.historyDigest(),\n            sim: this.sim.checkpoint(), score: this.score.checkpoint(), accumulator: this.accumulator,\n            tick: this.tick, bucketIndex: this.bucketIndex, interactionIndex: this.interactionIndex, branchTick: this.branchTick,\n            random: this.random.state(), behaviour: this.behaviour, until: this.until,\n            targetX: this.targetX, targetY: this.targetY, x: this.x, y: this.y, flip: this.flip, lit: this.lit,\n            lastActivity: this.lastActivity, addressedAt: Number.isFinite(this.addressedAt) ? this.addressedAt : null,\n            waitSince: this.waitSince, food: this.food, members: [...this.members.values()], lastStill: this.lastStill,\n            frame: this.frame, voices: this.voices });\n    }","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/pet_watch/pet-native.js#L199-L235","documentation":"The PetWorld constructor validates every entry in the interactionLog before building the initial frame. An entry is rejected if its timeMs is not a finite non-negative number, if it goes backwards relative to the previous interaction, if its kind is not 'attention' or 'food', or if its x/y coordinates are not finite numbers in [-1, 1]. The library throws to guarantee the recorded input tape is replayable deterministically.","triggerScenarios":"Calling `new PetWorld(points, tape, interactions, version)` (directly or via PetWorld.restore) where `interactions` contains an entry with: non-finite or negative timeMs, timeMs less than the preceding entry's timeMs, kind outside ['attention','food'], or x/y that are non-finite or outside [-1,1].","commonSituations":"Hand-crafting interaction objects for tests, deserializing recordings from an untrusted or older file with a different interaction schema, floating-point corruption or NaN from upstream math producing x/y, appending interactions out of chronological order.","solutions":["Validate each interaction: finite timeMs >= 0, monotonically non-decreasing, kind in ['attention','food'], finite x/y with |x|<=1 and |y|<=1, before constructing PetWorld.","Sort the interaction array by timeMs if ordering may be unsorted.","Clamp or reject x/y outside [-1,1] at the input-capture site (pointer/keyboard handler) before logging.","Re-export or regenerate the recording file if it was produced by an incompatible producer version."],"exampleFix":"// before\nnew PetWorld(points, tape, [{ timeMs: 100, kind: 'pet', x: 2, y: 0 }], 2);\n// after\nnew PetWorld(points, tape, [{ timeMs: 100, kind: 'attention', x: 0.5, y: 0 }], 2);","handlingStrategy":"validation","validationCode":"function validInteractions(list) {\n  return Array.isArray(list) && list.every((e, i) =>\n    Number.isFinite(e.timeMs) && e.timeMs >= 0 &&\n    (i === 0 || e.timeMs >= list[i - 1].timeMs) &&\n    ['attention', 'food'].includes(e.kind) &&\n    Number.isFinite(e.x) && Number.isFinite(e.y) &&\n    Math.abs(e.x) <= 1 && Math.abs(e.y) <= 1);\n}","typeGuard":"const isPetInteraction = (e) =>\n  Number.isFinite(e?.timeMs) && e.timeMs >= 0 &&\n  (e.kind === 'attention' || e.kind === 'food') &&\n  Number.isFinite(e?.x) && Number.isFinite(e?.y) &&\n  Math.abs(e.x) <= 1 && Math.abs(e.y) <= 1;","tryCatchPattern":"try {\n  const world = new PetWorld(points, tape, interactions, version);\n} catch (err) {\n  if (err.message === 'Invalid pet interaction.') {\n    const bad = interactions.findIndex((e, i) => !isPetInteraction(e));\n    console.error('Bad interaction at index', bad, interactions[bad]);\n  } else throw err;\n}","preventionTips":["Clamp x/y to [-1,1] at the input-capture site before logging interactions.","Append interactions in chronological order; sort by timeMs if sources can interleave.","Restrict kind to 'attention' | 'food' with a union type or enum at construction.","Never log interactions computed from potentially-NaN math without a finite check."],"tags":["validation","input","replay"],"backgroundTag":"invalid-argument-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}