Hmbown/CodeWhale · error · Error

Unsupported pet expression version.

Error message

Unsupported pet expression version.

What it means

Thrown by PetWorld.fromRecording when the recording's expressionVersion is not one this build understands. Recordings persist the simulator's expressionVersion; a recording produced by a newer (or unknown) simulator cannot be replayed faithfully, so restoring is refused instead of misinterpreting expression state.

Solutions

  1. Upgrade the app/library to a version supporting the recording's expressionVersion.
  2. Re-record or re-export the pet with a supported expression version (1 or 2).
  3. Fix or remove the corrupted expressionVersion field if the file was hand-edited.

Example fix

// before
const world = PetWorld.fromRecording(points, newRecordingFromFutureVersion);
// after
if (![1, 2].includes(newRecordingFromFutureVersion.expressionVersion)) {
  throw new Error('Upgrade the app to load this recording.');
}
const world = PetWorld.fromRecording(points, newRecordingFromFutureVersion);
Defensive patterns

Strategy: validation

Validate before calling

const version = rec.expressionVersion ?? 1;
if (![1,2].includes(version)) throw new Error('expressionVersion ' + version + ' not supported; upgrade the app');

Try / catch

try { PetWorld.fromRecording(points, rec); } catch (e) { if (e.message.includes('expression version')) promptUpgrade(); }

Prevention

When it happens

Trigger: Loading a recording whose expressionVersion is 0, 3, or any value other than 1/2 (undefined is allowed and means 1).

Common situations: Downgrading the app and opening recordings made by a newer version; hand-edited recording files; a corrupt expressionVersion field.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/b9610f9662b416b4. Report an issue: GitHub.

Appendix: source

Thrown at pet/src/core/pet-world.ts:140

      waitSince: this.waitSince, food: this.food, members: [...this.members.values()], lastStill: this.lastStill,
      frame: this.frame, voices: this.voices });
  }

  recording(withCheckpoint = true, completed = false): PetRecording {
    const tapeEnd = completed ? this.bucketIndex + 1 : this.tapeLog.length;
    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: [number, number][], value: unknown): PetWorld {
    const r = value as PetRecording;
    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(): PetSegment {
    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 433685b202)