Skip to the content.

Custom function kinds, types, and decorators

Use custom only when the universal schema has no field for the kind of declaration. Do not put if syntax in custom.

custom is a free object in the template. The engine reads these arrays if present:

"custom": {
  "function_kinds": [
    {
      "name": "command",
      "declaration_keywords": ["command"],
      "description": "Host-invoked command. Same call semantics as fn."
    },
    {
      "name": "quest",
      "declaration_keywords": ["quest"],
      "description": "A scripted quest hook."
    }
  ],
  "types": [
    {
      "name": "entity",
      "declaration_keywords": ["entity"],
      "description": "Named map of fields the host may interpret."
    },
    {
      "name": "item",
      "declaration_keywords": ["item"],
      "description": "Item definition block."
    }
  ],
  "decorators": [
    {
      "name": "export",
      "prefix": "@",
      "description": "Register with the host on load."
    },
    {
      "name": "tick",
      "prefix": "@",
      "description": "Run every frame from C++."
    }
  ],
  "host_functions": ["print", "spawn", "damage"]
}

Also add those keywords to keywords.reserved / declaration / control_flow as appropriate so they tokenize as keywords. The reader appends declaration_keywords from custom kinds onto the function-keyword list automatically.

Function kinds

command ping() { return "pong"; } parses like fn ping() { ... }. The keyword is whatever you listed. Host:

engine.call("ping");

Several kinds can coexist (fn, command, quest). They share the same runtime function object.

Types

entity Portal {
  x = 10;
  y = 4;
  dest = "keep";
}

After run, engine.get("Portal") is a map with string keys. The host decides that this map is a “portal.”

Fields are name = expression. Separators: comma or statement terminator.

Decorators

@tick
@export
command think() {
  return 0;
}

Multiple prefixes in a row are allowed. Each name is looked up in bind_decorator. Unknown decorator names are ignored (no error). If the user wanted a hard failure, check the name in a catch-all hook or validate after load by walking get.

What custom is not for

Document every custom kind’s meaning in description so the next agent does not guess.