The bug is that the regex has the g flag, so batDetector keeps its lastIndex between sections.
After the first section finishes, lastIndex is already at the end of that string. When exec() is called on the next section, it starts from that old index and can miss matches.
The simple fix is to reset lastIndex before processing each section:
for (const section of caveSections) {
batDetector.lastIndex = 0;
const batMatches = [];
let match;
while ((match = batDetector.exec(section)) !== null) {
batMatches.push(match.index);
}
// ...
}