Hmbown/CodeWhale · error · Error

The pet recording segment has changed.

Error message

The pet recording segment has changed.

What it means

prepareSegment returns a commit() closure that splices consumed input off the live world's logs. Before mutating, it re-checks that this.origin is still the checkpoint it captured and that the world has not ticked backwards past the segment boundary (this.tick < c.tick). If the world moved on — the origin advanced or ticks were consumed elsewhere — the segment being committed no longer matches the live state, so committing would corrupt the tape and the error is thrown instead.

Solutions

  1. Call commit() exactly once, immediately after prepareSegment(), before any further ticks.
  2. Discard the stale closure and call prepareSegment() again to get a fresh commit handle.
  3. Ensure only one code path owns the world and performs segment rotation.
  4. Do not restore/rollback the world between prepareSegment() and commit().

Example fix

// before
const seg = world.prepareSegment();
world.tick(); // state advanced
seg.commit(); // throws
// after
const seg = world.prepareSegment();
seg.commit(); // commit before any further ticks
world.tick();
Defensive patterns

Strategy: try-catch

Validate before calling

// Track segment rotation on one owner:
if (pendingCommit) throw new Error('Previous segment commit not finished');

Try / catch

const seg = world.prepareSegment();
try {
  seg.commit();
} catch (err) {
  if (err.message === 'The pet recording segment has changed.') {
    seg = null;
    const fresh = world.prepareSegment(); fresh.commit(); // retry once with fresh state
  } else throw err;
}

Prevention

When it happens

Trigger: Calling the `commit()` returned by `prepareSegment()` after the world state changed between prepareSegment and commit: another commit already ran (committed flag), the origin checkpoint was replaced (e.g. by an earlier commit or restore), or the live world's tick dropped below the prepared checkpoint's tick.

Common situations: Calling commit() twice on the same prepared segment; interleaving prepareSegment/commit with a restore-from-checkpoint; driving the world from two code paths where one rolls it back; async capture of the commit closure invoked late after ticks/origin changed.

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@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/854b1a592876110e. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/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 73e0f67d83)