Skip to content

WIT Contract

The WIT (WebAssembly Interface Types) file at plantangenet/brand-wit/wit/plugin.wit is the sole public contract between a brand plugin and its host. Both sides generate Rust bindings from this file via wit-bindgen.

Full Interface

package plant:brand@0.1.0;

interface host-clock {
  current-frame-id: func() -> u64;
  current-phase: func() -> string;
}

interface host-inputs {
  record input-patch {
    hob-id: string,
    payload: string,
  }

  drain-pending-patches: func() -> list<input-patch>;
}

interface brand-lifecycle {
  record signal-envelope {
    schema: string,
    payload: string,
  }

  provision: func() -> result<_, string>;
  on-frame-init: func(frame-id: u64) -> result<_, string>;
  on-frame-active: func(frame-id: u64) -> result<_, string>;
  on-frame-aggregation: func(frame-id: u64) -> result<_, string>;
  on-frame-cleanup: func(frame-id: u64) -> result<_, string>;
  drain-signals: func() -> list<signal-envelope>;
}

world plugin {
  import host-clock;
  import host-inputs;
  export brand-lifecycle;
}

Interfaces

host-clock (imported by guest)

The host provides read-only clock state to the brand.

Function Returns Description
current-frame-id u64 Monotonically increasing frame counter.
current-phase string Current FramePhase name (e.g. "active", "aggregation").

The brand may call these at any point during a lifecycle hook to query the host clock. Most brands only need the frame-id passed as a parameter to the lifecycle exports.

host-inputs (imported by guest)

The host queues player input patches between frames. The brand drains them during on-frame-active.

Function Returns Description
drain-pending-patches list<input-patch> Destructive read: returns all queued patches and clears the buffer.

input-patch

Field Type Description
hob-id string The hob (agent) this input targets.
payload string JSON-serialised input data. The schema is brand-defined.

:::caution Destructive Drain drain-pending-patches clears the queue. Call it exactly once per frame, typically at the start of on-frame-active. Calling it twice returns an empty list on the second call. :::

brand-lifecycle (exported by guest)

The brand implements these functions. The host calls them in phase order during each frame tick.

Function Parameters Returns When Called
provision result<_, string> Once, at service registration.
on-frame-init frame-id: u64 result<_, string> BeginningFrame phase.
on-frame-active frame-id: u64 result<_, string> Active phase. Main simulation tick.
on-frame-aggregation frame-id: u64 result<_, string> Aggregation phase.
on-frame-cleanup frame-id: u64 result<_, string> EndingFrame phase.
drain-signals list<signal-envelope> After on-frame-active, called by the host.

signal-envelope

Field Type Description
schema string Schema key following the HasSchema convention (e.g. "racing.signal.v1", "delivery.pickup.v1").
payload string JSON-serialised signal data. Structure is schema-defined.

Lifecycle Call Order

On each clock tick, the host calls the guest exports in this order:

provision()                  ← once at boot
│
├── on_frame_init(N)         ← BeginningFrame
├── on_frame_active(N)       ← Active (main tick)
├── drain_signals()          ← after active, host-initiated
├── on_frame_aggregation(N)  ← Aggregation
└── on_frame_cleanup(N)      ← EndingFrame

drain-signals is not a lifecycle export in the traditional sense — it is called by the host between on-frame-active and on-frame-aggregation to collect any signals the brand produced during its tick.

Design Decisions

Payloads are JSON strings. The WIT interface uses string for all payloads rather than typed WIT records. This avoids a combinatorial explosion of WIT record definitions for every signal and input type, and matches the existing HasSchema convention used throughout Plantangenet.

Lifecycle exports return result<_, string>. Returning an error string causes the host to log and (depending on configuration) halt the service. Panics inside the WASM component are trapped by wasmtime and surfaced as errors.

drain-signals is separate from on-frame-active. This keeps the tick function focused on mutation and lets the host decide when to collect results. It also allows the host to skip signal collection if no consumers are connected.

Versioning

The WIT package is versioned as plant:brand@0.1.0. The host validates the component's WIT version during instantiation and rejects components whose version does not match the expected semver range.

Codegen

Bindings are generated via cargo xtask codegen-wit, not via build.rs. This keeps the build graph predictable and avoids compiler-cached silent diffs.

  • Host bindings: world/src/brand_plugin/generated_host.rs
  • Guest bindings: rally/brand-component/src/generated_guest.rs

Both files are checked into source control and regenerated explicitly when the WIT file changes.