AgentSkillsCN

Test Campaign Scaffolder

测试战役脚手架

SKILL.md

Test Campaign Scaffolder Skill

RPG Pipeline Only — This skill creates actual data files (JSON, .char, .dtree, .worldmap.json) from a CRPG engine campaign specification. It generates characters with RPG stats, dialogue trees with skill checks, quest files with objectives, encounter files with enemy composition, and worldmaps with interior grid layouts. For non-RPG games, the content-architect should create simpler data files suited to the game's needs. The spec-driven scaffolding workflow pattern applies broadly.

Workflow Context

FieldValue
Assigned Agentcontent-architect
Sprint PhasePhase B (Implementation) — used for bulk file generation from specs
Directory Scopedata/ (all subdirectories: campaigns, world, characters, dialogue, encounters, quests)
GenreRPG / CRPG only
Schema DependencyRequires all CRPG engine schemas (character, quest, dialogue, encounter)
PrerequisiteRequires a spec file from test-campaign-generator in docs/test-campaigns/
Workflow ReferenceSee docs/agent-team-workflow.md

This skill CREATES ACTUAL DATA FILES from a test campaign specification. It reads a spec document from docs/test-campaigns/ and generates all the JSON, .char, .dtree, and worldmap files needed, including full interior grid layouts with NPC placements.


CRITICAL: What This Skill Does and Does NOT Do

code
+------------------------------------------+------------------------------------------+
|              THIS SKILL DOES             |          THIS SKILL DOES NOT DO         |
+------------------------------------------+------------------------------------------+
| Create JSON files in data/               | Create specification documents           |
| Create .char files for characters        | Design campaign content                  |
| Create .dtree files for dialogues        | Ask about tiers, themes, or requirements |
| Create .worldmap.json files              | Save anything to docs/test-campaigns/    |
| Generate interior grid layouts           | Make creative decisions about content    |
| Generate NPC placements on grids         | Generate content from scratch            |
| Generate terrain cells and structures    | Add NPCs to game_hud.gd (LEGACY)         |
| Update main.gd DEFAULT_CAMPAIGN          |                                          |
+------------------------------------------+------------------------------------------+

This skill REQUIRES a specification file from test-campaign-generator to exist first.

IMPORTANT: This skill does NOT modify game_hud.gd. NPCs are placed on interior grids via interior.definition.npc_placements, not the legacy GameHUD NPC array.


Two-Step Workflow

code
┌─────────────────────────────────────────────────────────────────────────────┐
│                         TEST CAMPAIGN CREATION WORKFLOW                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   STEP 1: test-campaign-generator (PREREQUISITE SKILL)                       │
│   ─────────────────────────────────────────────────────                       │
│   Input:  User requirements (tier, theme, focus areas)                       │
│   Output: docs/test-campaigns/[name].md (SPECIFICATION ONLY)                 │
│                                                                              │
│                              ↓                                               │
│                                                                              │
│   STEP 2: test-campaign-scaffolder (THIS SKILL)                              │
│   ─────────────────────────────────────────────────                          │
│   Input:  The spec file from Step 1                                          │
│   Output: All JSON/data files in data/ directory                             │
│           - data/campaigns/[id].json                                         │
│           - data/world/[id].worldmap.json (with interior definitions)        │
│           - data/characters/npcs/[id].char                                   │
│           - data/characters/enemies/[id].char                                │
│           - data/dialogue/[id].dtree                                         │
│           - data/encounters/[id].json                                        │
│           - data/quests/[id].json                                            │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

When to Use This Skill

Invoke this skill when the user:

  • Has a test campaign spec and wants to generate the actual data files
  • Says "scaffold the test campaign" or "generate files for test campaign"
  • Says "create the files from the spec"
  • Asks to "implement the test campaign spec"
  • Says "yes" when asked if they want to scaffold after running test-campaign-generator

DO NOT use this skill if:

  • No spec file exists in docs/test-campaigns/
  • User is still designing the campaign content
  • User wants to modify the spec (use test-campaign-generator instead)

Prerequisites

Before running this skill:

  1. A specification file MUST exist at docs/test-campaigns/[campaign-name].md
  2. The spec MUST have been generated by test-campaign-generator
  3. The spec MUST contain Content Manifest tables with IDs
  4. The spec SHOULD contain Interior Layout sections for locations with designed interiors

