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
- Clamp or choose duration into the 0.05..30 range; for longer holds, chain multiple hold_key calls or use a loop.
- Ensure duration is a number, not a string — coerce with Number(duration) and check Number.isFinite before calling.
- 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
- Coerce string durations with Number() and check Number.isFinite before calling.
- Clamp computed durations into 0.05..30; omit duration entirely for momentary presses.
- Never pass 0 as 'momentary' — omit duration instead.
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
- --days must be an integer from 1 through 90
- open_application needs a plain executable/desktop name
- Required Workflow result unavailable: ' + id
- app_denied
- Burn rate must be 10000 $/hr or less.
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)