hcengineering/platform · error

Ticks per second has an invalid value: must be >= 1 && <= 10

Error message

Ticks per second has an invalid value: must be >= 1 && <= 1000

What it means

The tick/timer utility validates its tps (ticks per second) in the constructor and rejects any value outside the 1–1000 inclusive range. A tps above 1000 would require sub-millisecond timers the runtime cannot honor, and values below 1 are meaningless. The constructor fails fast so an invalid clock is never used.

Source

Thrown at foundations/net/packages/core/src/utils.ts:36

}

/**
 * Handles a time unification and inform about ticks.
 */
export class TickManagerImpl implements TickManager {
  handlers = new Map<number, [TickHandler, number, number]>()

  hashCounter: number = 0

  _tick: number = 0

  tickListeners = new Map<number, (() => void)[]>()

  started: boolean = false

  constructor (readonly tps: number) {
    if (tps > 1000 || tps < 1) {
      throw new Error('Ticks per second has an invalid value: must be >= 1 && <= 1000')
    }
  }

  now (): number {
    // Use performance.now() when available, otherwise fall back to Date.now()
    // performance is available in recent Node versions, but guard for portability.
    return (globalThis as any).performance?.now?.() ?? Date.now()
  }

  register (handler: TickHandler, interval: number): () => void {
    if (!Number.isFinite(interval) || interval < 1) {
      throw new Error('Interval must be a finite number >= 1 (seconds)')
    }
    const handlerId = this.hashCounter++
    this.handlers.set(handlerId, [handler, handlerId % this.tps, interval])
    return () => {
      this.handlers.delete(handlerId)
    }

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Pass a tps between 1 and 1000 inclusive (e.g. new Ticker(60)).
  2. Sanitize config: Number.parseInt the env value and clamp it with Math.min(1000, Math.max(1, value)).
  3. Provide a sane default (e.g. 60) when the config value is missing or invalid instead of passing 0/undefined.
  4. If you need sub-millisecond resolution, this utility cannot provide it; redesign the timing loop instead of raising tps.

Example fix

// before
const ticker = new Ticker(Number(process.env.TPS)) // undefined -> invalid
// after
const tps = Math.min(1000, Math.max(1, Number(process.env.TPS ?? 60)))
const ticker = new Ticker(tps)
Defensive patterns

Strategy: validation

Validate before calling

function parseTps(raw: unknown, fallback = 60): number {
  const n = typeof raw === 'number' ? raw : Number(raw)
  if (!Number.isFinite(n) || n < 1 || n > 1000) return fallback
  return Math.floor(n)
}
const ticker = new Ticker(parseTps(process.env.TPS))

Type guard

function isValidTps(v: unknown): v is number {
  return typeof v === 'number' && Number.isFinite(v) && Number.isInteger(v) && v >= 1 && v <= 1000
}

Try / catch

let ticker: Ticker
try {
  ticker = new Ticker(configuredTps)
} catch (e) {
  if ((e as Error).message.includes('Ticks per second')) {
    console.warn(`invalid tps ${configuredTps}, falling back to 60`)
    ticker = new Ticker(60)
  } else throw e
}

Prevention

When it happens

Trigger: Constructing the tick utility with tps = 0, a negative number, NaN-adjacent values comparing oddly, or a value > 1000 (e.g. passing milliseconds instead of seconds-per-tick, or multiplying tps by 1000 by mistake).

Common situations: Confusing tps with interval milliseconds (1000 used as '1 second'); loading tps from config/env where an empty or malformed string yields a bad number; defaulting logic producing 0 when a config key is missing.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/bd2d4ccb1457251d. Report an issue: GitHub.