Your own JSON

Six ways to put data and behaviour the format never anticipated into your world.

Every object in the format rejects a property it does not recognise. Adding your own goes through one of these.

1. The extra bag

Most entities carry an extra object. The engine passes it through untouched.

{ "id": "bog_hound",
  "extra": { "morale": 6, "packLeader": true, "houseRule": "hates iron" } }
extra is not readable from a formula
It survives export, reaches mods, and shows in the editor. It is not in the scope a ref path walks, so a formula cannot read it. Use a flag for data a formula needs.
Not everything has one
No narrative entity does: not text pools, dialogues, nodes, options, quests, stages, objectives, arcs, lore, threads, or deed kinds. Nor do loot tables and their entries, traps, factions, NPCs, reactions, gates, encounter tables, or most simple rules collections. Use a flag or an id convention there.

2. A custom predicate

Every requirement carries custom, which takes a raw predicate. It runs alongside the other clauses. All must hold.

"requires": {
  "minLevel": 3,
  "custom": { "any": [ { "gte": [ { "ref": "reputation.wardens" }, 20 ] },
                       { "test": { "ref": "flags.spoke_for_us" } } ] } }

3. Your own events

The emit effect plus on: "custom" is an event bus. Quests, triggers, reactions, and opportunities all watch it.

// an effect emits
{ "emit": { "event": "bell_rung", "data": { "tower": "west" } } }

// a trigger listens
{ "id": "the_answer", "on": "custom", "event": "bell_rung",
  "mode": "once", "effects": [ ... ] }

// an objective completes on it
{ "id": "ring_it", "kind": "reach", "target": "bell_rung" }

4. Flags

An arbitrary key holding a string, a number, or a boolean. start.initialFlags seeds them, setFlag writes them, flags.<name> reads them. Nothing checks the spelling. Keep a prefix convention. See linking for the ones the engine writes.

5. Tags

Most entities carry a free tags list. Two places read them:

npc.shop.buysTagsWhat a merchant will buy.
damageInteraction.unlessDamage tags that cancel an immunity. Immune to slashing except silvered.

Everywhere else they are yours, for filtering and for your own predicates.

6. Layering on another world

{ "id": "more_husks", "extends": "aurendel@1.0.0",
  "content": { "monsters": [ { "id": "husk", "xp": 25 },
                             { "id": "old_husk", "$delete": true } ] } }
CollectionsMerge by id. An existing id is overridden field by field. A new one is added.
Other objectsMerge key by key.
Other arraysReplaced whole.
{ "$delete": true }Removes an inherited entry.
CyclesRejected.
Your document is checked before the merge
Something that only becomes valid once the base is folded in is reported as broken.extends overrides whole entries; it does not supply half of one.

Beyond JSON: mods

A mod is code in a sandbox. Your world lists the ones it expects under mods, each pinned by id and by a fingerprint of its contents. A changed mod is reported, not run.

Ten attachment points:

before an action · after an action · an unrecognised operation · an occasion · after the world settles · after time passes · whether a trigger fires · passives · reactions · events being emitted

While building: prefabs