Hmbown/CodeWhale · error · Error
Invalid native whale body.
Error message
Invalid native whale body.
What it means
The PetNative constructor validates the whale body points JSON: it must parse to an array of exactly 980 [x,y] pairs where each coordinate is a finite number with |n| <= 1. Anything else (wrong count, non-array, non-finite or out-of-range coordinates) throws 'Invalid native whale body.' The 980-point mesh is the pet renderer's fixed body topology.
Solutions
- Regenerate the whale body so it contains exactly 980 [x,y] points with coordinates normalized to [-1,1]
- Validate the parsed points array (length 980, pairs of finite numbers within ±1) before constructing PetNative
- Load the body from the bundled/default asset rather than a custom file
- If coordinates are pixel-based, normalize: x' = (x / width) * 2 - 1, y' = (y / height) * 2 - 1
Example fix
// before
const pet = new PetNative(fs.readFileSync('body.json','utf8'));
// after
const pts = JSON.parse(fs.readFileSync('body.json','utf8'));
const ok = Array.isArray(pts) && pts.length === 980 && pts.every(p => Array.isArray(p) && p.length === 2 && p.every(n => Number.isFinite(n) && Math.abs(n) <= 1));
if (!ok) throw new Error('body.json must be 980 [x,y] points in [-1,1]');
const pet = new PetNative(JSON.stringify(pts)); Defensive patterns
Strategy: validation
Validate before calling
function validWhaleBody(json) {
let pts;
try { pts = JSON.parse(json); } catch { return false; }
return Array.isArray(pts) && pts.length === 980 &&
pts.every(p => Array.isArray(p) && p.length === 2 && p.every(n => Number.isFinite(n) && Math.abs(n) <= 1));
} Type guard
type Pt = [number, number]; const isPt = (p: unknown): p is Pt => Array.isArray(p) && p.length === 2 && p.every(n => typeof n === 'number' && Number.isFinite(n) && Math.abs(n) <= 1); const isWhaleBody = (v: unknown): v is Pt[] => Array.isArray(v) && v.length === 980 && v.every(isPt);
Try / catch
try {
pet = new PetNative(pointsJSON);
} catch (err) {
if (err.message === 'Invalid native whale body.') pet = new PetNative(DEFAULT_BODY_JSON);
else throw err;
} Prevention
- Ship a checksummed default body asset and validate custom bodies at load time
- Always normalize coordinates to [-1,1] when exporting meshes
- Pin the body asset format to the pet library version
When it happens
Trigger: new PetNative(pointsJSON, ...) where pointsJSON parses to an array of length != 980, contains nested arrays of length != 2, or contains NaN/Infinity/coordinates outside [-1,1]. Also thrown when pointsJSON is not valid JSON at all via JSON.parse (SyntaxError) — this specific error fires after successful parse.
Common situations: Using a body file saved by a different pet version with a different point count; truncated or hand-edited points JSON; units confusion (passing pixel coords 0-800 instead of normalized -1..1); passing null/undefined stringified as 'null'.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Invalid native whale body.
- region must be integer [x,y,width,height] with positive size
- Unsupported pet expression version.
- 1
- A pinned task provider requires an explicit model
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/3b25977c010a3405.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/pet-native.ts:19
import { PetWorld, type PetInteraction, type PetSegment } from './pet-world.js';
import { compilePetTelemetry, decodePetJSONL, PetLiveTape } from './pet-telemetry.js';
import { ARCH_OF, digest, layout, PetSim } from './pet-sim.js';
import { renderPetPCM, type PetVoice } from './pet-audio.js';
import { PetEngineTelemetry } from './pet-engine.js';
/** Synchronous native boundary: JSON state and directly transferable PCM.
* Native hosts share the actual world / score implementation, not a rewrite. */
export class PetNative {
private world: PetWorld;
private engine = new PetEngineTelemetry();
private engineTick = 0;
private segment?: PetSegment;
private liveTape = new PetLiveTape();
private stillProjection?: { key: string; points: number[][]; style: PetSim['frame'] };
constructor(pointsJSON: string, tapeJSONL = '', interactionsJSON = '[]', live = false, expressionVersion: 1 | 2 = 2) {
const points = JSON.parse(pointsJSON) as [number, number][];
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 PetWorld(points, live ? compilePetTelemetry([]) : decodePetJSONL(tapeJSONL), JSON.parse(interactionsJSON) as PetInteraction[], expressionVersion, true);
}
step(dt: number, motion: boolean): string { this.world.step(dt, { motion, sensitivity: 1 }); return this.snapshot(); }
snapshot(): string { return JSON.stringify({ ...this.world.frame, voices: this.world.voices, digest: digest(this.world.sim) }); }
/** View-only projection. Display cadence and accessibility preferences never
* advance the owner, consume randomness, or change its score/checkpoint. */
presentation(): string {
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 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] as const) : undefined });
this.stillProjection = { key, points: still.p.map(p => [p.x, p.y]), style: still.frame };
}
return JSON.stringify({ ...frame, digest: digest(sim), style: sim.frame, activity: this.engine.activity(frame.timeMs),View on GitHub (pinned to 433685b202)