World structure
Core and worlds are kept apart the way an engine is kept apart from the themes and plugins built on it. The core never names a place, a person or a word of any world.
Files
content/<world>/ theme.json # manifest: version, requires_core, hook paths <world>.live.json # the world file: locations, residents, threads drives/ economy.json objects.json sheet.json addons/<id>.json # add-on manifests content/calendar/<world>.json # extras content/news/<world>.json content/visitors/<world>.json content/errands/<world>.json
theme.json
{
"name": "copper-bend",
"version": "0.1.0",
"requires_core": ">=0.2.0 <1",
"drives": "drives/",
"economy": "economy.json",
"text": { "paper_name": "The Bend Gazette" },
"tells": { "words": ["..."] }
}
A theme applies only to the world whose id and name match. There is no shared default: a world with no theme gets none of the hooks.
Add-ons
{ "id": "river-trail", "name": "River Trail", "version": "1.0.0",
"requires_core": ">=0.2.0 <1",
"requires_world": { "id": "copper-bend", "range": ">=0.1.0" },
"provides": ["content/copper-bend/drives/guide.json"] }
The file name must be <id>.json and every provides path must exist. Add-ons are declarative: they version and own files the theme and extras already load.
Release order
1. Core releases a version (semver; a removal is deprecated one minor first).
2. Each world raises its requires_core when it wants the new version.
3. The build gate refuses any core + world + add-on set whose ranges don't meet.
Rule of thumb
If it names a place, a person or a word of the world, it belongs in the theme, not in core.