Hmbown/CodeWhale · error · Error

Engine pet clock jump.

Error message

Engine pet clock jump.

What it means

In pet-watch's advanceEngine, the world clock derives engineTick from frame.timeMs and feeds it to PetEngineTelemetry; this error fires when the engine telemetry model rejects the resulting clock movement (a backward or otherwise unrepresentable tick jump), because letting the pet simulation continue would desynchronize its state from the recorded engine telemetry.

Solutions

  1. Provide non-empty text, e.g. {action:"type", text:"hello world"}.
  2. If the intent was to press Enter/Tab etc., use the key tool instead of browser type.
  3. Guard the source string: only call when typeof text === "string" && text.length > 0.

Example fix

// before
resolveTool("browser", { action: "type", text: "" });
// after
resolveTool("browser", { action: "type", text: "hello world" });
Defensive patterns

Strategy: validation

Validate before calling

if (typeof text !== "string" || text.length === 0) {
  throw new Error("browser type requires non-empty text");
}

Type guard

const typableText = (t) => typeof t === "string" && t.length > 0;

Prevention

When it happens

Trigger: resolveTool("browser", {action:"type"}) with text missing/undefined, text: null, text: 42, or text: "" (zero length).

Common situations: A model emitting an empty text field when the actual text should come from a previous tool result that returned nothing; interpolated variable that was undefined/empty string; confusing type (types literal text) with press/keys (which types key names).

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/870d101aad1215dd. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/pet-native.js:106

        this.engineTick = Math.round(this.world.frame.timeMs * 30 / 1000);
        this.engine = new pet_engine_js_1.PetEngineTelemetry();
        this.world.voices = [];
        return this.world.frame.timeMs;
    }
    observeEngine(metadataJSON, timeMs) { this.engine.observe(JSON.parse(metadataJSON), timeMs); }
    observeEngineBatch(metadataJSON, timeMs) {
        const events = 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, motion, waiting) {
        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 = [];
        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, height) {
        if (![width, height].every(n => Number.isSafeInteger(n) && n >= 1 && n <= 512))
            throw new Error('Invalid pet raster size.');
        const { sim, frame } = this.world, l = (0, pet_sim_js_1.layout)(width * 2, height * 4, frame.state);
        const cells = Array(width * height).fill(0), bits = [[1, 8], [2, 16], [4, 32], [64, 128]];
        sim.p.forEach((p, i) => {
            if (frame.state.observed < .92 && i % 2 === 1)

View on GitHub (pinned to 73e0f67d83)