Skip to content

AI RPG Export & Import Pipeline

The @world-forge/export-ai-rpg package converts a WorldProject into a set of JSON files that ai-rpg-engine can load directly.

  1. ValidatevalidateProject() runs 89 structural checks. If any fail, export aborts with error details.
  2. Convert zonesZone[] becomes ZoneDefinition[] with description as TextBlock, exits, neighbors, hazards.
  3. Convert districtsDistrict[] becomes DistrictDefinition[] with safety mapped to surveillance.
  4. Convert entitiesEntityPlacement[] becomes EntityBlueprint[] with role-based defaults, authored stats/resources/AI.
  5. Convert itemsItemPlacement[] becomes ItemDefinition[] with slot, rarity, modifiers, provenance.
  6. Convert dialoguesDialogueDefinition[] passes through to engine’s matching type.
  7. Convert player templatePlayerTemplate becomes ExportedPlayerTemplate with stats, inventory, equipment, spawn.
  8. Convert build catalogBuildCatalogDefinition becomes ExportedBuildCatalog with archetypes, backgrounds, traits, disciplines.
  9. Convert progression treesProgressionTreeDefinition[] maps nodes with requirements and effects.
  10. Build manifest — game ID, title, modules, content pack references.
  11. Build pack metadata — genres, tones, difficulty, narrator tone, authoring mode tag.
  12. Collect warnings — missing player template, build catalog, progression trees, landmarks, factions, hotspots.
  13. Collect assets — asset manifest and zone/entity/item/landmark bindings are attached to the ExportResult for round-trip preservation.
  14. Collect asset packs — asset pack definitions are attached to the ExportResult when present.

The export produces a ContentPack with all authored domains plus manifest and metadata:

type ContentPack = {
_debug?: ExportDebugBlock; // only with --profile debug
schemaVersion?: string; // default-on; --no-emit-schema-version strips it
entities: EntityBlueprint[];
placements: ExportedPlacement[]; // where entities stand (not on the blueprint)
zones: ExportedZone[]; // includes entryGate, not bare ZoneDefinition
districts: DistrictDefinition[];
dialogues: DialogueDefinition[];
items: ItemDefinition[];
playerTemplate?: ExportedPlayerTemplate;
buildCatalog?: ExportedBuildCatalog;
progressionTrees: ProgressionTreeDefinition[];
encounterAnchors: EncounterAnchor[];
factionPresences: FactionPresence[];
pressureHotspots: PressureHotspot[];
hazardDefinitions: HazardDefinition[];
lootTables: LootTable[];
craftingStations: CraftingStation[];
marketNodes: MarketNode[];
};
Terminal window
# Export to directory
npx world-forge-export project.json --out ./my-pack
# Validate only (no output files)
npx world-forge-export project.json --validate-only
# Dry-run: validate + report sizes, never write (--out is mutually exclusive)
npx world-forge-export project.json --dry-run
# Debug profile (adds _debug block, keeps every fidelity entry)
npx world-forge-export project.json --out ./my-pack --profile debug
# Strip ContentPack.schemaVersion (default is to emit it)
npx world-forge-export project.json --out ./my-pack --no-emit-schema-version
# Verbose output (detailed conversion log)
npx world-forge-export project.json --out ./my-pack --verbose
# Import a pack directory back to WorldProject JSON
npx world-forge-export --import ./my-pack --out ./round-trip
# Import content-pack.json + pack-meta.json + manifest.json (and sidecars)
npx world-forge-export --from-pack ./my-pack --out ./round-trip
import { exportToEngine } from '@world-forge/export-ai-rpg';
const result = exportToEngine(myProject);
if (!result.success) {
// Validation failed
console.error(result.errors);
} else {
// Success
const { contentPack, manifest, packMeta, warnings, assets, assetBindings, assetPacks } = result;
}

Role-based defaults are applied when the author hasn’t specified values:

Role Engine Type Default AI Default Tags
npc npc passive
enemy enemy aggressive hostile
merchant npc passive merchant, trader
companion npc follower recruitable, companion
boss enemy territorial hostile, boss, elite

Authored values always override defaults. For example, if you set ai.profileId: 'aggressive' on a boss, it uses that instead of the default 'territorial'.

World Forge can import exported JSON back into the editor. The import pipeline reverses the export process with 8 converters:

