<!-- Generated from sdk/bindings/rust/README.md and protocol/v2/api.json by protocol/v2/codegen/docs.js; do not edit. Run `make api`. -->
# The Rust binding

The generated Rust binding wraps the core's C ABI with value records,
subsystem objects and `Result` and `Option` results. Every record has its
checked C layout. Tracking, geometry, protocol parsing and act serialization
remain in the core. Controls take optional fields, so later calls merge with
earlier ones; typed getters read pending fields back.

Install with `make install SDK=/opt/clunkyard/sdk`. Build with
`rustc --edition=2021 -O --extern clunkyard=/opt/clunkyard/sdk/bindings/rust/libclunkyard.rlib
-L native=/opt/clunkyard/sdk/lib -C link-arg=-lpthread -C link-arg=-ldl
-C link-arg=-lm bot.rs -o bot`. The native core is statically linked.

Implement `Strategy`, then call `clunkyard::run(strategy)`. A returned error
or panic logs its trace and sends the controls already set.
`Error::forfeit(code, message)` reports a robot forfeit.
`clunkyard::play(strategy, lines)` uses a memory session for tests.

## A tiny robot

```rust
use clunkyard::{Error, Robot, Strategy};
struct Tiny;
impl Strategy for Tiny {
    fn on_turn(&mut self, robot: &Robot) -> Result<(), Error> {
        let Some(target) = robot.sensors.nearest()? else {
            robot.drive.set(Some(0.75), Some(0.25), None)?;
            robot.scanner.set(Some(robot.me.heading()?), Some(40.0), None)?;
            robot.status.set(Some("SEARCH"), Some(robot.scanner.last()?.arc))?;
            return Ok(());
        };
        let power = robot.gun.power_for_flight_time(target.range, 1.0)?;
        let aim = robot.gun.lead(&target, power)?;
        let (heading, pitch) = aim.map(|aim| (aim.heading, aim.pitch)).unwrap_or((target.bearing, target.elevation));
        robot.gun.aim(Some(heading), Some(pitch))?;
        if robot.gun.can_fire(power, 10.0)? { robot.gun.fire(Some(power))?; }
        robot.drive.set(Some(0.5), Some(robot.angles.clamp(robot.angles.angle_diff(target.bearing, robot.me.heading()?)?, -1.0, 1.0)?), None)?;
        robot.scanner.set(Some(target.bearing), Some(10.0), None)?;
        robot.status.set(Some("TRACK"), Some(target.range))?;
        Ok(())
    }
}
fn main() -> Result<(), Error> { clunkyard::run(Tiny) }
```

`make test` runs the complete core conformance transcript through the safe
Rust wrapper. It checks exact floating-point bits, layouts, optional records,
text, controls and act lines. The tiny robot is also compared across bindings.

## Names

Every entry of the robot API 2.0.0 in Rust.
[names.md](names.md) has every language side by side.

