{"record":{"id":"a34d23b28c74f882","repo":"Hmbown/CodeWhale","slug":"invalid-pet-export-cursor-native","errorCode":null,"errorMessage":"Invalid pet export cursor.","messagePattern":"Invalid pet export cursor\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/ios/Resources/pet-native.js","lineNumber":305,"sourceCode":"                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;\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)\n            return `{\"petReplayVersion\":${this.segmented ? 2 : 1},\"expressionVersion\":${this.sim.expressionVersion},\"tape\":[`;\n        if (index <= tapes)\n            return (index === 1 ? '' : ',') + JSON.stringify(this.tapeLog.slice((index - 1) * size, Math.min(tapeEnd, index * size))).slice(1, -1);\n        if (index === tapes + 1)\n            return '],\"interactions\":[';\n        const part = index - tapes - 2;\n        if (part < inputs)\n            return (part === 0 ? '' : ',') + JSON.stringify(this.interactionLog.slice(part * size, Math.min(inputEnd, (part + 1) * size))).slice(1, -1);\n        if (part === inputs) {\n            const history = this.historyDigest(tapeEnd, inputEnd);\n            return `]${this.origin ? ',\"start\":' + JSON.stringify({ ...this.origin, hasTelemetry: this.hasTelemetry, history }) : ''},\"checkpoint\":${JSON.stringify({ ...this.checkpoint(), history })}}`;\n        }\n        return null;\n    }","sourceCodeStart":287,"sourceCodeEnd":323,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/ios/Resources/pet-native.js#L287-L323","documentation":"recordingChunk(index) paginates the recording export into fixed-size slices and validates the cursor: it must be a non-negative safe integer. This error means the export cursor is malformed — negative, fractional, NaN, or exceeding Number.MAX_SAFE_INTEGER.","triggerScenarios":"Calling world.recordingChunk(-1), recordingChunk(0.5), recordingChunk(NaN), or passing a non-integer variable (e.g., an uninitialized counter or a string coerced to NaN).","commonSituations":"An off-by-one loop condition producing a negative index; a driver that computes the next index with broken arithmetic; a driver forgetting the loop should stop when the chunk no longer ends in ',' and instead computing indices past the end incorrectly.","solutions":["Fix the driver loop to start at 0 and increment by 1, stopping when the returned chunk no longer ends with a comma (or after the completed=true chunk).","Clamp/validate the index before calling: if (!Number.isSafeInteger(i) || i < 0) throw or break.","Initialize the cursor variable (a undefined-initialized counter becomes NaN on ++).","Coerce external input with Number() and check Number.isSafeInteger before use."],"exampleFix":"// before\nfor (let i = start; ; i += 0.5) out += world.recordingChunk(i);\n// after\nfor (let i = 0; ; i++) {\n  const chunk = world.recordingChunk(i);\n  out += chunk;\n  if (!chunk.endsWith(',')) { out += world.recordingChunk(i + 1, true); break; }\n}","handlingStrategy":"validation","validationCode":"if (!Number.isSafeInteger(index) || index < 0) throw new Error('cursor must be a non-negative safe integer');","typeGuard":"const isValidCursor = (i: unknown): i is number =>\n  typeof i === 'number' && Number.isSafeInteger(i) && i >= 0;","tryCatchPattern":"let i = 0;\ntry { for (;; i++) { const c = world.recordingChunk(i); if (!c.endsWith(',')) { world.recordingChunk(i + 1, true); break; } } }\ncatch (e) { if (e.message === 'Invalid pet export cursor.') throw new Error('export loop cursor bug at ' + i); throw e; }","preventionTips":["Initialize cursor variables (undefined + 1 is NaN).","Use a canonical driver loop: start at 0, ++i, stop when the chunk lacks a trailing comma.","Wrap external cursor input with Number.isSafeInteger checks."],"tags":["export","pagination","arguments"],"backgroundTag":"invalid-argument-value","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"}