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

  1. 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.
  2. Sort the interaction array by timeMs if ordering may be unsorted.
  3. Clamp or reject x/y outside [-1,1] at the input-capture site (pointer/keyboard handler) before logging.
  4. 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

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


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)