Hmbown/CodeWhale · error · Error

Invalid native whale body.

Error message

Invalid native whale body.

What it means

The native pet bridge constructor validates the whale body points passed as JSON before building a PetWorld. It expects exactly 980 points, each a 2-element array of finite numbers in [-1, 1]. If the shape, count, or value range is wrong, the constructor refuses to create an invalid whale body.

Solutions

  1. Regenerate the whale body JSON so it contains exactly 980 points of [x, y] with values clamped to [-1, 1].
  2. Validate/parse the JSON on the producing side (Swift/native layer) before passing it into the bridge.
  3. Check that the pet asset bundle version matches the JS bridge version expecting 980 points.
  4. Log points.length and the first offending point to identify whether it is a count or value-range problem.

Example fix

// before
const points = JSON.parse(rawPointsString); // e.g. 979 points after a bad export
new PetNativeBridge(rawPointsString);
// after
const points = JSON.parse(rawPointsString).map(([x, y]) =>
  [Math.max(-1, Math.min(1, x)), Math.max(-1, Math.min(1, y))]);
if (points.length !== 980) throw new Error(`whale asset must export 980 points, got ${points.length}`);
new PetNativeBridge(JSON.stringify(points));
Defensive patterns

Strategy: validation

Validate before calling

function validWhalePoints(pointsJSON) {
  try {
    const pts = JSON.parse(pointsJSON);
    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));
  } catch { return false; }
}
if (!validWhalePoints(pointsJSON)) throw new Error('bad whale asset');

Type guard

const isPoint = (p) => Array.isArray(p) && p.length === 2 &&
  p.every(n => Number.isFinite(n) && Math.abs(n) <= 1);
const isWhaleBody = (pts) => Array.isArray(pts) && pts.length === 980 && pts.every(isPoint);

Try / catch

try {
  bridge = new PetNativeBridge(pointsJSON);
} catch (e) {
  if (e.message === 'Invalid native whale body.') {
    console.error('whale points failed 980x[-1,1] validation');
    bridge = loadFallbackBundledWhale();
  } else throw e;
}

Prevention

When it happens

Trigger: new NativePetBridge(pointsJSON, ...) called with a JSON string that does not parse to an array of exactly 980 [x, y] pairs, or with pairs containing non-finite numbers or |x|/|y| > 1.

Common situations: Stale or truncated cached whale geometry; a native/Swift side passing a different point count after an art update; older bundled assets not matching a newer expressionVersion; JSON serialization dropping precision or wrapping arrays in an object.

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/ea22d4066eb9340f. Report an issue: GitHub.

Appendix: source

Thrown at pet/ios/Resources/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 433685b202)