Hmbown/CodeWhale · error · Error

The pet recording segment has changed.

Error message

The pet recording segment has changed.

What it means

prepareSegment archives consumed input by capturing a next checkpoint and returning a commit() closure. commit() re-verifies that the world has not advanced or mutated since the checkpoint was taken: it fails if commit was already called, this.origin changed, or this.tick moved past c.tick. This error means the segment changed between snapshot and commit, so splicing the logs would corrupt the replay.

Solutions

  1. Call commit() synchronously immediately after prepareSegment(), before any tick runs.
  2. Track and remove the duplicate commit call (the committed flag guards double invocation).
  3. Pause the tick loop (or snapshot under the same lock) while a segment commit is pending.
  4. If the world legitimately advanced, discard this prepareSegment result and call prepareSegment() again on the current state.

Example fix

// before
const seg = world.prepareSegment();
world.tick(dt); // world advanced
seg.commit(); // throws
// after
const seg = world.prepareSegment();
seg.commit();   // commit first
world.tick(dt); // then continue simulating
Defensive patterns

Strategy: try-catch

Validate before calling

let tickBefore = world.tick; const seg = world.prepareSegment(); const canCommit = world.origin === segBeforeOrigin && world.tick === tickBefore;

Try / catch

const seg = world.prepareSegment();
try { seg.commit(); }
catch (e) {
  if (e.message === 'The pet recording segment has changed.') {
    // world ticked mid-archive: discard and retry on current state
    const retry = world.prepareSegment(); retry.commit();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling commit() twice; ticking the PetWorld (advancing this.tick) or changing origin between prepareSegment() and commit(); concurrent code paths mutating the world while a segment archive is pending.

Common situations: A game loop tick firing during a long-running export; a retry wrapper that calls commit twice; sharing one PetWorld across modules where another feature calls tick() mid-archive.

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/59d03fbdd1e8fe8b. Report an issue: GitHub.

Appendix: source

Thrown at pet/ios/Resources/pet-native.js:288

    /** 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);
        const points = this.sim.p.map(p => [p.hx, p.hy]);
        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, completed = false) {
        if (!Number.isSafeInteger(index) || index < 0)
            throw new Error('Invalid pet export cursor.');
        const size = 16, tapeEnd = completed ? this.bucketIndex + 1 : this.tapeLog.length;

View on GitHub (pinned to 433685b202)