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

The installed adapter solves your model once at hello, then Python plays
its plan through the canonical robot core. `clunkyard.mzn` declares typed
hello parameters generated from `protocol/v2/schema.json`. Its `core_*`
tables are computed through core queries; the model contains no copied game
formula. Install with `make install SDK=/opt/clunkyard/sdk` in this directory,
after the Python binding. Run with
`PYTHONPATH=/opt/clunkyard/sdk/bindings/minizinc:/opt/clunkyard/sdk/bindings/python python3 bot.py`.

Subclass `Planner`, set `model`, and implement `on_turn`. `self.plan` reads
the solver's JSON output. Override `model_data` for extra parameters;
`self.core_query("gun.shot_speed", power)` calls any canonical core API while
preparing them. This hook returns the Python binding's typed records and
optional `None` values. Controls, helpers, tracker and live turn data use
the Python API. MiniZinc runs only at hello; it never starts a solver per
turn. `run` and `play` accept a `Planner`; exceptions and forfeits use the
Python binding's behaviour.

`core_steer` and `core_home` index -180..180. `core_power` and `core_cue_arc`
index 0..`core_range_steps` with `core_range_step` pixels between entries.
`core_sustainable_drive` and `core_hover_lift` are core values. `Plan.at`
rounds a requested index and bounds it to the table. Solver JSON represents
floating point numbers at solver precision.

## A tiny robot

Save this model as `tiny.mzn`:

```minizinc
include "clunkyard.mzn";
output ["{}"];
```

Save this launcher as `bot.py`:

```python
import clunkyard as cy
import clunkyard_minizinc as mz


class Tiny(mz.Planner):
    model = "tiny.mzn"

    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)


mz.run(Tiny())
```

`make test` checks the complete query hook against the API vectors and runs
six real solver plans, comparing their tables with core query results.
The complete API reference is [MiniZinc names](minizinc.md).

## Names

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

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