Hmbown/CodeWhale · error · Error
Invalid legacy pet recording.
Error message
Invalid legacy pet recording.
What it means
Version 1 (legacy) recordings must never carry a start checkpoint, and any checkpoint they do carry must have petCheckpointVersion === 1. fromRecording throws when a legacy recording violates either rule, because v1 has no segmented replay support and mixing checkpoint versions would make deterministic restore impossible.
Solutions
- Remove the stray start/checkpoint from the legacy recording, or set checkpoint.petCheckpointVersion to 1 if it truly is a legacy checkpoint.
- Re-export the recording as petReplayVersion 2 with a valid v2 start checkpoint if segmentation is intended.
- Regenerate the recording with the tooling version that matches its declared petReplayVersion.
Example fix
// before
const rec = { petReplayVersion: 1, tape, interactions, start: someV2Checkpoint }; // throws
// after
const rec2 = { petReplayVersion: 2, expressionVersion: 1, tape, interactions, start: someV2Checkpoint }; Defensive patterns
Strategy: validation
Validate before calling
if (rec.petReplayVersion === 1 && (rec.start || (rec.checkpoint && rec.checkpoint.petCheckpointVersion !== 1)))
throw new Error('recording is not a valid legacy v1 blob'); Type guard
function isLegacyRecording(r: unknown): r is PetRecording {
const rec = r as PetRecording;
return rec?.petReplayVersion === 1 && !rec.start && (!rec.checkpoint || rec.checkpoint.petCheckpointVersion === 1);
} Try / catch
try {
return PetWorld.fromRecording(points, rec);
} catch (e) {
if (e.message === 'Invalid legacy pet recording.') {
// upgrade to v2 or strip the stray checkpoint
} else throw e;
} Prevention
- Keep legacy v1 recordings untouched — never inject checkpoints into them.
- Migrate v1 to v2 once via a one-way migration script.
- Tag recordings with the exporting app version.
When it happens
Trigger: Calling PetWorld.fromRecording(points, value) with petReplayVersion === 1 and either r.start set, or r.checkpoint whose petCheckpointVersion is not 1.
Common situations: Upgrading a v1 recording to include a modern v2 checkpoint; a serialization bug that injects a start field into old recordings; importing recordings exported by a newer app into an older runtime that still tags them version 1.
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
- Pet expression version does not match its checkpoint.
- Archive the legacy recording before accepting more…
- Archive this pet recording before accepting more telemetry.
- bundle at has schema_version ; this build understands
- codewhale_config::LEGACY_ANTIGRAVITY_TOMBSTONE_MESSAGE
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/8e1d8b7a3146d2f1.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/pet-world.ts:143
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();
c.petCheckpointVersion = 2; c.historyStart = this.tick; c.hasTelemetry = this.hasTelemetry;View on GitHub (pinned to 433685b202)