Technical Report · Evolutionary Neuro-Control Sandbox

BalanceForge: teaching a tiny brain to move things on purpose

A browser sandbox that evolves a small neural controller for a 2-D physics task and lets you watch it learn live. This report maps the territory we charted: a recipe for tasks that train fast, the wall where they stop, sixteen working demos, the foils, and the theory under each one.

16 trainable demos shipped <15–60 generations to solve 8 new reward objectives 1 boundary: stability 16/16 robust across 5 independent runs double pendulum solved by exact gradients 100% headless-verified

01 What this is

The loop is simple, the emergent behaviour isn't.

A setup defines a 2-D scene — a cart on a frictionless rail (position-controlled, so it never falls), optionally carrying a multi-link arm, a ball, or a target. A policy (a small multi-layer perceptron) reads an observation each physics step and outputs one or more actions (cart force, joint torques). An objective scores every step with a dense reward. A black-box optimizer — chiefly CMA-ES — evolves a population of policies against that reward, and the canvas plays the current champion as it improves.

The whole thing is deterministic and headless-runnable, which let us hold every demo to one rule: it must pass an automated training probe (random policy → solved, in <~15 generations) before it ships. That discipline is what produced a clean map instead of a pile of half-working toys.

02 The recipe for a task that trains fast

Six properties. Hit all six and a random policy climbs to mastery in seconds of training. Miss one and you usually have a foil.

Dense reward

Nonzero everywhere, near-monotone improvement from a random policy. The optimizer always has a slope to climb.

Gen-0 success rigged

The very first random policy already scores something climbable — the first target sits where the body starts.

Short credit horizon

Reward now reflects action now. No long delays between doing the right thing and being paid for it.

Reactive-sufficient

Solvable by reacting to the current observation — no anticipation, planning, or momentum-banking required.

Low-dim & stable

A position-controlled cart never falls. Stable bodies self-recover; the policy refines, it doesn't rescue.

Non-deceptive

No well-paying local optimum that leads away from the goal. Potential-field shaping can turn even "go around" reactive.

03 The boundary — the one finding that organizes everything

Task complexity is not the wall. Instability is. Stable control of any flavour — multi-joint coordination, moving targets, obstacle routing, selection, sequencing — trains fast. The two things that break it are an unstable pose (you must balance/swing up) and an unshaped sparse reward (you only score on a rare event).

THE RELIABLE ZONE stable · dense · reactive STABILITY  →  (left = self-righting · right = must be balanced / swung up) REWARD DENSITY → (bottom = dense · top = sparse / anticipatory) 16 shipped demos tracking · interception · evasion · arms · juggle accelerating-track rides the actuator speed-limit balance-angle unstable swept set-point chain-reach (up-target) multi-link swing-up chain-trace needs anticipation / momentum pong-volley sparse: rare ball contact THE WALLS instability & unshaped sparsity
Every working demo sits in the stable + dense + reactive corner. The four foils are pushed against the walls — and crucially, they fail for different reasons, none of them "too complex".

04 The demo suite

Sixteen demos, each gated on a headless probe. Grouped by the control problem they pose. Most also ship a "watch it learn" curriculum variant.

◎

Position tracking — the canonical fast demo

reactive · a proportional controller suffices

rail-catcher

works

Shadow a drifting dot

theoryPure 1-D position tracking. The optimal action is a function of the current cart→target error — textbook proportional control.
rewardchain_trace — 2-D closeness, collapses to 1 − |Δx|/REF.
result~5 gens · tracks to 7px
easy/hardEasy: monotone gradient, gen-0 aligned. The reference point for everything else.

accelerating-track

works

A chirp dot — find the speed wall

theoryTracking with a time-varying bandwidth. The target oscillates faster and faster, crossing the cart's max speed at ≈ t = 6.4 s.
rewardchain_trace · cartAccel 3200
resulthead tracked to 9px, then visibly breaks
easy/hardA built-in honesty gauge: reactive tracking holds right up to the actuator limit, then degrades — not a learning failure, just physics.

safe-zone

works

Stay inside a drifting band

theorySet-membership control with a flat-topped cost: anywhere inside the band is maximal, so the goal is "stay within the moving zone", not "sit on a point".
rewardzone_keep — full inside, ramps outside.
resultin-band ~100% · ~10 gens
easy/hardEasy, but the flat plateau gives no within-band gradient — the band's motion supplies the tracking signal.
⤓

