Hmbown/CodeWhale · error · Error

Invalid legacy pet recording.

Error message

Invalid legacy pet recording.

What it means

Version 1 (legacy, non-segmented) recordings must be self-contained from tick 0: they must not carry a start checkpoint, and any checkpoint they carry must be a v1 checkpoint (petCheckpointVersion === 1). If a legacy recording contains a start, or its checkpoint claims a newer checkpoint version, it is neither a valid v1 recording nor a valid v2 segment and is rejected.

Solutions

  1. Set petReplayVersion to 2 if the recording genuinely has a start checkpoint and v2 checkpoint format.
  2. Remove the start key and use a v1 (petCheckpointVersion 1) checkpoint if it truly is a legacy recording.
  3. Re-export the recording from the app version that produced it so version fields are self-consistent.
  4. Do not hand-merge v1 and v2 recording fields; regenerate the file instead.

Example fix

// before
{ petReplayVersion: 1, start: {...}, checkpoint: { petCheckpointVersion: 2, ... } } // throws
// after
{ petReplayVersion: 2, start: {...}, checkpoint: { petCheckpointVersion: 2, ... } }
Defensive patterns

Strategy: validation

Validate before calling

if (r.petReplayVersion === 1 && (r.start || (r.checkpoint && r.checkpoint.petCheckpointVersion !== 1)))
  throw new Error('File is a v2-style recording labeled v1');

Try / catch

try {
  world = PetWorld.fromRecording(points, r);
} catch (err) {
  if (err.message === 'Invalid legacy pet recording.') {
    // try re-labeled as v2 segment
    world = PetWorld.fromRecording(points, { ...r, petReplayVersion: 2 });
  } else throw err;
}

Prevention

When it happens

Trigger: Calling `PetWorld.fromRecording(points, r)` with r.petReplayVersion === 1 while either r.start is set, or r.checkpoint is set with r.checkpoint.petCheckpointVersion !== 1.

Common situations: A v2 segment file renamed or mislabeled as v1; a producer writing checkpointVersion 2 checkpoints into legacy recordings; mixing fields from v1 and v2 exports during a manual migration.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/718384ced5a08757. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/pet-native.js:258

        return { petReplayVersion: this.segmented ? 2 : 1, expressionVersion: this.sim.expressionVersion,
            tape: this.tapeLog.slice(0, tapeEnd), interactions: this.interactionLog.slice(0, inputEnd).map(e => ({ ...e })),
            ...(this.origin ? { start: { ...structuredClone(this.origin), hasTelemetry: this.hasTelemetry, history } } : {}),
            ...(withCheckpoint ? { checkpoint: { ...this.checkpoint(), history } } : {}) };
    }
    static fromRecording(points, value) {
        const r = value;
        if (!r || ![1, 2].includes(r.petReplayVersion) || !Array.isArray(r.tape) || !Array.isArray(r.interactions))
            throw new Error('Invalid pet recording.');
        const version = r.expressionVersion === undefined ? 1 : r.expressionVersion;
        if (![1, 2].includes(version))
            throw new Error('Unsupported pet expression version.');
        if (r.checkpoint !== undefined && !r.checkpoint || r.start !== undefined && !r.start)
            throw new Error('Invalid pet checkpoint.');
        for (const c of [r.start, r.checkpoint])
            if (c && (c.sim?.expressionVersion ?? 1) !== version)
                throw new Error('Pet expression version does not match its checkpoint.');
        if (r.petReplayVersion === 1 && (r.start || r.checkpoint && r.checkpoint.petCheckpointVersion !== 1))
            throw new Error('Invalid legacy pet recording.');
        if (r.petReplayVersion === 2 && (!r.start || r.start.petCheckpointVersion !== 2 || r.start.historyStart !== r.start.tick))
            throw new Error('The recording segment is missing its starting checkpoint.');
        const first = r.start && PetWorld.restore(points, r.tape, r.interactions, r.start);
        const world = r.checkpoint ? PetWorld.restore(points, r.tape, r.interactions, r.checkpoint) : first ?? new PetWorld(points, r.tape, r.interactions, version);
        if (first) {
            if (!world.segmented || world.tick < first.tick || r.checkpoint && r.checkpoint.historyStart !== first.tick)
                throw new Error('Pet segment checkpoints do not agree.');
            world.origin = first.checkpoint();
        }
        return world;
    }
    /** Retire only consumed input. The exact origin makes each archived segment
     * independently replayable; no particle, random stream or score is reset. */
    prepareSegment() {
        const dropTape = Math.max(0, this.bucketIndex), dropInputs = this.interactionIndex, previous = this.origin;
        const tape = this.tapeLog.slice(dropTape), interactions = this.interactionLog.slice(dropInputs);
        const points = this.sim.p.map(p => [p.hx, p.hy]);
        const c = this.checkpoint();

View on GitHub (pinned to 73e0f67d83)