Skip to content

jengine-runner

An endless runner: chunks as ASCII drawings in data tables, a difficulty that follows the distance, and a grid-exact character.

KindProject package: linked into the builds of the projects that list it
Sourcepackages/jengine-runner
Version0.1.0
Entry pointjengine_runner::RunnerPackage
Script namespaceRunner
Listed byprojects/arcade-classics

List it in the top-level "packages" array of project.jengine:

{
"packages": [
{ "name": "jengine-runner", "source": { "type": "path", "path": "../../packages/jengine-runner" } }
]
}

Then rebuild the editor viewport for the project (jengine viewport-build projects/<name>, or the editor’s Rebuild viewport banner). Native and web builds link it on their own.

A scene that uses its components must be a v2 scene ("version": 2): a v1 scene has no field for a package component and keeps it as inert custom data.

A script can check that the package is linked with if Runner == nil then return end.

Scene keyRust type
runner_runRunnerRunpackage only: needs a v2 scene

Commands run in every runtime and return nothing; a package publishes what scripts read onto components (see the notes below). The flat name also works.

CallFlat name
Runner.start(e, seed)runner_start
Runner.jump(e)runner_jump
Runner.slide(e)runner_slide
Runner.auto(e, on)runner_auto
  • projects/arcade-classics/assets/scenes/dawn.scene

From packages/jengine-runner/README.md.

An endless runner: a character that only goes one way, and a level that appears in front of it.

Used by projects/arcade-classics (Long Dawn).

ModuleWhat it is
chunksThe pieces a level is made of — ASCII drawings in a data table, each with the distance it may first appear at and how likely it is once it can.
runThe run itself: the character, the queue of chunks ahead of it, and the distance that is the score.
botA player for an attract mode, which is also the package’s own test harness.

The level does not exist until you reach it

Section titled “The level does not exist until you reach it”

A chunk is a drawing — # ground, ^ spike, o coin, space empty — plus a from distance and a weight. The run picks by weight from whatever the distance allows, so difficulty is a property of the content rather than a curve in code: adding a hard chunk with from = 90 is the whole of “make the late game harder”, and nobody authors a level.

The character is simulated against cells, not bodies

Section titled “The character is simulated against cells, not bodies”

A runner is a grid game. An AABB against a tile grid is exact, cheap and identical on every machine — which matters when the entire content of the game is a number you compare with your friends. The same seed replays the same level, which is what makes a score mean anything.

Coyote time and a jump buffer are in the character, not the script: they are what “it feels right” is made of, and every runner wants them.

The tiles a chunk spawns carry the engine’s DespawnOffscreen, so they clean themselves up once the view has passed them (BUGS #133). Before Sep 2026 every scrolling game in the arcade kept its own list of what it had spawned and compared each thing against the camera by hand, every frame — five games, five copies of the same loop. behind_only is the part that matters: a chunk is spawned a long way ahead of the runner and must not be swept up before its turn comes.

runner_run on one entity, in a "version": 2 scene. The package draws nothing: it publishes the chunks it has decided on, and the script spawns whatever art it likes for each cell.

"runner_run": {
"tables": { "chunks": "data/chunks", "runners": "data/runners" },
"runner": "dart", "origin": [-360.0, -220.0], "seed": 7
}
CallDoes
Runner.start(e, seed)start a run; 0 seeds it from the clock
Runner.jump(e)jump, buffered a few frames like a good runner
Runner.slide(e)slide, if it is on the ground
Runner.auto(e, on)a bot plays, for an attract mode
  • RunState every frame: phase (ready, running, dead), x, y, speed, grounded, sliding, jumps, coins, score, best, Math.distance, and tile (the world size of one cell).
  • RunChunks.chunks: each chunk the run has decided on but not yet handed over, with its rows and where it starts. Handed over once.
  • RunEvents.events: jump, land, slide, coin, dead, best.