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

  1. Call acceptTelemetry() with fresh packets so the tape's last sequence is within 24 ticks of the current tick before resuming
  2. Check that this.hasTelemetry / tapeLog is non-empty before calling resumeObservation
  3. If the gap is larger, replay or re-create the PetWorld from the archive rather than resuming
  4. 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

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


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)