npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@world-forge/export-godot

v4.9.1

Published

Godot 4 export pipeline for World Forge — zones, entities, items, dialogues, navigation, and .tscn scene generation.

Readme

@world-forge/export-godot

Godot 4 export pipeline for World Forge — converts a WorldProject into a structured content pack with .tscn scene generation.

buildWorldScene() emits a single playable .tscn — not a metadata graph — that opens navigable in the Godot 4 editor. Walkable interiors are open (walls come from per-cell collision); a CharacterBody2D pawn (with a following Camera2D and move script) sits at the default spawn; entities are Node2D + ColorRect/Label placeholders and items/props are Marker2D gizmos so a clean project does not need PackedScenes this pack does not ship. CLI --out is a loadable Godot 4 project (project.godot + res:// layout).

What lands in the scene

World (Node2D, y_sort_enabled)
├── Player (CharacterBody2D) — pawn at the default spawn; Camera2D child follows
│   ├── CollisionShape2D
│   ├── Camera2D
│   └── Body (Polygon2D)
├── <TileLayer> (TileMapLayer) — image layers bake tile_map_data; color-only emit ColorRect cells
├── <ZoneName> (Node2D) — at zone origin, y_sort_enabled, z_index from elevation
│   ├── Collision (StaticBody2D) — only when collisionType is void/hazard
│   │   └── CollisionShape2D — RectangleShape2D covering the zone bounds
│   ├── Navigation (NavigationRegion2D) — walkable interiors only; skipped for void/hazard
│   ├── Entities/ (Node2D)
│   │   └── <EntityName> (Node2D) — ColorRect + Label placeholder; sceneTemplate in metadata
│   ├── Items/ (Node2D)
│   │   └── <ItemName> (Marker2D) — ExtResource item/loot .tres + metadata
│   ├── SpawnPoints/ (Node2D)
│   │   └── <SpawnName> (Marker2D) — extra spawns; default is the pawn
│   └── Transitions/ (Node2D)
│       └── <TransitionName> (Area2D + CollisionShape2D trigger)
└── NavigationLinks/ (Node2D)

Color-only layers (no tileset imagePath — the editor/renderer-2d default) emit ColorRect children from the wall #555555 / water #2244aa / door #886622 / floor #333333 tag palette (plus TileDefinition.opacity); without that generated-atlas / ColorRect fallback Godot's TileMapLayer would be empty while the editor still paints tagged rects.

  • Per zone — a Node2D with a NavigationRegion2D navmesh (walkable interiors are not filled with a StaticBody2D — walls come from per-cell collision; void/hazard zones still get a solid hull), y_sort_enabled, and a z_index from its stratum band (+ elevation). A CharacterBody2D player pawn at the default spawn carries a following Camera2D.
  • Tiles — TileMapLayer + TileSet (image tilesets bake tile_map_data cells; color-only layers emit ColorRect children so the map is visible without an atlas), with per-cell wall StaticBody2D collision for non-walkable tiles.
  • Props — a Props container of Marker2D gizmos.
  • Town — Markets / CraftingStations (Marker2D), plus Buildings (StaticBody2D footprints with a CollisionShape2D), Hubs, and Strongholds.
  • World modeling — Strata + StratumLinks containers (zones carry stratum_id + a z_index band so layers sort), Hazards as Area2D regions (effects in metadata, read on body_entered), and entry-gate metadata on gated zones (entry_gate / entry_gate_mode / entry_gate_reason).
  • Content — entities (ColorRect + Label), items (Marker2D), navigation links, loot tables, spawn markers, transition nodes, dialogue resources, asset bindings, and district groupings.
  • Fidelity report — structured tracking of lossless / approximated / dropped data, grouped by domain.

Every node is a self-contained engine primitive — entities carry a ColorRect + Label (PackedScene paths live in metadata), items and props are Marker2D gizmos, transitions are Area2D placeholders, so a clean Godot project loads the export with no missing PackedScene ExtResources. Image-backed tilesets still declare Texture2D ext_resources for authored tileset files.

ai-rpg-stage's play camera is not this pawn

The CharacterBody2D pawn, its following Camera2D, and scripts/player.gd above exist so a clean Godot project opens this export and walks around in it. They are a cartesian join graph — the same grid the editor paints on. The ai-rpg-stage client does something else: it hides the pawn and draws its own 2:1 dimetric harbour from pack.json.presentation, binding sprites to 256x128 diamond cells that were measured on the stage, not computed here.

So the two never argue, keep the grids apart. Never derive a dimetric cell from EntityPlacement.gridX/gridY — the cartesian grid is an authoring convenience that a sandbox scale may multiply, and it says nothing about where a sprite stands on a diamond. The authored presentation block is the only source for dimetric cells; the sim's zone id is the only source for which room someone is in; and this exporter converts between none of them. It carries presentation through untouched and reports presentationAdvisories on warnings[] when the two records disagree. player.gd is unchanged by any of this and should stay that way.

CLI

npx @world-forge/export-godot --help
npx world-forge-export-godot project.json --out ./GodotPack
npx world-forge-export-godot project.json --validate-only
npx world-forge-export-godot project.json --out ./GodotPack --no-world-tscn

--out requires a path that does not start with -. Writes a Godot 4 project root: project.godot (run/main_scene="res://world.tscn"), world.tscn, .tres files at the res://-stripped resourcePath (world_data/…), copied authored textures under assets/, scripts/player.gd, plus pack.json and fidelity.json alongside. Exit 1 on GodotExportError or write failure, with path + message + a fix hint.

Usage

exportToGodot returns a discriminated union (GodotExportResult | GodotExportError). Narrow on success before reading contentPack. Options includeWorldTscn and sceneUidPrefix live on GodotExportOptions.

import {
  exportToGodot,
  migrateGodotPack,
  isMigrationError,
  GODOT_PACK_FORMAT_VERSION,
} from '@world-forge/export-godot';

const result = exportToGodot(project, {
  includeWorldTscn: true,
  sceneUidPrefix: 'wf',
});

if (!result.success) {
  for (const e of result.errors) {
    console.error(`[${e.path}] ${e.message}`);
  }
  throw new Error('Godot export blocked until the project validates');
}

const { contentPack, warnings, fidelity } = result;
// write contentPack.worldSceneTscn (the playable .tscn) and contentPack.files
// (each stamped resourcePath → .tres body) onto disk, or hand them to a loader.
void contentPack.worldSceneTscn;
void contentPack.files;
for (const w of warnings) console.warn(w);
if (fidelity.summary.incomplete) {
  console.warn('Pack is incomplete — inspect fidelity.entries for dropped/approximated data.');
}

const migrated = migrateGodotPack(contentPack, GODOT_PACK_FORMAT_VERSION);
if (isMigrationError(migrated)) {
  // MALFORMED_VERSION — pack.meta.formatVersion is not N.N.N
  // UNKNOWN_MAJOR    — different major than the loader; re-export with a compatible exporter
  // NO_PATH          — no migration chain from this version to the loader target; re-export
  throw new Error(`${migrated.code}: ${migrated.message}`);
}

Format Version

GODOT_PACK_FORMAT_VERSION — currently interpolated from the exporter constant (1.1.0 as of this writing; import GODOT_PACK_FORMAT_VERSION rather than hard-coding 1.0.0).

Bump rules (keep in sync with migrations.ts):

  • Major — required field added/removed, or field semantics change in a way a loader must see.
  • Minor — optional field added. Old loaders ignore it; new loaders may read it.
  • Patch — clarifications, doc-only changes.

When the pack shape changes, bump the constant and add a migration in src/migrations.ts. migrateGodotPack() walks that chain. 1.1.0 added files (each stamped resourcePath → .tres body) and zoneGates on the JSON pack so a data-driven loader does not need to parse the .tscn. isMigrationError() narrows MALFORMED_VERSION / UNKNOWN_MAJOR / NO_PATH; those loaders should re-export rather than guess.

Pass includeWorldTscn: false on GodotExportOptions to skip scene generation when only the JSON pack is needed.

License

MIT