A controller in C, compiled to WebAssembly in the sandbox and checked by its trajectory

The results on this page were measured in real runs through plimsoll's docker tier (container) on 2026-09-24; the inputs the runs were given are named as inputs where they appear. The caller sent two files: controller.c, a controller that swings a hanging pole up and balances it, and controller.js, a Node shim with no arithmetic of its own. The run's first step compiled the C to WebAssembly inside the sandbox, with no network. The later steps handed the result to the runner baked into the image, which pushed a simulated cart with a pole hinged on it (the plant, control engineering's word for the thing being controlled, itself compiled to WebAssembly) through the controller one 10 ms tick at a time and recorded every tick. The SHA-256 of that record is the fingerprint.

What the plant did under the C controller, scenario

t = 0.00 s

The browser only replays the runner's record; no physics runs here. Loops every 20 s.

The verdict

controller.wasm, compiled in the sandbox
The same source compiled again, in a second run
IDENTICAL
The same scenario, second run
IDENTICAL
The JavaScript controller it was ported from
DIFFERS
The same C, built against Node's Math.cos
SAME AS JAVASCRIPT
Where the C record differs from the JavaScript one

controller.c, compiled inside the run

// A cart-pole swing-up controller in C, compiled to WebAssembly inside the sandbox
// and run against the plant /models/cartpole.wasm.
//
// The law: while the pole is down, drive the cart so the pole's mechanical energy
// climbs toward its upright value (push in the direction that adds energy, with a
// small centring term so the cart stays on the track). Once the pole is near
// upright and slow, hand over to a linear balancing law with the angle wrapped
// into [-pi, pi]. Pole mass 0.1 kg and half-length 0.5 m are the plant's defaults;
// the cart mass is not known and the law does not need it.
//
// The contract with controller.js, the Node shim the runner spawns: the module exports
// one function, control, called once per tick with the state and the tick index,
// returning the force in newtons. State between ticks lives in globals, because a
// module instance lives for the whole episode. The module must import nothing: the
// shim instantiates it with an empty import object.
//
// Every expression keeps the operand order of the JavaScript law it was ported
// from, and the build compiles with -ffp-contract=off, so the only arithmetic that
// can differ from a JavaScript run of the same law is cos itself: wasi-libc's cos
// here, V8's Math.cos there.
#include <math.h>

#define M 0.1
#define G 9.81
#define L 0.5
#define KE 60.0
#define KX 2.0
#define KV 3.0
#define KP 40.0
#define KD 5.0
#define KXB 1.0
#define KVB 1.0

static int catching = 0;

// wrap maps an angle into [-pi, pi). fmod is exact, like JavaScript's %.
static double wrap(double a) {
  double w = fmod(a + M_PI, 2 * M_PI);
  if (w < 0) w += 2 * M_PI;
  return w - M_PI;
}

__attribute__((export_name("control")))
double control(double x, double v, double theta, double omega, int k) {
  (void)k; // this law does not need the tick index; a scheduled one would
  const double eUp = M * G * L;
  double tw = wrap(theta);
  double c = cos(theta);
  if (!catching && c > 0.9 && fabs(omega) < 3.5) catching = 1;
  if (catching && c < 0.3) catching = 0;
  double u;
  if (catching) {
    u = KP * tw + KD * omega + KXB * x + KVB * v;
  } else {
    double E = (2.0 / 3.0) * M * L * L * omega * omega + M * G * L * c;
    double dir = omega * c >= 0 ? 1 : -1;
    u = KE * (E - eUp) * dir - KX * x - KV * v;
  }
  if (u > 20) u = 20;
  if (u < -20) u = -20;
  return u;
}

reference.js, the JavaScript law it is a port of

// The JavaScript controller that controller.c is a line-for-line port of: the same
// swing-up law, spawned by the runner as a Node process. While the pole is down it drives
// the cart so the pole's mechanical energy climbs toward its upright value (push in
// the direction that adds energy, with a small centring term so the cart stays on
// the track); once the pole is near upright and slow it hands over to a linear
// balancing law with the angle wrapped into [-pi, pi]. Pole mass 0.1 kg and
// half-length 0.5 m are the plant's defaults.
//
// The example runs this file beside the C build so the page can compare their
// fingerprints from one run. Every expression has the operand order of its C twin,
// so the only arithmetic that differs between the two is cos: V8's Math.cos here,
// the cos compiled into controller.wasm there.
import { createInterface } from 'node:readline';
const m = 0.1, g = 9.81, l = 0.5;
const eUp = m * g * l;
const kE = 60, kx = 2.0, kv = 3.0;
const kp = 40, kd = 5, kxb = 1, kvb = 1;
let catching = false;
const wrap = (a) => { let w = (a + Math.PI) % (2 * Math.PI); if (w < 0) w += 2 * Math.PI; return w - Math.PI; };
const rl = createInterface({ input: process.stdin });
rl.on('line', (line) => {
  const [x, v, theta, omega] = line.split('|')[0].trim().split(' ').map(Number);
  const tw = wrap(theta);
  const c = Math.cos(theta);
  if (!catching && c > 0.9 && Math.abs(omega) < 3.5) catching = true;
  if (catching && c < 0.3) catching = false;
  let u;
  if (catching) {
    u = kp * tw + kd * omega + kxb * x + kvb * v;
  } else {
    const E = (2 / 3) * m * l * l * omega * omega + m * g * l * c;
    const dir = omega * c >= 0 ? 1 : -1;
    u = kE * (E - eUp) * dir - kx * x - kv * v;
  }
  if (u > 20) u = 20;
  if (u < -20) u = -20;
  process.stdout.write(u + '\n');
});

The build step, as the run executed it: clang --target=wasm32-wasip1 -mexec-model=reactor -O2 -ffp-contract=off -Wall -Werror -o controller.wasm controller.c. -ffp-contract=off keeps the compiler from fusing a multiply and an add into one rounding, so every expression rounds exactly where its JavaScript twin does.

Four scenarios, one fingerprint each

The pole starts hanging (the start angle is measured from upright, so pi rad is straight down) and the cart mass varies. Upright from is when the final unbroken stretch with the pole within ° of vertical began; the score is 1 minus that time over the run's length, and zero if the cart ever leaves the track. Each C fingerprint was the same in both runs.

ScenarioStart angleCart massUpright fromScoreFingerprint of the C buildAgainst the JavaScript lawC built against Math.cos

Where the C and JavaScript records differ, and where they do not

This controller's arithmetic is +, -, *, /, fmod and one cos per tick. The first four and fmod are exactly rounded by IEEE 754 in both languages, and the build keeps every operation in the JavaScript order, so the C and the JavaScript controllers can differ only where their cos differs. The C module's cos is wasi-libc's, compiled into the module; the JavaScript controller's is V8's Math.cos. The two agree to the last bit on nearly every input, and not on all of them. The run tests that this is the whole difference by building the same controller.c a second way, clang --target=wasm32-wasip1 -mexec-model=reactor -O2 -ffp-contract=off -Wall -Werror -Dcos=host_cos -Wl,--allow-undefined -o controller.wasm controller.c, with the shim binding host_cos to Math.cos: that build and the JavaScript controller give the same fingerprint on every scenario in the table.

C controller, wasi-libc cosJavaScript controller, Node's Math.cos

What the fingerprint proves, and what it does not

The same plant, with a JavaScript controller written by an agent and the physics oracle's own runner: the physics oracle run report (INTERNAL · run report →). Reproduce this page: make docker-images && go run ./examples/wasm-controller in the plimsoll repository, with docker. The page is written from the run.