Hmbown/CodeWhale · error · Error

World dt must be in [0, 10] seconds.

Error message

World dt must be in [0, 10] seconds.

What it means

step(dt) advances the world by a bounded delta time so a suspended or backgrounded surface cannot trigger an unbounded catch-up simulation when it resumes. dt must be a finite number of seconds in [0, 10]. NaN, Infinity, negatives, or anything above 10 seconds is rejected before the accumulator grows.

Solutions

  1. Clamp dt before calling: `dt = Math.min(Math.max(dt, 0), 10)`.
  2. Compute dt from a monotonic clock in seconds and reset the last-timestamp after suspension so the first frame after resume uses a small dt.
  3. Treat NaN/undefined clock readings as 0 (or skip the frame) instead of forwarding them.

Example fix

// before
world.step((now - last) / 1000); // ~5400 after overnight suspend
// after
let dt = (now - last) / 1000;
last = now;
dt = Number.isFinite(dt) ? Math.min(Math.max(dt, 0), 10) : 0;
world.step(dt);
Defensive patterns

Strategy: validation

Validate before calling

function safeStep(world, dt) {
  const d = Number(dt);
  if (!Number.isFinite(d) || d < 0 || d > 10) return false;
  world.step(d);
  return true;
}

Type guard

const isFiniteDt = (dt) => typeof dt === 'number' && Number.isFinite(dt) && dt >= 0 && dt <= 10;

Try / catch

try {
  world.step(dt);
} catch (e) {
  if (e.message.includes('World dt must be')) {
    world.step(0); // skip the bad frame rather than crashing the loop
  } else throw e;
}

Prevention

When it happens

Trigger: Calling world.step(dt) with NaN or Infinity (e.g. from a broken clock delta), a negative dt from a clock that jumped backwards, or a huge dt such as the raw elapsed seconds after the tab was suspended overnight.

Common situations: Computing dt as (now - last) after a system sleep or tab suspension; performance.now() units confusion (ms passed where s expected, giving dt=5000); a paused animation frame returning undefined dt; monotonic-clock rollback in VMs.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/1725bb559f7e8c2c. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/pet-native.js:464

            this.hashTape(index);
            return;
        }
        if (sequence >= 216_000)
            throw new Error('Archive the legacy recording before accepting more telemetry.');
        const changedFrom = Math.min(sequence, this.tapeLog.length);
        while (this.tapeLog.length <= sequence) {
            const at = this.tapeLog.length;
            this.tapeLog.push({ version: 1, sequence: at, simTimeMs: at * 400, durationMs: 400,
                activity: .12, coherence: .25, attention: 0, channel: 'other', observed: 0, roamX: 0, roamY: 0, flip: 1, lit: 1,
                onsets: Array(13).fill(0), activeMs: Array(13).fill(0), errors: 0, agentIds: [], waiting: false });
        }
        this.tapeLog[sequence] = { ...structuredClone(input), sequence, simTimeMs: sequence * 400 };
        this.hashTape(changedFrom);
    }
    /** dt is bounded so suspending a surface cannot cause an unbounded catch-up. */
    step(dt, opts = { motion: true, sensitivity: 1 }) {
        if (!Number.isFinite(dt) || dt < 0 || dt > 10)
            throw new Error('World dt must be in [0, 10] seconds.');
        this.accumulator += dt;
        this.voices = [];
        while (this.accumulator + 1e-10 >= 1 / HZ) {
            this.accumulator -= 1 / HZ;
            const time = ++this.tick / HZ;
            this.frame = this.makeFrame(time);
            this.voices.push(...this.score.voices(this.frame));
            if (!opts.motion) {
                this.frame.state = { ...this.frame.state, roamX: 0, roamY: 0, flip: 1, lit: this.behaviour === 'doze' ? .18 : 1 };
                this.frame.surface = -.86;
                this.frame.caustic = .5;
                if (this.frame.food)
                    this.frame.food.y = this.food.y;
            }
            const signature = JSON.stringify(this.frame.state);
            const peers = this.frame.pod.filter(p => p.present);
            const podSlots = peers.length >= 3 ? peers.map(p => [[0, 2, 4, 1, 3, 5][p.slot], p.phase]) : undefined;
            if (opts.motion || signature !== this.lastStill || podSlots)

View on GitHub (pinned to 73e0f67d83)