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

  1. Regenerate the whale body so it contains exactly 980 [x,y] points with coordinates normalized to [-1,1]
  2. Validate the parsed points array (length 980, pairs of finite numbers within ±1) before constructing PetNative
  3. Load the body from the bundled/default asset rather than a custom file
  4. 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

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


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)