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
- Regenerate the whale body JSON so it contains exactly 980 points of [x, y] with values clamped to [-1, 1].
- Validate/parse the JSON on the producing side (Swift/native layer) before passing it into the bridge.
- Check that the pet asset bundle version matches the JS bridge version expecting 980 points.
- 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
- Checksum whale assets at build time and validate point count in CI.
- Clamp coordinates to [-1,1] in the asset export pipeline.
- Version the asset schema alongside the bridge code expecting 980 points.
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
- 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/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)