Spec Sources Supported:

  • Tier-based specs (Tier 1-4)
  • D&D/Tabletop module imports (Tier 5)
  • Custom scope specs
  • Editor focus specs

D&D Module Specs: When scaffolding D&D-sourced specs, this skill applies the standard conversion rules (stats × 5, CR × 5 for levels, etc.) as defined in the spec.

If no spec exists, tell the user:

"No specification file found. Please run test-campaign-generator first to create a campaign spec, then I can scaffold the files from it."


Input

A test campaign specification file from docs/test-campaigns/[name].md

If multiple specs exist, ask the user which one to scaffold.


Output

Files Generated

code
data/
├── campaigns/
│   └── [campaign_id].json           # Campaign config file
├── world/
│   └── [campaign_id].worldmap.json  # Worldmap with locations, routes, AND interior definitions
├── characters/
│   ├── enemies/
│   │   └── [enemy_id].char          # Enemy character files
│   └── npcs/
│       └── [npc_id].char            # NPC character files
├── dialogue/
│   └── [dialogue_id].dtree          # Dialogue tree files
├── encounters/
│   └── [encounter_id].json          # Encounter files
└── quests/
    └── [quest_id].json              # Quest files

Code Modifications

This skill may:

  • Update scenes/main.gd DEFAULT_CAMPAIGN constant (optional, ask user)

This skill does NOT modify:

  • crpg_engine/ui/game_hud.gd - NPCs are placed via interior grids, not GameHUD

File Schemas

Campaign File (.json)

json
{
  "id": "[campaign_id]",
  "name": "[Campaign Name]",
  "description": "[Campaign description]",
  "version": 1,
  "author": "Test Campaign Scaffolder",
  "created_date": "[date]",
  "player_character": "player",
  "worldmap": "[campaign_id]",
  "starting_location": "[location_id]",
  "starting_locations": ["[location_id]"],
  "starting_npcs": ["[npc_id]"],
  "initial_state": {
    "gold": 0,
    "items": [],
    "flags": ["game_started"],
    "variables": {}
  },
  "chapters": [...],
  "endings": [...],
  "_metadata": {...}
}

Worldmap File (.worldmap.json) - WITH INTERIOR DEFINITIONS

json
{
  "name": "[Worldmap Name]",
  "description": "[Description]",
  "author": "Test Campaign Scaffolder",
  "version": "1.0",
  "file_version": 1,
  "map_data": {...},
  "editor_state": {...},
  "regions": [...],
  "locations": [
    {
      "id": "[location_id]",
      "name": "[Location Name]",
      "type": "[settlement/dungeon/wilderness]",
      "region": "[region_id]",
      "description": "[Description]",
      "position": {"x": 0, "y": 0},
      "map_scene": "",
      "custom_data": {
        "safe_zone": true,
        "services": [],
        "npcs": [
          {"name": "[NPC Name]", "id": "[npc_id]", "conditional": false}
        ],
        "hostile": false,
        "encounter": "[encounter_id]"
      },
      "interior": {
        "mode": 1,
        "template": "",
        "seed": 0,
        "definition": {
          "grid_width": 12,
          "grid_height": 10,
          "cells": [1,1,1,1,1,1,...],
          "structures": [
            {
              "id": "struct_0",
              "type": 8,
              "position": {"x": 6, "y": 5},
              "rotation": 0,
              "custom_data": {}
            }
          ],
          "paths": [],
          "zones": [
            {
              "id": "zone_entrance",
              "type": 1,
              "rect": {"x": 5, "y": 8, "w": 3, "h": 2},
              "custom_data": {}
            },
            {
              "id": "zone_combat",
              "type": 9,
              "rect": {"x": 2, "y": 3, "w": 4, "h": 2},
              "custom_data": {
                "encounter_id": "[encounter_id]",
                "required_flag": "[flag_name]"
              }
            }
          ],
          "npc_placements": [
            {
              "npc_id": "[npc_id]",
              "position": {"x": 5, "y": 3},
              "facing": "down",
              "patrol_path": [],
              "conditions": {}
            }
          ],
          "interactables": [],
          "spawn_points": [
            {
              "id": "default",
              "position": {"x": 6, "y": 9},
              "from_location": ""
            }
          ],
          "template_id": "",
          "generation_seed": 0,
          "created_at": "[date]",
          "modified_at": "[date]"
        }
      }
    }
  ],
  "routes": [...]
}

Interior Cell Types

When generating the cells array, use these values:

