Hmbown/CodeWhale · error · Error

Invalid Engine batch.

Error message

Invalid Engine batch.

What it means

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.

Solutions

  1. Ensure the payload is a JSON array and chunk it to <= 64 events per call
  2. If you have a single event, call observeEngine(metadataJSON, timeMs) instead, or wrap it: JSON.stringify([event])
  3. Unwrap producer envelopes: pass obj.events rather than the wrapper object
  4. Validate Array.isArray(parsed) && parsed.length <= 64 before calling

Example fix

// before
pet.observeEngineBatch(JSON.stringify(event), now);
// after
const batch = Array.isArray(event) ? event : [event];
for (let i = 0; i < batch.length; i += 64) {
  pet.observeEngineBatch(JSON.stringify(batch.slice(i, i + 64)), now);
}
Defensive patterns

Strategy: validation

Validate before calling

function validBatch(json) {
  let v; try { v = JSON.parse(json); } catch { return false; }
  return Array.isArray(v) && v.length <= 64;
}

Type guard

const isEngineBatch = (v: unknown): v is unknown[] => Array.isArray(v) && v.length <= 64;

Try / catch

try {
  pet.observeEngineBatch(json, now);
} catch (err) {
  if (err.message === 'Invalid Engine batch.') {
    for (const e of chunk(toArray(json), 64)) pet.observeEngineBatch(JSON.stringify(e), now);
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/499ce48283c980b2. Report an issue: GitHub.

Appendix: source

Thrown at pet/src/core/pet-native.ts:80

    if (text.length > 8 * 1024 * 1024) throw new Error('Native habitat exceeds 8 MiB.');
    const points = this.world.sim.p.map(p => [p.hx, p.hy] as [number, number]);
    this.world = PetWorld.fromRecording(points, JSON.parse(text));
    this.liveTape.reset();
    this.segment = undefined; this.engineTick = Math.round(this.world.frame.timeMs * 30 / 1000); this.engine = new PetEngineTelemetry();
  }
  /** Resume a live host at the first unrecorded bucket. Keep every accepted
   * interval, but never present its last observed frame as current evidence. */
  resumeEngine(): number {
    this.world.resumeObservation();
    this.engineTick = Math.round(this.world.frame.timeMs * 30 / 1000);
    this.engine = new PetEngineTelemetry();
    this.world.voices = [];
    return this.world.frame.timeMs;
  }
  observeEngine(metadataJSON: string, timeMs: number): void { this.engine.observe(JSON.parse(metadataJSON), timeMs); }
  observeEngineBatch(metadataJSON: string, timeMs: number): void {
    const events: unknown = JSON.parse(metadataJSON);
    if (!Array.isArray(events) || events.length > 64) throw new Error('Invalid Engine batch.');
    const next = this.engine.clone();
    for (const event of events) next.observe(event, timeMs);
    this.engine = next;
  }
  advanceEngine(timeMs: number, motion: boolean, waiting: boolean): void {
    const target = Math.floor(timeMs * 30 / 1000 + 1e-8);
    if (!Number.isFinite(timeMs) || target < this.engineTick || target - this.engineTick > 300) throw new Error('Engine pet clock jump.');
    this.engine.confirmWaiting(timeMs, waiting);
    const voices: PetVoice[] = [];
    while (this.engineTick < target) {
      if ((this.engineTick + 1) % 12 === 0) this.world.acceptTelemetry(this.engine.bucket(Math.floor(this.engineTick / 12)));
      this.world.step(1 / 30, { motion, sensitivity: 1 });
      voices.push(...this.world.voices);
      this.engineTick++;
    }
    this.world.voices = voices;
  }
  terminal(width: number, height: number): string {

View on GitHub (pinned to 433685b202)