{"record":{"id":"304f8c08dbd907b3","repo":"moeru-ai/airi","slug":"the-ar-hmm-state-count-must-be-an-integer-greater","errorCode":null,"errorMessage":"The AR-HMM state count must be an integer greater than one.","messagePattern":"The AR-HMM state count must be an integer greater than one\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/motion-driver-magic/src/ar-hmm.ts","lineNumber":358,"sourceCode":"    () => Array.from<number>({ length: options.stateCount }).fill(0),\n  )\n  for (let row = 0; row < rowCount - 1; row++) {\n    const values = alpha[row].flatMap((value, state) => parameters.states.map(\n      (_nextStateModel, nextState) => value + logTransitions[state][nextState] + emissions[row + 1][nextState] + beta[row + 1][nextState],\n    ))\n    const normalization = logSumExp(values)\n    for (let state = 0; state < options.stateCount; state++) {\n      for (let nextState = 0; nextState < options.stateCount; nextState++)\n        transitionCounts[state][nextState] += Math.exp(values[state * options.stateCount + nextState] - normalization)\n    }\n  }\n  return { gamma, transitionCounts, logLikelihood }\n}\n\n/** Creates a linear Gaussian AR-HMM model with deterministic clustering and EM updates. */\nexport function createArHmmModel(sequence: TrainingSequence, options: FitOptions): ArHmmModel {\n  if (options.stateCount < 2 || !Number.isInteger(options.stateCount))\n    throw new Error('The AR-HMM state count must be an integer greater than one.')\n  if (options.iterations < 1 || !Number.isInteger(options.iterations))\n    throw new Error('The AR-HMM iteration count must be a positive integer.')\n\n  const sourceModel = fitVarParameters(sequence, {\n    order: options.order,\n    ridge: options.ridge,\n  })\n  const rowCount = sourceModel.trainingFrames.length - options.order\n  if (rowCount < options.stateCount * (sourceModel.featureCount + 1))\n    throw new Error('The current motion is too short for this AR-HMM shape.')\n\n  const clusterFeatures = createClusterFeatures(sourceModel.trainingFrames, options.order)\n  const assignments = initializeAssignments(clusterFeatures, options.stateCount)\n  let expectation = createInitialExpectations(assignments, options.stateCount)\n  let stateParameters = maximizeParameters(sourceModel, expectation, options)\n  const logLikelihoods: number[] = []\n  for (let iteration = 0; iteration < options.iterations; iteration++) {\n    expectation = expectationStep(sourceModel, stateParameters, options)","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/motion-driver-magic/src/ar-hmm.ts#L340-L376","documentation":"`createArHmmModel()` fits a linear Gaussian AR-HMM and needs at least two hidden states to form a meaningful mixture (transitions require >=2 states). `options.stateCount` must be an integer >= 2; other values are rejected up front so EM does not run on a degenerate model.","triggerScenarios":"Calling `createArHmmModel(seq, { stateCount: 0 })`, `{ stateCount: 1 }`, or a non-integer like 2.5. Also occurs when stateCount is derived from a computation (e.g. parsing user config or dividing) that yields a non-integer or <=1 value.","commonSituations":"Reading `stateCount` from a UI slider or config file without enforcing an integer minimum of 2; a default of 1 used as 'single behavior'; passing Math results like `totalStates / groups` that round to a non-integer.","solutions":["Pass an integer `stateCount >= 2` in FitOptions.","Clamp/round config-derived values: `Math.max(2, Math.round(rawStateCount))`.","If only one behavior is expected, do not use AR-HMM; fit a single VAR model instead.","Add schema validation (e.g. Valibot) on the options object before fitting."],"exampleFix":"// before\ncreateArHmmModel(seq, { stateCount: 1 })\n// after\ncreateArHmmModel(seq, { stateCount: Math.max(2, Math.round(rawStateCount)) })","handlingStrategy":"validation","validationCode":"function isValidStateCount(n: unknown): n is number {\n  return typeof n === 'number' && Number.isInteger(n) && n >= 2\n}","typeGuard":"function isFitOptions(o: unknown): o is FitOptions {\n  return typeof o === 'object' && o !== null\n    && 'stateCount' in o && isValidStateCount((o as FitOptions).stateCount)\n    && 'iterations' in o && isValidIterations((o as FitOptions).iterations)\n}","tryCatchPattern":"try {\n  const model = createArHmmModel(seq, options)\n} catch (error) {\n  if (error instanceof Error && error.message.includes('state count')) {\n    return createArHmmModel(seq, { ...options, stateCount: 2 })\n  }\n  throw error\n}","preventionTips":["Clamp config-driven stateCount with Math.max(2, Math.round(x)).","Validate options with a schema (Valibot) before fitting.","Never hardcode stateCount: 1 for AR-HMM — use a plain VAR fit instead.","Watch for division producing non-integers when deriving stateCount."],"tags":["validation","options","ar-hmm","motion"],"backgroundTag":"invalid-model-options","analyzedSha":"9c213115f8bd0fff9e6eabab02b077ac32da21be","analyzedAt":"2026-09-02T04:27:24.639Z","contentChangedAt":"2026-09-02T04:27:24.639Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}