Interception, sequencing & selection

step-change targets · closed loops · argmin

catch-drop

works

Slide under a falling dot

theoryReactive interception — track the x of a faller, be under it when it lands; on landing it respawns at a new x (a step-change target).
rewardcatch_drop — x-only closeness · cartAccel 3200
result100% caught, landing <10px · ~15 gens
easy/hardHarder than rail-catcher only in transit: the cart must re-cross to each new drop. A strong actuator makes it clean.

pick-side

works

Hop to whichever post is lit

theoryBinary set-point regulation on a clock — the target is a square wave between two posts, flipping every 2.0 s.
rewardcatch_drop reuse
resulton the lit post ~60% of the time
easy/hardThe 2.0 s hold is load-bearing: shrink it and the cart must leave a post before the flip — that turns it anticipatory, i.e. a foil.

tag-sequence

works

Touch the lit posts in a self-paced cycle

theoryClosed-loop waypoint sequencing: touching a post lights the next. The policy's own success drives the task forward — a genuine closed loop.
rewardtag_sequence — closeness + a +1 tag bonus · cartAccel 3200
result~16 posts tagged in 8s
easy/hardThe bonus is essential — see Reward-shaping lessons. Without it, touching a post relocates it and drops the reward, so a greedy policy hovers forever just outside the radius.

prioritize-nearest

works

Chase the closest of three dots

theoryAn argmin selection task: three dots drift; the policy must compute "which is nearest" from all three in its observation and chase it.
rewardcatch_drop against the nearest dot
resultholds the nearest to ~11px
easy/hardA richer observation→action map than single-target tracking, but still reactive + dense. A small MLP learns the piecewise min.

midpoint-meet

works

Hold the midpoint of two dots

theoryDerived-target regulation: the goal is on neither dot but the average between them — the policy must learn the relation x* = ½(x₁+x₂).
rewardcatch_drop on the computed midpoint (sharpened distScale)
resultholds the average to ~7px
easy/hardLearning a [0.5, 0.5] weighting is subtler than copying one channel — a tightened reward basin keeps it from stalling on "track one dot".
⇄

Other control domains — evasion & velocity

the same machinery, inverted

flee-the-dot

works

Reactive evasion — the inverse of chasing

