Anvil
Creating capabilities

Overview

Create typed behavior for a player or process and supply its implementation.

Create a capability when test authors need another typed action or observation. Start by choosing its owner, then choose the mechanism that supplies the behavior.

BehaviorPublic contractProvider
Player behavior using observations or other capabilitiesPlayerCapabilityPlayerCapabilityProvider
Player actions through the native protocol clientPlayerCapabilityProtocolPlayerCapabilityProvider
Server or proxy behavior through its agent, independent of a playerProcessCapabilityAgentProcessCapabilityProvider

ProcessCapability belongs to the global API. A custom RunningProcess implementation can supply it through the inherited CapabilityOwner lookup contract without an agent. AgentProcessCapabilityProvider is the factory used when an agent supplies that process behavior.

For example, PluginCommands could belong to a player and use Messages to execute commands with that player's permissions. A process capability could read your plugin's server-wide state through a native handler. Choose the owner by who can normally do or know the thing, not by how you implement it: what a client can do or see belongs to the player, and what a server or proxy can do or know belongs to the process. The built-in Players capability is process-owned because a server knows who is on it; Alice's client does not.

Create the example capability

This section builds a process-owned Echo capability backed by an agent. It proves that your packaged native handler can run in the selected process and return a result, without creating a simulated player. Replace its contract with a useful domain operation after the installation path works.

  1. Define the public contract.
  2. Define its operation contract and handler, then build the agent provider.
  3. Install the handler and package and test the extension through a consumer build.

A protocol adapter supplies player behavior carried by packets. That path does not require completing the agent example first.

Arrange the artifacts

Example artifactOwnsAnvil API dependency
echo-apiEcho and its public modelsapi
echo-operationsShared request/response descriptorsagent-api
echo-hostProcess capability provider using a typed request channelcapability-agent-api
echo-agentHandler loaded by the platform agentagent-server-api
echoConsumer dependency wiringThe API and selected host implementation

Your artifacts use your own Maven group and package namespace. The host and agent artifacts share operation contracts through echo-operations, which exports agent-api for its descriptor types. Host wiring derives a ChannelOperation from those descriptors and sends it through the capability request channel. The host does not depend on the handler implementation. For packet behavior, the protocol adapter occupies the host implementation role and may also supply worker-side code. Anvil artifact IDs in this table use the Maven group me.whereareiam.anvil.

Declare dependencies and support

The runtime orders creation by declared predecessor capabilities. Access to other capabilities is restricted to those dependencies, within the same owner. A player capability can declare Messages and obtain it through context.requireCapability(Messages.class). A process capability backed by an agent can declare Dispatch and obtain it through its provider context.

A required dependency the owner lacks keeps the capability from being created, and retrieving it then names the missing dependency. A capability that only uses another when it is there declares it as optional instead, with .optionalCapability(Type.class): it is created after that capability when the owner has it, and context.findCapability(Type.class) is empty when the owner does not. The built-in Dialogs uses Commands this way.

All capability providers return a CapabilityDescriptor from descriptor(). It contains provider identity, contract version, and required and optional capability types. The shared capability-api owns this contract, generic provider/context interfaces, typed request descriptors/channels, and neutral player contracts under capability.api.player. Use PlayerCapabilityProvider when the implementation needs only player identity/version, observations, declared dependencies, and cleanup.

Protocol-backed player providers use capability-protocol-api, and their native worker extensions implement WorkerExtension from protocol-api, which it includes. ProtocolPlayerCapabilityProvider names what it needs from the player's library: requiredProtocols(), the edition protocols its worker extension speaks, such as JavaProtocol, and workerBacked() for an extension that needs no edition protocol. Only a capability that uses one library's own services restricts supportedLibraries(). Its creation context supplies the protocol channel and the services of the player's protocol library. Composition skips the provider and its dependents for players whose library lacks a required protocol, whose library runs no worker extensions while the provider is worker-backed, or whose worker does not install its capability. Declare Session only when the implementation uses it.

Agent-backed process providers use capability-agent-api, which includes the shared capability contracts. Its process context adds a scoped request channel for native agent work. AgentProcessCapabilityProvider can restrict platforms with supportsPlatform(...). Agent providers explains its scope and cleanup hooks.

For exact signatures, consult the PlayerCapabilityProvider, ProtocolPlayerCapabilityProvider and AgentProcessCapabilityProvider source Javadocs. These links use dev; select your release tag when checking a released version.

Implement an existing capability

An alternative implementation of Movement supplies the existing API's methods. Arrange the installed wiring and protocol selection so exactly one provider contributes Movement.class to a player. The same uniqueness rule applies to capabilities contributed to a process. Multiple matching providers are an error.

Capability lookup uses the exact registered class. A Java interface extending another capability interface does not automatically register the parent type. There is no implicit augmentation of an already installed capability. To compose additional behavior, define a capability with explicit dependencies on the existing APIs.

On this page