{"record":{"id":"854b1a592876110e","repo":"Hmbown/CodeWhale","slug":"the-pet-recording-segment-has-changed","errorCode":null,"errorMessage":"The pet recording segment has changed.","messagePattern":"The pet recording segment has changed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tui/pet_watch/pet-native.js","lineNumber":288,"sourceCode":"    /** Retire only consumed input. The exact origin makes each archived segment\n     * independently replayable; no particle, random stream or score is reset. */\n    prepareSegment() {\n        const dropTape = Math.max(0, this.bucketIndex), dropInputs = this.interactionIndex, previous = this.origin;\n        const tape = this.tapeLog.slice(dropTape), interactions = this.interactionLog.slice(dropInputs);\n        const points = this.sim.p.map(p => [p.hx, p.hy]);\n        const c = this.checkpoint();\n        c.petCheckpointVersion = 2;\n        c.historyStart = this.tick;\n        c.hasTelemetry = this.hasTelemetry;\n        c.bucketIndex -= dropTape;\n        c.interactionIndex = 0;\n        c.history = new PetWorld(points, tape, interactions, this.sim.expressionVersion, true).historyDigest();\n        const next = PetWorld.restore(points, tape, interactions, c);\n        next.origin = next.checkpoint();\n        let committed = false;\n        return { recording: next.recording(), archive: this.recording(true, true), commit: () => {\n                if (committed || this.origin !== previous || this.tick < c.tick)\n                    throw new Error('The pet recording segment has changed.');\n                this.tapeLog.splice(0, dropTape);\n                this.interactionLog.splice(0, dropInputs);\n                this.bucketIndex -= dropTape;\n                this.interactionIndex -= dropInputs;\n                this.tapeHashes = [];\n                this.hashTape(0);\n                this.segmented = true;\n                this.origin = next.origin;\n                committed = true;\n            } };\n    }\n    /** Lossless version 1 export, including the current pose and score. Drivers\n     * consume all chunks synchronously on the world's owner before another tick.\n     * Only a small slice is serialized inside an embedded runtime at a time. */\n    recordingChunk(index, completed = false) {\n        if (!Number.isSafeInteger(index) || index < 0)\n            throw new Error('Invalid pet export cursor.');\n        const size = 16, tapeEnd = completed ? this.bucketIndex + 1 : this.tapeLog.length;","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/pet_watch/pet-native.js#L270-L306","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call commit() exactly once, immediately after prepareSegment(), before any further ticks.","Discard the stale closure and call prepareSegment() again to get a fresh commit handle.","Ensure only one code path owns the world and performs segment rotation.","Do not restore/rollback the world between prepareSegment() and commit()."],"exampleFix":"// before\nconst seg = world.prepareSegment();\nworld.tick(); // state advanced\nseg.commit(); // throws\n// after\nconst seg = world.prepareSegment();\nseg.commit(); // commit before any further ticks\nworld.tick();","handlingStrategy":"try-catch","validationCode":"// Track segment rotation on one owner:\nif (pendingCommit) throw new Error('Previous segment commit not finished');","typeGuard":null,"tryCatchPattern":"const seg = world.prepareSegment();\ntry {\n  seg.commit();\n} catch (err) {\n  if (err.message === 'The pet recording segment has changed.') {\n    seg = null;\n    const fresh = world.prepareSegment(); fresh.commit(); // retry once with fresh state\n  } else throw err;\n}","preventionTips":["Call commit() synchronously right after prepareSegment(), before any tick.","Never invoke a prepared commit twice; null it out after use.","Funnel all world mutation and segment rotation through one owner/path.","Do not restore or roll back the world between prepareSegment and commit."],"tags":["state","replay","race-condition"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}