{"record":{"id":"59d03fbdd1e8fe8b","repo":"Hmbown/CodeWhale","slug":"the-pet-recording-segment-has-changed-native","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":"pet/ios/Resources/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/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/ios/Resources/pet-native.js#L270-L306","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call commit() synchronously immediately after prepareSegment(), before any tick runs.","Track and remove the duplicate commit call (the committed flag guards double invocation).","Pause the tick loop (or snapshot under the same lock) while a segment commit is pending.","If the world legitimately advanced, discard this prepareSegment result and call prepareSegment() again on the current state."],"exampleFix":"// before\nconst seg = world.prepareSegment();\nworld.tick(dt); // world advanced\nseg.commit(); // throws\n// after\nconst seg = world.prepareSegment();\nseg.commit();   // commit first\nworld.tick(dt); // then continue simulating","handlingStrategy":"try-catch","validationCode":"let tickBefore = world.tick; const seg = world.prepareSegment(); const canCommit = world.origin === segBeforeOrigin && world.tick === tickBefore;","typeGuard":null,"tryCatchPattern":"const seg = world.prepareSegment();\ntry { seg.commit(); }\ncatch (e) {\n  if (e.message === 'The pet recording segment has changed.') {\n    // world ticked mid-archive: discard and retry on current state\n    const retry = world.prepareSegment(); retry.commit();\n  } else throw e;\n}","preventionTips":["Call commit() synchronously right after prepareSegment(), before any tick.","Never call commit() twice; treat the returned object as single-use.","Freeze the tick loop (or hold a lock) while a segment archive is pending.","Do not share a live PetWorld across concurrent features during export."],"tags":["concurrency","state","replay"],"backgroundTag":"invalid-state-transition","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}