Hmbown/CodeWhale · error · Error
Only a live recording can resume observation.
Error message
Only a live recording can resume observation.
What it means
resumeObservation() fast-forwards the simulation to the end of the last accepted live packet, then resumes observing. It only works when the recording is genuinely live: the last tape bucket's sequence must end within 24 ticks (~800ms at 1/30s steps) of the current tick. If there is no tape at all, or the gap between the current playhead and the end of the recording exceeds 24 ticks, the recording is stale or finished and resuming would skip or re-simulate history, so the world throws.
Solutions
- Call acceptTelemetry() with fresh packets so the tape's last sequence is within 24 ticks of the current tick before resuming
- Check that this.hasTelemetry / tapeLog is non-empty before calling resumeObservation
- If the gap is larger, replay or re-create the PetWorld from the archive rather than resuming
- Guard the call: only enable resume in the UI while the live stream is actively delivering packets
Example fix
// before world.resumeObservation(); // may be stale // after const last = world.tapeLog.at(-1); const end = last ? (last.sequence + 1) * 12 : 0; if (end && end - world.tick <= 24) world.resumeObservation(); else world.acceptTelemetry(freshBucket); // then resume
Defensive patterns
Strategy: validation
Validate before calling
function canResume(world) {
const last = world.tapeLog.at(-1);
if (!last) return false;
const end = (last.sequence + 1) * 12;
return end - world.tick <= 24;
} Try / catch
try {
world.resumeObservation();
} catch (e) {
if (e.message.includes('live recording')) {
// fall back: replay from archive or wait for fresh packets
} else throw e;
} Prevention
- Only offer resume in the UI while the live stream is actively delivering packets
- Accept fresh telemetry before resuming after a pause
- Never call resumeObservation on a world with an empty tapeLog
- Treat resume as a live-only path; use replay/archives for finished recordings
When it happens
Trigger: Calling resumeObservation() on a PetWorld with an empty tapeLog (end === 0); calling it after playback/observation has drifted more than 24 ticks past the last accepted telemetry packet (end - tick > 24); calling it after the live source stopped sending packets long ago.
Common situations: UI 'resume watching' button pressed after the pet recording ended; live stream dropped and packets stopped, then resume is attempted; calling resumeObservation before any acceptTelemetry call.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Agent Mail can be canceled only while queued
- Agent Mail can be marked read only after delivery
- Archive this pet recording before accepting more telemetry.
- audited owned root does not match mutation target
- audited skill root identity changed; refusing mutation
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/006c3442cae69202.
Report an issue: GitHub.
Appendix: source
Thrown at pet/ios/Resources/pet-native.js:419
world.flip = c.flip;
world.lit = c.lit;
world.lastActivity = c.lastActivity;
world.addressedAt = c.addressedAt ?? -Infinity;
world.waitSince = c.waitSince;
world.food = structuredClone(c.food);
world.members = new Map(c.members.map(m => [m.id, { ...m }]));
world.lastStill = c.lastStill;
// JSON omits undefined properties; keep the same frame shape as makeFrame.
world.frame = { ...structuredClone(c.frame), telemetry: telemetry ? structuredClone(telemetry) : undefined };
world.voices = structuredClone(c.voices);
return world;
}
/** Resume observation beyond all already accepted live packets, without
* replaying their sound or exposing a stale request as current work. */
resumeObservation() {
const last = this.tapeLog.at(-1), end = last ? (last.sequence + 1) * 12 : 0;
if (!end || end - this.tick > 24)
throw new Error('Only a live recording can resume observation.');
while (this.tick < end)
this.step(1 / 30, { motion: false, sensitivity: 1 });
this.voices = [];
}
/** Branch at the current playhead; input is journalled for the next fixed tick.
* A live touch never needs to re-simulate the creature's entire lifetime. */
interact(kind, x, y) {
if (!['attention', 'food'].includes(kind) || !Number.isFinite(x) || !Number.isFinite(y) || Math.abs(x) > 1 || Math.abs(y) > 1)
throw new Error('Invalid pet interaction.');
if (this.branchTick !== this.tick)
this.interactionLog.splice(this.interactionIndex);
this.branchTick = this.tick;
this.interactionLog.push({ timeMs: (this.tick + 1) * 1000 / HZ, kind, x, y });
}
/** Accept a live source packet at the next 400ms boundary. The accepted tape,
* including any missing intervals, is the exact replay authority for this host. */
acceptTelemetry(input) {
(0, pet_telemetry_js_1.validatePetBucket)(input);View on GitHub (pinned to 433685b202)