Content Validation
Doodle Engine includes a comprehensive content validation system that catches errors in your YAML files and dialogue DSL before you deploy your game. Validation runs automatically during development and is required before production builds.
When Validation Runs
Section titled “When Validation Runs”During Development (npm run dev)
Section titled “During Development (npm run dev)”When you run npm run dev, validation runs automatically whenever you change a content file:
npm run devIf errors are found, they’re printed to the terminal, but the dev server keeps running. You can continue working while fixing errors.
Example output:
✏️ Content changed: content/dialogues/bartender_greeting.dlg
✗ Found 1 validation error:
content/dialogues/bartender_greeting.dlg Node "greet" GOTO "continue" points to non-existent node Add NODE continue or fix the GOTO targetBefore Building (npm run build)
Section titled “Before Building (npm run build)”When you run npm run build, validation runs first. If errors are found, the build fails and no production bundle is created:
npm run buildExample output:
🐕 Building Doodle Engine game...
Validating content...
✗ Found 2 validation errors:
content/dialogues/bartender_greeting.dlg Node "greet" GOTO "continue" points to non-existent node Add NODE continue or fix the GOTO target
content/characters/merchant.yaml Character "merchant" references non-existent dialogue "merchant_chat" Create dialogue "merchant_chat" or fix the reference
Build failed due to validation errors.Manual Validation (npm run validate)
Section titled “Manual Validation (npm run validate)”You can run validation manually without starting the dev server or building:
npm run validateThis is useful for:
- Quick content checks before committing
- CI/CD pipelines (validation returns exit code 1 on errors)
- Manual testing of specific changes
What Gets Validated
Section titled “What Gets Validated”Dialogue Structure
Section titled “Dialogue Structure”- Start node exists: The
startNodespecified in a dialogue must be a valid node ID - No duplicate node IDs: Each node ID must be unique within its dialogue
- GOTO targets exist: All
node.next,choice.next, andconditionalNexttargets must point to existing nodes. Exception: choices that containEND dialogueorGOTO locationdon’t need aGOTOtarget. They terminate the dialogue.
Example error:
content/dialogues/bartender_greeting.dlg Start node "invalid" not found Add a NODE invalid or fix the startNode referenceConditions
Section titled “Conditions”All conditions must have their required arguments:
| Condition | Required Arguments |
|---|---|
hasFlag, notFlag | flag |
hasItem, notItem | itemId |
questAtStage | questId, stageId |
atLocation | locationId |
characterAt | characterId, locationId |
characterInParty | characterId |
relationshipAbove, relationshipBelow | characterId, value |
variableEquals, variableGreaterThan, variableLessThan | variable, value |
itemAt | itemId, locationId |
timeIs | at least one of hour or day |
Example error:
content/dialogues/bartender_greeting.dlg Node "ask_rumors" condition "hasFlag" missing required "flag" argumentEffects
Section titled “Effects”All effects must have their required arguments:
| Effect | Required Arguments |
|---|---|
setFlag, clearFlag | flag |
setVariable, addVariable | variable, value |
addItem, removeItem | itemId |
moveItem | itemId, locationId |
setQuestStage | questId, stageId |
addJournalEntry | entryId |
setCharacterLocation | characterId, locationId |
addToParty, removeFromParty | characterId |
setRelationship, addRelationship | characterId, value |
setCharacterStat, addCharacterStat | characterId, stat, value |
setMapEnabled | enabled |
advanceTime | hours |
goToLocation | locationId |
startDialogue | dialogueId |
playMusic | track |
playSound | sound |
playVideo | file |
notify | message |
endDialogue | (none) |
Example error:
content/dialogues/bartender_greeting.dlg Node "greet" effect "setVariable" missing required "value" argumentCharacter Dialogue References
Section titled “Character Dialogue References”Characters’ dialogue field must reference existing dialogue IDs:
id: bartendername: 'Old Pete'dialogue: bartender_greeting # Must exist in content/dialogues/Example error:
content/characters/merchant.yaml Character "merchant" references non-existent dialogue "merchant_chat" Create dialogue "merchant_chat" or fix the referenceLocalization Keys
Section titled “Localization Keys”All @key references must exist in at least one locale file:
id: tavernname: '@location.tavern.name' # Must exist in locales/*.yamldescription: '@location.tavern.desc' # Must exist in locales/*.yamlExample error:
content/locations/tavern.yaml Localization key "@location.tavern.name" not found in any locale file Add "location.tavern.name: ..." to your locale filesCommon Validation Errors and Fixes
Section titled “Common Validation Errors and Fixes”GOTO Target Not Found
Section titled “GOTO Target Not Found”Error:
Node "greet" GOTO "continue" points to non-existent nodeCause: You referenced a node ID in GOTO that doesn’t exist.
Fix: Either create the missing node or fix the typo:
NODE greet Bartender: "Welcome to the tavern!" GOTO continue # Make sure this matches exactly
NODE continue # Add this node Bartender: "What can I get you?"Duplicate Node IDs
Section titled “Duplicate Node IDs”Error:
Duplicate node ID "greet"Cause: Two nodes have the same ID.
Fix: Rename one of the nodes:
NODE greet Bartender: "Welcome!"
NODE greet_again # Changed from "greet" Bartender: "Welcome back!"Missing Required Argument
Section titled “Missing Required Argument”Error:
Node "greet" condition "hasFlag" missing required "flag" argumentCause: A condition or effect is missing a required field.
Fix: Add the missing argument:
REQUIRE hasFlag("quest_started") # Add the flag name in quotes CHOICE "Ask about the quest" -> ask_questCharacter References Non-Existent Dialogue
Section titled “Character References Non-Existent Dialogue”Error:
Character "merchant" references non-existent dialogue "merchant_chat"Cause: The character’s dialogue field points to a dialogue that doesn’t exist.
Fix: Either create the dialogue or fix the reference:
id: merchantname: 'Merchant'dialogue: merchant_intro # Fix: changed from merchant_chatLocalization Key Not Found
Section titled “Localization Key Not Found”Error:
Localization key "@location.tavern.name" not found in any locale fileCause: A @key reference doesn’t exist in any locale file.
Fix: Add the key to your locale files:
location.tavern.name: 'The Rusty Tankard'location.tavern.desc: 'A cozy tavern with warm firelight.'Validation in CI/CD
Section titled “Validation in CI/CD”Add validation to your CI pipeline to catch errors before merging:
name: Validate Content
on: [push, pull_request]
jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 - run: npm install - run: npm run validatenpm run validate exits with code 1 if errors are found, which fails the CI build.
Best Practices
Section titled “Best Practices”-
Fix errors as they appear: Don’t let validation errors accumulate. Fix them immediately when they appear in
npm run dev. -
Run validation before committing: Run
npm run validatebefore pushing changes to catch errors early. -
Use descriptive IDs: Clear node IDs, quest IDs, and dialogue IDs make validation errors easier to understand.
-
Keep dialogue files small: Smaller files make it easier to locate errors. Split large dialogues into multiple files.
-
Use locale keys consistently: Follow a naming convention for locale keys (e.g.,
entity_type.entity_id.field) to make missing keys easier to spot.
Limitations
Section titled “Limitations”Validation catches structural errors but not logic errors:
✅ Catches:
- Missing nodes
- Missing required arguments
- Non-existent references
❌ Doesn’t catch:
- Dialogue that doesn’t make narrative sense
- Incorrectly set flags (e.g., setting
quest_completedtoo early) - Logic that creates softlocks or dead ends
- Typos in text content
You still need to playtest your game to catch logic and narrative issues.