| Canonical | Form | Rust |
| --- | --- | --- |
| `match.mode` | property | r#match.mode() |
| `match.ruleset` | property | r#match.ruleset() |
| `match.control` | property | r#match.control() |
| `match.robots` | property | r#match.robots() |
| `match.is_3d` | property | r#match.is_3d() |
| `match.tick_rate` | property | r#match.tick_rate() |
| `match.max_ticks` | property | r#match.max_ticks() |
| `match.robot_id` | call | r#match.robot_id() |
| `clock.tick` | property | clock.tick() |
| `clock.seconds` | property | clock.seconds() |
| `clock.remaining_ticks` | property | clock.remaining_ticks() |
| `arena.size` | property | arena.size() |
| `arena.gravity` | property | arena.gravity() |
| `arena.terrain_kind` | property | arena.terrain_kind() |
| `arena.ground_height` | call | arena.ground_height() |
| `me.health` | property | me.health() |
| `me.position` | property | me.position() |
| `me.velocity` | property | me.velocity() |
| `me.heading` | property | me.heading() |
| `me.pitch` | property | me.pitch() |
| `me.roll` | property | me.roll() |
| `me.angular_velocity` | property | me.angular_velocity() |
| `me.attitude` | property | me.attitude() |
| `me.grounded` | property | me.grounded() |
| `me.altitude` | property | me.altitude() |
| `me.applied` | property | me.applied() |
| `battery.charge` | property | battery.charge() |
| `battery.capacity` | property | battery.capacity() |
| `battery.regen` | property | battery.regen() |
| `battery.sustainable_drive` | property | battery.sustainable_drive() |
| `battery.compute` | property | battery.compute() |
| `battery.drive_cost` | call | battery.drive_cost() |
| `drive.hover_lift` | property | drive.hover_lift() |
| `drive.set` | control | drive.set() |
| `drive.forces` | control | drive.forces() |
| `drive.get` | call | drive.get_<field>() |
| `gun.aim_now` | property | gun.aim_now() |
| `gun.cooldown_ticks` | property | gun.cooldown_ticks() |
| `gun.spread` | property | gun.spread() |
| `gun.max_power` | property | gun.max_power() |
| `gun.damage_scale` | property | gun.damage_scale() |
| `gun.can_fire` | call | gun.can_fire() |
| `gun.shot_speed` | call | gun.shot_speed() |
| `gun.shot_cost` | call | gun.shot_cost() |
| `gun.reload_ticks` | call | gun.reload_ticks() |
| `gun.shot_damage` | call | gun.shot_damage() |
| `gun.power_for_speed` | call | gun.power_for_speed() |
| `gun.power_for_flight_time` | call | gun.power_for_flight_time() |
| `gun.lead` | call | gun.lead() |
| `gun.lead_point` | call | gun.lead_point() |
| `gun.aim` | control | gun.aim() |
| `gun.fire` | control | gun.fire() |
| `gun.get` | call | gun.get_<field>() |
| `scanner.last` | property | scanner.last() |
| `scanner.proximity_range` | property | scanner.proximity_range() |
| `scanner.reach` | call | scanner.reach() |
| `scanner.arc_for_reach` | call | scanner.arc_for_reach() |
| `scanner.set` | control | scanner.set() |
| `scanner.get` | call | scanner.get_<field>() |
| `sensors.contacts` | property | sensors.contacts() |
| `sensors.nearest` | property | sensors.nearest() |
| `sensors.cues` | property | sensors.cues() |
| `sensors.contact` | call | sensors.contact() |
| `sensors.position_of` | call | sensors.position_of() |
| `tracker.cue` | property | tracker.cue() |
| `tracker.nearest_bearing_rate` | property | tracker.nearest_bearing_rate() |
| `tracker.velocity` | call | tracker.velocity() |
| `tracker.position` | call | tracker.position() |
| `tracker.last_seen` | call | tracker.last_seen() |
| `tracker.configure` | call | tracker.configure() |
| `nav.zone` | property | nav.zone() |
| `nav.zone.margin` | call | nav.zone_margin() |
| `nav.zone.contains` | call | nav.zone_contains() |
| `nav.zone.distance_from` | call | nav.zone_distance_from() |
| `nav.storm` | property | nav.storm() |
| `nav.storm.margin` | call | nav.storm_margin() |
| `nav.storm.contains` | call | nav.storm_contains() |
| `nav.storm.distance_from` | call | nav.storm_distance_from() |
| `nav.hill` | property | nav.hill() |
| `nav.hill.margin` | call | nav.hill_margin() |
| `nav.hill.contains` | call | nav.hill_contains() |
| `nav.hill.distance_from` | call | nav.hill_distance_from() |
| `nav.pickups` | property | nav.pickups() |
| `nav.nearest_pickup` | call | nav.nearest_pickup() |
| `nav.field_distance` | call | nav.field_distance() |
| `score.me` | property | score.me() |
| `score.best` | property | score.best() |
| `score.armour` | property | score.armour() |
| `angles.normalize` | call | angles.normalize() |
| `angles.angle_diff` | call | angles.angle_diff() |
| `angles.bearing_to` | call | angles.bearing_to() |
| `angles.elevation_to` | call | angles.elevation_to() |
| `angles.direction` | call | angles.direction() |
| `angles.distance` | call | angles.distance() |
| `angles.clamp` | call | angles.clamp() |
| `status.set` | control | status.set() |
| `status.get` | call | status.get_<field>() |
| `raw.number` | call | raw.number() |
| `raw.text` | call | raw.text() |
| `raw.has` | call | raw.has() |
| `raw.flag` | call | raw.flag() |
| `raw.count` | call | raw.count() |
| `raw.line` | call | raw.line() |
| `raw.extra` | call | raw.extra() |
| `session.event` | property | session.event() |
| `session.next` | call | session.next() |
| `session.send` | call | session.send() |
| `session.forfeit` | call | session.forfeit() |
| `session.log` | call | session.log() |
| `session.feed` | call | session.feed() |
| `session.reply` | call | session.reply() |
