Hmbown/CodeWhale · error · Error

The pet recording segment has changed.

Error message

The pet recording segment has changed.

What it means

The commit() closure returned by prepareSegment captures the world's origin checkpoint and tick at segment-preparation time. When commit is invoked it verifies nothing advanced or mutated the world in between; if the origin checkpoint changed or ticks were consumed, the prepared archive is stale and committing would corrupt the live recording, so it throws.

Solutions

  1. Invoke commit() synchronously, immediately after prepareSegment, before any further ticks.
  2. Prepare a fresh segment (call prepareSegment again) if the world has advanced, discarding the stale commit callback.
  3. Do not tick or restore the world between prepareSegment and commit.

Example fix

// before
const seg = world.prepareSegment(points, tape, interactions, dropTape, dropInputs);
world.step(1 / 30, opts); // advances origin
seg.commit(); // throws
// after
const seg = world.prepareSegment(points, tape, interactions, dropTape, dropInputs);
seg.commit(); // commit first, then tick
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify world is untouched since prepareSegment:
if (world.origin !== capturedOrigin || world.tick < preparedTick)
  throw new Error('world advanced; re-prepare segment before commit');

Try / catch

const seg = world.prepareSegment(points, tape, interactions, dropTape, dropInputs);
try {
  seg.commit();
} catch (e) {
  if (e.message === 'The pet recording segment has changed.') {
    const fresh = world.prepareSegment(/* same args */); fresh.commit(); // re-prepare and commit
  } else throw e;
}

Prevention

When it happens

Trigger: Calling prepareSegment(...), then ticking the world or calling prepareSegment again, and finally invoking the first segment's commit() callback.

Common situations: Storing the commit callback and running animation frames before committing; calling commit twice (though the committed flag catches that); interleaving segmentation of multiple worlds.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

      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;
    c.bucketIndex -= dropTape; c.interactionIndex = 0;
    c.history = new PetWorld(points, tape, interactions, this.sim.expressionVersion, true).historyDigest();
    const next = PetWorld.restore(points, tape, interactions, c); next.origin = next.checkpoint();
    let committed = false;
    return { recording: next.recording(), archive: this.recording(true, true), commit: () => {
      if (committed || this.origin !== previous || this.tick < c.tick) throw new Error('The pet recording segment has changed.');
      this.tapeLog.splice(0, dropTape); this.interactionLog.splice(0, dropInputs);
      this.bucketIndex -= dropTape; this.interactionIndex -= dropInputs;
      this.tapeHashes = []; this.hashTape(0); this.segmented = true; this.origin = next.origin;
      committed = true;
    } };
  }

  /** Lossless version 1 export, including the current pose and score. Drivers
   * consume all chunks synchronously on the world's owner before another tick.
   * Only a small slice is serialized inside an embedded runtime at a time. */
  recordingChunk(index: number, completed = false): string | null {
    if (!Number.isSafeInteger(index) || index < 0) throw new Error('Invalid pet export cursor.');
    const size = 16, tapeEnd = completed ? this.bucketIndex + 1 : this.tapeLog.length;
    const inputEnd = completed ? this.interactionIndex : this.interactionLog.length;
    const tapes = Math.ceil(tapeEnd / size), inputs = Math.ceil(inputEnd / size);
    if (index === 0) return `{"petReplayVersion":${this.segmented ? 2 : 1},"expressionVersion":${this.sim.expressionVersion},"tape":[`;
    if (index <= tapes) return (index === 1 ? '' : ',') + JSON.stringify(this.tapeLog.slice((index - 1) * size, Math.min(tapeEnd, index * size))).slice(1, -1);
    if (index === tapes + 1) return '],"interactions":[';

View on GitHub (pinned to 433685b202)