export function deduplicateRoomsByName(rooms) { if (!rooms || rooms.length === 0) return []; const seenNames = new Set(); return rooms.filter(room => { if (!room || !room.name) return false; const nameLower = room.name.toLowerCase().trim(); if (seenNames.has(nameLower)) { console.warn(`Duplicate room name detected: "${room.name}", skipping duplicate`); return false; } seenNames.add(nameLower); return true; }); } export function padNpcsToMinimum(parsedNpcs, coreConcepts, minCount = 4) { const factionName = coreConcepts?.match(/Primary Faction[:\s]+([^.]+)/i)?.[1]?.trim() || 'the primary faction'; if (!parsedNpcs || parsedNpcs.length >= minCount || parsedNpcs.length === 0) return parsedNpcs || []; const list = [...parsedNpcs]; while (list.length < minCount) { list.push({ name: `NPC ${list.length + 1}`, trait: `A member of ${factionName.toLowerCase()} with unknown motives.` }); } return list; } export function buildEncountersList(parsedEncounters, rooms, coreConcepts) { const dynamicElement = coreConcepts?.match(/Dynamic Element[:\s]+([^.]+)/i)?.[1]?.trim() || 'strange occurrences'; const conflict = coreConcepts?.match(/Central Conflict[:\s]+([^.]+)/i)?.[1]?.trim() || 'a threat'; const fallbackNames = (roomName) => [ `${roomName} Guardian`, `${roomName} Threat`, `${roomName} Challenge`, `${dynamicElement.split(' ')[0]} Manifestation`, `${conflict.split(' ')[0]} Encounter`, `${roomName} Hazard` ]; if (parsedEncounters.length > 0 && parsedEncounters.length < 6) { return [ ...parsedEncounters, ...Array.from({ length: 6 - parsedEncounters.length }, (_, i) => { const roomIndex = (parsedEncounters.length + i) % rooms.length; const roomName = rooms[roomIndex]?.name || 'Unknown Location'; return { name: fallbackNames(roomName)[(parsedEncounters.length + i) % 6], details: `An encounter related to ${dynamicElement.toLowerCase()} occurs here.` }; }) ]; } if (parsedEncounters.length === 0) { return Array.from({ length: 6 }, (_, i) => { const roomName = rooms[i % rooms.length]?.name || 'Unknown Location'; return { name: `${roomName} Encounter`, details: `An encounter related to ${dynamicElement.toLowerCase()} occurs here.` }; }); } return parsedEncounters; } export function mergeRandomEventsWithFallbacks(parsedEvents, coreConcepts, maxCount = 6) { const dynamicElement = coreConcepts?.match(/Dynamic Element[:\s]+([^.]+)/i)?.[1]?.trim() || 'strange occurrences'; const conflict = (coreConcepts?.match(/Central Conflict[:\s]+([^.]+)/i)?.[1]?.trim() || 'a mysterious threat').toLowerCase(); const fallbackEvents = [ { name: 'Environmental Shift', description: `The ${dynamicElement.toLowerCase()} causes unexpected changes in the environment.` }, { name: 'Conflict Manifestation', description: `A sign of ${conflict} appears, requiring immediate attention.` }, { name: 'Dungeon Shift', description: `The dungeon shifts, revealing a previously hidden passage or danger.` }, { name: 'Faction Messenger', description: `An NPC from the primary faction appears with urgent information.` }, { name: 'Power Fluctuation', description: `The power source fluctuates, creating temporary hazards or opportunities.` }, { name: 'Echoes of the Past', description: `Echoes of past events manifest, providing clues or complications.` } ]; const truncated = (parsedEvents || []).slice(0, maxCount); if (truncated.length > 0 && truncated.length < maxCount) { return [ ...truncated, ...Array.from({ length: maxCount - truncated.length }, (_, i) => fallbackEvents[(truncated.length + i) % fallbackEvents.length]) ]; } return truncated; } export function limitIntermediateRooms(rooms, maxCount = 3) { if (rooms.length > maxCount) { console.warn(`Expected exactly ${maxCount} intermediate locations but got ${rooms.length}, limiting to first ${maxCount}`); } return rooms.slice(0, maxCount); } export function fixRoomPlaceholderName(room) { if (!room) return room; if (room.name && (room.name.toLowerCase().includes('room name') || room.name.toLowerCase() === 'room name')) { const desc = room.description || ''; const nameMatch = desc.match(/^([^:]+?)(?:\s+Description|\s*:)/i) || desc.match(/^([A-Z][^.!?]{5,40}?)(?:\s+is\s|\.)/); if (nameMatch) { room.name = nameMatch[1].trim().replace(/^(The|A|An)\s+/i, '').trim(); room.description = desc.replace(new RegExp(`^${nameMatch[1]}\\s*(Description|:)?\\s*`, 'i'), '').trim(); } else { const words = desc.split(/\s+/).slice(0, 4).join(' '); room.name = words.replace(/^(The|A|An)\s+/i, '').trim(); } } return room; }