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

The Clunkyard robot API in PHP, through the `FFI` extension and the core's
C ABI. The API classes, records and declarations are generated from
`protocol/v2/api.json`; only the run loop and exception mapping are written
by hand. Tracking, physics, parsing and serialization stay in the core.

Install with `make install SDK=/opt/clunkyard/sdk`. Run with
`php -d ffi.enable=preload -d include_path=/opt/clunkyard/sdk/bindings/php
bot.php`. The binding loads `libclunkyard.so.2`, or `CLUNKYARD_LIB`.

Subsystems are properties and facts are methods. Names use camelCase.
Optional results are `null`; records have public fields and enum values
use their protocol strings. Controls accept positional or named arguments
and change only supplied fields in the turn. `drive->get('throttle')`
reads a pending field. Throw `new Clunkyard\Forfeit(code, message)` to
forfeit. Other exceptions are logged with their trace and send the controls
already set. `Clunkyard\play(robot, lines)` plays in memory for tests.

## A tiny robot

```php
<?php
require 'clunkyard.php';
use Clunkyard as cy;

class Tiny extends cy\Robot {
    public function onTurn():void{
        $target=$this->sensors->nearest();
        if($target===null){
            $this->drive->set(throttle:0.75,turn:0.25);
            $this->scanner->set($this->me->heading(),40);
            $this->status->set('SEARCH',$this->scanner->last()->arc);
            return;
        }
        $power=$this->gun->powerForFlightTime($target->range,1);
        $aim=$this->gun->lead($target,$power);
        $this->gun->aim($aim?->heading??$target->bearing,$aim?->pitch??$target->elevation);
        if($this->gun->canFire($power,10))$this->gun->fire($power);
        $this->drive->set(throttle:0.5,turn:$this->angles->clamp($this->angles->angleDiff($target->bearing,$this->me->heading())));
        $this->scanner->set($target->bearing,10);
        $this->status->set('TRACK',$target->range);
    }
}
cy\run(new Tiny());
```

`make test SDK=...` compares every API vector and emitted act with the
core's transcript, including exact floating-point bits. The README robot
also participates in the cross-language identity check.

## Names

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

| Canonical | Form | PHP |
| --- | --- | --- |
| `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() |
| `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() |
| `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() |
| `drive.hover_lift` | property | drive->hoverLift() |
| `drive.set` | control | drive->set() |
| `drive.forces` | control | drive->forces() |
| `drive.get` | call | drive->get() |
| `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() |
| `gun.shot_speed` | call | gun->shotSpeed() |
| `gun.shot_cost` | call | gun->shotCost() |
| `gun.reload_ticks` | call | gun->reloadTicks() |
| `gun.shot_damage` | call | gun->shotDamage() |
| `gun.power_for_speed` | call | gun->powerForSpeed() |
| `gun.power_for_flight_time` | call | gun->powerForFlightTime() |
| `gun.lead` | call | gun->lead() |
| `gun.lead_point` | call | gun->leadPoint() |
| `gun.aim` | control | gun->aim() |
| `gun.fire` | control | gun->fire() |
| `gun.get` | call | gun->get() |
| `scanner.last` | property | scanner->last() |
| `scanner.proximity_range` | property | scanner->proximityRange() |
| `scanner.reach` | call | scanner->reach() |
| `scanner.arc_for_reach` | call | scanner->arcForReach() |
| `scanner.set` | control | scanner->set() |
| `scanner.get` | call | scanner->get() |
| `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->positionOf() |
| `tracker.cue` | property | tracker->cue() |
| `tracker.nearest_bearing_rate` | property | tracker->nearestBearingRate() |
| `tracker.velocity` | call | tracker->velocity() |
| `tracker.position` | call | tracker->position() |
| `tracker.last_seen` | call | tracker->lastSeen() |
| `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->distanceFrom() |
| `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->distanceFrom() |
| `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->distanceFrom() |
| `nav.pickups` | property | nav->pickups() |
| `nav.nearest_pickup` | call | nav->nearestPickup() |
| `nav.field_distance` | call | nav->fieldDistance() |
| `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->angleDiff() |
| `angles.bearing_to` | call | angles->bearingTo() |
| `angles.elevation_to` | call | angles->elevationTo() |
| `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() |
| `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() |
