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

`Clunkyard` provides the core's C functions as Haskell `IO` actions, native
record types with generated `Storable` instances and camel case names such
as `cySensorsNearest`. Functions return a status and copy values through
pointers. `check robot status` throws `CoreError` for a negative status;
`query robot action` copies a required result and `optional robot action`
returns `Nothing` for `cyNone`.

Install with `make install SDK=/opt/clunkyard/sdk`. Compile with
`ghc -O2 -i/opt/clunkyard/sdk/bindings/haskell -odir build -hidir build bot.hs /opt/clunkyard/sdk/lib/libclunkyard.a -lpthread -ldl -lm -o bot`.
Create `build` first. The core links statically; only the executable goes in
the robot artifact.

`run onTurn onStart` owns the protocol loop and closes the session. Both
callbacks receive the core handle; use `const (pure ())` when no start hook
is needed. Exceptions are logged and the turn sends controls already set.
Throw `Forfeit code message` for an explicit forfeit. A core fault sends the
empty reply and continues. `play lines onTurn onStart` returns the replies
from a memory session. `withText` scopes a UTF-8 text pointer and byte count.

## A tiny robot

```haskell
module Main where
import Clunkyard
import Foreign
import Control.Monad
turnBot robot = do
  target <- optional robot (cySensorsNearest robot)
  heading <- query robot (cyMeHeading robot)
  case target of
    Nothing -> do
      cyDriveThrottle robot 0.75 >>= check robot
      cyDriveTurn robot 0.25 >>= check robot
      cyScannerHeading robot heading >>= check robot
      cyScannerArc robot 40 >>= check robot
      lastScan <- query robot (cyScannerLast robot)
      withText "SEARCH" (cyStatusText robot) >>= check robot
      cyStatusValue robot (scanArc lastScan) >>= check robot
    Just t -> do
      power <- query robot (cyGunPowerForFlightTime robot (contactRange t) 1)
      lead <- with t $ \contact -> optional robot (cyGunLead robot contact power)
      let (aimHeading,aimPitch) = maybe (contactBearing t,contactElevation t) (\s -> (solutionHeading s,solutionPitch s)) lead
      cyGunAimHeading robot aimHeading >>= check robot
      cyGunAimPitch robot aimPitch >>= check robot
      canFire <- query robot (cyGunCanFire robot power 10)
      when (canFire /= 0) $ do
        cyGunFire robot >>= check robot
        cyGunPower robot power >>= check robot
      delta <- query robot (cyAnglesAngleDiff (contactBearing t) heading)
      control <- query robot (cyAnglesClamp delta (-1) 1)
      cyDriveThrottle robot 0.5 >>= check robot
      cyDriveTurn robot control >>= check robot
      cyScannerHeading robot (contactBearing t) >>= check robot
      cyScannerArc robot 10 >>= check robot
      withText "TRACK" (cyStatusText robot) >>= check robot
      cyStatusValue robot (contactRange t) >>= check robot
main = run turnBot (const (pure ()))
```

`make test` checks every Haskell declaration against the core's full vectors,
including typed foreign arguments, records, exact floating-point results and
serialized acts.

## Names

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

