```ts
export function findFreeRun(words: number[], bits: number, length: number): number {
  if (!Number.isInteger(length) || length <= 0) {
    throw new Error('Invalid run length');
  }
  const wordCount = Math.ceil(bits / 32);
  if (words.length !== wordCount) {
    throw new Error('Word count does not match capacity');
  }
  for (const word of words) {
    if (!Number.isInteger(word) || word < 0 || word > 4294967295) {
      throw new Error('Invalid bitmap word');
    }
  }

  for (let start = 0; start <= bits - length * 32; start++) {
    let freeBlocks = 0;
    for (let i = start / 32; i < (start + length * 32) / 32; i++) {
      const word = words[i];
      if (word === 4294967295) {
        break;
      }
      let freeBitsInWord = 0;
      for (let j = start % 32; j < 32 && j < (start + length * 32) % 32; j++) {
        if ((word & (1 << j)) === 0) {
          freeBitsInWord++;
        } else {
          break;
        }
      }
      freeBlocks += freeBitsInWord / 32;
    }
    if (freeBlocks >= length) {
      return start;
    }
  }

  return -1;
}

export function occupyRun(words: number[], start: number, length: number): number[] {
  const newWords = [...words];
  for (let i = start / 32; i < (start + length * 32) / 32; i++) {
    if (i >= newWords.length) {
      throw new Error('Run does not lie wholly inside the words');
    }
    const wordIndex = Math.floor(i);
    const bitIndex = i % 32;
    let mask = 0xFFFFFFFF;
    for (let j = bitIndex; j < 32 && j < (start + length * 32) % 32; j++) {
      if ((newWords[wordIndex] & (1 << j)) !== 0) {
        throw new Error('Run touches an occupied block');
      }
      mask ^= 1 << j;
    }
    newWords[wordIndex] |= mask;
  }
  return newWords;
}

export function bitAt(words: number[], index: number): number {
  if (!Number.isInteger(index)) {
    throw new Error('Invalid index');
  }
  const wordIndex = Math.floor(index / 32);
  const bitIndex = index % 32;
  if (wordIndex >= words.length) {
    throw new Error('Index out of bounds');
  }
  return (words[wordIndex] & (1 << bitIndex)) !== 0 ? 1 : 0;
}
```