Converter Input Output
importZones ZoneDefinition[] Zone[]
importDistricts DistrictDefinition[] District[]
importEntities EntityBlueprint[] EntityPlacement[]
importItems ItemDefinition[] ItemPlacement[]
importDialogues DialogueDefinition[] DialogueDefinition[]
importPlayerTemplate ExportedPlayerTemplate PlayerTemplate
importBuildCatalog ExportedBuildCatalog BuildCatalogDefinition
importProgressionTrees ProgressionTreeDefinition[] ProgressionTreeDefinition[]

The importProject() function auto-detects the input format (WorldProject, ExportResult, or ContentPack) and orchestrates all converters.

import { importProject } from '@world-forge/export-ai-rpg';
const result = importProject(jsonString);
if (result.success) {
const { project, format, lossless, fidelityReport } = result;
}
  • WorldProject — lossless round-trip, no conversion needed
  • ExportResult{ contentPack, manifest, packMeta, assets, assetBindings } from exportToEngine()
  • ContentPack — engine content without manifest/metadata wrapper
  • ProjectBundle — portable .wfproject.json file exported from the editor (lossless)

An exporter that runs is not the same thing as a world that boots. v4.6.0 makes the difference measurable instead of assumed.

docs/c0-alignment/ holds a generated, checked-in export table: a leaf-path differ walks every authored field in a fixture project, exports it, and records which fields actually arrive in the ContentPack — lossless, approximated, or dropped. It is regenerated and verified on every test run, so a converter that silently stops carrying a field fails a test rather than going unnoticed.

This exists because the alternative had already happened. Two required fields — craftingStations and marketNodes — were dropped by the exporter with no warning and no fidelity entry, and returned as empty arrays on import, so a round trip erased authored town economy in both directions without a single failing test.

The emitted manifest carries a real engine semver range, real module ids, a content hash over the simulation-affecting content, and compiled exit conditions — values that were previously nominal.

Module ids are gated on real content. A pack with no crafting stations no longer declares the crafting module active. Claiming a module that has nothing to act on is worse than dropping the content quietly, because it tells the runtime to expect something that is not there.

Per-entity placements with compiled spawn conditions, typed hazards, entry gates, and scene descriptors all reach the engine’s content pack — not just the schema.

Every import produces a structured FidelityReport that tracks exactly what happened to each piece of data during conversion. Each entry has:

  • levellossless, approximated, or dropped
  • domain — which system was affected (zones, districts, entities, items, etc.)
  • severityinfo, warning, or error
  • reason — machine-stable key for programmatic use

Common fidelity entries:

Reason Key Level Description
grid-auto-generated approximated Zone grid positions auto-generated (engine doesn’t store spatial layout)
surveillance-to-safety approximated District safety reverse-mapped from engine’s surveillance metric
economy-data-lost dropped District economy profile not stored in engine format
zone-placement-round-robin approximated Entities assigned to zones via round-robin (original zones unknown)
role-reverse-mapped approximated Entity role inferred from engine tags
textblock-to-string approximated Dialogue text normalized from TextBlock arrays to strings
visual-layers-dropped dropped Visual layers (tiles, props, ambient) not stored in engine format
assets-recovered lossless Asset manifest and bindings restored from ExportResult
asset-packs-recovered lossless Asset packs restored from ExportResult
assets-dropped dropped Assets not available in bare ContentPack format
asset-packs-dropped dropped Asset packs not available in bare ContentPack format
mode-inferred approximated Authoring mode inferred from connection kinds and grid area

The report includes a summary with overall lossless percentage and per-domain breakdowns, displayed in the editor’s Import Summary panel.

The export pipeline stores the project’s authoring mode as a mode:<value> tag in PackMetadata. On import:

  • ExportResult — mode is recovered from the mode: tag in packMeta.tags (lossless)
  • ContentPack / pre-mode projectsinferMode() uses heuristics to recover the likely mode:
    • channel or route connections → ocean
    • warp or docking connections → space
    • trail connections with camp/wild zone tags → wilderness
    • Grid area ≤ 400 → interior
    • Grid area ≥ 4000 → world
    • Fallback → dungeon

Inferred modes generate a mode-inferred fidelity entry at the approximated level.

The dogfood/ directory contains a full export test using the Chapel Threshold fixture — 5 zones, 2 districts, 4 entities, 3 items, 1 dialogue, 1 player template, 1 build catalog, 2 progression trees. Running npx tsx dogfood/chapel-threshold.ts exports the fixture and performs a gap analysis against engine expectations. As of v1.2, the gap analysis reports zero gaps — full engine handshake.