ValueTypeDescription
0EMPTYNon-walkable empty space
1FLOORBasic walkable floor
2WALLNon-walkable wall
3DOORWalkable door cell
4PATHWalkable path/road
5WATERNon-walkable water
6GRASSWalkable grass terrain
7ROADWalkable road

Interior Structure Types

When placing structures, use these values:

ValueTypeDescription
0NONENo structure
1BUILDINGBuilding structure
2TENTTent
3STALLMarket stall
4WELLWell
5STATUEStatue
6TREETree
7ROCKRock
8CAMPFIRECampfire
9SHRINEShrine
10CHESTChest
11SIGNSign

Interior Zone Types

When defining zones, use these values:

ValueTypeDescription
0NONENo special zone
1ENTRANCEEntry point zone
2EXITExit point zone
3SAFESafe zone
4DANGERDanger zone
5SHOPShop area
6RESTRest area
7QUESTQuest trigger zone
8SECRETSecret area
9COMBATCombat arena

Combat Zone Setup (CRITICAL)

When creating COMBAT zones (type 9), the custom_data MUST include:

json
{
  "id": "zone_combat",
  "type": 9,
  "rect": {"x": 3, "y": 3, "w": 4, "h": 2},
  "custom_data": {
    "name": "ambush_zone",
    "encounter_id": "enc_example_encounter",
    "required_flag": "quest_accepted"
  }
}
FieldRequiredDescription
encounter_idYESThe encounter ID to trigger when player enters this zone
required_flagOptionalFlag that must be set before encounter triggers

⚠️ IMPORTANT: The encounter trigger system (main._on_zone_entered()) reads encounter_id and required_flag from the zone's custom_data, NOT from the location's custom_data. Without these fields in the zone, the encounter will never trigger!

Character File (.char)

json
{
  "version": 1,
  "id": "[character_id]",
  "identity": {
    "display_name": "[Display Name]",
    "titles": [],
    "aliases": []
  },
  "classification": {
    "role": 3,
    "factions": ["[Faction]"],
    "tier": 1,
    "tags": ["tag1", "tag2"]
  },
  "appearance": {
    "portrait": "res://assets/sprites/characters/[category]/[sprite_name]/base/south.png",
    "sprite_project": "res://assets/sprites/characters/[category]/[sprite_name]",
    "description": "[Physical description]",
    "height": "medium",
    "build": "average"
  },
  "personality": {
    "traits": [],
    "ideals": [],
    "bonds": [],
    "flaws": [],
    "background": "[Character background]",
    "voice_style": ""
  },
  "stats": {
    "level": 1,
    "experience": 0,
    "health": 10,
    "max_health": 10,
    "mana": 0,
    "max_mana": 0,
    "attributes": {
      "strength": 10,
      "dexterity": 10,
      "constitution": 10,
      "intelligence": 10,
      "wisdom": 10,
      "charisma": 10
    },
    "skills": {},
    "resistances": {},
    "abilities": ["ability_id_1", "ability_id_2"],
    "equipment": {
      "weapon": "",
      "armor": "",
      "accessories": []
    }
  },
  "relationships": [],
  "notes": {
    "designer_notes": "",
    "quest_notes": "",
    "combat_notes": ""
  },
  "metadata": {
    "created_date": "[date]",
    "modified_date": "[date]",
    "author": "Test Campaign Scaffolder",
    "campaign_id": "[campaign_id]"
  }
}

IMPORTANT Field Names:

  • metadata.campaign_id - NOT campaign (required for Character Creator filtering)
  • appearance.sprite_project - NOT sprite (path to sprite folder WITHOUT /base/)
  • appearance.portrait - Full path to south-facing sprite image

Role Values:

  • 1 = player
  • 2 = npc
  • 3 = enemy
  • 4 = companion

Available Placeholder Sprites:

  • NPCs: quest_givers/village_elder, quest_givers/priest, quest_givers/mysterious_stranger, merchants/merchant, townsfolk/innkeeper, townsfolk/blacksmith, townsfolk/herbalist, guards/guard, guards/guard_captain
  • Enemies: undead/skeleton_warrior, undead/skeleton_archer, undead/ghost, undead/wraith, bandits/bandit_thug, bandits/bandit_archer, bandits/bandit_captain, bosses/bandit_chief, cult/cultist, cult/necromancer, beasts/wolf, demons/imp, military/dark_knight
  • Companions: warrior, mage, rogue, thorne_fighter, lyra_rogue

