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.