WorldFormat - JakeJamesDev/formamorph GitHub Wiki
This page describes the .json file of a Formamorph world: the file Export World writes and Import World reads. Use it when you edit a world file by hand or write a tool for one.
๐ก The World Editor writes this format for you. Edit the file by hand only for a change the editor can't make, or for a bulk change.
๐ This page covers world files only. It does not describe save files.
- In the main menu, select the world. In its world dialog, select Export World.
- Open the
.jsonfile in a text editor. Make your changes. - In the main menu, open the Worlds tab and select Import World. Pick the file.
๐ก The World Editor's Overview tab has Export World too.
The import adds a new world. It never replaces the world you exported. Import skips a file that fails to load, and the menu tells you.
- Add an object to the top-level
statslist. - Give it a new
idand aname. Scripts and the AI find a stat by its name, so keep names unique. - Set
typeto"number", or to"percentage"for a 0โ100 stat. - Set
min,max,value(the Base Value) andregen. - Write a
description, and setdescriptorsto[]or to a list of descriptors.
{ "id": "stamina", "name": "Stamina", "type": "number", "description": "Energy for hard work",
"min": 0, "max": 100, "value": 80, "regen": 2, "descriptors": [] }- Add an object to the top-level
traitslist. For a trait that one entity owns, add it to that entity'straitslist. - Give it a new
idand aname. Trait ids must be unique across the world and every entity. - Write
playerDescriptionandaiDescription. - Set
statChangesto[]or to a list of stat changes. - To put it in a group, set
groupIdto the group'sid.
{ "id": "strong", "name": "Strong", "playerDescription": "Above-average strength",
"aiDescription": "This person is unusually strong.", "groupId": "physical",
"statChanges": [{ "statId": "stamina", "value": 10, "type": "max" }] }- Add an object to the top-level
entitieslist. - Give it a new
idand aname. - Write
playerDescription,aiDescriptionand, for a long description,aiSummary. - Set
locationsto the ids of the locations where the entity is. Leave it out for an entity with no fixed place. - To make the entity playable, set
personatotrue. See Persona Marks.
{ "id": "smith", "name": "Mara", "aliases": ["the smith"], "pronouns": "she/her",
"playerDescription": "The village blacksmith.", "aiDescription": "Mara is a gruff, fair blacksmith.",
"locations": ["town"] }| Mark | Meaning |
|---|---|
| โ in Req. | The world types always carry this field. Write it. |
| No mark | Optional. The description says what an absent field means. |
id |
A unique string. The editor writes random UUIDs. Any unique string works. |
Editor-only fields organize the World Editor. The AI never reads them.
| Field | Req. | Type | Meaning |
|---|---|---|---|
formamorphKind |
"world" |
Export writes it. Import does not need it. | |
version |
String | The app version that wrote the file, such as "3.1.2". See Versions and Older Files
|
|
worldOverview |
โ | Object | Name, listing text and world-wide settings. See worldOverview |
stats |
โ | Stat[] | The player's numbers |
locations |
โ | Location[] | Places. A location can nest inside another |
connections |
Connection[] | Travel links between locations. Absent = travel follows nesting only | |
entities |
โ | Entity[] | People, creatures and objects |
entityGroups |
Group[] | Editor-only groups for entities | |
traits |
โ | Trait[] | The world's traits |
traitGroups |
TraitGroup[] | Groups for the world's traits | |
placeholders |
Placeholder[] | The world's shared placeholders | |
placeholderGroups |
Group[] | Editor-only groups for shared placeholders | |
dictionaries |
โ | Dictionary[] | Lore books. Import makes one empty book when the list is absent or empty |
statUpdates |
โ | StatUpdate[] | An older field. Write []
|
scripts |
Script[] | Scripts no stat owns. They run with no self
|
A stored world also has an id. Export leaves it out, and import gives the world a new one.
โ ๏ธ Import refuses a world withoutworldOverview,stats,locations,entities,traitsandstatUpdates. An empty list is enough.
Import runs every world through a migration. The migration changes an older shape into the current one, so a file from any version loads. These older forms still load:
| Older form | Loads as |
|---|---|
No version (before 2.0) |
The current shape |
A flat dictionary list |
One book named "Default" in dictionaries
|
A dictionary key or secondaryKeys as one comma-separated string |
A list of keywords |
An entity image string |
The first item of images
|
An entity images list of strings |
Each string as { "data": โฆ }
|
A location entities list |
Each entity's locations
|
A location connections list of names |
Records in the top-level connections
|
A connection with from, to, twoWay and aiHint
|
A connection with a, b and legs |
A location isStartLocation flag |
isStarting |
Placeholder values as strings, weights keyed by text |
Value records, weights keyed by value id
|
worldOverview.openingCue and openingCueEnabled
|
One Opening Action in openings, and openingsEnabled
|
worldOverview.playerSetting |
allowedPersonas and startPersona
|
A trait group exclusive: true
|
maxPicks: 1 |
| An entity link to a trait outside Blueprints | Removed, with its overrides |
A stat's beforeCode and code
|
Scripts with the timings "before" and "after"
|
A stat with type: "list"
|
A number stat at its min. The items are dropped |
A script that uses stats.find(โฆ)
|
The same code with the stat map |
A Stomach, Fatness or Breastsize stat with no morphBindings
|
The matching body morphs |
A root customPlayerVRM
|
worldOverview.customPlayerVRM |
Entity and location inGameDescription and detailedDescription
|
playerDescription and aiDescription
|
A trait description
|
Both playerDescription and aiDescription
|
โ ๏ธ The last six rows run only whenversionis not the app's version. A file stamped with the current version keeps those older forms as written.
Write new files in the current shape. A file with scripts loads in an older version of the app, but its scripts don't run there.
| Field | Req. | Type | Meaning |
|---|---|---|---|
name |
โ | String | The world's name |
description |
โ | String | The Player-Facing Description: card and listing text. The AI never reads it |
author |
โ | String | Free text that names the author |
thumbnail |
โ |
Image | null
|
The card image |
bgm |
โ |
Audio | null
|
Background music, as a data URL |
systemPrompt |
โ | String | The AI-Facing Description: what the AI reads about the world. Players never see it |
use3DModel |
โ | Boolean | The world shows a 3D avatar |
tags |
โ | String[] | Listing tags |
customPlayerVRM |
MediaAsset | null
|
The world's own player avatar, a .vrm file |
|
readme |
String | Markdown the player sees when play starts | |
introReadme |
String | Markdown the player sees on the first setup screen. It shares the readme's on and off switch | |
promptOverrides |
Object | The world's Custom Prompts. See below. Absent = the player's prompts | |
openings |
Opening[] | The world's own openings, in order | |
openingWeights |
Object | Draw weight per opening id. A missing entry weighs 1. 0 keeps an opening and never draws it |
|
openingsEnabled |
Boolean |
false stops every opening of the world, its locations and its entities from drawing. Absent = on |
|
allowedPersonas |
"any" | "world"
|
Which personas the player can pick. "world" allows only this world's personas and its Custom Persona. Absent = "any"
|
|
startPersona |
Object | The persona a new player starts on: { "source": "none" }, or { "source": "world", "entityId": "<entity id>" }. Absent = the player's default persona |
See Persona Rules for how the last two fields behave.
promptOverrides replaces some of the player's prompts while they play this world:
| Field | Type | Meaning |
|---|---|---|
systemPrompt |
String | Replaces the narration system prompt |
systemPromptEnabled |
Boolean |
false keeps the text and does not use it. Absent = used |
choicesPrompt |
String | Replaces the choices system prompt |
choicesPromptEnabled |
Boolean |
false keeps the text and does not use it |
statUpdatesPrompt |
String | Replaces the stat updates system prompt |
statUpdatesPromptEnabled |
Boolean |
false keeps the text and does not use it |
Each stat is a number the player has. The AI reads it, and a script can calculate it.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
name |
โ | String | The name. Scripts and the AI find the stat by it |
type |
โ |
"number" | "percentage"
|
A percentage stat has min 0 and max 100, and shows as N%. The AI can't change its max |
description |
โ | String | What the stat means |
min |
โ | Number | The lowest value |
max |
โ | Number | The highest value |
value |
Number | The Base Value: what a new game starts at, before traits apply. Absent or 0 = min
|
|
starting |
Number | The value a save gets when it did not have this stat yet. A new game also measures turn one's change from it. Absent = value. Keep it equal to value
|
|
regen |
โ | Number | The change per story hour. Negative drains |
regenUnit |
"minutes" | "hours" | "days" | "weeks"
|
The span the editor shows Regen per. regen stays per hour. Absent = "hours"
|
|
descriptors |
โ | Descriptor[] | Text for each band of values |
thresholdUnit |
"raw" | "percent"
|
What a descriptor's threshold measures. "raw" = the stat's own units. "percent" = a percent of min to max. Absent = "raw". A percentage stat is always percent |
|
scripts |
Script[] | Code with this stat as self. See the Scripts Guide
|
|
morphBindings |
String[] | Body morph names this stat drives. min to max maps to 0 to 1 |
|
enabled |
Boolean |
false starts the stat off. The player and the AI don't see it, and regen and code stop. A trait's statToggles switches it on. Absent = on |
|
hidden |
Boolean |
true hides the stat from the player. The AI still reads it, and regen and code still run |
|
noIncrease / noDecrease
|
Boolean | The AI can't raise or lower the value | |
noIncreaseMax / noDecreaseMax
|
Boolean | The AI can't raise or lower the max |
A script is one box of code with a timing. A stat, a trait and the world each hold a scripts list.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
name |
String | The script's name. Absent = the owner and timing name it | |
timing |
โ |
"before" | "after" | "output" | "start" | "switch"
|
When it runs. "before" runs before the AI's turn, and its changes reach that turn's prompt. "after" runs after the AI's changes and regen. "output" runs on each completed narration sentence. "start" runs once on a new game's opening turn. "switch" runs when a trait turns on or off |
code |
โ | String | The JavaScript |
order |
Number | Run position within the timing. Scripts with an order run first, lowest first. The rest run after them, world scripts first, then each stat's in stat order |
When two scripts write the same field, the later one wins. A world script has no self. A trait's script runs only while the trait is active. The Scripts Guide lists what each timing reads.
A descriptor gives the AI a word or phrase for a band of values.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Number | Unique id |
threshold |
โ | Number | The top of the band, in the stat's thresholdUnit
|
description |
โ | String | The text the AI reads while the value is in the band |
placeholderPins |
Pin[] | Placeholder values pinned while the value is in the band |
The lowest band that holds the value wins, in any list order. A value above every threshold has no descriptor.
A trait changes a stat with a list of these.
| Field | Req. | Type | Meaning |
|---|---|---|---|
statId |
โ | String | The stat's id
|
value |
โ | Number | The amount to add. Negative subtracts |
type |
"min" | "max" | "starting" | "regen"
|
What the change adds to. "starting" adds to the value. Absent = no change |
|
interval |
String | An older field. Nothing reads it | |
target |
"global" |
On an entity's trait, "global" moves the global stat with this id, even when the entity has its own stat with the same id. Absent = the entity's own stat first, else the global stat |
A statToggles entry takes the same target.
๐ Older files write
"target": "player". The app reads it as"global", and new files write"global"only. Apps older than this change don't read"global", so they apply these rows to the entity's own stat until updated.
A trait describes the player or an entity. The player picks traits before play. An entity can own traits too.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique across the world and every entity |
name |
โ | String | The name |
playerDescription |
String | What the player reads on the setup screen | |
aiDescription |
String | What the AI reads while the trait is active | |
statChanges |
โ | StatChange[] | Changes to stats while the trait is active. A global trait changes global stats. An entity's trait changes the entity's own stats, and global stats through a "global" row |
groupId |
String | null
|
The group's id. Absent or null = the top level |
|
order |
Number | The order among items in the same group | |
isDefault |
Boolean | The trait starts selected | |
playerToggle |
Boolean | The player can switch the trait on and off during the game | |
mode |
"alwaysOn" | "hidden"
|
Absent = Optional: the player picks. "alwaysOn" is active while its requirements hold, and the player can't switch it. "hidden" acts as "alwaysOn", and the player never sees it. Both ignore isDefault and playerToggle
|
|
requires |
Requirement row[] | Any one row that holds makes the trait available. Absent or empty = always available | |
statToggles |
{ "statId", "enabled" }[] |
Stats switched on or off while the trait is active | |
placeholderPins |
Pin[] | Placeholder values pinned while the trait is active | |
scripts |
Script[] | Code with this trait as self. It runs while the trait is active. See the Scripts Guide
|
See World Editor: Traits for how modes, requirements and pick counts behave during the game.
requires is a list of rows. Each row is { "all": [ โฆ ] }: a list of requirements. A row holds when every requirement in it holds. A row is never empty.
"requires": [
{ "all": [{ "kind": "trait", "id": "knight" }, { "kind": "trait", "id": "heavy-build" }] },
{ "all": [{ "kind": "trait", "id": "mercenary" }] }
]๐ Older files store
requiresas a flat list of requirements. The app reads each one as a row of its own.
Each requirement has a kind:
kind |
Other fields | Holds when |
|---|---|---|
"trait" |
id, name, owner
|
The owner has the trait with this id
|
"group" |
id, name, owner
|
The owner has any trait in the group with this id
|
"playingAs" |
id, name
|
The player plays as the entity with this id
|
"stat" |
id, name, owner, operator, value, unit
|
The owner's stat with this id compares true against value
|
-
nameis optional. It holds the target's name, so a requirement still reads when the target is gone. - A
"stat"requirement'soperatoris one of">","<",">=","<=","="or"โ ". Itsunitis"raw"for the stat's own units or"percent"for a share of the stat's minโmax span.{ "kind": "stat", "id": "health", "operator": "<", "value": 20, "unit": "percent" }holds while Health is under 20% of its range. -
owneris optional, and it applies only to a trait, group or stat an entity owns. A requirement that names a global item (a root trait, a root group or a global stat) reads the global scope, and the app drops anyownerstored with it. Absent = the trait's own owner.{ "kind": "you" }checks the entity the player plays, and never holds when the player plays none.{ "kind": "entity", "id": "<entity id>", "name": "โฆ" }checks that entity. -
notis optional.trueflips the requirement: it holds while its target is off.{ "kind": "trait", "id": "paladin", "not": true }holds while the owner doesn't have Paladin. - A requirement whose target is gone never holds, with or without
not.
๐ Older files name the owner under
bearer. The app reads it asowner, and new files writeowneronly. Apps from v3.1 and v3.2 don't readowner, so they misread these requirements until updated.
A group holds traits and other groups. Both the world and each entity have a traitGroups list.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
name |
โ | String | The heading on the setup screen |
playerDescription |
String | Text the player reads under the heading | |
aiDescription |
String | A header the AI reads above the group's active traits | |
aiVisible |
Boolean |
false keeps the group's name and description from the AI. Its traits and subgroups move up one level. Absent = true
|
|
parentId |
โ | String | null
|
The parent group's id. null = the top level |
order |
Number | The order among items with the same parent | |
minPicks |
Number | The fewest traits placed directly in the group that must be active. Absent = 0 | |
maxPicks |
Number | The most traits placed directly in the group that can be active. Absent = no limit. 1 shows radio buttons |
|
system |
"blueprints" |
Marks the world's Blueprints group. Only one world group has it, at the top level |
The world group with system: "blueprints", and every group below it, holds Blueprints items. The player never picks a Blueprints trait. It reaches play only through an entity's link. Only a Blueprints trait or group can be linked.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
name |
โ | String | The name |
playerDescription |
String | What the player reads | |
aiDescription |
String | The full description the AI reads | |
aiSummary |
String | A short description the AI reads where the full one is too long | |
description |
String | An older field. The player reads it when playerDescription is absent |
|
backgroundImage |
Image | The background | |
imageTags |
String | Comma-separated tags for image generation | |
ambientSound |
MediaAsset | Looping sound | |
isStarting |
Boolean | A new game can start here. One marked = every game starts there. Several = the player picks. None = any location. A played entity's startingLocationId joins the choices |
|
parentId |
String | null
|
The parent location's id. Absent or null = the top level. List order is the order among siblings |
|
canvasPosition |
{ "x", "y" } |
Editor-only. The location's place on the Locations canvas, relative to its parent. Absent = automatic | |
placeholderPins |
Pin[] | Placeholder values pinned while the player is here. A child location does not get its parent's pins | |
openings |
Opening[] | Openings that draw when a game starts at this exact location | |
openingWeights |
Object | Draw weight per opening id, as in worldOverview
|
A connection is a travel link between two locations. A connection between two locations replaces the travel that nesting gives them. Its legs are then the only travel between the two.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
a |
โ | String | One location's id
|
b |
โ | String | The other location's id
|
aToB |
Leg | Present when travel goes from a to b
|
|
bToA |
Leg | Present when travel goes from b to a
|
A connection needs at least one leg. Both legs = two-way. A leg has one optional field, hint: how the trip is made, such as "through the old gate". The AI reads it after the destination's name.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
name |
โ | String | The name |
aliases |
String[] | Other names. The AI reads them. The app finds them in the story text, case-sensitive and whole-word | |
pronouns |
String | Free text, such as "she/her". The AI reads it | |
type |
String | An optional category label | |
persona |
Boolean | The Persona mark. See Persona Marks | |
personaOnly |
Boolean | The entity exists only while the player plays it | |
customPersona |
Boolean | The Custom Persona mark | |
startingLocationId |
String | The Starting Location preselected when the player plays this entity. Absent = Automatic: the first of its locations marked isStarting
|
|
playerDescription |
String | What the player reads | |
aiDescription |
String | The full description the AI reads | |
aiSummary |
String | A short description the AI reads where the full one is too long | |
images |
Gallery image[] | The gallery, in author order. The first image is the main one | |
tags |
String[] | Listing tags for Community Creations | |
imageTags |
String | Comma-separated tags for image generation | |
sound |
MediaAsset | A sound | |
model |
MediaAsset | A 3D model | |
locations |
String[] | The ids of the locations where the entity is. It is at all of them |
|
groupId |
String | null
|
Editor-only. The entity group's id. Absent or null = no group |
|
order |
Number | Editor-only. The order in its group | |
openings |
Opening[] | The entity's own openings | |
openingWeights |
Object | Draw weight per opening id, as in worldOverview
|
|
placeholders |
Placeholder[] | Placeholders the entity owns, and its copies | |
traits |
Trait[] | Traits the entity owns. They describe it to the AI. They are the player's when the player plays it | |
traitGroups |
TraitGroup[] | Groups for the entity's own traits | |
traitLinks |
TraitLink[] | Links to Blueprints traits and groups | |
traitPlacement |
{ "groupId", "order" } |
Where the entity's node sits in the world's Traits tree. groupId is a world group id or null for the top level. Absent = the end of the top level |
|
link |
ContentLink | What library item this copy follows. Absent = it follows nothing |
Only the player has stats. So statChanges and statToggles on an entity's own traits apply only on a playable, persona-only or Custom Persona entity. A playable or persona-only entity's apply while the player plays it. The Custom Persona entity's apply while the player has no world persona.
The Persona control on the entity's Profile tab writes three fields:
| Choice | persona |
personaOnly |
customPersona |
|---|---|---|---|
| Cast | absent | absent | absent |
| Playable | true |
absent | absent |
| Persona-Only | true |
true |
absent |
| Custom Persona | absent | absent | true |
One entity at most has customPersona, at the top level of the Traits tree. In a library entity, persona: true makes it one of the player's personas. See Personas for Authors for the rules.
A link gives a Blueprints trait or group to an entity. The entity does not get its own trait. The link reads its original live, except for the fields it overrides.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
originalId |
โ | String | The id of the Blueprints trait or group |
kind |
โ |
"trait" | "group"
|
What the original is |
originalName |
โ | String | The original's name. When an entity joins a world, its link finds the original by originalId, then by this name |
groupId |
โ | String | null
|
One of the entity's own group ids. null = the entity's top level |
order |
Number | The order among the entity's items with the same parent | |
overrides |
Object | Overrides per trait. The key is the original trait's id. A linked group keys each of its traits here |
|
keyNames |
Object | Outside a world only, such as in an entity file: trait id to name, for each key of overrides
|
Each override map can hold isDefault, requires, placeholderPins, playerToggle, statChanges and mode. mode takes "optional", "alwaysOn" or "hidden". Each field holds { "value": โฆ, "blueprint": โฆ }: your value, and the original's value when you set it. The app uses blueprint to tell you when the original changed.
An entity links each original one time.
Editor-only groups for entities.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
name |
โ | String | The group name |
parentId |
โ | String | null
|
The parent group's id. null = the top level |
order |
Number | The order among groups with the same parent |
An entity file, or an entity in the library, can also carry these fields. The app clears them when the entity joins a world, so a world file does not need them.
| Field | Type | Meaning |
|---|---|---|
sharedPlaceholders |
Placeholder[] | The world placeholders the entity's chips use |
blueprints |
Placeholder[] | The blueprints the entity's copies read |
locationRefs |
{ "id", "name" }[] |
The locations the entity stood in, by name |
An opening is one way a game can start. The world, each location and each entity have an openings list.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique within its owner. openingWeights uses it as the key |
text |
โ | String | The text. It can hold chips |
kind |
โ |
"action" | "narration"
|
"action" fills the player's input box. "narration" is page one, as written |
self |
true |
A Self opening. It draws only while the player plays its owner. Only an entity with the Persona or Custom Persona mark can own one. Absent = an Others opening |
See World Editor: Openings for which openings draw.
A placeholder is a named value that a chip shows in text. The world's placeholders are shared. An entity's or a book's placeholders belong to it.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id. Do not use :, { or } in it |
name |
โ | String | The name in the editor. Chips use the id, so a new name breaks nothing |
values |
โ | Value[] | The values. See below. Empty = the chip reads as nothing |
weights |
Object | Draw weight per value id. A missing entry weighs 1. 0 keeps a value and never draws it |
|
roll |
Boolean |
true = a Wildcard, which draws one value per game. false = an Object, which joins all values with ", ". Absent = a Wildcard with two or more values. With one value, the placeholder is a Variable |
|
ownerId |
String | The placeholder this one belongs to. Absent = the top level | |
sharedWeights |
Object | Draw weights this placeholder sets on shared placeholders it reaches through its values. The outer key is a path of ids joined with /. The inner map is like weights
|
|
blueprintId |
String | Makes this a copy of that blueprint | |
valueOverrides |
Object | On a copy: changes per blueprint value id
|
|
groupId |
String | null
|
Editor-only. A shared placeholder's group id. Absent or null = no group |
Each value:
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id. Pins and weights use it |
text |
โ | String | The text. It can hold chips. A value that is exactly one chip makes that placeholder a part of this one |
pins |
Pin[] | Placeholder values pinned while this value is the drawn one |
See World Editor: Placeholders for kinds, parts and weights.
A pin sets one placeholder to one value while its source is active. Traits, locations, stat descriptors and placeholder values carry pins.
| Field | Req. | Type | Meaning |
|---|---|---|---|
placeholderId |
โ | String | The placeholder's id. On a trait, a blueprint id pins each owner's copy |
value |
โ | String | The value's text |
valueId |
String | The value's id. When present, it wins over value, so a reworded value still matches |
A world shared placeholder in the Blueprints group, or a group below it, is a blueprint. An entity holds a copy of each blueprint its traits need, in its own placeholders, with blueprintId set. The app makes copies when it loads the world.
A copy reads its blueprint's values live. Its own values hold only the values it adds. Its valueOverrides map a blueprint value id to changes:
| Field | Type | Meaning |
|---|---|---|
text |
{ "value", "blueprint" } |
Your text for the value, and the blueprint's text when you set it |
weight |
{ "value", "blueprint" } |
Your draw weight, and the blueprint's weight when you set it |
removed |
true |
This copy never draws the value |
Editor-only groups for the world's shared placeholders.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
name |
โ | String | The group name |
parentId |
โ | String | null
|
The parent group's id. null = the top level |
order |
Number | The order among groups with the same parent | |
system |
"blueprints" |
Marks the Blueprints group. Only one group has it, at the top level |
A chip is a token in a text field. The editor inserts chips. To write one by hand, use this form:
{{ph:<placeholder id>:<world|unique>:<placement id>}}
| Part | Meaning |
|---|---|
<placeholder id> |
The placeholder's id
|
world |
Every chip of this placeholder shows the same drawn value in a game |
unique |
This chip draws its own value |
<placement id> |
A unique id for this chip. A unique chip keeps its draw under it |
The editor can add two more parts after the placement id: a path to a part of the placeholder, and a label after :=. Copy those from a chip the editor wrote.
Two chips need no placeholder. {{user}} is the Player Name chip. {{char}} is the Character Name chip.
dictionaries is a list of books. Each book holds lore entries. An entry goes into the AI's prompt when one of its keywords is in the scanned text. See What Gets Scanned.
Each book:
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
name |
โ | String | The book's name |
description |
String | A note for people. The AI never reads it | |
enabled |
Boolean |
false turns off every entry in the book. Absent = on |
|
tags |
String[] | Listing tags for Community Creations | |
thumbnail |
Image | null
|
The listing image | |
entries |
โ | Entry[] | The entries, in order |
placeholders |
Placeholder[] | Placeholders the book owns | |
sharedPlaceholders |
Placeholder[] | In a dictionary file only: the world placeholders its entries use. Import clears it | |
link |
ContentLink | What library item this copy follows |
Book order sets the order in the prompt.
| Field | Req. | Type | Meaning |
|---|---|---|---|
id |
โ | String | Unique id |
name |
โ | String | A label. Blank = the first keyword |
key |
โ | String[] | The keywords, one per item |
value |
โ | String | The text the AI reads when the entry is active |
enabled |
Boolean |
false turns the entry off. Absent = on |
|
constant |
Boolean | The entry is always active | |
secondaryKeys |
String[] | Second keywords. When present, a keyword match also needs a second keyword | |
secondaryAll |
Boolean | Needs all second keywords, not one | |
secondaryExclude |
Boolean | Inverts the second test: the entry is active only when the second keywords are absent | |
useRegex |
Boolean | Keywords are regular expressions | |
matchWholeWords |
Boolean | Keywords match whole words only, regex keywords included | |
caseSensitive |
Boolean | Keywords match case. Absent = any case | |
recursive |
Boolean | Text from other active entries can activate this entry | |
position |
"before" | "after"
|
"before" = Background Lore, early in the prompt. "after" = Foreground Lore, late. Absent = "after"
|
|
scanDepth |
Number | Scan only the last N messages. The app always scans the current scene. Absent = all | |
priority |
Number | Kept from an imported lorebook for export. The app does not use it | |
tokenBudget |
Number | Kept from an imported lorebook for export. The app does not use it | |
extensions |
Object | Other imported lorebook fields, kept for export |
An entity or a book in a world can follow a library item. Its link object records that. Let the app write it. See Linked Content.
| Field | Type | Meaning |
|---|---|---|
libraryId |
String | The library item's id
|
sourceId |
String | The published listing behind the library item |
sourceRevision |
String | The source revision this copy holds |
reviewedRevision |
String | The last revision the author reviewed |
localReplacement |
Boolean | The copy was edited and still follows its source |
sourceName |
String | The source's name, for display |
connections |
Object | What each world placeholder or location the source expects is in this world, by id
|
bundledFrom |
String | The copy came in an imported world file, and its library item is not on this machine |
statUpdates is an older list. No screen edits it, and play does not read it. Write []. An older file can hold records with id, name, prompt, stats (stat names) and messageHistory.
Images (thumbnail, location backgroundImage, a gallery image's data, book thumbnail) hold one string:
- A base64 data URL, such as
data:image/png;base64,โฆ - Or an
httporhttpsURL. The app loads the image from that address
Gallery images (entity images) are objects:
| Field | Req. | Type | Meaning |
|---|---|---|---|
data |
โ | Image | The picture |
requires |
Requirement rows | The Visibility rule. Absent = always shown |
Background music (bgm) is a base64 data URL.
Uploaded audio and models (customPlayerVRM, location ambientSound, entity sound and model) are a MediaAsset:
| Field | Req. | Type | Meaning |
|---|---|---|---|
data |
โ | String | A base64 data URL |
type |
โ | String | The MIME type, such as model/vrm
|
name |
String | The original file name | |
size |
Number | The file size in bytes |
{ "data": "data:model/vrm;base64,...", "type": "model/vrm", "name": "hero.vrm" }A short world. ... replaces the media data.
{
"formamorphKind": "world",
"version": "3.1.2",
"worldOverview": {
"name": "Example World",
"description": "A small town at the edge of the woods.",
"author": "Docs",
"thumbnail": "data:image/png;base64,...",
"bgm": null,
"systemPrompt": "A quiet fantasy town. Magic is rare and feared.",
"use3DModel": false,
"tags": ["fantasy"],
"readme": "# Welcome\nYou arrive at dusk.",
"openings": [
{ "id": "op-1", "text": "{{user}} reaches the town gate as the bell rings.", "kind": "narration" }
]
},
"stats": [
{
"id": "health",
"name": "Health",
"type": "number",
"description": "How hurt you are",
"min": 0,
"max": 100,
"value": 100,
"regen": 1,
"descriptors": [
{ "id": "hp-low", "threshold": 30, "description": "badly hurt" },
{ "id": "hp-high", "threshold": 100, "description": "healthy" }
]
}
],
"locations": [
{ "id": "town", "name": "Town Square", "playerDescription": "The center of town.", "isStarting": true },
{ "id": "forge", "name": "Forge", "playerDescription": "Heat and noise.", "parentId": "town" }
],
"connections": [],
"entities": [
{
"id": "smith",
"name": "Mara",
"pronouns": "she/her",
"playerDescription": "The town blacksmith.",
"aiDescription": "Mara is gruff and fair. She wears a {{ph:garb:world:p-1}}.",
"locations": ["forge"]
}
],
"traits": [
{
"id": "strong",
"name": "Strong",
"playerDescription": "Above-average strength",
"aiDescription": "This person is unusually strong.",
"groupId": "body",
"statChanges": [{ "statId": "health", "value": 20, "type": "max" }]
}
],
"traitGroups": [
{ "id": "body", "name": "Body", "parentId": null, "order": 0, "maxPicks": 1 }
],
"placeholders": [
{
"id": "garb",
"name": "Garb",
"values": [
{ "id": "v-1", "text": "leather apron" },
{ "id": "v-2", "text": "soot-black smock" }
]
}
],
"dictionaries": [
{
"id": "book-1",
"name": "Default",
"enabled": true,
"entries": [
{ "id": "lore-1", "name": "The War", "key": ["war", "old war"], "value": "A war ended here 40 years ago." }
]
}
],
"statUpdates": []
}- ๐ ๏ธ World Editor: the editor that writes this format
-
๐งฎ Scripts Guide: what a script's
codecan do - ๐ชช Personas for Authors: persona marks and rules
- ๐ Linked Content: linked copies and world files