Nine Bends

A bamboo raft is not one boat — it is six poles lashed together, and the river takes them one at a time. Clip a limestone boulder hard enough and a lashing parts: you lose a pole, the raft gets narrower and twitchier, and the only way back is to run down floating bamboo and lash a fresh one on. Nine bends of karst river, built end to end by AI agents on Cocos Creator, headless on Linux, and measured against the 4 MB WeChat mini-game cap every build.

Play it now

Drag left / right, or A D, or the arrow keys, to pole against the current · S brakes · P pause · M reduced motion · N mute
Payload 2,818,942 B raw / 1,067,030 B gzip · loads and plays in a few seconds · portrait, desktop and phone

▶ Play

The concept is the contract

The concept sheet is generated first and the build is audited against it, region by region, until they agree. Not "inspired by" — the palette on the right is the palette on the left, and when the build drifted to grey water during the art pass that counted as a defect and was fixed, not explained.

Concept sheet C1: a bamboo raft on a turquoise karst river
C1 — the hero concept. Turquoise water, pale limestone breaking the current, white foam collars.
The live Cocos build at bend three
The live build at bend 3. Same palette, same foam collar on the upstream face of every rock.

Gray boxes before art

The whole game played to completion in grey primitives before a single sprite existed — but the physics was real from the first frame, because the entire design rides on it. The blind readability check also ran here, at the blockout, rather than after ship: three reserved channels (what kills you, what is scenery, where the water goes) had to separate by value and shape while everything was still grey.

The grey blockout: raft, rocks and channel as primitives
Blockout. Six physics bodies, foam collars, flow streaks — already readable, still grey.
The finished build in the gorge at bend nine
The same systems at bend 9, dressed. The channel narrows from 5.2 m to 3.6 m across the run.

A Blender pipeline with a normal-map pass

Thirteen props are code-modelled in Blender from the callout sheet and rendered top-down at one shared scale of 96 px per metre, each with a matching camera-space normal pass. Nothing is hand-drawn and nothing is stock. Two review rounds were failed on purpose before this one passed — the first put the bank masses at the same value as the limestone hazards, which is backdrop wearing the hazard's clothes.

The thirteen rendered props
All thirteen props, as shipped. Reeds are rosettes because an upright cone seen from above is a dot.
Concept sheet C2, the eleven-prop callout board
C2, the callout board they are audited against — regenerated mid-cycle so it matches what shipped.
The packed albedo atlas
Albedo atlas, 640×512.
The packed camera-space normal atlas
The matching normal atlas. Identical layout, so one UV addresses both.

The raft is six bodies, and the audit proves it

A skilled pilot that never crashes cannot prove a crash mechanic. The audit therefore ships a second, deliberately named collision probe that steers at hazards and never collects bamboo — a stress probe, not a simulated player. It fails the build unless ropes actually part, poles are actually lost, and the wreck state is actually reached.

The raft broken up, poles drifting as debris
Collision probe, bend 2: five lashings parted, six poles lost, raft broken up. Loose poles drift away as debris.
The delta at the end of bend nine
The other outcome: the delta at the end of bend 9, with the poles you managed to keep.

Measured, honestly

Every number below is measured on the build that shipped, with its conditions stated. The unflattering rows stay in — including the one where the builder's own default assets were eating a fifth of the entire budget.

MeasurementValueCondition
WeChat main package2,902,096 B (2.77 MiB)69.2% of the 4,194,304 B cap
Headroom remaining1,292,208 B30.8% of the cap, unused
Engine core1,966,613 B17 modules, minified, Box2D included
Cost of real Box2D415,693 B9.9% of the cap; measured against the same build without it
Prop atlas283,524 Balbedo + camera-space normal, 640×512
Audio203,452 B5 mono MP3s from sampled instruments
Unused fonts pruned per build868,140 B20.7% of the cap the builder ships by default and this piece does not use
Web build2,818,942 B raw / 1,067,030 B gzipweb-mobile target
River length131 m over 9 bends12,600 world px at 96 px/m
Verified playthroughbend 9/9, 0 errorsdesktop 1280×720 and phone 390×844, Playwright
Device verificationPENDINGWeChat devtools are Windows/macOS-only; see the note below

What is not proven. This piece has never run on a handset. The WeChat package is structurally valid and the same code plays cleanly under headless GL, but that proves coherence and rendering, not that it boots inside WeChat. That check needs operator-side credentials and a device, and until then the claim is not made.

Real engine features, earned

A piece that could ship on a plain canvas unchanged is a failed concept. Remove the physics here and there is no game left.

Box2D rigid bodies

Six dynamic bodies, not one boat. The raft's flex and twist are simulation, not animation.

Hinge joints as lashings

Five limited hinges. Destroying one is how a rope parts; a pole with none left becomes debris.

Contact impulse thresholds

Cocos joints have no break force, so the rope threshold reads solved contact impulses directly.

Custom normal-mapped shader

A fixed midday sun rakes the camera-space normal pass, so a boulder reads as a mass from directly above.

Streamed static hazards

Rocks, snags and sensor pickups spawn and retire in a window around the raft.

Provably passable generation

The river sweeps itself and deletes rocks until every point admits a 190 px raft.

6 poles, 5 lashings lashing breaks at 320 kg·m/s lash stiffness 165 spin cap 1.6 rad/s flow 210→444 px/s steer 330 px/s channel 250→172 half-width min gate 190 px 96 px per metre