{"record":{"id":"6f8843e139f406ee","repo":"Hmbown/CodeWhale","slug":"the-pet-recording-segment-has-changed-world","errorCode":null,"errorMessage":"The pet recording segment has changed.","messagePattern":"The pet recording segment has changed\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/pet-world.ts","lineNumber":167,"sourceCode":"      world.origin = first.checkpoint();\n    }\n    return world;\n  }\n\n  /** Retire only consumed input. The exact origin makes each archived segment\n   * independently replayable; no particle, random stream or score is reset. */\n  prepareSegment(): PetSegment {\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] as [number, number]);\n    const c = this.checkpoint();\n    c.petCheckpointVersion = 2; c.historyStart = this.tick; c.hasTelemetry = this.hasTelemetry;\n    c.bucketIndex -= dropTape; 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); 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) throw new Error('The pet recording segment has changed.');\n      this.tapeLog.splice(0, dropTape); this.interactionLog.splice(0, dropInputs);\n      this.bucketIndex -= dropTape; this.interactionIndex -= dropInputs;\n      this.tapeHashes = []; this.hashTape(0); this.segmented = true; this.origin = next.origin;\n      committed = true;\n    } };\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: number, completed = false): string | null {\n    if (!Number.isSafeInteger(index) || index < 0) throw new Error('Invalid pet export cursor.');\n    const size = 16, tapeEnd = completed ? this.bucketIndex + 1 : this.tapeLog.length;\n    const inputEnd = completed ? this.interactionIndex : this.interactionLog.length;\n    const tapes = Math.ceil(tapeEnd / size), inputs = Math.ceil(inputEnd / size);\n    if (index === 0) return `{\"petReplayVersion\":${this.segmented ? 2 : 1},\"expressionVersion\":${this.sim.expressionVersion},\"tape\":[`;\n    if (index <= tapes) return (index === 1 ? '' : ',') + JSON.stringify(this.tapeLog.slice((index - 1) * size, Math.min(tapeEnd, index * size))).slice(1, -1);\n    if (index === tapes + 1) return '],\"interactions\":[';","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/pet-world.ts#L149-L185","documentation":"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.","triggerScenarios":"Calling prepareSegment(...), then ticking the world or calling prepareSegment again, and finally invoking the first segment's commit() callback.","commonSituations":"Storing the commit callback and running animation frames before committing; calling commit twice (though the committed flag catches that); interleaving segmentation of multiple worlds.","solutions":["Invoke commit() synchronously, immediately after prepareSegment, before any further ticks.","Prepare a fresh segment (call prepareSegment again) if the world has advanced, discarding the stale commit callback.","Do not tick or restore the world between prepareSegment and commit."],"exampleFix":"// before\nconst seg = world.prepareSegment(points, tape, interactions, dropTape, dropInputs);\nworld.step(1 / 30, opts); // advances origin\nseg.commit(); // throws\n// after\nconst seg = world.prepareSegment(points, tape, interactions, dropTape, dropInputs);\nseg.commit(); // commit first, then tick","handlingStrategy":"try-catch","validationCode":"// Verify world is untouched since prepareSegment:\nif (world.origin !== capturedOrigin || world.tick < preparedTick)\n  throw new Error('world advanced; re-prepare segment before commit');","typeGuard":null,"tryCatchPattern":"const seg = world.prepareSegment(points, tape, interactions, dropTape, dropInputs);\ntry {\n  seg.commit();\n} catch (e) {\n  if (e.message === 'The pet recording segment has changed.') {\n    const fresh = world.prepareSegment(/* same args */); fresh.commit(); // re-prepare and commit\n  } else throw e;\n}","preventionTips":["Call commit() immediately after prepareSegment, before any tick.","Never interleave prepareSegment calls on the same world.","Keep segment lifecycle inside one synchronous block."],"tags":["segmentation","stale-state","concurrency"],"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"}