Hmbown/CodeWhale · error · Error

Invalid pet world checkpoint.

Error message

Invalid pet world checkpoint.

What it means

PetWorld.restore performs deep validation of a checkpoint before rebuilding a world: frame field ranges and enums, food bounds, pod matching the member list, voice limits/ids, pet state, and more. If any structural invariant of the checkpoint blob fails, restore refuses to construct a world from corrupted or foreign data.

Solutions

  1. Obtain checkpoints only from world.checkpoint()/recording() output of a matching library version.
  2. Verify the checkpoint blob was stored/loaded losslessly (no truncation, no manual edits).
  3. If migrating across versions, run the checkpoint through the library's migration/validation path before restore.

Example fix

// before
const c = JSON.parse(partialString); // truncated
PetWorld.restore(points, tape, interactions, c); // throws
// after
if (!isPetCheckpoint(c)) throw new Error('not a checkpoint');
const world = PetWorld.restore(points, tape, interactions, c);
Defensive patterns

Strategy: type-guard

Validate before calling

// Structural pre-check before restore:
if (!c?.frame || !['none','orient','approach','call'].includes(c.frame.needs) ||
    !Array.isArray(c.voices) || c.voices.length > 1024 ||
    JSON.stringify(c.frame.pod) !== JSON.stringify(c.members))
  throw new Error('checkpoint fails structural pre-check');

Type guard

function isPetCheckpoint(v: unknown): v is PetCheckpoint {
  const c = v as PetCheckpoint;
  return !!c && !!c.frame && typeof c.frame.timeMs === 'number' &&
    Array.isArray(c.voices) && c.voices.length <= 1024 &&
    (!c.petCheckpointVersion || [1, 2].includes(c.petCheckpointVersion));
}

Try / catch

try {
  const world = PetWorld.restore(points, tape, interactions, c);
} catch (e) {
  if (e.message === 'Invalid pet world checkpoint.') {
    // discard corrupted blob or re-export from the source world
  } else throw e;
}

Prevention

When it happens

Trigger: Calling PetWorld.restore(points, tape, interactions, c) with a checkpoint whose frame fields are out of range, frame.pod differs from c.members, voices exceed 1024 entries or have invalid ids, or which was produced by a different/older schema.

Common situations: Hand-edited or partially serialized checkpoints; truncation during storage; loading a checkpoint saved by another app version whose schema drifted; JSON round-trips that drop undefined fields.

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/80ab0bb9ce25f8f1. Report an issue: GitHub.

Appendix: source

Thrown at pet/src/core/pet-world.ts:237

      || !integer(c.branchTick, -1, c.tick) || !integer(c.random, 0, 0xffffffff)
      || !['swim', 'dive', 'roll', 'breathe', 'drift', 'doze', 'wake'].includes(c.behaviour)
      || !range(c.until, 0, PET_MAX_SECONDS + 10) || ![c.targetX, c.targetY, c.x, c.y, c.flip].every(n => range(n, -1, 1))
      || !range(c.lit, 0, 1) || !range(c.lastActivity, 0, c.tick / HZ)
      || c.addressedAt !== null && !range(c.addressedAt, 0, c.tick / HZ)
      || !range(c.waitSince, -1, c.tick / HZ) || typeof c.lastStill !== 'string' || c.lastStill.length > 2048
      || !Array.isArray(c.members) || c.members.length > 6
      || c.members.some(m => !m || typeof m.id !== 'string' || !m.id || m.id.length > 4096
        || !integer(m.slot, 0, 5) || !range(m.phase, 0, Math.PI * 2) || typeof m.present !== 'boolean')
      || new Set(c.members.map(m => m.id)).size !== c.members.length || new Set(c.members.map(m => m.slot)).size !== c.members.length
      || c.food !== null && (!c.food || !range(c.food.time, 0, c.tick / HZ) || ![c.food.x, c.food.y].every(n => range(n, -1, 1)))
      || !c.frame || c.frame.timeMs !== c.tick * 1000 / HZ || c.frame.behaviour !== c.behaviour
      || !['none', 'orient', 'approach', 'call'].includes(c.frame.needs)
      || !range(c.frame.surface, -.9, -.8) || !range(c.frame.caustic, 0, 1)
      || c.frame.food !== null && (!c.frame.food || !range(c.frame.food.x, -1, 1) || !range(c.frame.food.y, -1, 1.21) || !range(c.frame.food.life, 0, 1))
      || JSON.stringify(c.frame.pod) !== JSON.stringify(c.members)
      || !Array.isArray(c.voices) || c.voices.length > 1024
      || c.voices.some(v => !v || typeof v.id !== 'string' || v.id.length > 256))
      throw new Error('Invalid pet world checkpoint.');
    validatePetState(c.frame.state);
    renderPetPCM(c.voices, 0, 0);
    const world = new PetWorld(points, tape, interactions, c.sim?.expressionVersion ?? 1, c.petCheckpointVersion === 2);
    if (c.petCheckpointVersion === 2) { world.hasTelemetry = c.hasTelemetry!; world.origin = structuredClone(c); }
    if (c.history !== world.historyDigest()
      || c.bucketIndex >= 0 && world.tape[c.bucketIndex].simTimeMs > c.frame.timeMs + 1e-7
      // acceptTelemetry may fill past gaps after the most recent fixed tick.
      // Preserve that pending cursor exactly; it may lag only over empty gaps.
      || world.tape.some((b, i) => i > c.bucketIndex && b.simTimeMs <= c.frame.timeMs + 1e-7
        && (b.observed !== 0 || b.channel !== 'other' || b.errors || b.waiting || b.agentIds.length
          || b.onsets.some(Boolean) || b.activeMs.some(Boolean)))
      || world.interactionLog.slice(0, c.interactionIndex).some(e => e.timeMs > c.frame.timeMs + 1e-7)
      || world.interactionLog[c.interactionIndex]?.timeMs <= c.frame.timeMs + 1e-7)
      throw new Error('Pet checkpoint does not match its recording.');
    const candidate = world.tape[c.bucketIndex];
    const telemetry = candidate && c.frame.timeMs < candidate.simTimeMs + candidate.durationMs ? candidate : undefined;
    if (JSON.stringify(c.frame.telemetry) !== JSON.stringify(telemetry)) throw new Error('Pet checkpoint telemetry does not match its clock.');
    world.sim.restore(c.sim); world.score.restore(c.score); world.random.restore(c.random);

View on GitHub (pinned to 433685b202)