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

PetWorld.fromRecording validates that any start/checkpoint embedded in a recording carries the same expressionVersion as the recording's own declared expression version. If a checkpoint's sim.expressionVersion (defaulting to 1) differs from the resolved top-level version, the recording's segments would replay under divergent behavior models, so the library refuses to build a world. This guards deterministic replay across expression engine upgrades.

Solutions

  1. Re-capture the recording with a single consistent build so the top-level expressionVersion and each checkpoint's sim.expressionVersion agree.
  2. Align the recording's top-level expressionVersion with the nested checkpoint's sim.expressionVersion (or vice versa) before calling fromRecording.
  3. If intentionally upgrading, run the checkpoint through the proper migration path that rewrites both version markers together.

Example fix

// before (mismatched versions)
const rec = { petReplayVersion: 2, expressionVersion: 2, tape: [...], interactions: [...], checkpoint: { petCheckpointVersion: 2, sim: { expressionVersion: 1 } } };
PetWorld.fromRecording(points, rec); // throws
// after
rec.checkpoint.sim.expressionVersion = 2;
PetWorld.fromRecording(points, rec); // ok
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('checkpoint expressionVersion mismatch before fromRecording');
}

Type guard

function hasMatchingExpressionVersion(r: unknown): r is PetRecording & { expressionVersion: 1 | 2 } {
  const rec = r as PetRecording;
  const v = rec?.expressionVersion ?? 1;
  return [1, 2].includes(v) && [rec.start, rec.checkpoint].every(c => !c || (c.sim?.expressionVersion ?? 1) === v);
}

Try / catch

try {
  const world = PetWorld.fromRecording(points, rec);
} catch (e) {
  if (e.message.includes('does not match its checkpoint')) {
    // re-capture or migrate the recording to a single expression version
  } else throw e;
}

Prevention

When it happens

Trigger: Calling PetWorld.fromRecording(points, value) with a recording whose expressionVersion is 1 or 2 while r.start.sim.expressionVersion or r.checkpoint.sim.expressionVersion is a different value (e.g. a checkpoint saved by an older pet build embedded into a newer-versioned recording).

Common situations: Mixing recordings and checkpoints from different app versions; hand-editing a recording's top-level expressionVersion without updating the nested checkpoint sim blob; migrating archived segments by changing only one of the two version fields.

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@433685b202 (2026-09-15). Data as JSON: /api/errors/b787848444d51530. Report an issue: GitHub.

Appendix: source

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

  }

  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);
    const points = this.sim.p.map(p => [p.hx, p.hy] as [number, number]);
    const c = this.checkpoint();

View on GitHub (pinned to 433685b202)