Available Combat Abilities: Characters MUST have abilities assigned for combat to be interesting. Available abilities in data/combat/abilities/:

Ability IDTypeElementResourceBest For
fireballattackfiremanamages, cultists
ice_lanceattackicemanamages
lightning_boltattacklightningmanamages
chain_lightningattacklightningmanamages (AoE)
healing_wordhealingholymanahealers, priests
mass_healhealingholymanahealers (AoE)
poison_cloudattackpoisonmanaassassins, cultists
divine_shieldbuffholymanapaladins, priests
shield_bashattackphysicalstaminawarriors, guards, bandits
backstabattackphysicalstaminarogues, assassins, bandits
battle_crybuffnonestaminawarriors, leaders
teleportutilityarcanemanamages

Recommended Abilities by Character Type:

  • Bandits/Thugs: ["shield_bash", "backstab"]
  • Bandit Captains: ["shield_bash", "battle_cry"]
  • Mages/Cultists: ["fireball", "ice_lance"] or ["lightning_bolt", "chain_lightning"]
  • Healers/Priests: ["healing_word", "divine_shield"]
  • Warriors/Guards: ["shield_bash", "battle_cry"]
  • Rogues/Assassins: ["backstab", "poison_cloud"]
  • Bosses: 3-4 abilities mixing offense, defense, and utility

Dialogue File (.dtree)

json
{
  "version": 1,
  "metadata": {
    "dialogue_id": "[dialogue_id]",
    "display_name": "[Dialogue Display Name]",
    "description": "[Description]",
    "location_id": "[location_id]",
    "author": "Test Campaign Scaffolder",
    "created_date": "[date]",
    "modified_date": "[date]",
    "campaign_id": "[campaign_id]"
  },
  "canvas": {...},
  "nodes": [...],
  "connections": [...]
}

CRITICAL: Dialogue Choice Node Pattern

⚠️ NEVER use a choices array inside a single Choice node! This causes the "[1]" display bug.

The dialogue runtime's _gather_connected_choices function looks for the text property on individual Choice nodes, NOT a choices array.

✅ CORRECT Pattern - Individual Choice nodes:

json
{
  "nodes": [
    {"id": "Speaker_greeting", "type": "Speaker", "position_x": 300, "position_y": 200,
     "speaker": "npc_example", "text": "Hello traveler, how can I help you?"},

    {"id": "Choice_help", "type": "Choice", "position_x": 100, "position_y": 400,
     "text": "I need your help."},
    {"id": "Choice_info", "type": "Choice", "position_x": 300, "position_y": 400,
     "text": "Tell me about this place."},
    {"id": "Choice_goodbye", "type": "Choice", "position_x": 500, "position_y": 400,
     "text": "Goodbye."}
  ],
  "connections": [
    {"from_node": "Speaker_greeting", "from_port": 0, "to_node": "Choice_help", "to_port": 0},
    {"from_node": "Speaker_greeting", "from_port": 0, "to_node": "Choice_info", "to_port": 0},
    {"from_node": "Speaker_greeting", "from_port": 0, "to_node": "Choice_goodbye", "to_port": 0},
    {"from_node": "Choice_help", "from_port": 0, "to_node": "Speaker_help_response", "to_port": 0},
    {"from_node": "Choice_info", "from_port": 0, "to_node": "Speaker_info_response", "to_port": 0},
    {"from_node": "Choice_goodbye", "from_port": 0, "to_node": "End_1", "to_port": 0}
  ]
}

Key Points:

  1. Each Choice is a separate node with its own text property
  2. The Speaker node connects to multiple Choice nodes (same from_port: 0)
  3. Each Choice node connects to its own response (Speaker or End node)
  4. Choice nodes do NOT have a choices array - they ARE the choice

❌ WRONG Pattern - DO NOT USE:

json
{
  "id": "Choice_options", "type": "Choice",
  "choices": [
    {"text": "Option 1", "next": "Speaker_1"},
    {"text": "Option 2", "next": "Speaker_2"}
  ]
}

This pattern will display "[1]", "[2]" instead of the actual text!

Quest-Dialogue Integration (CRITICAL)

Every quest created MUST have associated dialogues that:

  1. Start the quest via a Quest node with quest_action: "Start"
  2. Complete the quest via a Quest node with quest_action: "Complete"

Quest Node Example:

