{"record":{"id":"499ce48283c980b2","repo":"Hmbown/CodeWhale","slug":"invalid-engine-batch-499ce4","errorCode":null,"errorMessage":"Invalid Engine batch.","messagePattern":"Invalid Engine batch\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/pet-native.ts","lineNumber":80,"sourceCode":"    if (text.length > 8 * 1024 * 1024) throw new Error('Native habitat exceeds 8 MiB.');\n    const points = this.world.sim.p.map(p => [p.hx, p.hy] as [number, number]);\n    this.world = PetWorld.fromRecording(points, JSON.parse(text));\n    this.liveTape.reset();\n    this.segment = undefined; this.engineTick = Math.round(this.world.frame.timeMs * 30 / 1000); this.engine = new PetEngineTelemetry();\n  }\n  /** Resume a live host at the first unrecorded bucket. Keep every accepted\n   * interval, but never present its last observed frame as current evidence. */\n  resumeEngine(): number {\n    this.world.resumeObservation();\n    this.engineTick = Math.round(this.world.frame.timeMs * 30 / 1000);\n    this.engine = new PetEngineTelemetry();\n    this.world.voices = [];\n    return this.world.frame.timeMs;\n  }\n  observeEngine(metadataJSON: string, timeMs: number): void { this.engine.observe(JSON.parse(metadataJSON), timeMs); }\n  observeEngineBatch(metadataJSON: string, timeMs: number): void {\n    const events: unknown = JSON.parse(metadataJSON);\n    if (!Array.isArray(events) || events.length > 64) throw new Error('Invalid Engine batch.');\n    const next = this.engine.clone();\n    for (const event of events) next.observe(event, timeMs);\n    this.engine = next;\n  }\n  advanceEngine(timeMs: number, motion: boolean, waiting: boolean): void {\n    const target = Math.floor(timeMs * 30 / 1000 + 1e-8);\n    if (!Number.isFinite(timeMs) || target < this.engineTick || target - this.engineTick > 300) throw new Error('Engine pet clock jump.');\n    this.engine.confirmWaiting(timeMs, waiting);\n    const voices: PetVoice[] = [];\n    while (this.engineTick < target) {\n      if ((this.engineTick + 1) % 12 === 0) this.world.acceptTelemetry(this.engine.bucket(Math.floor(this.engineTick / 12)));\n      this.world.step(1 / 30, { motion, sensitivity: 1 });\n      voices.push(...this.world.voices);\n      this.engineTick++;\n    }\n    this.world.voices = voices;\n  }\n  terminal(width: number, height: number): string {","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/pet-native.ts#L62-L98","documentation":"observeEngineBatch(metadataJSON, timeMs) parses the metadata JSON and requires it to be an array of at most 64 engine events; anything else (non-array JSON, or an array longer than 64) throws 'Invalid Engine batch.' The batch is applied to a cloned engine so a failure never leaves the live engine partially updated.","triggerScenarios":"Passing a single event object (not wrapped in an array) to observeEngineBatch; passing an array with more than 64 events; passing JSON that parses to null/string/number; malformed JSON also fails earlier in JSON.parse.","commonSituations":"Caller switching from observeEngine (single object) to observeEngineBatch without wrapping; batching an entire session's events at once instead of chunking; telemetry producer emitting a wrapped object like {events:[...]} that was passed through directly.","solutions":["Ensure the payload is a JSON array and chunk it to <= 64 events per call","If you have a single event, call observeEngine(metadataJSON, timeMs) instead, or wrap it: JSON.stringify([event])","Unwrap producer envelopes: pass obj.events rather than the wrapper object","Validate Array.isArray(parsed) && parsed.length <= 64 before calling"],"exampleFix":"// before\npet.observeEngineBatch(JSON.stringify(event), now);\n// after\nconst batch = Array.isArray(event) ? event : [event];\nfor (let i = 0; i < batch.length; i += 64) {\n  pet.observeEngineBatch(JSON.stringify(batch.slice(i, i + 64)), now);\n}","handlingStrategy":"validation","validationCode":"function validBatch(json) {\n  let v; try { v = JSON.parse(json); } catch { return false; }\n  return Array.isArray(v) && v.length <= 64;\n}","typeGuard":"const isEngineBatch = (v: unknown): v is unknown[] => Array.isArray(v) && v.length <= 64;","tryCatchPattern":"try {\n  pet.observeEngineBatch(json, now);\n} catch (err) {\n  if (err.message === 'Invalid Engine batch.') {\n    for (const e of chunk(toArray(json), 64)) pet.observeEngineBatch(JSON.stringify(e), now);\n  } else throw err;\n}","preventionTips":["Chunk event queues at 64 before flushing","Keep single-event path (observeEngine) and batch path clearly separated","Unwrap producer envelopes before calling"],"tags":["batching","validation","engine"],"backgroundTag":"invalid-argument-format","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"}