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

The Clunkyard robot API in Java, over the robot core's C ABI through the
Foreign Function and Memory API on JDK 25
([docs/design/robot-core.md](../design/robot-core.md), sections 5.3
and 5.4). The classes are generated by `protocol/v2/codegen/bindings/java.js`
(`make api`), except `Robot`, `Clunkyard`, `Forfeit` and
`ClunkyardException`, which are written by hand, as is the reflective
conformance test, `test/TestConformance.java`.
[docs/sdk/names.md](names.md) gives every name.

- **Install:** `make install SDK=/opt/clunkyard/sdk` builds
  `$(SDK)/bindings/java/clunkyard.jar`.
- **Build and run:** `javac -cp /opt/clunkyard/sdk/bindings/java/clunkyard.jar
  Bot.java`, then `java --enable-native-access=ALL-UNNAMED -cp
  .:/opt/clunkyard/sdk/bindings/java/clunkyard.jar Bot`. The jar loads
  `libclunkyard.so.2` through the loader's cache, or the file `CLUNKYARD_LIB`
  names.
- **Test:** `make test SDK=...` runs the installed jar against every API
  vector, exact against the core.

Subsystems are methods of the robot (`me()`, `match()`,
`scanner().last()`). An absent result is an empty `Optional`,
`OptionalDouble` or `OptionalInt`. A control takes its fields in order
(`gun().aim(heading, pitch)`), and one with several fields also takes a
builder (`drive().set(Drive.turn(0.5))`); either way only the fields given are
set, so a later call merges, and `drive().get("throttle")` reads one back.
`angles()` holds the angle helpers, the core's to the last bit, so no robot
needs `Math.atan2`. Throw `new 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

```java
import clunkyard.*;

public final class Bot extends Robot {
    @Override protected void onTurn() {
        var nearest = sensors().nearest();
        if (nearest.isEmpty()) {
            drive().set(Drive.throttle(0.75).turn(0.25));
            scanner().set(me().heading(), 40);
            status().set("SEARCH", scanner().last().arc());
            return;
        }
        Contact target = nearest.get();
        double power = gun().powerForFlightTime(target.range(), 1);
        var aim = gun().lead(target, power);
        if (aim.isEmpty()) gun().aim(target.bearing(), target.elevation());
        else gun().aim(aim.get().heading(), aim.get().pitch());
        if (gun().canFire(power, 10)) gun().fire(power);
        drive().set(Drive.throttle(0.5).turn(angles().clamp(angles().angleDiff(target.bearing(), me().heading()))));
        scanner().set(target.bearing(), 10);
        status().set("TRACK", target.range());
    }

