Skip to content

Option datasets

A param picker normally gets its choices inline: a JSON Schema enum or an x-mobrule-options list right in the param’s schema. That’s fine for a six-item status dropdown. It is the wrong tool for a 500-entry item picker: inlining hundreds of choices bloats your pack.toml, the manifest’s canonical form, and its manifest_hash.

An option dataset is a named choice list a param references by name (x-mobrule-source) instead of inlining. It has two backends:

  • a file (path = "data/items.json") for a list too long to hand-write into the schema (a 500-entry item picker). The platform fetches the file to populate a searchable picker.
  • inline (options = [...]) for a small list you reuse across several params (a target_side toggle, a status-condition set). Authored once in pack.toml; the loader copies it onto every referencing param, so you never paste the same option block twice.

Reach for the file backend when a list is long; reach for the inline backend when the same short list appears on more than one param.

It is presentation only: the dataset decides which choices the UI shows, never which values are valid. The param’s own JSON Schema (type, minimum, maximum, enum) still does all validation, on every write. A stale, missing, or wrong dataset can never let through a value the schema would reject.

A [data.<name>] table maps a dataset name to a static file your pack ships, relative to the pack root:

[data.items]
path = "data/items.json"

The path must stay inside the pack directory. The loader reads the file and content-hashes it into the manifest (see Pinning below), so the file must exist when the pack loads.

The file is a JSON array of choice entries, the same shape as x-mobrule-options:

[
{ "value": 0, "label": "Random (within category)" },
{ "value": 1, "label": "Health potion" },
{ "value": 2, "label": "Shield" },
{ "value": 4, "label": "Ammo box" }
]

Each entry needs a value and a label; color is optional, and extra fields (e.g. a category for future client-side filtering) are ignored. Generate it from whatever source of truth you already have: a game data table, a spreadsheet export.

Add x-mobrule-source = "<name>" to the param. It joins the x-mobrule-* presentation family; the validator ignores it.

[events.adjust_item.params_schema.properties.item_id]
type = "integer"
minimum = 0
maximum = 500
"x-mobrule-source" = "items"
description = "Specific item id (0 = random within category)."

A sourced param defaults to the searchable combobox widget (the dataset is assumed large). The broadcaster sees a type-to-filter picker fed live from your file.

Inline x-mobrule-options on the same param takes precedence over x-mobrule-source. Declare one or the other, not both.

Inline backend: author once, reuse anywhere

Section titled “Inline backend: author once, reuse anywhere”

When the same short option list appears on several params, declare it once with options = [...] instead of path, and reference it the same way. The list is the same choice-entry shape (value, label, optional color):

[data.target_side]
options = [
{ value = 0, label = "Player", color = "#86efac" },
{ value = 1, label = "Opponent", color = "#ff7a7a" },
]
[events.apply_stat.params_schema.properties.target_side]
type = "integer"
enum = [0, 1]
"x-mobrule-widget" = "chips"
"x-mobrule-source" = "target_side"

A [data.<name>] declares exactly one backend: path (file) or options (inline). Declaring both is an error.

Unlike the file backend, the loader expands an inline dataset into x-mobrule-options on every referencing param when the pack loads. That means:

  • The choices ride the manifest directly: they are not asset-served, and there is no separate file to ship.
  • Your param’s x-mobrule-widget is honoured as written. Use chips or segmented for a small set (the file backend’s searchable combobox default does not apply). Colours render exactly as inline options would.
  • Keep the param’s enum (or other validation) as you would for an inline list: the dataset is presentation only and validates nothing.

A dataset’s name and a content hash ride in the manifest, so it is part of manifest_hash. For a file dataset the hash is over the file’s bytes; for an inline dataset it is over the canonical option JSON. Either way, editing the choices changes manifest_hash. That makes schema↔data skew and tampering detectable, and means you must re-pin any compiled-in MANIFEST_HASH (e.g. an Adapter’s) afterward. A file dataset’s local path is not hashed: relocating the file within the pack keeps the hash; changing its bytes re-pins it.

This section is about the file backend; an inline dataset is expanded into the params at load (above) and is never delivered separately.

The dataset is served over the existing /pack-assets/* surface, the same place your overlay files come from, so there is no new protocol surface and nothing for your Adapter to implement. The platform UI resolves the source name to the file’s asset URL and fetches it to populate the picker. Only files you declare in a [data.<name>] table are reachable this way; the rest of your pack stays private.

Because it is asset-served and static, the picker populates offline: the broadcaster can configure rewards before launch, when the Adapter isn’t even connected.