json
{
  "id": "Quest_start_quest1",
  "type": "Quest",
  "position_x": 300,
  "position_y": 600,
  "quest_id": "quest_example",
  "quest_action": "Start"
}

Dialogue Structure for Quest Givers:

code
Start → Branch (quest_complete?)
        ├── TRUE → Speaker (thanks) → FlagSet → Quest (Complete) → End
        └── FALSE → Speaker (greeting) → Choice nodes → Speaker (quest info) → Quest (Start) → End

Encounter File (.json)

json
{
  "_meta": {
    "created": "[date]",
    "modified": "[date]"
  },
  "id": "[encounter_id]",
  "campaign_id": "[campaign_id]",
  "display_name": "[Encounter Name]",
  "description": "[Description]",
  "encounter_type": "combat",
  "difficulty": "easy",
  "level_range": {"min": 1, "max": 3},
  "location_id": "[location_id]",
  "enemies": [...],
  "victory": {...},
  "defeat": {...},
  "rewards": {...},
  "one_time": true,
  "can_flee": true,
  "scales_with_level": false,
  "flags_set_on_complete": [...],
  "trigger_conditions": {...},
  "grid_settings": {
    "enabled": true
  }
}

IMPORTANT: The grid_settings.enabled: true field is REQUIRED for the tactical grid-based combat system. Without it, encounters will use the legacy simple combat UI.


CRITICAL: campaign_id for Editor Filtering

⚠️ ALL content files MUST include campaign_id at the TOP LEVEL of the JSON for editor filtering to work!

The CRPG Engine editors use CampaignRegistry.matches_active_campaign() to filter content by the currently selected campaign in the Campaign Selector dropdown. The editors read campaign_id from the ROOT level of the JSON, NOT from inside _metadata or _meta.

Correct Placement (TOP LEVEL - Required):

json
{
  "_metadata": { ... },
  "id": "quest_example",
  "campaign_id": "doip",     // <-- MUST be at top level!
  "name": "Example Quest",
  ...
}

Wrong Placement (inside _metadata - NOT read by editors):

json
{
  "_metadata": {
    "campaign_id": "doip"    // <-- Editors do NOT read this!
  },
  "id": "quest_example",
  ...
}
File TypeRequired LocationExample
Quest (.json)Top-level campaign_id"campaign_id": "doip" after "id"
Encounter (.json)Top-level campaign_id"campaign_id": "doip" after "id"
Character (.char)metadata.campaign_idInside metadata object
Dialogue (.dtree)metadata.campaign_idInside metadata object

If content doesn't appear in editors:

  1. Check that the file has campaign_id AT THE TOP LEVEL (not just in _metadata)
  2. Ensure the Campaign Selector dropdown is set to the correct campaign
  3. Verify the campaign_id value matches the campaign's id field exactly

Quest File (.json)

json
{
  "_metadata": {
    "author": "Test Campaign Scaffolder",
    "last_modified": "[date]",
    "validation_status": "not_validated",
    "version": 1
  },
  "id": "[quest_id]",
  "campaign_id": "[campaign_id]",
  "name": "[Quest Name]",
  "display_name": "[Quest Display Name]",
  "type": "side_quest",
  "quest_type": "side",
  "difficulty": "easy",
  "recommended_level": 1,
  "quest_giver_id": "[npc_id]",
  "starting_location_id": "[location_id]",
  "description": {...},
  "designer_notes": "[Notes]",
  "tags": [...],
  "objectives": [...],
  "objective_flow": "linear",
  "prerequisites": {...},
  "outcomes": [...],
  "rewards": {...},
  "dialogues": [...],
  "journal": {...}
}

Workflow

Step 1: Find the Spec File

  1. Look in docs/test-campaigns/ for spec files
  2. If user specified a name, find that spec
  3. If multiple specs exist, ask which one to scaffold
  4. If no spec exists, tell user to run test-campaign-generator first

Step 2: Parse the Spec

  1. Read the markdown spec file
  2. Extract all content from the tables:
    • Locations (ID, Name, Type, Connections, Interior Mode, Grid Size, NPCs)
    • NPCs (ID, Name, Role, Location, Grid Position, Dialogue)
    • Enemies (ID, Name, Type, Level)
    • Quests (ID, Name, Giver, Objectives)
    • Dialogues (ID, Speaker, Quest Links)
    • Encounters (ID, Name, Enemies, Quest Link)
    • Campaign Config (Starting Location, NPCs, Chapters)
    • Interior Layouts (Structures, Zones, Spawn Points)

