Hmbown/CodeWhale · error · Error

Pet expression version does not match its checkpoint.

Error message

Pet expression version does not match its checkpoint.

What it means

Every checkpoint in a recording (start and checkpoint) carries its own sim.expressionVersion, defaulting to 1 for legacy data. fromRecording requires each checkpoint's version to match the recording's top-level expressionVersion. A mismatch means the checkpoint and the recording envelope came from different producers or formats, so restoring would mix incompatible simulation state.

Solutions

  1. Make expressionVersion and every checkpoint's sim.expressionVersion agree (use the checkpoint's version if it is the genuine one).
  2. Re-export the recording (recordingChunk/prepareSegment) from a single app version so envelope and checkpoint versions match.
  3. Restore checkpoints only from recordings produced by the same binary that made them.
  4. If the checkpoint is legacy data, re-record the segment rather than patching versions by hand.

Example fix

// before
r.expressionVersion = 2; // envelope upgraded, checkpoint still v1
const world = PetWorld.fromRecording(points, r); // throws
// after
r.checkpoint.sim.expressionVersion = 2; // migrate the checkpoint too
// or better: keep both at their true version
r.expressionVersion = r.checkpoint.sim.expressionVersion;
const world = PetWorld.fromRecording(points, r);
Defensive patterns

Strategy: validation

Validate before calling

const version = r.expressionVersion ?? 1;
for (const c of [r.start, r.checkpoint])
  if (c && (c.sim?.expressionVersion ?? 1) !== version)
    throw new Error('Recording and checkpoint expression versions disagree');

Try / catch

try {
  world = PetWorld.fromRecording(points, r);
} catch (err) {
  if (err.message === 'Pet expression version does not match its checkpoint.')
    console.error('Mixed-version recording; re-export from one app version.');
  else throw err;
}

Prevention

When it happens

Trigger: Calling `PetWorld.fromRecording(points, r)` where r.expressionVersion is, say, 2, but r.start.sim.expressionVersion or r.checkpoint.sim.expressionVersion is 1 (or absent, defaulting to 1), or vice versa.

Common situations: Stitching a checkpoint from an old recording into a new one; a producer bug that stamped the envelope version but not the checkpoint sim; partially migrated recordings from an app upgrade; hand-editing one of the two version fields.

Related errors


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

Appendix: source

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

        const inputEnd = completed ? this.interactionIndex : this.interactionLog.length;
        const history = this.historyDigest(tapeEnd, inputEnd);
        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);

View on GitHub (pinned to 73e0f67d83)