Hmbown/CodeWhale · error · Error

Invalid native whale body.

Error message

Invalid native whale body.

What it means

When the `key` tool resolves to `hold_key`, the duration is validated to be a finite number of seconds between 0.05 and 30. Values that are non-numeric, NaN/Infinity, below the floor, or above the ceiling throw this error before any input is synthesized.

Solutions

  1. Clamp or choose duration into the 0.05..30 range; for longer holds, chain multiple hold_key calls or use a loop.
  2. Ensure duration is a number, not a string — coerce with Number(duration) and check Number.isFinite before calling.
  3. For a momentary press, omit duration entirely so it resolves as the plain key tool.

Example fix

// before
resolveTool("key", { key: "a", duration: "1.5" });
// after
const d = Number("1.5");
resolveTool("key", { key: "a", duration: Math.min(30, Math.max(0.05, d)) });
Defensive patterns

Strategy: validation

Validate before calling

const d = Number(args.duration);
if (!Number.isFinite(d) || d < 0.05 || d > 30) {
  throw new Error("duration must be a finite number of seconds in 0.05..30");
}

Type guard

const isValidDuration = (d) => typeof d === "number" && Number.isFinite(d) && d >= 0.05 && d <= 30;

Prevention

When it happens

Trigger: resolveTool("key", {key, duration}) where duration is not a finite number, duration < 0.05, or duration > 30 — e.g. duration: "2" (string), duration: 0, duration: 60.

Common situations: Passing a duration as a string from JSON config; computing a duration with a formula that yields NaN; trying to hold a key for a minute to simulate a stuck key; zero duration intended as 'momentary' instead of omitting duration.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

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

exports.PetNative = void 0;
const pet_world_js_1 = require("./pet-world.js");
const pet_telemetry_js_1 = require("./pet-telemetry.js");
const pet_sim_js_1 = require("./pet-sim.js");
const pet_audio_js_1 = require("./pet-audio.js");
const pet_engine_js_1 = require("./pet-engine.js");
/** Synchronous native boundary: JSON state and directly transferable PCM.
 * Native hosts share the actual world / score implementation, not a rewrite. */
class PetNative {
    world;
    engine = new pet_engine_js_1.PetEngineTelemetry();
    engineTick = 0;
    segment;
    liveTape = new pet_telemetry_js_1.PetLiveTape();
    stillProjection;
    constructor(pointsJSON, tapeJSONL = '', interactionsJSON = '[]', live = false, expressionVersion = 2) {
        const points = JSON.parse(pointsJSON);
        if (!Array.isArray(points) || points.length !== 980 || points.some(p => !Array.isArray(p) || p.length !== 2 || !p.every(n => Number.isFinite(n) && Math.abs(n) <= 1)))
            throw new Error('Invalid native whale body.');
        this.world = new pet_world_js_1.PetWorld(points, live ? (0, pet_telemetry_js_1.compilePetTelemetry)([]) : (0, pet_telemetry_js_1.decodePetJSONL)(tapeJSONL), JSON.parse(interactionsJSON), expressionVersion, true);
    }
    step(dt, motion) { this.world.step(dt, { motion, sensitivity: 1 }); return this.snapshot(); }
    snapshot() { return JSON.stringify({ ...this.world.frame, voices: this.world.voices, digest: (0, pet_sim_js_1.digest)(this.world.sim) }); }
    /** View-only projection. Display cadence and accessibility preferences never
     * advance the owner, consume randomness, or change its score/checkpoint. */
    presentation() {
        const { sim, frame } = this.world;
        const state = { ...frame.state, roamX: 0, roamY: 0, flip: 1, lit: frame.behaviour === 'doze' ? .18 : 1 };
        const key = JSON.stringify([state, frame.pod]);
        if (this.stillProjection?.key !== key) {
            const still = new pet_sim_js_1.PetSim(sim.p.map(p => [p.hx, p.hy]), 0xC0FFEE, sim.expressionVersion);
            const peers = frame.pod.filter(p => p.present);
            still.step(1 / 30, state, { motion: false, sensitivity: 1,
                podSlots: peers.length >= 3 ? peers.map(p => [[0, 2, 4, 1, 3, 5][p.slot], p.phase]) : undefined });
            this.stillProjection = { key, points: still.p.map(p => [p.x, p.y]), style: still.frame };
        }
        return JSON.stringify({ ...frame, digest: (0, pet_sim_js_1.digest)(sim), style: sim.frame, activity: this.engine.activity(frame.timeMs),

View on GitHub (pinned to 73e0f67d83)