This commit is contained in:
@@ -448,6 +448,207 @@ function validateNarrativeCoherence(dungeonData) {
|
||||
return issues;
|
||||
}
|
||||
|
||||
function fixStructureIssues(dungeonData) {
|
||||
const fixes = [];
|
||||
|
||||
// Fix missing or invalid room names
|
||||
if (dungeonData.rooms) {
|
||||
dungeonData.rooms.forEach((room, i) => {
|
||||
if (!room.name || !room.name.trim()) {
|
||||
// Extract name from description if possible
|
||||
const desc = room.description || '';
|
||||
const nameMatch = desc.match(/^([A-Z][^.!?]{5,30}?)(?:\s|\.|:)/);
|
||||
if (nameMatch) {
|
||||
room.name = nameMatch[1].trim();
|
||||
fixes.push(`Extracted room name from description: "${room.name}"`);
|
||||
} else {
|
||||
room.name = `Room ${i + 1}`;
|
||||
fixes.push(`Added default name for room ${i + 1}`);
|
||||
}
|
||||
}
|
||||
// Truncate overly long room names
|
||||
const words = room.name.split(/\s+/);
|
||||
if (words.length > 6) {
|
||||
const original = room.name;
|
||||
room.name = words.slice(0, 6).join(' ');
|
||||
fixes.push(`Truncated room name: "${original}" -> "${room.name}"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fix missing or invalid encounter names
|
||||
if (dungeonData.encounters) {
|
||||
dungeonData.encounters.forEach((encounter, i) => {
|
||||
if (!encounter.name || !encounter.name.trim()) {
|
||||
// Extract name from details if possible
|
||||
const details = encounter.details || '';
|
||||
const nameMatch = details.match(/^([^:]+):\s*(.+)$/);
|
||||
if (nameMatch) {
|
||||
encounter.name = nameMatch[1].trim();
|
||||
encounter.details = nameMatch[2].trim();
|
||||
fixes.push(`Extracted encounter name from details: "${encounter.name}"`);
|
||||
} else {
|
||||
encounter.name = `Encounter ${i + 1}`;
|
||||
fixes.push(`Added default name for encounter ${i + 1}`);
|
||||
}
|
||||
}
|
||||
// Truncate overly long encounter names
|
||||
const words = encounter.name.split(/\s+/);
|
||||
if (words.length > 6) {
|
||||
const original = encounter.name;
|
||||
encounter.name = words.slice(0, 6).join(' ');
|
||||
fixes.push(`Truncated encounter name: "${original}" -> "${encounter.name}"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fix missing or invalid NPC names
|
||||
if (dungeonData.npcs) {
|
||||
dungeonData.npcs.forEach((npc, i) => {
|
||||
if (!npc.name || !npc.name.trim()) {
|
||||
// Extract name from trait if possible
|
||||
const trait = npc.trait || '';
|
||||
const nameMatch = trait.match(/^([A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,2})(?:\s|:)/);
|
||||
if (nameMatch) {
|
||||
npc.name = nameMatch[1].trim();
|
||||
fixes.push(`Extracted NPC name from trait: "${npc.name}"`);
|
||||
} else {
|
||||
npc.name = `NPC ${i + 1}`;
|
||||
fixes.push(`Added default name for NPC ${i + 1}`);
|
||||
}
|
||||
}
|
||||
// Truncate overly long NPC names
|
||||
const words = npc.name.split(/\s+/);
|
||||
if (words.length > 4) {
|
||||
const original = npc.name;
|
||||
npc.name = words.slice(0, 4).join(' ');
|
||||
fixes.push(`Truncated NPC name: "${original}" -> "${npc.name}"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return fixes;
|
||||
}
|
||||
|
||||
function fixMissingContent(dungeonData) {
|
||||
const fixes = [];
|
||||
|
||||
// Pad NPCs if needed
|
||||
if (!dungeonData.npcs || dungeonData.npcs.length < 4) {
|
||||
if (!dungeonData.npcs) dungeonData.npcs = [];
|
||||
const factionName = dungeonData.coreConcepts?.match(/Primary Faction[:\s]+([^.]+)/i)?.[1]?.trim() || 'the primary faction';
|
||||
while (dungeonData.npcs.length < 4) {
|
||||
dungeonData.npcs.push({
|
||||
name: `NPC ${dungeonData.npcs.length + 1}`,
|
||||
trait: `A member of ${factionName.toLowerCase()} with unknown motives.`
|
||||
});
|
||||
fixes.push(`Added fallback NPC ${dungeonData.npcs.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Pad encounters if needed
|
||||
if (!dungeonData.encounters || dungeonData.encounters.length < 6) {
|
||||
if (!dungeonData.encounters) dungeonData.encounters = [];
|
||||
if (dungeonData.encounters.length > 0 && dungeonData.rooms && dungeonData.rooms.length > 0) {
|
||||
const dynamicElement = dungeonData.coreConcepts?.match(/Dynamic Element[:\s]+([^.]+)/i)?.[1]?.trim() || 'strange occurrences';
|
||||
const conflict = dungeonData.coreConcepts?.match(/Central Conflict[:\s]+([^.]+)/i)?.[1]?.trim() || 'a threat';
|
||||
while (dungeonData.encounters.length < 6) {
|
||||
const roomIndex = dungeonData.encounters.length % dungeonData.rooms.length;
|
||||
const roomName = dungeonData.rooms[roomIndex]?.name || 'Unknown Location';
|
||||
const fallbackNames = [
|
||||
`${roomName} Guardian`,
|
||||
`${roomName} Threat`,
|
||||
`${roomName} Challenge`,
|
||||
`${dynamicElement.split(' ')[0]} Manifestation`,
|
||||
`${conflict.split(' ')[0]} Encounter`,
|
||||
`${roomName} Hazard`
|
||||
];
|
||||
dungeonData.encounters.push({
|
||||
name: fallbackNames[dungeonData.encounters.length % fallbackNames.length],
|
||||
details: `${roomName}: An encounter related to ${dynamicElement.toLowerCase()} occurs here.`
|
||||
});
|
||||
fixes.push(`Added fallback encounter: "${dungeonData.encounters[dungeonData.encounters.length - 1].name}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pad treasure if needed
|
||||
if (!dungeonData.treasure || dungeonData.treasure.length < 4) {
|
||||
if (!dungeonData.treasure) dungeonData.treasure = [];
|
||||
while (dungeonData.treasure.length < 4) {
|
||||
dungeonData.treasure.push({
|
||||
name: `Treasure ${dungeonData.treasure.length + 1}`,
|
||||
description: `A mysterious item found in the dungeon.`
|
||||
});
|
||||
fixes.push(`Added fallback treasure ${dungeonData.treasure.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Pad random events if needed
|
||||
if (!dungeonData.randomEvents || dungeonData.randomEvents.length < 6) {
|
||||
if (!dungeonData.randomEvents) dungeonData.randomEvents = [];
|
||||
if (dungeonData.randomEvents.length > 0 && dungeonData.coreConcepts) {
|
||||
const dynamicElement = dungeonData.coreConcepts.match(/Dynamic Element[:\s]+([^.]+)/i)?.[1]?.trim() || 'strange occurrences';
|
||||
const conflict = dungeonData.coreConcepts.match(/Central Conflict[:\s]+([^.]+)/i)?.[1]?.trim() || 'a mysterious threat';
|
||||
const fallbackEvents = [
|
||||
{ name: 'Environmental Shift', description: `The ${dynamicElement.toLowerCase()} causes unexpected changes in the environment.` },
|
||||
{ name: 'Conflict Manifestation', description: `A sign of ${conflict.toLowerCase()} 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.` }
|
||||
];
|
||||
while (dungeonData.randomEvents.length < 6) {
|
||||
dungeonData.randomEvents.push(fallbackEvents[dungeonData.randomEvents.length % fallbackEvents.length]);
|
||||
fixes.push(`Added fallback random event: "${dungeonData.randomEvents[dungeonData.randomEvents.length - 1].name}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pad plot resolutions if needed
|
||||
if (!dungeonData.plotResolutions || dungeonData.plotResolutions.length < 4) {
|
||||
if (!dungeonData.plotResolutions) dungeonData.plotResolutions = [];
|
||||
while (dungeonData.plotResolutions.length < 4) {
|
||||
dungeonData.plotResolutions.push(`The adventurers could resolve the central conflict through decisive action.`);
|
||||
fixes.push(`Added fallback plot resolution ${dungeonData.plotResolutions.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
return fixes;
|
||||
}
|
||||
|
||||
function fixNarrativeCoherence(dungeonData) {
|
||||
const fixes = [];
|
||||
|
||||
// Fix encounters referencing unknown locations
|
||||
if (dungeonData.encounters && dungeonData.rooms) {
|
||||
const roomNames = dungeonData.rooms.map(r => r.name.trim().toLowerCase());
|
||||
dungeonData.encounters.forEach(encounter => {
|
||||
if (!encounter.details) return;
|
||||
const locationMatch = encounter.details.match(/^([^:]+):/);
|
||||
if (locationMatch) {
|
||||
const locName = locationMatch[1].trim().toLowerCase();
|
||||
// Check if location matches any room name (fuzzy match)
|
||||
const matches = roomNames.some(rn =>
|
||||
locName === rn ||
|
||||
locName.includes(rn) ||
|
||||
rn.includes(locName) ||
|
||||
locName.split(/\s+/).some(word => rn.includes(word))
|
||||
);
|
||||
if (!matches && roomNames.length > 0) {
|
||||
// Assign to a random room
|
||||
const roomIdx = Math.floor(Math.random() * roomNames.length);
|
||||
const roomName = dungeonData.rooms[roomIdx].name;
|
||||
encounter.details = encounter.details.replace(/^[^:]+:\s*/, `${roomName}: `);
|
||||
fixes.push(`Fixed unknown location in encounter "${encounter.name}" to "${roomName}"`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return fixes;
|
||||
}
|
||||
|
||||
function validateAndFixContent(dungeonData) {
|
||||
const allFixes = [];
|
||||
const allIssues = [];
|
||||
@@ -456,6 +657,10 @@ function validateAndFixContent(dungeonData) {
|
||||
const nameFixes = validateNameConsistency(dungeonData);
|
||||
allFixes.push(...nameFixes);
|
||||
|
||||
// Fix structure issues (missing names, too long names)
|
||||
const structureFixes = fixStructureIssues(dungeonData);
|
||||
allFixes.push(...structureFixes);
|
||||
|
||||
// Standardize encounter locations and add missing ones
|
||||
if (dungeonData.encounters && dungeonData.rooms) {
|
||||
const roomNames = dungeonData.rooms.map(r => r.name.trim());
|
||||
@@ -475,7 +680,15 @@ function validateAndFixContent(dungeonData) {
|
||||
allFixes.push(...locationResult.fixes);
|
||||
}
|
||||
|
||||
// Run content validation
|
||||
// Fix narrative coherence issues
|
||||
const coherenceFixes = fixNarrativeCoherence(dungeonData);
|
||||
allFixes.push(...coherenceFixes);
|
||||
|
||||
// Fix missing content (pad arrays)
|
||||
const contentFixes = fixMissingContent(dungeonData);
|
||||
allFixes.push(...contentFixes);
|
||||
|
||||
// Run content validation (for reporting remaining issues)
|
||||
const completenessIssues = validateContentCompleteness(dungeonData);
|
||||
const qualityIssues = validateContentQuality(dungeonData);
|
||||
const structureIssues = validateContentStructure(dungeonData);
|
||||
@@ -489,7 +702,7 @@ function validateAndFixContent(dungeonData) {
|
||||
}
|
||||
|
||||
if (allIssues.length > 0) {
|
||||
console.log("\n[Validation] Content quality issues found:");
|
||||
console.log("\n[Validation] Content quality issues found (not auto-fixable):");
|
||||
allIssues.forEach(issue => console.warn(` ⚠ ${issue}`));
|
||||
} else {
|
||||
console.log("\n[Validation] Content quality checks passed");
|
||||
@@ -743,6 +956,13 @@ Random Events:
|
||||
CRITICAL: Every name must be unique and creative. Never use generic placeholders like "Location Name", "NPC Name", "Encounter Name", or "Treasure Name". Use actual descriptive names that fit the dungeon's theme.
|
||||
|
||||
CRITICAL: Ensure all spelling is correct. Double-check all words, especially proper nouns, character names, and location names. Verify consistency of names across all sections.
|
||||
|
||||
CRITICAL: Location name matching - When writing encounters, the location name in the encounter details MUST exactly match one of the room names you've created (Entrance Room, Climax Room, or one of the 3 Locations). Double-check that every encounter location matches an actual room name.
|
||||
|
||||
CRITICAL: Avoid vague language - Do not use words like "some", "various", "several", "many", "few", "things", "stuff", "items", or "objects" without specific details. Be concrete and specific in all descriptions.
|
||||
|
||||
CRITICAL: All names required - Every room, encounter, NPC, and treasure MUST have a name. Do not leave names blank or use placeholders. If you cannot think of a name, create one based on the dungeon's theme.
|
||||
|
||||
CRITICAL: You MUST output exactly five separate sections with these exact labels on their own lines:
|
||||
"Locations:"
|
||||
"Encounters:"
|
||||
|
||||
Reference in New Issue
Block a user