Hmbown/CodeWhale · critical · Error
Whale point asset is unavailable.
Error message
Whale point asset is unavailable.
What it means
Thrown at startup when `fetch('./whale-points.tsv')` returns a non-ok HTTP status. The pet UI requires this asset to build the whale point cloud and cannot start the habitat without it, so the fetch failure is converted into this explicit error inside the startup try block.
Solutions
- Ensure `whale-points.tsv` exists next to the page and is served with a 200 status.
- Check the network tab for the actual status/URL of `./whale-points.tsv` and fix the deployment path.
- If using a service worker, add the asset to the precache list.
Example fix
// before: asset missing at ./whale-points.tsv // after: copy it into the deploy output cp assets/whale-points.tsv dist/whale-points.tsv
Defensive patterns
Strategy: try-catch
Validate before calling
const head = await fetch('./whale-points.tsv', { method: 'HEAD' });
if (!head.ok) showFatal('whale-points.tsv is not deployed.'); Type guard
null
Try / catch
try { await loadWhalePoints(); } catch (e) { showFatal('Pet assets failed to load: ' + e.message); } Prevention
- Include whale-points.tsv in the deploy/build output checklist.
- Add the asset to the service-worker precache list.
- Smoke-test the deployed URL for a 200 before release.
When it happens
Trigger: Opening pet.ts when `./whale-points.tsv` is missing from the served directory, the server returns 404/500, or a service worker/offline cache serves an error response.
Common situations: Deploying the UI without copying static assets, wrong base path / reverse proxy rewrite, offline PWA with uncached asset, dev server serving only bundled JS.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- ${message}
- Preview points could not load.
- Anthropic API error (HTTP )
- build platform HTTP client
- building bundle fetch client failed
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/fb1181e8a64fa647.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/ui/pet.ts:278
liveTape.reset();
if (!document.hidden && world && !restoring) { world.resumeObservation(); seconds = world.frame.timeMs / 1000; }
}
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;
}View on GitHub (pinned to 433685b202)