{"record":{"id":"e6f898e30d2f7e5a","repo":"Hmbown/CodeWhale","slug":"maxbins-must-be-an-integer-in-16-1048576-signal","errorCode":null,"errorMessage":"maxBins must be an integer in [16, 1048576].","messagePattern":"maxBins must be an integer in \\[16, 1048576\\]\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/signal.ts","lineNumber":11,"sourceCode":"import { CATEGORIES, clamp, errorOnsetOf, quantile, type BinLevel, type Metric, type SignalPyramid, type WhaleEvent } from './model.js';\n\nfunction emptyLevel(length: number, binMs: number): BinLevel {\n  const n = CATEGORIES.length * length;\n  return { binMs, length, onsets: new Float64Array(n), activeMs: new Float64Array(n),\n    outputTokens: new Float64Array(n), cost: new Float64Array(n), errors: new Float64Array(n), peak: new Float64Array(n) };\n}\nconst FIELDS = ['onsets', 'activeMs', 'outputTokens', 'cost', 'errors'] as const;\n/** O(events + channels × bins), including long intervals. No span-length inner loop. */\nexport function buildPyramid(events: readonly WhaleEvent[], requestedDuration?: number, maxBins = 16_384): SignalPyramid {\n  if (!Number.isInteger(maxBins) || maxBins < 16 || maxBins > 1_048_576) throw new Error('maxBins must be an integer in [16, 1048576].');\n  let duration = requestedDuration ?? 1;\n  for (const e of events) duration = Math.max(duration, e.endTime, e.startTime, e.status === 'error' ? errorOnsetOf(e) : 0);\n  if (!Number.isFinite(duration) || duration < 0) throw new Error('Signal duration must be finite and nonnegative.');\n  duration = Math.max(1, duration);\n  const binMs = 2 ** Math.ceil(Math.log2(Math.max(1, duration / (maxBins - 1))));\n  const length = Math.floor(duration / binMs) + 1, fine = emptyLevel(length, binMs);\n  const stride = length + 1;\n  const activeDiff = new Float64Array(CATEGORIES.length * stride), tokenDiff = new Float64Array(CATEGORIES.length * stride);\n  for (const e of events) {\n    if (!Number.isFinite(e.startTime) || !Number.isFinite(e.endTime) || e.startTime < 0 || e.endTime < e.startTime) throw new Error(`Invalid interval for ${e.id}.`);\n    const channel = CATEGORIES.indexOf(e.category);\n    if (channel < 0) throw new Error(`Unknown category for ${e.id}.`);\n    const a = Math.floor(e.startTime / binMs), b = Math.floor(e.endTime / binMs);\n    const at = channel * length, diff = channel * stride;\n    fine.onsets[at + a]++;\n    fine.cost[at + a] += e.cost ?? 0;\n    if (e.status === 'error') fine.errors[at + Math.floor(errorOnsetOf(e) / binMs)]++;\n    const d = e.endTime - e.startTime, tokens = e.outputTokens ?? 0;","sourceCodeStart":1,"sourceCodeEnd":29,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/signal.ts#L1-L29","documentation":"buildPyramid() allocates a multi-level signal pyramid from whale events and lets callers tune the finest bin resolution via maxBins. It throws when maxBins is not an integer or falls outside [16, 1048576], because bin sizes are derived from it (duration / (maxBins - 1)) and absurd or fractional values would corrupt the pyramid layout.","triggerScenarios":"Calling buildPyramid(events, duration, maxBins) with a non-integer (e.g. 100.5), a value below 16 (including the naive 0 or 1 passed to mean 'no binning'), or above 1,048,576 (e.g. passing Number.MAX_SAFE_INTEGER to force max resolution).","commonSituations":"A config knob read from user input or a query string arriving as a string like '16384' that fails Number.isInteger after coercion quirks; a caller passing a desired bin *width* (e.g. 100 for 100ms) instead of a bin *count*; unbounded user zoom levels exceeding the cap.","solutions":["Pass an integer between 16 and 1,048,576 (default 16,384 is fine for most traces).","If the value comes from user input, parse with Number.parseInt and clamp: maxBins = Math.min(1_048_576, Math.max(16, Math.round(value))).","If you meant to control bin width in ms, compute the count instead: maxBins = Math.ceil(duration / binWidthMs), then clamp.","Check for string/number confusion when the value originates from JSON, CLI args, or URL params."],"exampleFix":"// before\nbuildPyramid(events, duration, '16384');\n// after\nconst maxBins = Math.min(1_048_576, Math.max(16, Math.round(Number('16384'))));\nbuildPyramid(events, duration, maxBins);","handlingStrategy":"validation","validationCode":"const maxBins = Math.min(1_048_576, Math.max(16, Math.round(raw)));\nif (!Number.isInteger(raw) && Number.isFinite(raw)) /* decided above: round+clamp is safe */;","typeGuard":"function isValidMaxBins(v: unknown): v is number { return Number.isInteger(v) && (v as number) >= 16 && (v as number) <= 1_048_576; }","tryCatchPattern":"try { buildPyramid(events, duration, maxBins); } catch (e) { if (/maxBins must be/.test(e.message)) buildPyramid(events, duration, 16_384); else throw e; }","preventionTips":["Parse numeric config through one clamp/round helper before it reaches the API.","Do not confuse bin width with bin count.","Bound user-driven zoom inputs to the 16..1048576 range at the UI layer."],"tags":["validation","configuration","signal-processing"],"backgroundTag":"value-out-of-range","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}