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

The Clunkyard robot API in Python, over the robot core's C ABI through
`ctypes` ([docs/design/robot-core.md](../design/robot-core.md),
section 5.3). `clunkyard/_api.py` is generated by
`protocol/v2/codegen/bindings/python.js` (`make api`); the run loop in
`clunkyard/__init__.py` and the loader in `clunkyard/_core.py` are written by
hand. [docs/sdk/names.md](names.md) gives every name.

- **Install:** `make install SDK=/opt/clunkyard/sdk` copies the package, compiled,
  into `$(SDK)/bindings/python`. It needs no compiler at run time.
- **Run:** `PYTHONPATH=/opt/clunkyard/sdk/bindings/python python3 bot.py`. The
  package loads `libclunkyard.so.2` through the loader's cache, or the file
  `CLUNKYARD_LIB` names.
- **Test:** `make test SDK=...` runs the installed package against every API
  vector, exact against the core.

Subsystems are attributes of the robot (`self.me`, `self.match`,
`self.scanner.last`); facts are properties and the rest are calls. A control
sets only the fields given, so `self.drive.set(turn=0.5)` keeps the throttle,
and `self.drive.get("throttle")` reads a field back. An absent result is
`None`. `cy.angles` holds the angle helpers, the core's to the last bit. Raise
`cy.Forfeit(code, message)` to forfeit; any other exception is logged with its
trace and the turn answers with what it had set.

## A tiny robot

```python
import clunkyard as cy


class Tiny(cy.Robot):
    def on_turn(self):
        target = self.sensors.nearest
        if target is None:
            self.drive.set(throttle=0.75, turn=0.25)
            self.scanner.set(heading=self.me.heading, arc=40)
            self.status.set("SEARCH", self.scanner.last.arc)
            return
        power = self.gun.power_for_flight_time(target.range, 1)
        aim = self.gun.lead(target, power)
        if aim is None:
            self.gun.aim(target.bearing, target.elevation)
        else:
            self.gun.aim(aim.heading, aim.pitch)
        if self.gun.can_fire(power, 10):
            self.gun.fire(power)
        self.drive.set(throttle=0.5, turn=cy.angles.clamp(cy.angles.angle_diff(target.bearing, self.me.heading)))
        self.scanner.set(heading=target.bearing, arc=10)
        self.status.set("TRACK", target.range)


cy.run(Tiny())
```

## Names

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