| Canonical | Form | Haskell |
| --- | --- | --- |
| `match.mode` | property | cyMatchMode |
| `match.ruleset` | property | cyMatchRuleset |
| `match.control` | property | cyMatchControl |
| `match.robots` | property | cyMatchRobots |
| `match.is_3d` | property | cyMatchIs3d |
| `match.tick_rate` | property | cyMatchTickRate |
| `match.max_ticks` | property | cyMatchMaxTicks |
| `match.robot_id` | call | cyRobotId |
| `clock.tick` | property | cyClockTick |
| `clock.seconds` | property | cyClockSeconds |
| `clock.remaining_ticks` | property | cyClockRemainingTicks |
| `arena.size` | property | cyArenaSize |
| `arena.gravity` | property | cyArenaGravity |
| `arena.terrain_kind` | property | cyArenaTerrainKind |
| `arena.ground_height` | call | cyArenaGroundHeight |
| `me.health` | property | cyMeHealth |
| `me.position` | property | cyMePosition |
| `me.velocity` | property | cyMeVelocity |
| `me.heading` | property | cyMeHeading |
| `me.pitch` | property | cyMePitch |
| `me.roll` | property | cyMeRoll |
| `me.angular_velocity` | property | cyMeAngularVelocity |
| `me.attitude` | property | cyMeAttitude |
| `me.grounded` | property | cyMeGrounded |
| `me.altitude` | property | cyMeAltitude |
| `me.applied` | property | cyMeApplied |
| `battery.charge` | property | cyBatteryCharge |
| `battery.capacity` | property | cyBatteryCapacity |
| `battery.regen` | property | cyBatteryRegen |
| `battery.sustainable_drive` | property | cyBatterySustainableDrive |
| `battery.compute` | property | cyBatteryCompute |
| `battery.drive_cost` | call | cyBatteryDriveCost |
| `drive.hover_lift` | property | cyDriveHoverLift |
| `drive.set` | control | cyDriveThrottle, cyDriveTurn, cyDrivePitchTurn |
| `drive.forces` | control | cyDriveThrust, cyDriveStrafe, cyDriveLift, cyDriveYawTorque, cyDrivePitchTorque, cyDriveRollTorque, cyDriveBrake |
| `drive.get` | call | cyDriveGetThrottle, cyDriveGetTurn, cyDriveGetPitchTurn, cyDriveGetThrust, cyDriveGetStrafe, cyDriveGetLift, cyDriveGetYawTorque, cyDriveGetPitchTorque, cyDriveGetRollTorque, cyDriveGetBrake |
| `gun.aim_now` | property | cyGunAimNow |
| `gun.cooldown_ticks` | property | cyGunCooldownTicks |
| `gun.spread` | property | cyGunSpread |
| `gun.max_power` | property | cyGunMaxPower |
| `gun.damage_scale` | property | cyGunDamageScale |
| `gun.can_fire` | call | cyGunCanFire |
| `gun.shot_speed` | call | cyGunShotSpeed |
| `gun.shot_cost` | call | cyGunShotCost |
| `gun.reload_ticks` | call | cyGunReloadTicks |
| `gun.shot_damage` | call | cyGunShotDamage |
| `gun.power_for_speed` | call | cyGunPowerForSpeed |
| `gun.power_for_flight_time` | call | cyGunPowerForFlightTime |
| `gun.lead` | call | cyGunLead |
| `gun.lead_point` | call | cyGunLeadPoint |
| `gun.aim` | control | cyGunAimHeading, cyGunAimPitch |
| `gun.fire` | control | cyGunFire, cyGunPower |
| `gun.get` | call | cyGunGetAimHeading, cyGunGetAimPitch, cyGunGetFire, cyGunGetPower |
| `scanner.last` | property | cyScannerLast |
| `scanner.proximity_range` | property | cyScannerProximityRange |
| `scanner.reach` | call | cyScannerReach |
| `scanner.arc_for_reach` | call | cyScannerArcForReach |
| `scanner.set` | control | cyScannerHeading, cyScannerArc, cyScannerPitch |
| `scanner.get` | call | cyScannerGetHeading, cyScannerGetArc, cyScannerGetPitch |
| `sensors.contacts` | property | cySensorsContacts |
| `sensors.nearest` | property | cySensorsNearest |
| `sensors.cues` | property | cySensorsCues |
| `sensors.contact` | call | cySensorsContact |
| `sensors.position_of` | call | cySensorsPositionOf |
| `tracker.cue` | property | cyTrackerCue |
| `tracker.nearest_bearing_rate` | property | cyTrackerNearestBearingRate |
| `tracker.velocity` | call | cyTrackerVelocity |
| `tracker.position` | call | cyTrackerPosition |
| `tracker.last_seen` | call | cyTrackerLastSeen |
| `tracker.configure` | call | cyTrackerConfigure |
| `nav.zone` | property | cyNavZone |
| `nav.zone.margin` | call | cyNavZoneMargin |
| `nav.zone.contains` | call | cyNavZoneContains |
| `nav.zone.distance_from` | call | cyNavZoneDistanceFrom |
| `nav.storm` | property | cyNavStorm |
| `nav.storm.margin` | call | cyNavStormMargin |
| `nav.storm.contains` | call | cyNavStormContains |
| `nav.storm.distance_from` | call | cyNavStormDistanceFrom |
| `nav.hill` | property | cyNavHill |
| `nav.hill.margin` | call | cyNavHillMargin |
| `nav.hill.contains` | call | cyNavHillContains |
| `nav.hill.distance_from` | call | cyNavHillDistanceFrom |
| `nav.pickups` | property | cyNavPickups |
| `nav.nearest_pickup` | call | cyNavNearestPickup |
| `nav.field_distance` | call | cyNavFieldDistance |
| `score.me` | property | cyScoreMe |
| `score.best` | property | cyScoreBest |
| `score.armour` | property | cyScoreArmour |
| `angles.normalize` | call | cyAnglesNormalize |
| `angles.angle_diff` | call | cyAnglesAngleDiff |
| `angles.bearing_to` | call | cyAnglesBearingTo |
| `angles.elevation_to` | call | cyAnglesElevationTo |
| `angles.direction` | call | cyAnglesDirection |
| `angles.distance` | call | cyAnglesDistance |
| `angles.clamp` | call | cyAnglesClamp |
| `status.set` | control | cyStatusText, cyStatusValue |
| `status.get` | call | cyStatusGetText, cyStatusGetValue |
| `raw.number` | call | cyRawNumber |
| `raw.text` | call | cyRawText |
| `raw.has` | call | cyRawHas |
| `raw.flag` | call | cyRawFlag |
| `raw.count` | call | cyRawCount |
| `raw.line` | call | cyRawLine |
| `raw.extra` | call | cyRawExtra |
| `session.event` | property | cySessionEvent |
| `session.next` | call | cySessionNext |
| `session.send` | call | cySessionSend |
| `session.forfeit` | call | cySessionForfeit |
| `session.log` | call | cySessionLog |
| `session.feed` | call | cySessionFeed |
| `session.reply` | call | cySessionReply |
