Hmbown/CodeWhale · error · Error
Invalid pet interaction.
Error message
Invalid pet interaction.
What it means
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.
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.
Example fix
// before
new PetWorld(points, tape, [{ timeMs: 100, kind: 'pet', x: 2, y: 0 }], 2);
// after
new PetWorld(points, tape, [{ timeMs: 100, kind: 'attention', x: 0.5, y: 0 }], 2); Defensive patterns
Strategy: validation
Validate before calling
function validInteractions(list) {
return Array.isArray(list) && list.every((e, i) =>
Number.isFinite(e.timeMs) && e.timeMs >= 0 &&
(i === 0 || e.timeMs >= list[i - 1].timeMs) &&
['attention', 'food'].includes(e.kind) &&
Number.isFinite(e.x) && Number.isFinite(e.y) &&
Math.abs(e.x) <= 1 && Math.abs(e.y) <= 1);
} Type guard
const isPetInteraction = (e) => Number.isFinite(e?.timeMs) && e.timeMs >= 0 && (e.kind === 'attention' || e.kind === 'food') && Number.isFinite(e?.x) && Number.isFinite(e?.y) && Math.abs(e.x) <= 1 && Math.abs(e.y) <= 1;
Try / catch
try {
const world = new PetWorld(points, tape, interactions, version);
} catch (err) {
if (err.message === 'Invalid pet interaction.') {
const bad = interactions.findIndex((e, i) => !isPetInteraction(e));
console.error('Bad interaction at index', bad, interactions[bad]);
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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].
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Automation name is required
- Automation prompt is required
- coordinates must be finite numbers
- coordinates must be finite numbers
- invalid draft id
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/994393a88522ad84.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/pet_watch/pet-native.js:217
if (tape.length > 216_000 || interactions.length > 100_000)
throw new Error('Pet recording exceeds its input limit.');
this.sim = new pet_sim_js_1.PetSim(points, 0xC0FFEE, expressionVersion);
this.segmented = segmented;
this.hasTelemetry = tape.length > 0;
this.tapeLog = structuredClone([...tape]);
this.interactionLog = structuredClone([...interactions]);
for (let i = 0; i < this.tape.length; i++) {
const b = this.tape[i];
(0, pet_telemetry_js_1.validatePetBucket)(b);
if (segmented ? i > 0 && b.sequence <= this.tape[i - 1].sequence : b.sequence !== i)
throw new Error('World requires contiguous version 1 pet buckets.');
}
for (let i = 0; i < this.interactionLog.length; i++) {
const e = this.interactionLog[i];
if (!Number.isFinite(e.timeMs) || e.timeMs < 0 || i > 0 && e.timeMs < this.interactionLog[i - 1].timeMs
|| !['attention', 'food'].includes(e.kind) || !Number.isFinite(e.x) || !Number.isFinite(e.y)
|| Math.abs(e.x) > 1 || Math.abs(e.y) > 1)
throw new Error('Invalid pet interaction.');
}
this.hashTape(0);
this.frame = this.makeFrame(0);
this.voices = this.score.voices(this.frame);
if (segmented)
this.origin = this.checkpoint();
}
checkpoint() {
return structuredClone({ petCheckpointVersion: this.segmented ? 2 : 1,
...(this.segmented ? { historyStart: (this.origin?.tick ?? this.tick), hasTelemetry: this.hasTelemetry } : {}), history: this.historyDigest(),
sim: this.sim.checkpoint(), score: this.score.checkpoint(), accumulator: this.accumulator,
tick: this.tick, bucketIndex: this.bucketIndex, interactionIndex: this.interactionIndex, branchTick: this.branchTick,
random: this.random.state(), behaviour: this.behaviour, until: this.until,
targetX: this.targetX, targetY: this.targetY, x: this.x, y: this.y, flip: this.flip, lit: this.lit,
lastActivity: this.lastActivity, addressedAt: Number.isFinite(this.addressedAt) ? this.addressedAt : null,
waitSince: this.waitSince, food: this.food, members: [...this.members.values()], lastStill: this.lastStill,
frame: this.frame, voices: this.voices });
}View on GitHub (pinned to 73e0f67d83)