    public static void main(String[] args) {
        Clunkyard.run(new Bot());
    }
}
```

## Names

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

| Canonical | Form | Java |
| --- | --- | --- |
| `match.mode` | property | `match().mode()` |
| `match.ruleset` | property | `match().ruleset()` |
| `match.control` | property | `match().control()` |
| `match.robots` | property | `match().robots()` |
| `match.is_3d` | property | `match().is3d()` |
| `match.tick_rate` | property | `match().tickRate()` |
| `match.max_ticks` | property | `match().maxTicks()` |
| `match.robot_id` | call | `match().robotId(slot)` |
| `clock.tick` | property | `clock().tick()` |
| `clock.seconds` | property | `clock().seconds()` |
| `clock.remaining_ticks` | property | `clock().remainingTicks()` |
| `arena.size` | property | `arena().size()` |
| `arena.gravity` | property | `arena().gravity()` |
| `arena.terrain_kind` | property | `arena().terrainKind()` |
| `arena.ground_height` | call | `arena().groundHeight(x, y)` |
| `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().angularVelocity()` |
| `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().sustainableDrive()` |
| `battery.compute` | property | `battery().compute()` |
| `battery.drive_cost` | call | `battery().driveCost(fraction)` |
| `drive.hover_lift` | property | `drive().hoverLift()` |
| `drive.set` | control | `drive().set(throttle, turn, pitchTurn)`, `drive().set(Drive.pitchTurn(x))` |
| `drive.forces` | control | `drive().forces(thrust, strafe, lift, yawTorque, pitchTorque, rollTorque, brake)`, `drive().forces(Drive.brake(x))` |
| `drive.get` | call | `drive().get("throttle")` |
| `gun.aim_now` | property | `gun().aimNow()` |
| `gun.cooldown_ticks` | property | `gun().cooldownTicks()` |
| `gun.spread` | property | `gun().spread()` |
| `gun.max_power` | property | `gun().maxPower()` |
| `gun.damage_scale` | property | `gun().damageScale()` |
| `gun.can_fire` | call | `gun().canFire(power, reserve)` |
| `gun.shot_speed` | call | `gun().shotSpeed(power)` |
| `gun.shot_cost` | call | `gun().shotCost(power)` |
| `gun.reload_ticks` | call | `gun().reloadTicks(power)` |
| `gun.shot_damage` | call | `gun().shotDamage(power)` |
| `gun.power_for_speed` | call | `gun().powerForSpeed(speed)` |
| `gun.power_for_flight_time` | call | `gun().powerForFlightTime(range, seconds)` |
| `gun.lead` | call | `gun().lead(contact, power)` |
| `gun.lead_point` | call | `gun().leadPoint(position, velocity, power)` |
| `gun.aim` | control | `gun().aim(heading, pitch)`, `gun().aim(Gun.pitch(x))` |
| `gun.fire` | control | `gun().fire(power)` |
| `gun.get` | call | `gun().get("heading")` |
| `scanner.last` | property | `scanner().last()` |
| `scanner.proximity_range` | property | `scanner().proximityRange()` |
| `scanner.reach` | call | `scanner().reach(arc)` |
| `scanner.arc_for_reach` | call | `scanner().arcForReach(range)` |
| `scanner.set` | control | `scanner().set(heading, arc, pitch)`, `scanner().set(Scanner.pitch(x))` |
| `scanner.get` | call | `scanner().get("heading")` |
| `sensors.contacts` | property | `sensors().contacts()` |
| `sensors.nearest` | property | `sensors().nearest()` |
| `sensors.cues` | property | `sensors().cues()` |
| `sensors.contact` | call | `sensors().contact(slot)` |
| `sensors.position_of` | call | `sensors().positionOf(contact)` |
| `tracker.cue` | property | `tracker().cue()` |
| `tracker.nearest_bearing_rate` | property | `tracker().nearestBearingRate()` |
| `tracker.velocity` | call | `tracker().velocity(slot)` |
| `tracker.position` | call | `tracker().position(slot)` |
| `tracker.last_seen` | call | `tracker().lastSeen(slot)` |
| `tracker.configure` | call | `tracker().configure(smoothing, forgetTicks, cueTicks)` |
| `nav.zone` | property | `nav().zone()` |
| `nav.zone.margin` | call | `nav().zone().margin(point)` |
| `nav.zone.contains` | call | `nav().zone().contains(point, margin)` |
| `nav.zone.distance_from` | call | `nav().zone().distanceFrom(point)` |
| `nav.storm` | property | `nav().storm()` |
| `nav.storm.margin` | call | `nav().storm().margin(point)` |
| `nav.storm.contains` | call | `nav().storm().contains(point, margin)` |
| `nav.storm.distance_from` | call | `nav().storm().distanceFrom(point)` |
| `nav.hill` | property | `nav().hill()` |
| `nav.hill.margin` | call | `nav().hill().margin(point)` |
| `nav.hill.contains` | call | `nav().hill().contains(point, margin)` |
| `nav.hill.distance_from` | call | `nav().hill().distanceFrom(point)` |
| `nav.pickups` | property | `nav().pickups()` |
| `nav.nearest_pickup` | call | `nav().nearestPickup(kind)` |
| `nav.field_distance` | call | `nav().fieldDistance(a, b)` |
| `score.me` | property | `score().me()` |
| `score.best` | property | `score().best()` |
| `score.armour` | property | `score().armour()` |
| `angles.normalize` | call | `angles().normalize(degrees)` |
| `angles.angle_diff` | call | `angles().angleDiff(target, current)` |
| `angles.bearing_to` | call | `angles().bearingTo(from, to)` |
| `angles.elevation_to` | call | `angles().elevationTo(from, to)` |
| `angles.direction` | call | `angles().direction(heading, pitch)` |
| `angles.distance` | call | `angles().distance(a, b)` |
| `angles.clamp` | call | `angles().clamp(value, low, high)` |
| `status.set` | control | `status().set(text, value)`, `status().set(Status.value(x))` |
| `status.get` | call | `status().get("text")` |
| `raw.number` | call | `raw().number(path)` |
| `raw.text` | call | `raw().text(path)` |
| `raw.has` | call | `raw().has(path)` |
| `raw.flag` | call | `raw().flag(path)` |
| `raw.count` | call | `raw().count(path)` |
| `raw.line` | call | `raw().line()` |
| `raw.extra` | call | `raw().extra(json)` |
| `session.next` | call | `session().next()` |
| `session.send` | call | `session().send()` |
| `session.forfeit` | call | `session().forfeit(code, message)` |
| `session.log` | call | `session().log(line)` |