Step 3: Generate Files in Dependency Order

Create files in this order (respecting dependencies):

  1. Enemy character files (no dependencies)
  2. NPC character files (no dependencies)
  3. Encounter files (depends on enemies)
  4. Dialogue files (depends on NPCs, quests)
  5. Quest files (depends on NPCs, encounters, dialogues)
  6. Worldmap file with interior definitions (depends on locations, NPCs, encounters)
  7. Campaign file (depends on worldmap, quests, NPCs)

Step 4: Generate Interior Grids

For each location with interior_mode: designed:

  1. Generate cells array:

    • Create array of size grid_width * grid_height
    • Fill with appropriate terrain type based on theme
    • Use FLOOR (1) or GRASS (6) for walkable areas
    • Mark walls/boundaries as WALL (2) or EMPTY (0)
  2. Place structures:

    • Read structure placements from spec
    • Convert structure type names to enum values
    • Add to structures array
  3. Create NPC placements:

    • For each NPC assigned to this location
    • Read grid position from spec (or auto-assign if not specified)
    • Add to npc_placements array with facing direction
  4. Define zones:

    • Read zone definitions from spec
    • Add entrance zone at spawn point
    • Add any shop/quest/danger zones specified
  5. Set spawn point:

    • Read from spec or default to south-center of grid
    • Add to spawn_points array

Step 5: Ask About Code Modifications

Ask user if they want to update scenes/main.gd DEFAULT_CAMPAIGN.

DO NOT modify crpg_engine/ui/game_hud.gd - this is legacy code.

Step 6: Generate Report

Create a summary of what was created:

markdown
## Scaffolding Complete

### Files Created
- `data/campaigns/[id].json`
- `data/world/[id].worldmap.json` (includes interior definitions)
- `data/characters/npcs/[npc].char`
- `data/characters/enemies/[enemy].char`
- `data/dialogue/[dlg].dtree`
- `data/encounters/[enc].json`
- `data/quests/[quest].json`

### Interior Grids Generated
| Location | Grid Size | Structures | NPCs Placed | Spawn Point |
|----------|-----------|------------|-------------|-------------|
| [loc_id] | 12x10 | 3 | 2 | (6, 9) |

### Code Modified
- `scenes/main.gd` - Updated DEFAULT_CAMPAIGN (if requested)

### Next Steps
1. Run the game (F5) to test
2. Verify NPCs appear on location grids at correct positions
3. Test click-to-move and WASD movement
4. Test NPC interactions and dialogue
5. Test quest flow
6. Validate with Entity Editor

Interior Grid Generation Details

Basic Floor Layout

For a simple interior, generate cells like this:

gdscript
# Example: 10x8 floor grid with grass border
func generate_floor_grid(width: int, height: int) -> Array[int]:
    var cells: Array[int] = []
    cells.resize(width * height)

    for y in range(height):
        for x in range(width):
            var idx = y * width + x
            # Border is grass, interior is floor
            if x == 0 or x == width - 1 or y == 0 or y == height - 1:
                cells[idx] = 6  # GRASS
            else:
                cells[idx] = 1  # FLOOR

    return cells

Terrain Themes

Based on location type, use appropriate terrain:

Location TypePrimary TerrainBorder/Edge
settlementFLOOR (1)GRASS (6)
tavernFLOOR (1)WALL (2)
campGRASS (6)GRASS (6)
dungeonFLOOR (1)WALL (2)
wildernessGRASS (6)GRASS (6)
roadPATH (4) or ROAD (7)GRASS (6)

NPC Placement Rules

  1. Always place on walkable terrain - Verify cell type is walkable
  2. Avoid spawn point - Don't place NPCs where player spawns
  3. Avoid structures - Don't overlap with structure positions
  4. Use spec positions - If spec provides (x, y), use those
  5. Auto-assign if needed - If no position in spec, find valid walkable cell

Example Invocations

Standard Campaigns:

  • "Scaffold the haunted mine test campaign"
  • "Generate files from the rat cellar spec"
  • "Create the data files for the test campaign"
  • "Yes, scaffold the files" (after test-campaign-generator)
  • "Implement the tier 2 village spec"

D&D Module Imports:

  • "Scaffold the Dragons of Icespire Peak spec"
  • "Generate files from the Lost Mine of Phandelver spec"
  • "Create the CRPG files from my D&D campaign spec"
  • "Scaffold the Phandalin starter campaign"

