{"record":{"id":"ea22d4066eb9340f","repo":"Hmbown/CodeWhale","slug":"invalid-native-whale-body-pet","errorCode":null,"errorMessage":"Invalid native whale body.","messagePattern":"Invalid native whale body\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/ios/Resources/pet-native.js","lineNumber":27,"sourceCode":"exports.PetNative = void 0;\nconst pet_world_js_1 = require(\"./pet-world.js\");\nconst pet_telemetry_js_1 = require(\"./pet-telemetry.js\");\nconst pet_sim_js_1 = require(\"./pet-sim.js\");\nconst pet_audio_js_1 = require(\"./pet-audio.js\");\nconst pet_engine_js_1 = require(\"./pet-engine.js\");\n/** Synchronous native boundary: JSON state and directly transferable PCM.\n * Native hosts share the actual world / score implementation, not a rewrite. */\nclass PetNative {\n    world;\n    engine = new pet_engine_js_1.PetEngineTelemetry();\n    engineTick = 0;\n    segment;\n    liveTape = new pet_telemetry_js_1.PetLiveTape();\n    stillProjection;\n    constructor(pointsJSON, tapeJSONL = '', interactionsJSON = '[]', live = false, expressionVersion = 2) {\n        const points = JSON.parse(pointsJSON);\n        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)))\n            throw new Error('Invalid native whale body.');\n        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);\n    }\n    step(dt, motion) { this.world.step(dt, { motion, sensitivity: 1 }); return this.snapshot(); }\n    snapshot() { return JSON.stringify({ ...this.world.frame, voices: this.world.voices, digest: (0, pet_sim_js_1.digest)(this.world.sim) }); }\n    /** View-only projection. Display cadence and accessibility preferences never\n     * advance the owner, consume randomness, or change its score/checkpoint. */\n    presentation() {\n        const { sim, frame } = this.world;\n        const state = { ...frame.state, roamX: 0, roamY: 0, flip: 1, lit: frame.behaviour === 'doze' ? .18 : 1 };\n        const key = JSON.stringify([state, frame.pod]);\n        if (this.stillProjection?.key !== key) {\n            const still = new pet_sim_js_1.PetSim(sim.p.map(p => [p.hx, p.hy]), 0xC0FFEE, sim.expressionVersion);\n            const peers = frame.pod.filter(p => p.present);\n            still.step(1 / 30, state, { motion: false, sensitivity: 1,\n                podSlots: peers.length >= 3 ? peers.map(p => [[0, 2, 4, 1, 3, 5][p.slot], p.phase]) : undefined });\n            this.stillProjection = { key, points: still.p.map(p => [p.x, p.y]), style: still.frame };\n        }\n        return JSON.stringify({ ...frame, digest: (0, pet_sim_js_1.digest)(sim), style: sim.frame, activity: this.engine.activity(frame.timeMs),","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/ios/Resources/pet-native.js#L9-L45","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst points = JSON.parse(rawPointsString); // e.g. 979 points after a bad export\nnew PetNativeBridge(rawPointsString);\n// after\nconst points = JSON.parse(rawPointsString).map(([x, y]) =>\n  [Math.max(-1, Math.min(1, x)), Math.max(-1, Math.min(1, y))]);\nif (points.length !== 980) throw new Error(`whale asset must export 980 points, got ${points.length}`);\nnew PetNativeBridge(JSON.stringify(points));","handlingStrategy":"validation","validationCode":"function validWhalePoints(pointsJSON) {\n  try {\n    const pts = JSON.parse(pointsJSON);\n    return Array.isArray(pts) && pts.length === 980 &&\n      pts.every(p => Array.isArray(p) && p.length === 2 &&\n        p.every(n => Number.isFinite(n) && Math.abs(n) <= 1));\n  } catch { return false; }\n}\nif (!validWhalePoints(pointsJSON)) throw new Error('bad whale asset');","typeGuard":"const isPoint = (p) => Array.isArray(p) && p.length === 2 &&\n  p.every(n => Number.isFinite(n) && Math.abs(n) <= 1);\nconst isWhaleBody = (pts) => Array.isArray(pts) && pts.length === 980 && pts.every(isPoint);","tryCatchPattern":"try {\n  bridge = new PetNativeBridge(pointsJSON);\n} catch (e) {\n  if (e.message === 'Invalid native whale body.') {\n    console.error('whale points failed 980x[-1,1] validation');\n    bridge = loadFallbackBundledWhale();\n  } else throw e;\n}","preventionTips":["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."],"tags":["validation","geometry","constructor"],"backgroundTag":"invalid-argument-value","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}