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
- Restore the original 980-point, two-column whitespace-separated `whale-points.tsv`.
- Re-export the asset from the tool that generates it and re-count rows/columns.
- 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
- Validate the asset in CI (980 rows, 2 finite numbers each).
- Never hand-edit the generated TSV.
- Keep asset generation and consumption versions in lockstep.
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
- 1
- A pinned task provider requires an explicit model
- A positive pull request number is required
- A provider and page loader are required.
- accepted_requests must equal requests_attempted; sender…
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)