Scripting with Rhai

Archipelago embeds Rhai through a Rustler NIF. Scripts receive a read-only snapshot of the world, emit declarative effects, and return. They have no filesystem, network, module-import, or dynamic-evaluation access.

The sandbox and its Elixir bridge are implemented. Script attachment and editing are not yet exposed as a complete operator workflow in the SSH client; /script checks the :author_scripts capability, but the rig> command surface is still incomplete.

Execution model

Archipelago.Core.ScriptRunner.run/5 joins three boundaries:

  1. EntityView builds a snapshot for the entity and room.
  2. Archipelago.Scripting evaluates Rhai under fixed limits.
  3. ScriptBag checks every emitted effect against the entity’s capabilities.

Accepted effects return to the caller for application by the Effect Interpreter. Rejected effects remain visible to operator tooling; a script cannot silently widen its authority.

if occupants().len() > 1 {
    emit("say", #{
        actor: self_id(),
        message: "The room has changed since you arrived."
    });
}

Read-only functions

FunctionValue
self_id()entity holding the script
room_id()current room ID
occupants()entity IDs in the room snapshot
inventory(entity_id)inventory entries for one entity
property(entity_id, key)property value or unit
time()ISO-8601 instant supplied to the snapshot
random(min, max)bounded integer using the invocation seed

Emission functions

FunctionResult
emit(type, args)records a declarative effect
abort(reason)stops the invocation with an authored reason
log(message)appends an operator-audience log line

ScriptRunner currently translates say, emote, set_property, and generic emit. Unknown effect types are rejected.

Limits

The defaults are 10,000 operations, 32 call levels, 10,000 bytes of string data, 1,000 array entries, 1,000 map entries, and expression depth 32. A caller may lower or raise them per invocation.

Budget exhaustion returns an operator diagnostic and a fiction-facing line such as “The spell unravels before completion.” Runtime and compile failures remain structured errors.

Rules and scripts

Declarative rules are stored as canonical CBOR in the content-addressed store and evaluated without crossing the NIF boundary. Use a rule for a stable predicate-and-effect response. Use Rhai when the behaviour needs branching, arithmetic, string work, or several reads from the entity snapshot.

Both paths end at effects. Neither path receives a mutable world object.