Integration with Other Skills

SkillRelationship
test-campaign-generatorPREREQUISITE - creates the spec this skill reads
character-creatorCan enhance/edit scaffolded .char files
quest-designerCan enhance/edit scaffolded quest files
dialogue-designerCan enhance/edit scaffolded .dtree files
encounter-designerCan enhance/edit scaffolded encounter files
world-builderCan enhance/edit scaffolded worldmap file
campaign-creatorFinal step after all content is validated
entity-editor validationUse after scaffolding to validate references
error-debuggerUse if generated files have issues
changelog-updaterTrack test campaigns scaffolded

Workflow Paths

Standard Bulk Creation:

code
1. test-campaign-generator → Create specification
2. test-campaign-scaffolder → Generate all files (THIS SKILL)
3. Entity Editor → Validate cross-references
4. Game → Test playthrough

D&D Import Path:

code
1. test-campaign-generator (D&D mode) → Create spec from module
2. Review/edit spec for CRPG adjustments
3. test-campaign-scaffolder → Generate all files (THIS SKILL)
4. Use individual skills for custom enhancement if needed
5. campaign-creator → Finalize campaign

Hybrid Path (Scaffold + Enhance):

code
1. test-campaign-generator → Create basic specification
2. test-campaign-scaffolder → Generate skeleton files (THIS SKILL)
3. Use individual skills to enhance:
   - dialogue-designer → Expand dialogue trees
   - encounter-designer → Tune encounters
   - character-creator → Flesh out NPCs
4. campaign-creator → Assemble final campaign

Error Handling

No Spec Found

code
"No specification file found at docs/test-campaigns/[name].md.
Please run `test-campaign-generator` first to create a campaign spec."

Invalid Spec Format

code
"The spec file is missing required sections. Expected tables for:
- Locations (with Interior Mode and Grid Size columns)
- NPCs (with Grid Position column)
- Enemies
- Quests
- Dialogues
- Encounters

Please regenerate the spec using `test-campaign-generator`."

File Already Exists

code
"File data/quests/[id].json already exists.
Overwrite? (y/n)"

Invalid Grid Position

code
"NPC [npc_id] has grid position (x, y) which is outside grid bounds or on non-walkable terrain.
Auto-assigning valid position..."

Checklist Before Completing This Skill

Before finishing, verify:

  • Spec file existed in docs/test-campaigns/
  • All JSON files created in data/ directory
  • All .char files created for NPCs and enemies
  • All .dtree files created for dialogues
  • Worldmap file created with locations and routes
  • Encounter files include grid_settings.enabled: true for tactical combat
  • Combat zones (type 9) have encounter_id in zone's custom_data (NOT in location's custom_data!)
  • Combat zones have required_flag in custom_data if encounter requires a flag
  • Interior definitions included for locations with designed interiors
  • NPC placements included in interior definitions
  • Cells array generated with appropriate terrain
  • Spawn points defined for each interior
  • Campaign file references correct worldmap
  • Did NOT modify game_hud.gd (legacy system)
  • Asked user about updating DEFAULT_CAMPAIGN
  • Provided summary of files created
  • Suggested next steps (run game, test grid movement, validate)

Character File Field Verification:

  • metadata.campaign_id is set (NOT metadata.campaign)
  • appearance.sprite_project is set (NOT appearance.sprite)
  • appearance.portrait points to valid sprite file
  • Sprite paths do NOT have trailing slashes
  • stats.abilities array is populated with valid ability IDs (NOT empty!)
  • Abilities are appropriate for character type (see Available Combat Abilities table)

Quest File Field Verification:

  • _metadata.campaign_id is set to the campaign's id (CRITICAL for editor filtering!)

Encounter File Field Verification:

  • _meta.campaign_id is set to the campaign's id (CRITICAL for editor filtering!)

Dialogue File Field Verification:

  • metadata.campaign_id is set to the campaign's id (CRITICAL for editor filtering!)
  • Choice nodes use individual text property (NOT a choices array!)
  • Speaker nodes connect to multiple Choice nodes (one connection per choice)
  • Each Choice node connects to its response node (Speaker or End)
  • Quest-giving dialogues have Quest nodes with quest_action: "Start" or "Complete"
  • Branch nodes used for state checking (e.g., quest already complete?)