Run and debug a language binding or adapter
Start with your language's workbench project. Keep its tactic and launcher files together, change the tactic, then build and run the project. Hosted profiles supply the SDK and its dependencies; they do not need a copied binding in your source tree.
For local work, install the SDK and the runtime or compiler named in the
language's generated guide. The examples here use /opt/clunkyard/sdk.
The API name table and each linked language guide contain the
callable names, record shapes and complete small robots. Use
local trials to run a project against opponents.
Which process owns the turn?
A native binding converts language values to the core's C ABI in the
same process. Python's ctypes, Java's FFM and a compiled Lua module all
fit this description. They do not launch another robot for each query.
A process adapter keeps a tactic in another language process and forwards its generated API calls to a Python or Node binding. Forth, GNU APL and BQN use persistent tactic processes. CLIPS runs in Python through clipspy; MiniZinc solves a plan at startup and Python follows it. These are separate execution models, even though all use the same core for canonical queries, tracking and controls.
The arena meters the complete robot workload, including adapter processes and solver children. Choosing an adapter does not make its CPU free. Standard uses the launched runtime's Season 3 tier: Forth, APL, CLIPS and MiniZinc use the Python route; BQN uses Node. The VM's JVM profiles use the same configured JVM mode. Own time includes CPU and time spent sleeping or blocking, with measured scheduler/VM delays subtracted; a blocked tactic can exhaust its own-time limit. DSL operations and guest WebAssembly fuel are the Reflex routes. Resources explains the measurements and limits.
Forth, GNU APL and BQN
These profiles accept a tactic directly as bot.json's entry. The hosted
launcher starts the adapter and keeps one tactic process for the match.
For example:
{
"name": "My Forth robot",
"language": "forth",
"protocol": 2,
"entry": "tactic.fs",
"supportedModes": ["2d", "3d"],
"supportedRulesets": ["classic", "inertial-2d", "gravity-flat", "gravity-hills", "zero-g"]
}
Use language: "apl" with an .apl entry, or language: "bqn" with a
.bqn entry, for the other two. The starter projects also include Python
or JavaScript launchers so you can add host-language setup around the tactic.
The selected entry decides which route launches. You do not need to write
protocol JSON in the tactic.
| Profile | Tactic contract | Installed components | Local direct-tactic command |
|---|---|---|---|
| Forth | on-turn, optional on-start; preserve both stacks across a turn |
Python core binding, clunkyard_forth, Gforth and generated words |
Command below |
| GNU APL | Niladic OnTurn, optional OnStart; generated Cy* functions |
Python core binding, clunkyard_apl, GNU APL and generated functions |
Command below |
| BQN | Import cy from •args; Turn, optional Start |
Node core binding, clunkyard_bqn, CBQN and generated namespace |
Command below |
Run these from the project directory; the absolute tactic argument avoids runtime-dependent working-directory resolution:
SDK=/opt/clunkyard/sdk
PYTHONPATH="$SDK/bindings/forth:$SDK/bindings/python" \
python3 -c 'import clunkyard_forth,sys; clunkyard_forth.run(sys.argv[1])' \
"$PWD/tactic.fs"
PYTHONPATH="$SDK/bindings/apl:$SDK/bindings/python" \
python3 -c 'import clunkyard_apl,sys; clunkyard_apl.run(sys.argv[1])' \
"$PWD/tactic.apl"
NODE_PATH="$SDK/bindings/bqn:$SDK/bindings/javascript" \
"$SDK/bindings/javascript/run" \
-e 'require("clunkyard_bqn").run(process.argv[1])' "$PWD/tactic.bqn"
These commands wait for protocol input. Redirect a fixture to smoke-test
one, or put the command in a local manifest for a match. For the teaching
projects' bot.py or bot.js launchers, use the same environment prefixes
and pass the launcher file instead of -c/-e and its expression.
Forth's canonical words use kebab case, with floating-point arguments on
the floating stack, integer/enum arguments on the data stack and text as
address plus length. cy-result turns status into a presence flag and
throws negative errors. APL passes enclosed text and flattened records;
CyPresent distinguishes absent values from present empty text. BQN uses
record namespaces and @ for absence. Their generated guides specify list
and record order; do not infer it from a printed protocol object.
Keep the persistent tactic pipe available for adapter traffic. Log through
canonical session logging, for example Forth's cy-session-log, APL's
CySessionLog or BQN's cy.SessionLog, using that language's documented
argument convention. Unframed output on a tactic's RPC stdout can be read
as a reply. Adapter/runtime errors appear in stderr; an error return from a
core call should be handled before consuming its result. The adapters'
play helpers can run a tactic against recorded lines without a referee.
CLIPS and MiniZinc
Both profiles use a Python launcher as their entry. Include the .clp or
.mzn tactic beside it; the builder supplies the adapter package.
SDK=/opt/clunkyard/sdk
PYTHONPATH="$SDK/bindings/clips:$SDK/bindings/python" python3 bot.py
PYTHONPATH="$SDK/bindings/minizinc:$SDK/bindings/python" python3 bot.py
CLIPS uses clunkyard_clips.ClipsRobot and the installed
clipspy module. It loads the rules once, resets facts for each state,
restores match facts and asserts current observations before running rules.
Globals persist between turns. Canonical (cy-...) functions call the core;
output facts such as drive, scan, fire, status and forfeit are
another control surface. Later facts replace the fields they supply.
CLIPS router output is sent to stderr. Use a rule trace or a short status
fact to inspect why a rule fired, and check whether a more recent output
fact replaced the same control.
MiniZinc uses clunkyard_minizinc.Planner. Set model,
include the supplied clunkyard.mzn declarations in the model, and implement
Python on_turn using self.plan. model_data can add parameters and call
core_query while preparing them. The adapter compiles with MiniZinc,
solves with fzn-gecode and decodes the result once at hello; it does not
start a solver for each state. Live observations and controls remain the
Python binding's. The installed image supplies MiniZinc and Gecode.
Check model compilation/solver stderr when startup fails, then inspect plan
values separately from the turn logic. Solver JSON uses solver precision.
Shared JVM and .NET bindings
| Languages | Import/reference | Build or launch locally |
|---|---|---|
| Java | clunkyard.* from bindings/java/clunkyard.jar |
javac -cp "$SDK/bindings/java/clunkyard.jar" Bot.java; then java --enable-native-access=ALL-UNNAMED -cp ".:$SDK/bindings/java/clunkyard.jar" Bot |
| Kotlin | clunkyard.* from the same jar |
Compile with kotlinc and that jar on its classpath; launch the output's main class with the jar on the Java classpath. |
| Scala | clunkyard._ from the same jar |
Compile with scalac; include Scala's runtime and the binding jar on the Java classpath. |
| Clojure | Normal interop with clunkyard classes |
Put the binding jar and Clojure jar on the classpath; launch clojure.main bot.clj. |
| C#, F# | Clunkyard.dll, with the reference's Private=false |
dotnet publish -c Release -o out -p:ClunkyardSdk="$SDK"; launch with dotnet "$SDK/bindings/csharp/host/Clunkyard.Host.dll" out/bot.dll. Use the assembly name your project emits. |
| PowerShell | [Clunkyard.Robot] and shared assembly |
pwsh -NoProfile -File "$SDK/bindings/powershell/run.ps1" bot.ps1 |
Java, Kotlin, Scala and Clojure share Java's Foreign Function and Memory
binding to the native core. Use a JDK/runtime with the FFM API required by
the binding; the release image supplies it. --enable-native-access is
required by the launch recipe. Hosted JVM launches also use
-XX:TieredStopAtLevel=1, so local JVM timing without that flag is a
different runtime configuration.
C#, F# and PowerShell share one P/Invoke assembly. The .NET host loads the
installed binding while the robot artifact contains its own program and
dependencies. Launch PowerShell through its SDK script so the assembly is
loaded before robot class declarations are parsed. Do not copy an old
Clunkyard.dll into the project output. The shared native library is
libclunkyard.so.2; these bindings accept CLUNKYARD_LIB for a local path.
The generated APIs convert absent values to each family's optional/null
form. Their run loops log ordinary callback exceptions and send controls
already set; explicit Forfeit exceptions use the core's forfeit reply.
Import/classpath errors happen before a turn: fix those in the build/start
log before debugging tactics.
Other bindings in the robot process
Set SDK=/opt/clunkyard/sdk before using the prefixes in this table.
A local shared-library installation must make libclunkyard.so.2 available;
use CLUNKYARD_LIB only where that language's guide supports it. Packages
with compiled shims are installed in the builder, so the robot needs only
the runtime and the supplied package.
| Language | Bridge and import | Local launch |
|---|---|---|
| Python | ctypes; import clunkyard as cy |
PYTHONPATH="$SDK/bindings/python" python3 bot.py |
| R | Compiled .Call package; library(clunkyard) |
R_LIBS="$SDK/bindings/r" Rscript --vanilla bot.R |
| Ruby | Fiddle; require 'clunkyard' |
RUBYLIB="$SDK/bindings/ruby" ruby bot.rb |
| Perl | Compiled XS; use Clunkyard |
PERL5LIB="$SDK/bindings/perl" perl bot.pl |
| Lua | Lua 5.4 C module; require('clunkyard') |
LUA_PATH="$SDK/bindings/lua/?.lua;;" LUA_CPATH="$SDK/bindings/lua/?.so;;" lua5.4 bot.lua |
| PHP | PHP FFI; installed clunkyard.php |
php -d ffi.enable=preload -d include_path="$SDK/bindings/php" bot.php |
| Julia | ccall; using ClunkyardSDK |
JULIA_LOAD_PATH="$SDK/bindings/julia:@stdlib" julia bot.jl |
| Common Lisp | SBCL sb-alien; cy package |
sbcl --noinform --disable-debugger --load "$SDK/bindings/common-lisp/clunkyard.fasl" --script bot.lisp |
| Erlang | In-process NIF; clunkyard module |
erl -noshell -pa "$SDK/bindings/erlang" -pa . -s bot start after erlc compilation |
| Elixir | Facade over the Erlang NIF | elixir -pa "$SDK/bindings/erlang" -pa "$SDK/bindings/elixir" bot.exs |
| Prolog, CHR | SWI-Prolog foreign module; library(clunkyard) or library(clunkyard_chr) |
swipl -q -p library="$SDK/bindings/prolog" -s bot.pl -g main -t halt; CHR also needs -p library="$SDK/bindings/chr" |
| Dart | dart:ffi; package:clunkyard/clunkyard.dart |
Compile using the installed package configuration; run the resulting executable. |
| OCaml | Compiled C stubs; Clunkyard |
Link the installed clunkyard.cmxa; run the executable with the image's shared core. |
The hosted BEAM recipes constrain schedulers for the robot's single CPU; the Julia recipe uses its supplied system image and runtime options. Use the hosted-profile route when comparing their resource use. Package/version mismatches can fail before any ready reply: use the matching builder/runtime pair and check the native-loader error first.
Native toolchains and generated shims
These profiles compile a native executable. Their binding marshals calls in that process and links the core statically. Include/import paths and any precompiled binding object differ by language; follow its generated guide.
| Languages | SDK surface |
|---|---|
| C | Generated clunkyard.h; no extra wrapper |
| C++ | clunkyard.hpp |
| Rust | clunkyard crate passed with --extern |
| Go | import "clunkyard"; cgo package in the installed GOPATH, or the builder's module workspace |
| Hew | import hew.clunkyard; generated scalar/bytes C shim |
| Swift, Zig | Generated native C-compatible declarations and language wrappers |
| Haskell | Clunkyard; FFI and generated Storable records |
| Fortran | clunkyard module using ISO_C_BINDING |
| D | clunkyard interface and compiled object |
| Ada | Clunkyard package with C-convention records |
| Nim | clunkyard module through Nim's C backend |
| Mercury | Installed Mercury library; typed native references and records |
| Free Pascal | Clunkyard unit |
| FreeBASIC | clunkyard.bi and compiled loop helper |
| COBOL | clunkyard.cpy and generated by-reference C shim |
A generated shim bridges a calling convention or record representation; it
is not another tactic process. Keep compiled objects and module interfaces
from the same SDK and target architecture. The arena's runtime contains no
compiler. Linker errors naming cy_* usually mean the core archive is
missing or in the wrong link order; missing module/type declarations point
to an include/import path or a stale SDK copy.
The C API guide explains ownership, statuses, lengths and the session lifecycle underneath these bindings. Exception handling follows the language guide: for example, Hew returned errors are recoverable but a Hew panic ends the process; FreeBASIC runtime errors also end the process.
Node, DSL, WebAssembly and shell
JavaScript and TypeScript import clunkyard from the
installed Node package. It loads the core's WebAssembly build in Node;
this is an ordinary Standard Node robot. Launch through
"$SDK/bindings/javascript/run" with NODE_PATH="$SDK/bindings/javascript".
The launcher reserves the protocol descriptor before loading user code and
sends stdout diagnostics to stderr. TypeScript uses the same package and
types, compiled through the hosted TypeScript profile.
Vane, Cadence and Glyph run through the built-in interpreter,
which obtains its vocabulary and helper values from the same core. Local
source entries run as node src/dsl/runner.js vane /absolute/path/bot.vane
(or cadence/glyph). Their corresponding *-wasm profiles compile the
same source into a guest module. The interpreted profiles count DSL
operations; compiled guests count fuel and imports.
ABI 4 guests cover WAT and AssemblyScript, C, Rust and Zig
compiled to WebAssembly, as well as the compiled DSL profiles. The trusted
Wasmtime host owns protocol I/O; the guest receives clunkyard.* imports
and exports startup/turn functions. It has no WASI environment. A Node
binding internally using a WebAssembly core does not make the robot an
ABI 4 guest; its user code still runs in Node and is metered as Node.
The shell helper is a raw protocol 2 route.
Its launcher uses one long-running jq, whose module supplies JSON helpers
and the persistent turn/memory loop. It does not call the C ABI. Use the
shell starter's installed/copied helper arrangement and jq --unbuffered
so each reply reaches the referee. cy::log writes diagnostics to stderr.
Avoid spawning a command for every small calculation inside a turn.
Diagnose a failed start or turn
First reproduce with the project's actual launcher, imports and dependencies.
Use a short protocol fixture or the binding's play helper, then run a real
match. A process waiting for its first hello is normal when launched without
input. Build errors, missing shared libraries and invalid imports must be
fixed before a ready reply is possible.
Keep protocol output reserved for the session. Native stdio sessions move stdout diagnostics to stderr at open; Node does so in its launcher. Memory sessions leave process streams alone. Raw protocol programs must write and flush exactly the expected ready or act reply. For process adapters, also keep the tactic-to-adapter RPC pipe free of unrelated output.
Use status text/value to expose a decision in the viewer and stderr for
longer diagnostics. Optional absence is normal: check it before reading a
contact, a shot solution or a region. Ordinary callback exceptions generally
log and send controls already set; consult the binding guide for explicit
forfeit and uncatchable-error behaviour. A core session fault follows the
shared core recovery contract.
The installed routes above are selected by the hosted worker. Runtime keys and accounting families come from the rates implementation, and the language-specific surfaces are generated under sdk/bindings.