> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.trysulfur.net/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.trysulfur.net/_mcp/server.

# Handles & capabilities

Goal callbacks declared with [`declare_mob`](/plugins/declare-mob) receive **three handles** as
positional arguments:

```python
def my_goal_tick(entity, world, nav):
    ...
```

A handle is a thin, id-carrying value — it never holds a live entity pointer. Every access
re-resolves the entity on the owning region's store on the tick goroutine, so reading a mob that
has since despawned returns a clean Starlark error (`entity 42 no longer exists`), never a stale
reference. Handles are built fresh per callback call; stashing one across calls is safe but it
still re-resolves on each access.

Every operation checks the owning plugin's manifest [capabilities](/plugins/manifest#capabilities)
at the operation boundary. A denied call raises a visible error:

```
capability denied: this plugin lacks "world.write"
```

## The entity handle

The mob the goal is running for. `str(entity)` is `<entity 42>`; `type(entity)` is `"entity"`.

### Reads — require `entities.read`

| Attribute          | Type                  | Meaning                                                                                   |
| ------------------ | --------------------- | ----------------------------------------------------------------------------------------- |
| `x`, `y`, `z`      | float                 | Position.                                                                                 |
| `yaw`, `pitch`     | float                 | Body rotation.                                                                            |
| `on_ground`        | bool                  | Grounded flag.                                                                            |
| `type`             | string                | Registry entity-type name (e.g. `"pig"`).                                                 |
| `velocity`         | (float, float, float) | Current `(vx, vy, vz)`.                                                                   |
| `health`           | float                 | Attribute-derived health value.                                                           |
| `was_hurt`         | bool                  | True while the mob is in its hurt-flash window (`hurtTime > 0`).                          |
| `last_damage_type` | int                   | Damage-type id of the mob's last damage source.                                           |
| `has_last_damage`  | bool                  | Whether a real last-damage source was recorded (mirrors `getLastDamageSource() != null`). |
| `in_water`         | bool                  | AABB intersects water (the `FloatGoal.canUse` predicate).                                 |
| `fluid_height`     | float                 | Water fluid height at the mob (max over the AABB).                                        |
| `in_lava`          | bool                  | AABB intersects lava.                                                                     |
| `is_in_love`       | bool                  | `Animal.isInLove()` — fed and ready to breed.                                             |
| `is_baby`          | bool                  | `AgeableMob.isBaby()` (`breed_age < 0`).                                                  |
| `breed_age`        | int                   | The signed age machine: `<0` baby, `>0` breed cooldown, `==0` adult.                      |

### Read methods

| Method                                                                                       | Capability      | Returns                                                                                                                                                 |
| -------------------------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `attribute(name)`                                                                            | `entities.read` | The folded value of a named attribute (e.g. `"movement_speed"`).                                                                                        |
| `damage_in_tag(tag)`                                                                         | `entities.read` | Whether the last damage source is in the named damage-type tag (e.g. `"panic_causes"`). Membership lives host-side.                                     |
| `nearest_player_holding_food(tag, range)`                                                    | `entities.read` | Position tuple `(x, y, z)` of the nearest player holding an item in the named food tag (`"cow_food"`, `"cat_food"`, …), or `None`. The tempt-goal scan. |
| `nearest_player_holding_pig_food(range)` / `nearest_player_holding_carrot_on_a_stick(range)` | `entities.read` | Pig-specific tempt scans (fixed item sets, host-owned).                                                                                                 |
| `nearest_breeding_partner(range)`                                                            | `entities.read` | Position of the nearest valid in-love same-class partner, or `None` (the `BreedGoal.getFreePartner` scan).                                              |
| `nearest_adult_parent(range)`                                                                | `entities.read` | Position of the nearest same-class adult for a baby's follow goal, or `None`.                                                                           |

### Mutations