| Canonical | Form | Python |
| --- | --- | --- |
| `match.mode` | property | `self.match.mode` |
| `match.ruleset` | property | `self.match.ruleset` |
| `match.control` | property | `self.match.control` |
| `match.robots` | property | `self.match.robots` |
| `match.is_3d` | property | `self.match.is_3d` |
| `match.tick_rate` | property | `self.match.tick_rate` |
| `match.max_ticks` | property | `self.match.max_ticks` |
| `match.robot_id` | call | `self.match.robot_id(slot)` |
| `clock.tick` | property | `self.clock.tick` |
| `clock.seconds` | property | `self.clock.seconds` |
| `clock.remaining_ticks` | property | `self.clock.remaining_ticks` |
| `arena.size` | property | `self.arena.size` |
| `arena.gravity` | property | `self.arena.gravity` |
| `arena.terrain_kind` | property | `self.arena.terrain_kind` |
| `arena.ground_height` | call | `self.arena.ground_height(x, y)` |
| `me.health` | property | `self.me.health` |
| `me.position` | property | `self.me.position` |
| `me.velocity` | property | `self.me.velocity` |
| `me.heading` | property | `self.me.heading` |
| `me.pitch` | property | `self.me.pitch` |
| `me.roll` | property | `self.me.roll` |
| `me.angular_velocity` | property | `self.me.angular_velocity` |
| `me.attitude` | property | `self.me.attitude` |
| `me.grounded` | property | `self.me.grounded` |
| `me.altitude` | property | `self.me.altitude` |
| `me.applied` | property | `self.me.applied` |
| `battery.charge` | property | `self.battery.charge` |
| `battery.capacity` | property | `self.battery.capacity` |
| `battery.regen` | property | `self.battery.regen` |
| `battery.sustainable_drive` | property | `self.battery.sustainable_drive` |
| `battery.compute` | property | `self.battery.compute` |
| `battery.drive_cost` | call | `self.battery.drive_cost(fraction)` |
| `drive.hover_lift` | property | `self.drive.hover_lift` |
| `drive.set` | control | `self.drive.set(throttle=None, turn=None, pitch_turn=None)` |
| `drive.forces` | control | `self.drive.forces(thrust=None, strafe=None, lift=None, yaw_torque=None, pitch_torque=None, roll_torque=None, brake=None)` |
| `drive.get` | call | `self.drive.get("throttle")` |
| `gun.aim_now` | property | `self.gun.aim_now` |
| `gun.cooldown_ticks` | property | `self.gun.cooldown_ticks` |
| `gun.spread` | property | `self.gun.spread` |
| `gun.max_power` | property | `self.gun.max_power` |
| `gun.damage_scale` | property | `self.gun.damage_scale` |
| `gun.can_fire` | call | `self.gun.can_fire(power, reserve)` |
| `gun.shot_speed` | call | `self.gun.shot_speed(power)` |
| `gun.shot_cost` | call | `self.gun.shot_cost(power)` |
| `gun.reload_ticks` | call | `self.gun.reload_ticks(power)` |
| `gun.shot_damage` | call | `self.gun.shot_damage(power)` |
| `gun.power_for_speed` | call | `self.gun.power_for_speed(speed)` |
| `gun.power_for_flight_time` | call | `self.gun.power_for_flight_time(range, seconds)` |
| `gun.lead` | call | `self.gun.lead(contact, power)` |
| `gun.lead_point` | call | `self.gun.lead_point(position, velocity, power)` |
| `gun.aim` | control | `self.gun.aim(heading=None, pitch=None)` |
| `gun.fire` | control | `self.gun.fire(power=None)` |
| `gun.get` | call | `self.gun.get("heading")` |
| `scanner.last` | property | `self.scanner.last` |
| `scanner.proximity_range` | property | `self.scanner.proximity_range` |
| `scanner.reach` | call | `self.scanner.reach(arc)` |
| `scanner.arc_for_reach` | call | `self.scanner.arc_for_reach(range)` |
| `scanner.set` | control | `self.scanner.set(heading=None, arc=None, pitch=None)` |
| `scanner.get` | call | `self.scanner.get("heading")` |
| `sensors.contacts` | property | `self.sensors.contacts` |
| `sensors.nearest` | property | `self.sensors.nearest` |
| `sensors.cues` | property | `self.sensors.cues` |
| `sensors.contact` | call | `self.sensors.contact(slot)` |
| `sensors.position_of` | call | `self.sensors.position_of(contact)` |
| `tracker.cue` | property | `self.tracker.cue` |
| `tracker.nearest_bearing_rate` | property | `self.tracker.nearest_bearing_rate` |
| `tracker.velocity` | call | `self.tracker.velocity(slot)` |
| `tracker.position` | call | `self.tracker.position(slot)` |
| `tracker.last_seen` | call | `self.tracker.last_seen(slot)` |
| `tracker.configure` | call | `self.tracker.configure(smoothing=0.5, forget_ticks=40, cue_ticks=60)` |
| `nav.zone` | property | `self.nav.zone` |
| `nav.zone.margin` | call | `self.nav.zone.margin(point)` |
| `nav.zone.contains` | call | `self.nav.zone.contains(point, margin=0)` |
| `nav.zone.distance_from` | call | `self.nav.zone.distance_from(point)` |
| `nav.storm` | property | `self.nav.storm` |
| `nav.storm.margin` | call | `self.nav.storm.margin(point)` |
| `nav.storm.contains` | call | `self.nav.storm.contains(point, margin=0)` |
| `nav.storm.distance_from` | call | `self.nav.storm.distance_from(point)` |
| `nav.hill` | property | `self.nav.hill` |
| `nav.hill.margin` | call | `self.nav.hill.margin(point)` |
| `nav.hill.contains` | call | `self.nav.hill.contains(point, margin=0)` |
| `nav.hill.distance_from` | call | `self.nav.hill.distance_from(point)` |
| `nav.pickups` | property | `self.nav.pickups` |
| `nav.nearest_pickup` | call | `self.nav.nearest_pickup(kind=None)` |
| `nav.field_distance` | call | `self.nav.field_distance(a, b)` |
| `score.me` | property | `self.score.me` |
| `score.best` | property | `self.score.best` |
| `score.armour` | property | `self.score.armour` |
| `angles.normalize` | call | `cy.angles.normalize(degrees)` |
| `angles.angle_diff` | call | `cy.angles.angle_diff(target, current)` |
| `angles.bearing_to` | call | `cy.angles.bearing_to(from_, to)` |
| `angles.elevation_to` | call | `cy.angles.elevation_to(from_, to)` |
| `angles.direction` | call | `cy.angles.direction(heading, pitch)` |
| `angles.distance` | call | `cy.angles.distance(a, b)` |
| `angles.clamp` | call | `cy.angles.clamp(value, low=-1, high=1)` |
| `status.set` | control | `self.status.set(text=None, value=None)` |
| `status.get` | call | `self.status.get("text")` |
| `raw.number` | call | `self.raw.number(path)` |
| `raw.text` | call | `self.raw.text(path)` |
| `raw.has` | call | `self.raw.has(path)` |
| `raw.flag` | call | `self.raw.flag(path)` |
| `raw.count` | call | `self.raw.count(path)` |
| `raw.line` | call | `self.raw.line()` |
| `raw.extra` | call | `self.raw.extra(json)` |
| `session.next` | call | `self.session.next()` |
| `session.send` | call | `self.session.send()` |
| `session.forfeit` | call | `self.session.forfeit(code, message)` |
| `session.log` | call | `self.session.log(line)` |
