jengine-runner
An endless runner: chunks as ASCII drawings in data tables, a difficulty that follows the distance, and a grid-exact character.
| Kind | Project package: linked into the builds of the projects that list it |
| Source | packages/jengine-runner |
| Version | 0.1.0 |
| Entry point | jengine_runner::RunnerPackage |
| Script namespace | Runner |
| Listed by | projects/arcade-classics |
Using it
Section titled “Using it”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.
Components
Section titled “Components”| Scene key | Rust type | |
|---|---|---|
runner_run | RunnerRun | package only: needs a v2 scene |
Script commands
Section titled “Script commands”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.
| Call | Flat name |
|---|---|
Runner.start(e, seed) | runner_start |
Runner.jump(e) | runner_jump |
Runner.slide(e) | runner_slide |
Runner.auto(e, on) | runner_auto |
Scenes that use it
Section titled “Scenes that use it”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).
| Module | What it is |
|---|---|
chunks | The 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. |
run | The run itself: the character, the queue of chunks ahead of it, and the distance that is the score. |
bot | A 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 package does not tidy up after itself
Section titled “The package does not tidy up after itself”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.
The component
Section titled “The component”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}Script commands
Section titled “Script commands”| Call | Does |
|---|---|
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 |
What scripts read
Section titled “What scripts read”RunStateevery frame:phase(ready,running,dead),x,y,speed,grounded,sliding,jumps,coins,score,best,Math.distance, andtile(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.