| Method                       | Capability                     | Effect                                                                                                                               |
| ---------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `move_to(x, y, z)`           | `entities.write` **and** `nav` | Sets a navigation target (routes through the async A\* pathfinder). The Go nav/physics move the mob; this never teleports.           |
| `set_velocity(vx, vy, vz)`   | `entities.write`               | Direct velocity write (the `Entity.setDeltaMovement` analogue); integrated by physics next tick.                                     |
| `set_attribute(name, value)` | `entities.write`               | Overrides the base value of a named attribute the entity has (errors if it doesn't).                                                 |
| `set_look(yaw, pitch=0.0)`   | `entities.write`               | Sets head + body yaw (and pitch) instantly. Non-finite values are clamped to 0.                                                      |
| `set_look_at(x, y, z)`       | `entities.write`               | Aims the mob at a world point (the `LookControl.setLookAt` analogue; yaw derived host-side).                                         |
| `try_breed(range)`           | `entities.write`               | Host-side breed: re-finds the partner and runs the full vanilla breed (child spawn, RNG draws, XP). Returns `True` if a breed fired. |
| `eat_grass_block()`          | `world.write`                  | The sheep `EatBlockGoal` eat: converts the grass block below to dirt, regrows wool. Only ever touches the mob's own below-position.  |
| `eat_broadcast_byte10()`     | `entities.write`               | Broadcasts the eat-animation entity event (byte 10) to trackers.                                                                     |

### Per-mob RNG and goal state — no capability required

| Method                        | Returns                                                                                                                                                          |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rand_int(n)`                 | Random int in `[0, n)` from the mob's per-entity seeded RNG (the `Mob.getRandom()` stream).                                                                      |
| `rand_float()`                | Random float in `[0, 1)` (`nextFloat`).                                                                                                                          |
| `rand_double()`               | Random float in `[0, 1)` (`nextDouble` — a **distinct** draw from `rand_float`; use whichever the vanilla goal you're porting uses, or the RNG stream diverges). |
| `get_state(key, default=0.0)` | Reads a per-(mob, goal) scratch value — the analogue of a Go goal's struct fields (countdowns, cached targets).                                                  |
| `set_state(key, value)`       | Writes a scratch value (numbers only). Each spawned mob gets its own scratch per goal; two mobs never share a countdown.                                         |

The RNG methods draw from the mob's own deterministic per-entity stream. Sulfur's vanilla-mob
plugins rely on this to stay in RNG lockstep with the ported Java code — a plugin-driven pig and a
Go-native pig draw byte-identical streams.

## The world handle

`type(world)` is `"world"`.

| Method                               | Capability    | Returns / effect                                                                                                                                                                                     |
| ------------------------------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `block_at(x, y, z)`                  | `world.read`  | Tuple `(state_id, ok)` — `ok` is `False` for an unloaded column (never a silent 0).                                                                                                                  |
| `set_block(x, y, z, state)`          | `world.write` | Sets a block state and broadcasts the update to clients. Returns `True` if the block changed.                                                                                                        |
| `entities_near(x, z, radius_chunks)` | `world.read`  | List of **entity handles** for mobs in the per-section buckets around the position (same capability set carried; reading them still requires `entities.read`). Scoped to the calling region's store. |
| `nearest_player(x, y, z, max_dist)`  | `world.read`  | Position tuple `(px, py, pz)` of the nearest player in range, or `None`. Players are not in `entities_near` — use this.                                                                              |
| `is_dark()`                          | `world.read`  | True when it is dark enough for hostiles (the day/night gate used by spawn logic and the spider's daylight-flee).                                                                                    |

## The nav handle

The mob's navigation. `type(nav)` is `"nav"`.

| Method             | Capability | Effect                                                                                                       |
| ------------------ | ---------- | ------------------------------------------------------------------------------------------------------------ |
| `path_to(x, y, z)` | `nav`      | Requests a path to the target via the async A\*. Sets a *want*; the Go navigation and physics do the moving. |
| `has_path()`       | none       | True while the nav has an active target (`!navigation.isDone()`); flips false on arrival.                    |
| `stop()`           | `nav`      | Clears the pending nav target (`navigation.stop()`).                                                         |
| `jump()`           | `nav`      | Arms the mob's jump control (one impulse per tick; repeated calls within a tick are idempotent).             |

`path_to` also accepts an advanced 31-float form (10 raw stroll candidates plus a land-mode flag)
used by the vanilla stroll-goal ports to hand candidate selection to the host. For custom mobs,
the 3-argument form is what you want.

## Design rules the handles enforce

* **Goals set targets; Go moves mobs.** There is no raw position write. Movement always flows
  through navigation → physics → collision, so a plugin mob obeys the same movement rules as a
  vanilla one.
* **Single-owner tick.** Callbacks run on the owning region's goroutine; handles resolve against
  that region's entity store. A callback can only see its own region's entities (matching the
  Folia-style region-ownership discipline).
* **Reads return copies.** Scalars and tuples only — no live references ever cross into Starlark.