Hmbown/CodeWhale · critical · Error

Invalid whale point asset.

Error message

Invalid whale point asset.

What it means

Thrown after successfully fetching `whale-points.tsv` when the parsed data does not have exactly 980 rows or any row does not contain exactly two finite numbers. This validates the whale geometry asset's strict format before building the world.

Solutions

  1. Restore the original 980-point, two-column whitespace-separated `whale-points.tsv`.
  2. Re-export the asset from the tool that generates it and re-count rows/columns.
  3. Strip headers/comments/blank lines and ensure every row has exactly two finite numeric values.

Example fix

// before: 979-point asset
// after: validate before shipping
const rows = text.trim().split('\n');
if (rows.length !== 980 || rows.some(r => r.trim().split(/\s+/).length !== 2)) throw new Error('regenerate asset');
Defensive patterns

Strategy: validation

Validate before calling

const rows = tsv.trim().split('\n');
const ok = rows.length === 980 && rows.every(r => { const c = r.trim().split(/\s+/); return c.length === 2 && c.every(v => Number.isFinite(Number(v))); });
if (!ok) throw new Error('bad whale-points.tsv');

Type guard

const isPoint = (p: unknown[]): p is [number, number] => p.length === 2 && p.every(Number.isFinite);

Try / catch

try { points = parseWhalePoints(tsv); } catch (e) { showFatal('Whale asset is corrupt: regenerate it.'); }

Prevention

When it happens

Trigger: The TSV is corrupted, truncated, has extra/missing columns, or was replaced with a file of a different point count; whitespace/encoding changes produce non-numeric values (`NaN` from `Number`).

Common situations: Editing or regenerating the asset with a script that changes point count, uploading a stale asset from another version, files saved with headers or comments.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/b718264f8180fad7. Report an issue: GitHub.

Appendix: source

Thrown at pet/src/ui/pet.ts:280

  }
  void persist(); if (audio) anchor = audio.currentTime - seconds + .08;
});
window.addEventListener('pagehide', () => { void persist(); });
function animate(now: number) {
  if (world && !restoring && !paused && !document.hidden) {
    accumulator += last ? Math.min(.1, (now - last) / 1000) : 0;
    while (accumulator >= 1 / 30 && (mode.value === 'live' || !tape.length || seconds < duration())) {
      world.step(1 / 30, { motion: !motion.checked, sensitivity: 1 }); seconds = world.frame.timeMs / 1000; play(world.voices); accumulator -= 1 / 30;
    }
    if ((mode.value === 'live' || !tape.length) && seconds >= duration()) seek.max = String(Math.ceil(seconds / 30) * 30 + 30);
    draw();
  }
  last = now; requestAnimationFrame(animate);
}
try {
  const response = await fetch('./whale-points.tsv'); if (!response.ok) throw new Error('Whale point asset is unavailable.');
  points = (await response.text()).trim().split('\n').map(row => row.trim().split(/\s+/).map(Number) as [number, number]);
  if (points.length !== 980 || points.some(p => p.length !== 2 || !p.every(Number.isFinite))) throw new Error('Invalid whale point asset.');
  try {
    const saved = await library.getHabitat();
    if (saved) {
      savedRevision = saved.revision; const h = saved.habitat;
      if (h.petPersistenceVersion !== 1 || !Number.isFinite(h.seconds) || h.seconds < 0 || h.seconds > PET_MAX_SECONDS
        || !['wild', 'demo', 'replay'].includes(h.source) || typeof h.still !== 'boolean' || typeof h.sourceName !== 'string') throw new Error('Saved habitat is invalid. Save a replay file before replacing it.');
      expressionVersion = h.expressionVersion === undefined ? 1 : h.expressionVersion;
      if (![1, 2].includes(expressionVersion) || h.checkpoint && (h.checkpoint.sim?.expressionVersion ?? 1) !== expressionVersion) throw new Error('Saved expression version does not match its checkpoint.');
      const savedWorld = PetWorld.fromRecording(points, { ...h, petReplayVersion: h.petReplayVersion === undefined ? 1 : h.petReplayVersion });
      if (h.checkpoint && savedWorld.frame.timeMs / 1000 !== h.seconds) throw new Error('Saved pet clock does not match its checkpoint.');
      tape = h.tape; interactions = [...h.interactions]; mode.value = h.source; motion.checked = h.still || media.matches;
      source.textContent = h.sourceName;
      seek.max = String(Math.max(h.source === 'demo' ? 80 : h.source === 'replay' ? 90 : 120, savedWorld.endTimeMs / 1000));
      if (h.source === 'replay') {
        mode.querySelector<HTMLOptionElement>('[value="replay"]')!.disabled = false;
      }
      message.textContent = 'Restoring the saved habitat…';
      if (h.checkpoint && h.still === motion.checked) adoptWorld(savedWorld);

View on GitHub (pinned to 433685b202)