theoryA repulsion-only field: maximize distance from a dot that homes onto the cart (plus a sway so it can't just be out-run to a wall).
rewardflee_evade — the negation of catch_drop closeness, saturating at a clearance.
resultholds ~240px clearance vs 68 parked
easy/hardEasy: the optimal action is "push away from the pursuer's current x". Watch for a pin-to-a-wall local optimum.

cruise-control

works

Match the dot's speed, not its position

theoryVelocity-domain tracking — drive cart.vx to a commanded cruise velocity. A proportional controller on velocity error, with a small inherent phase lag.
rewardvel_match (reads a one-line velTarget kinematics field)
result24px/s error vs 131 parked
easy/hardA genuinely different control problem from every position demo. The subtlety: perfect tracking wants feed-forward, but the slow command makes pure feedback reach ~98% — so it stays reactive.
⊿

Multi-DOF arms — coordination, navigation, dexterity

cart + servo joints · the proof that complexity ≠ the wall

arm-reach

works

Cart + elbow reach a target

theory2-DOF inverse-kinematics reach. The cart positions the base, the servo elbow bends — together they place the hand. The target is down-side, gravity-stable: a reach, not a swing-up.
rewardchain_trace positive-closeness (2 actions)
resulthand to ~35px · ~40 gens
easy/hardThe proof that multi-DOF coordination trains fine. Same actuator machinery as the failed swing-up reacher — stable pose instead of up-pose, and it just works.

arm-track

works

The arm follows a moving target

theoryReach-and-track: arm-reach with the target sweeping the reachable region. Continuous multi-DOF coordination on a moving goal.
rewardchain_trace
resultfollows within ~27px
easy/hardSlow sweep → still reactive. The first sign that "moving target" doesn't break multi-DOF.

arm-obstacle

works

Route the hand around an obstacle

theoryPotential-field navigation (3 actions): an attraction to the target plus a soft repulsion from a fixed obstacle that blocks the straight path. Both are in the observation → the routing is reactive, no planning.
rewardreach_avoid — closeness − soft repulsion
resultreaches ~28px, 0% inside the obstacle
easy/hardKey finding: a non-convex "go around" path is not a foil when shaped as a potential field and the obstacle is observed. The shaping makes the deceptive landscape reactive.

arm-dodge

works

Hold a target while an obstacle drifts through

theoryDynamic re-routing: the repulsor moves, sweeping through the reach region, so the hand is repeatedly pushed off the target and must dodge then return. The same potential-field controller follows a moving repulsor.
rewardreach_avoid (zero new code)
resultholds ~59px, 0% inside the drifting obstacle
easy/hardHarder hold (it's shoved off periodically) but still reactive + stable.

arm-slot

works

Thread the needle into a narrow gap

theoryConstrained reach: a target inside a slot flanked by two keep-out walls. The reward is a capped funnel — a small per-wall barrier that cancels at the slot center, so the field pulls the tip in without the walls pushing it out.
rewardthread_slot — closeness − two capped barriers
resultthreads to ~19px, 0% wall contact
easy/hardThe shaping is delicate — too strong a barrier pushes the tip out of the slot; see lessons.
◐

The juggle — dual-objective control

do two conflicting things at once

plate-spin

works

Carry a ball in a tray to a target — without spilling

theoryA stable pendulum-on-cart in disguise. The ball in a concave bowl is a 1-D dynamics — restoring toward center minus the cart's acceleration (the pseudo-force that sloshes it) minus damping — so centered is a stable energy minimum, the opposite of an inverted pole. The policy must do setpoint tracking (cart→target) and disturbance rejection (ball-centering) at once: accelerate smoothly.
rewardplate_spin = ½·ball-centered + ½·cart-on-target
resulttarget to ~19px, ball within ~16px of center (lip 70)
easy/hardConcave only. A flat tray makes ball-centering an unstable slosh — the inverted-pendulum foil in disguise. The bowl is what keeps it tractable.

The sandbox also carries a pre-existing menagerie — single/double/triple pendulums, a spring pendulum, golf & putt, a bullet-dodge scene, and the multi-link chain — which span the harder, sometimes-foil end of the spectrum (swing-up, sparse strikes). The sixteen above are the demos that train fast and immediately.

05 What doesn't work yet — and exactly why

Four tasks resist reactive evolution. None of them because they're "too complex" — each violates a specific recipe property. The honest part: knowing whether it's a fundamental limit or just a shaping gap.

TaskWhy it failsNaturePath to making it work
balance-angle
inverted pole tracks a swept lean
Two problems at once: the pole is unstable, and a moving set-point dilutes the "stay upright" signal that a fixed target gets for free. fundamental
instability
A curriculum that starts with a static target and a low-gravity bootstrap, then widens — the framework supports it, but it's no longer "immediately working".
chain-trace
a passive whip traces a moving curve
Requires anticipation & momentum exploitation — the tip is downstream of swinging links, so you must pre-load the swing. Reactive evolution stalls; the canonical zeroth-order foil. fundamental
anticipatory
The differentiable simulator's exact gradient traces it cleanly — but that's a different optimizer, not reactive CMA-ES. A capability gap, honestly reported.
pong-volley
a bat returns a pitched ball
Sparse: a random gen-0 bat never even contacts the ball, so the reward is ≈0 everywhere with no gradient to climb. shapeable
sparsity
Feed the predicted intercept point and reward shrinking distance to it every step — turns it into a catch-drop clone. Doable; just not free.
chain reacher (up-target)
4-link arm reaches a target overhead
The target is up while the chain hangs down — reaching it is a multi-link swing-up to an unstable pose. Fails from scratch (fitness −18) and stalls even with a target-ease curriculum (level 3/15). fundamental
instability
None that's cheap. But note: multi-DOF itself is fine — the shipped arm-reach proves it. The limiter is the unstable pose, not the action count.
The counterintuitive datum that nails it down — corrected by our own negative tests. The classic balance_up task trains reliably from a 30° start tilt (the trained policy holds upright ~90–100% of the time, run after run). From a 5° tilt the outcome is a lottery: across repeated runs, the best-of-3 upright fraction ranged from 22% to 74% — some runs stall farming the center/calm bonuses, some stumble into balancing. We first reported this as a clean single-run fact (9.86 vs 3.88 fitness); building an executable test for it (foils-probe.js) exposed the variance. The corrected claim is stronger, not weaker: near the unstable equilibrium the fitness landscape is flat and noisy, so the gradient doesn't just shrink — training degenerates into chance. A displaced start restores a strong, consistent "catch the falling pole" slope that CMA-ES locks onto every time.

06 The methods — how the brain is evolved

No backprop on the reward. These are black-box optimizers searching directly in policy-parameter space. The sandbox offers a whole zoo; this project leaned on the first two.

MethodWhat it is & why it fits
CMA-ES (workhorse)Covariance Matrix Adaptation Evolution Strategy. Samples a population from a Gaussian, then adapts the full covariance toward the directions that improved fitness — it learns the local curvature of the reward landscape. Smooth, sample-efficient, and ideal for a smooth low-dimensional MLP. Solved every demo here in tens of generations.
sep-CMA-ESCMA-ES with a diagonal covariance — O(n) state instead of O(n²), so it scales to larger networks at the cost of ignoring cross-parameter correlations.
NEAT / NEAT-fullNeuroEvolution of Augmenting Topologies — evolves the network structure (nodes and connections) alongside the weights, growing complexity from a minimal seed. Useful exactly where the policy-class shape matters, e.g. the antisymmetric reflex a swing-up needs.
The rest of the zooDifferential Evolution, PSO, a flock of swarm metaheuristics (cuckoo, whale, fish, ant-colony, firefly, bat), simulated annealing, parallel tempering, CEM, finite-difference & SPSA gradient estimators, NES, random search — all available for head-to-head comparison on identical tasks.
Differentiable simA separate autodiff (Adam) copy of the world for exact gradient probes. It's what shows chain-trace is solvable in principle — the foil is reactive-evolution-specific, not problem-specific.
Policy classesA small MLP throughout, but CNNs over a rendered phase-space image and NEAT topologies are first-class options.

07 Differentiable vs. gradient-free — the two ways to optimize

The suite trains most tasks blind — black-box search that needs only a reward. But most of these tasks have a known, smooth model, and when you use it the results change completely. This is the axis that really separates the techniques.

Gradient-free — the general, blind optimizer

CMA-ES, NEAT, DE, PSO… ask for nothing but a scalar reward — no model, no smoothness. Maximally general (works on discrete, contact-rich, unknown-dynamics tasks), but weaker: it stumbles, and on hard unstable tasks it plateaus. The honest slogan: "anything can be optimized, even blind — just not always well."

Differentiable — the principled baseline

When you have the analytical model (the equations of motion), you backprop the reward through the simulated trajectory and use the exact gradient (the adjoint method). This is the traditional path — control theory, differentiable simulation. Fast, reliable, and it cracks tasks blind search can't. The cost: you need the formula, and it must be smooth.

The proof — the double pendulum. Gradient-free evolution plateaus at ~13% hold on double-pendulum balance no matter the budget (double-balance-probe.js). Exact gradients through the differentiable simulator (Adam) reach 100% hold with a robust ±12° basin (double-balance-adam.js), and a single pose-conditioned controller holds all four equilibria on command — both-up, up/down, down/up (a pendulum on a hanging link), both-down — at 0.94–1.00 (double-poses-adam.js). Reach is the same story: the grad-free arm lands ~35px; the differentiable one reaches 4.7px (arm-reach-adam.js). The trick that makes it work: train in the near-linear regime (short horizon) so backprop-through-chaos stays clean.

Which of our setups can be differentiable?

A task is exact-gradient-trainable iff the loss is a smooth function of the policy parameters through the dynamics — smooth model, no hard events.

RegimeSetupsDifferentiable?Why
Smooth ODE controlpendulums (single/double/triple/spring/twin), chains, all arm-*, cart tracking (rail-catcher, catch-drop, cruise, accel, safe-zone, midpoint, flee), plate-spin✅ fullymass-spring ODE is smooth; even the obstacle repulsion is a smooth potential field
Contact / collisionball-single, golf, putt, pong, dodge⚠ soft-onlyimpact flips velocity discontinuously → needs a soft contact model (approximate); dodge's discrete random spawns break the tape
Discrete / selectionprioritize-nearest (argmin), tag-sequence (events), pick-side (square-wave flip)❌ noreward is piecewise-constant → zero/undefined gradient; grad-free only

Per-technique: does it need a gradient, and what that means

TechniqueNeedsMeaning
Differentiable physics (diff-sim + Adam)the equations of motionbackprop through the simulated trajectory (adjoint)
LQR / optimal controlthe linearized modelsolve Riccati analytically
Differentiable rendering / opticsthe light-transport / projection formulabackprop through image formation
Grad-free (CMA-ES, NEAT, DE, PSO…)nothing but a scalar rewardblind black-box search
Gradient-estimate (SPSA, FD-GD, NES, "adam")reward only, but many probesfinite-difference the gradient — the middle ground

One-liner: differentiable needs the formula (physics / optics); grad-free needs only a score. So we frame the differentiable versions as the baseline — the traditional, stronger tool — and grad-free as the general fallback that proves you can learn even without the model.

Where CNN actually helps

The CNN presets underperform because the hand-crafted Top-K observation already hands the MLP the relevant state. A CNN's edge is a spatial, translation-invariant prior — it only pays off on a raw field observation. The honest demo (planned): pixel-dodge on a raw occupancy grid, where CNN ≫ MLP-on-pixels but both < MLP+TopK. The lesson: CNN is for when you must see raw space and can't hand-craft features.

Why not Hanayama / Sokoban

Rigid-unravel puzzles are contact-dominated + combinatorial (which piece, which order) — a planning/search showcase, not a differentiable-physics one, and they'd reinforce the grad-free side we're trying to balance. Sokoban-style discrete solution-space viz doesn't fit the continuous focus either. Both are parked as stretch goals.

08 The curriculum — watching it learn, and when it backfires

A curriculum ramps one difficulty knob from easy to hard across levels; a fitness gate advances when the champion clears thresholdFrac × ceiling for two generations running. Every demo above ships a "watch it learn" variant.

The mechanism

Each demo exposes a difficulty parameter — rail-catcher's drift amplitude (30→150), the safe-zone band width (120→55), the obstacle radius (3→35), the bowl stiffness (deep→flatter). The level climbs 0→15 as the policy masters each rung, so you literally watch the task get harder while the controller keeps up.

The tuning rule we found

Set the advance threshold just below each demo's achievable cap. Too low and it breezes 2 generations per level (no visible struggle); too high and it grinds. The right setting produces a back-loaded arc — easy levels fly by, the hard ones genuinely take work.

When a curriculum helps vs. hurts. A curriculum bootstraps a task that's unlearnable directly — it's the only hope for the swing-up foils. But for a task that already trains directly (every arm demo solves in ~40 generations from scratch), a difficulty ramp adds pure overhead: at a mastery threshold it grinds 130+ generations re-mastering each intermediate rung. So the arm curricula deliberately use a lower "advance-when-competent" threshold — they're there to show the difficulty climb, not to make a hard task possible. Rule of thumb: high threshold for tasks that generalize across difficulty; low threshold (just show the ramp) for hard-but-directly-learnable ones.

09 Reward-shaping lessons — the deceptions we hit

Most "it won't learn" moments weren't the optimizer's fault — they were a reward that quietly paid for the wrong thing. Four worth remembering.

DemoThe deceptionThe fix
tag-sequenceWith pure closeness, touching the lit post relocates it — which drops the closeness reward. So the greedy policy learned to hover just outside the touch radius forever (high reward, zero tags).A one-shot +1 bonus on each tag, so advancing the cycle outweighs the momentary reward dip. 0 tags → 16.
arm-slotTwo wall-barriers strong enough to keep the tip out of the walls were also strong enough to push it back out of the slot — the funnel inverted (reaches → 58px, stalls).Keep the target outside the barrier zone (clean reach) and let the barrier bite only very near a wall. A monotone funnel in.
arm-reachThe reach reward's sparse "+1 inside the radius" bonus barely fires when the hand hovers at the radius edge — so fitness stayed ~0.8 and the curriculum gate starved (stalled at level 8).Switch to a positive-closeness reward that reflects reach quality, so the gate sees real progress.
plate-spinA flat tray turns "keep the ball centered" into balancing an inverted pendulum — the foil, hidden inside a friendly-looking demo.Ship a concave bowl only: gravity self-centers the ball, making centered a stable minimum.

10 How we kept it honest

Every claim in this report is backed by a script that runs without a browser.

11 Evidence appendix — the measured tables

Every number below regenerates from a committed probe (robustness-probe.js, before-after-probe.js, optimizer-bakeoff-probe.js, foils-probe.js). Run them with node <probe>.js — no browser required.

Robustness — 5 independent runs per demo, original pass criteria

Result: 16/16 demos robust at 5/5. The per-run metric samples show the honest spread (the trainer has unseeded RNG, so each run is an independent sample).

DemoRunsPer-run metricDemoRunsPer-run metric
rail-catcher5/56, 4, 6, 3, 5 pxprioritize-nearest5/59, 8, 9, 12, 10 px
catch-drop5/580–100% caughtarm-reach5/529–35 px
pick-side5/565–74% on-postarm-track5/525–35 px
flee-the-dot5/5241 px clearance ×5arm-obstacle5/527–52 px, 0% inside
cruise-control5/522–23 px/sarm-dodge5/543–68 px, 0% inside
accelerating-track5/53–7 px (head)arm-slot5/519–31 px, 0% contact
safe-zone5/5100% in-band ×5plate-spin5/58–13 px, no spill
midpoint-meet5/53–7 pxtag-sequence5/516–17 tags / 8 s

Before vs. after — gen-0 random policy vs. trained champion, same seeded world

The reward integral of the untrained gen-0 genome vs. the champion, on identical deterministic rollouts. 16/16 champions win. Small ratios are the demos whose dense reward gives gen-0 a high floor by design (the "gen-0 rigged" recipe property) — the champion still separates cleanly.

Demogen-0champion×Demogen-0champion×
rail-catcher1.217.886.5tag-sequence2.4118.657.7
catch-drop2.106.373.0prioritize-nearest4.977.631.5
pick-side1.936.503.4arm-reach0.105.9256.8
flee-the-dot3.397.562.2arm-track0.486.4313.3
cruise-control4.777.551.6arm-obstacle2.435.782.4
accelerating-track1.597.024.4arm-dodge0.234.6620.0
safe-zone1.408.005.7arm-slot0.506.1112.2
midpoint-meet0.127.6265.8plate-spin4.657.111.5

Optimizer bake-off — same tasks, same budget, eight optimizers

One easy task (rail-catcher, 30 gens × pop 30) and one hard one (arm-obstacle, 60 gens × pop 36); lower is better. The headline is honest and a little humbling: on a recipe-clean task, even random search clears the pass bar — the recipe does the feasibility work; the optimizer buys precision and consistency. CMA-ES is near-best on both, untuned — that's why it's the workhorse.

Optimizerrail-catcher (miss px)arm-obstacle (tip px, % inside)Note
CMA-ES335 (0%)near-best on both, no tuning — the workhorse
sep-CMA-ES335 (0%)matches CMA-ES here; scales to bigger nets
NEAT-full452 (0%)topology growth costs budget on fixed-shape tasks
DE550 (0%)solid on both
PSO351 (0%)excellent easy-task precision
NES5966 (0%)the one laggard — needs step-size tuning
Annealing1526 (0%)sniped the best hard-task score this seed
Random search2340 (0%)clears both pass bars(!) — quantifies the recipe

Negative tests — the boundary, asserted

12 What's next — batched & pending

Three ideas survived vetting but aren't shipped, each blocked on a known, named thing rather than a mystery.

Split Replay

core done · UI batched

gen-0 random vs. trained champion, side by side

statusThe enabling core is shipped + verified: the engine now snapshots the gen-0 genome, and the before/after reward gap is real (catch-drop 2.15 → 6.33 on the same seed).
blocked onThe side-by-side render is pure browser UI — and app.js can't run in Node, so it's the one thing not headless-verifiable. Batched for a live pass.

Cursor-Follow toy

batched UI

Drive the target with your mouse; the trained brain follows live

why it worksInference-only — a frozen position-tracking policy can't be "broken" by a human cursor. Squarely in the reliable zone.
blocked onBrowser-only plumbing (a pointer handler), same un-verifiable category.

Synchronized Catchers

needs engine work

Two carts split a falling stream — emergent division of labour

why it worksEach cart is a stable catch-drop; the only risk is a double-commit attractor, broken by a yield penalty. Not a foil.
blocked oninfra The engine is structurally single-cart — step(world, dt, cmd) drives one w.cart. Two carts means real physics work, not just a new setup.

One more boundary worth recording: the adversarial machinery in the engine co-evolves environment knobs (gravity, push strength), not a second neural policy — so AI-vs-AI duels are net-new infrastructure, and were correctly rejected as out of reach.