This release is a pre-release and may not be stable for production use.
OpenGLContext Hill Runner Demo
OpenGLContext engine demo that creates an endless runner game down a valley on a hill. Demonstrates tiles3d on-demand tile creation, GPU accelerated animations, vegetation, GUI widgets and the Heads Up Display.
uv run --pre --with openglcontext-hill-runner oglc-hill-runner
Or you can pip-install it and run like so:
oglc-hill-runner # the main screen: play, demo, settings, credits, quit
oglc-hill-runner --demo # the autopilot plays, with no main screen
oglc-hill-runner --seed 7 # a different hill
oglc-hill-runner --no-sound --size 1920 1080
oglc-hill-runner --no-forest --no-photos # no use of the forest demo's art or the engine's textures, no download
oglc-hill-runner --grass-reach 18 # draw grass as geometry out to 18 m, not 34: faster on a weak GPU
oglc-hill-runner --no-scatter-process # scatter the grass on threads of the game's own, not in processes
oglc-hill-runner --yes # fetch the packs below without asking, where they are not here
oglc-hill-runner --record run.mp4 # the autopilot's run, recorded to a 30 second video
The game runs on three downloads, all under Creative Commons terms: its own art and sound (the runner,
the music, the sounds), the forest demo's trees and ground cover, and the engine's photographic
textures (CC0). The first run lists them with their sizes and terms, asks once and fetches them
into the per-user content store; later runs find them there. A checkout holding the art in
src/openglcontext_hill_runner/assets does not fetch its own pack.
Recording
--record PATH records the autopilot's run to an MP4 file and exits when the video is done. It
implies --demo and --no-sound: the run is the autopilot's, there is no main screen, and the
file has no audio track.
oglc-hill-runner --record run.mp4 --record-codec hevc --record-seconds 20
oglc-hill-runner --record run.mp4 --record-codec av1 --record-fps 30 --seed 7 --size 1920 1080
| Option | Does |
|---|---|
--record-codec |
h264 (the default), hevc, vp9 or av1. The GPU's video encoder has to produce it; the program says which codec is missing and exits with status 1 when it does not |
--record-seconds |
Length of the video, 30 by default |
--record-fps |
Frames a second, 60 by default |
--record-delay |
Seconds the hill is given to stream in before recording starts, 3 by default. The run waits at its start meanwhile, so the video opens on the first metre |
--record-bitrate |
Bits a second. The default, 0, is the encoder's: 0.25 bits a pixel, which is about 29 Mbit/s for a 1856 by 1044 video at 60 frames a second. Dense foliage blocks and smears at a quarter of that |
The world advances by exactly one frame's worth of time for each frame written, so the video is
smooth however long the frames took to draw, and the same seed records the same course. A recording
is not in real time: 120 seconds of video takes as long to record as its frames take to draw, and a line every five seconds says how far along it is and how long is left. A run that is cut off leaves a video that plays up to a second or so before the end. The video is the size of the window's framebuffer, which is larger than
--size on a display with scaling. Recording needs OpenGLContext[video], which this package
depends on, and a GPU video encoder: see
OpenGLContext's docs/recording.rst for which GPUs and platforms have one.
Playing
| Key | Does |
|---|---|
| Left, Right | Hop to the next path, unless a hedge stands between the two or the paths have drawn apart |
| Up | Jump over a log or a boulder, or up to a jewel |
| Down | Slide under a gate (in the air: drop, then slide). Held on, the runner stays down, and rises only when the key is let go: the body rises over the last 0.3 s of a slide, and a gate met then is a hit |
| Escape | Menu: resume, restart, demo, settings, credits, quit. Demo hands the run to the computer, which runs without a hit for as long as it is left; the menu's Play takes it back. The main screen the game opens on offers Play and Demo too |
| Alt+F | Show or hide the developer overlay: frame rate, timings and draw counts |
| F2 | Save a screenshot to the pictures folder, as Hill-Runner-0001.png and so on |
The three trails are 0.5 m wide with 0.2 m between them, so a hop is one big step. The runner speeds up from 10 to 24 m/s and cannot slow down on its own. A log, a boulder, a gate board or a wall is a hit; they stand across one or two trails and are a metre high or more. Coins hang at knee height and are worth 1 coin. Jewels hang above a runner's reach and are worth 5 coins: they need a jump. The HUD shows coins, score, hits and metres run.
Hits and continues
A hit knocks the runner down. Its body falls as a ragdoll, with the speed it ran at, and tumbles on down the hill or against the obstacle until it comes to rest; for the last 0.68 s of the 1.3 s it is down it rises from where it lies, and it runs on from there at 10 m/s: the speed gained is lost. A body that comes to rest on or in an obstacle gets up just short of it. The keys do nothing while the runner is down. Each hit costs a continue of 100 coins. The edges of the screen turn red and flash at the hit, and the coins flash red as they count down by 100 over the 1.3 s of the roll. When the 100 are paid the red clears and the runner runs on. A runner with fewer than 100 coins cannot buy a continue: the run is over at the hit, with the edges red, and the coins it has are kept and shown on the end screen. The score is not spent. Nothing hits the runner again for the next 15 m, so a row of obstacles is one hit.
The country
The hill runs through forest, rocks, sand, meadow and dense forest, a few hundred metres of each, in an order the seed picks. The land, the ground, the trees, the ground cover and the rocks change with it. The trail wanders from side to side.
Caves
Every two or three kilometres, after a stretch of rock country, the trail runs into a cave. The
valley is filled up to the height of the hills beside it over 22 m, so the trail runs into the foot of
a hillside and the cave is a hole bored through it: the ground is tessellated out to the rim of the
opening, and the tunnel runs from there to a second opening in the far side of the hill. The tunnel
is 250 to 320 m long, an arch swept along a spline through the trail and cut into rock: flat facets,
steps and pits (OpenGLContext.scenegraph.rockwork). It leads out into more rock country or into
forest, the seed's choice.
- The ceiling is high in places (4.4 to 7.5 m over the floor) and low in others (2.4 to 3.0 m), changing over 16 m. Running or sliding under a low ceiling is safe; a jump under one is a hit, and so is a jump at the edge of one, since the head arrives at the top of the jump. The camera sinks under a low ceiling and rises again once it is clear; where it has sunk it stands to the right of the runner and looks across to the left, so the runner does not hide what is ahead.
- A crevice runs right across the floor, 1.8 m wide and 2.6 m deep. It has to be jumped, and a runner who is on the ground over it is a hit. Crevices stand only where no low ceiling is within 14 m, so the jump a crevice asks for is always safe; so is every jump the course asks for in a cave, and the jewels that hang out of reach of a standing runner.
- The logs, boughs and walls of the open hill are in caves too. A fallen log under a low ceiling is a bough to slide under instead.
- A torch on the wall every 18 m, on alternate sides, is all the light there is. The sun, the sky's light and the haze go out over the first 28 m inside the mouth. The torches' lights are the nearest of the scene's lights, which is what the engine binds when there are more than it has room for.
- No fork runs near a cave, and no trees or ground cover grow over one. The stone of an obstacle is paler than the rock about it and glows a little, so it shows in shade.
Forks
Every kilometre or so two more paths appear beside the trail, on the left or the right. They draw away and run through another country, then come back and join. Hop onto them in the first few seconds, while they are within a hop of the trail; after that they are on a trail of their own. A path that joins the trail carries its runner onto the nearest path of it.
What it uses from the engine
| Feature | From |
|---|---|
| The hill, one tile for each 72 m, made in worker processes as the runner nears it | GeneratedTiles with maker, OpenGLContext.loaders.tiles3d.generated, .tileprocess |
| The runner: a capsule that stands on the ground, jumps under gravity of 42 m/s squared, slides, is knocked down and gets up, wearing the figure, stepped and animated by the physics frame with the world | Physics.add_character (set_move with speed, the character's before-step and after-step handlers, slide(steer=True), extend_slide, knock_down, get_up), CharacterCapabilities.jumpFollowsSlope |
| The runner's fall: the body as a ragdoll from a hit until it gets up | add_character(ragdoll=), CharacterRagdoll (fall, recover) |
Which clip shows what the runner does: Jog_Fwd_Loop and Sprint_Loop by speed, Jump_Start, Jump_Loop, Slide, Roll and Death01; the foot falls |
OpenGLContext.character.CharacterModel, locomotion.ClipMap and Locomotion, Character footstep events |
| The runner turned to where it goes, tipped to the slope and leaned into a hop | OpenGLContext.character.facing.Facing |
Sun with cascaded shadow maps fitted to shadowDistance and fading out at its end, PBR materials, fog |
DirectionalLight, ContextDefinition.shadowDistance, the PBR pass, Fog |
| Ground, trail, rock and bark photographs (CC0) | OpenGLContext.loaders.cc0.physical_material, with procedural OpenGLContext.scenegraph.surfaces as the fallback |
| Rocks, logs, fence rails and dry-stone walls | OpenGLContext.scenegraph.natural (stone, timber, rubble) |
| Rock country's ground: flat faces scraped into the land | OpenGLContext.scenegraph.terrain.RockRelief |
| Procedural textures for bark, sawn ends and the ground | OpenGLContext.scenegraph.surfaces |
| Trees: the forest demo's models, real branches near and cards far, swapped a stretch at a time | VegetationField.replant, from the forest demo's art |
| Grass, ferns, nettles, flowers on the forest floor | GroundCover, from the forest demo's art: the grass and the other plants as two nodes (groundcover) |
| Chase camera: the look point glides ahead of the runner, the camera stands back further at speed, and the arm is shortened to what a sphere swept along it meets between the camera and the runner (the walls and roof of a tunnel; not the logs, boughs and walls the runner jumps, slides or hops past) | OpenGLContext.move.followcam.FollowCamera (look_ahead, smooth_time, sweep, target(subject=), snap, constrain), Physics.sweep_sphere |
| HUD, with the edge shading of damage; main, pause and end-of-run screens | OpenGLContext.ui.hudwidgets (DamageIndicator: level, hurt_all), OpenGLContext.ui panels |
| The hill in the physics world: ground, logs, gates and walls, crevices, coins and tunnel walls, near the runner | context.physics (OpenGLContext.physics.service.Physics): Proxy.heightfield (the ground), add_static (blocks_characters=False for the barriers and the tunnel), add_trigger, add_dynamic (the coins a hit throws) and Proxy.trimesh, stepped inline at 120 steps a second by the context's physics frame after OnIdle, and paused by enable(False) while a screen is up |
| Music, footsteps, grunt, slide, coins | context.audio (OpenGLContext.audio.manager.Audio): play with gain, rate, priority and loop, and muffle for the pause screen |
How it is put together
The rules have no GL and are tested without a window.
How a frame is stepped
The physics service steps the world and the runner's character at 120 fixed steps a second. In the
window the context runs that frame after OnIdle: OnIdle gives the pilot's commands
(Session.before_frame), the service takes the world's steps and then the character's, and a frame
hook added after the physics frame hears what happened, keeps the hill's bodies near the runner and
moves the camera (Session.after_frame). Without a window, Session.step(dt) and Game.advance(dt)
call physics.frame(dt) themselves, which is how the tests and the pilot run; a frame longer than
0.05 s is stepped as 0.05 s.
The frame draws the runner between its last two steps (Character.drawn_position), a part step
behind the rules' place, which moves on in whole steps. The camera follows the runner where it is
drawn (Runner.drawn), so the runner keeps its place in the picture at any frame rate and through a
long frame. The lift of a jump is added to the camera after its glide, so the view rises and falls
with the jump as it happens. tests/test_playthrough.py plays jumps and slides through a session
at 30, 60 and 144 frames a second and through a 20 ms hitch, and holds the runner's figure inside the
picture, in front of the camera and moving smoothly across it.
The rules run in the character's own step handlers. Before each step the runner moves its place on
the hill (speed, distance, the path and the glide of a hop) and gives the character the move toward
that place with the speed that reaches it in the step (set_move(direction, 'run', speed=)). After
the step it reads the landing, and the game judges what the runner takes and hits. Through a jump
the runner gives the character, before each step, the rise that keeps its feet on the rules' arc over
the ground under it: up at JUMP_SPEED, slowed by 42 m/s squared, 1.5 m at the top and 0.53 s in
the air, over a brow or into a hollow as on an even slope. A jump therefore has the same arc at any
frame rate and anywhere on the hill. The runner keeps to the paths of the Landscape with a move
toward the path's centre each step rather than Character.run_along, since the paths are not a road
course and the rules own the distance.
Hits are the rules'
A hit is judged from the rules' bands and lanes, not from the world's contacts. The forgiveness at
the feet and the head is a rule of the game, and the shapes in the world are close to the rules but
not the same: a contact would turn a toe over a log, which the rules forgive, into a hit. The
barriers and the tunnel are bodies the character walks through (blocks_characters=False), so the
world reports no contact for them. The coins are triggers in the world, and a coin is still taken by
the rules' reach across the path and along it, since a trigger event comes once a frame after the
steps and the rules take a coin in the step that reaches it.
Falls
The runner falls as a ragdoll, the engine's CharacterRagdoll; the capsule stays the rules'. At a hit
the rules knock the capsule down and hand the body to a ragdoll that starts in the pose the runner was
drawn in, moving as the capsule moved, and struck by the blow of what it hit (game.BLOWS): a log trips
the shins with 45 N s up the hill and throws the body up at 3 m/s, so it pitches forward onto the log;
a bough strikes the head and a wall the chest, and the body goes over backwards, under a bough sliding on
down the hill on its back; a crevice trips the shins more lightly, and there the body tumbles metres down
the hill. Its motors hold the running pose for 0.12 s and let go over 0.2 s. While it tumbles, the rules'
place on the hill follows the pelvis, so the camera and the hill's bodies follow the body. With
GET_UP_TIME (0.68 s) of the time down left, the capsule stands where the body lies, or just short of the
obstacle it lies on or in, and the figure blends from the body's last pose into the roll the runner plays
while down, which is at its lowest point then and rises with the blend; at the end of the time down the
runner runs on. A crash leaves the body lying until the run starts again.
The bones are built once from the model at the first run and kept for the next. A runner on its feet holds no ragdoll and does no ragdoll work; the frame a runner trips costs about 2 ms more than the frames around it.
| Module | Holds |
|---|---|
course |
Paths, the slope, the wandering trail, forks, caves and their ceilings, obstacles (a crevice among them), coins, jewels and hedges; a chunk is a function of the seed and its index |
landscape |
The countries, and the land, ground layers, tree, cover and rock densities of each |
runner |
The runner: speed, the path and the glide of a hop, which the rules own, and its character in the physics world: jumping, sliding, knocked down and falling as a ragdoll |
world |
The physics world a run happens in, the height of the ground in it, and a world of its own with level ground |
game |
The rules, run after each of the runner's fixed steps: collisions, the blow of each kind of obstacle, the fall and getting up, the continue bought with coins, head against a ceiling, treasure, the end of a run |
atmosphere |
How dark and how hazy the air is at a distance: bright in the open, dark in a cave |
pilot |
The autopilot; the test that every generated course can be run |
cues |
Which clip plays, which sounds fire, when a foot lands |
chase |
The engine's follow camera set for the hill, the point it follows, and the rule the caves add |
bodies |
The hill's bodies in the physics world, made as the runner nears them: a ground body of the footing on a half-metre lattice for each chunk, a box for each log, gate and wall, a trigger for each crevice, coin and jewel, and a mesh of the walls of a tunnel; the runner walks through the barriers and the tunnel, since the rules judge a hit, and the coins meet them, and the camera's sweep meets the tunnel and none of the barriers |
spill |
The coins a hit throws: dynamic bodies that fall on the ground, settle and are removed after 4 s; the rules never read them |
session |
One run advanced a frame at a time: the pilot's commands before the physics frame, and the events, the hill's bodies and the camera after it |
tunnel, portal |
The tunnel of a cave (the arch swept along the trail, the rock cut into it, the floor) and the ground tessellated out to the rim of each opening |
scenery, look, tiles |
The hill as meshes and materials, and the tile generator; where one country gives way to another the second ground is blended over the first |
groundcover |
The grass, grown tall, and the other plants of the forest demo's art as two nodes, with where each grows |
woods |
The forest demo's trees, planted a stretch of hill at a time, clear of the trails |
treasure, hud, menu, credits, sound |
Coins and jewels in view, the HUD, the screens, the credits shown at start and in the menu, the sounds played through context.audio |
app, run |
The window and the command |
Tests
pip install -e ".[dev]"
pytest
The render smoke test skips where there is no display.
Assets
Audio and the character are CC0. The music is Medieval: Market Day by RandomMind. The forest demo's tree models are CC-BY 4.0 and credited on launch. ASSET-LICENSES lists each file. The character's Slide clip is made by
grass-clumps/slide.py in the OpenGL-dev workspace.
The runner, the music and the sounds (2.7 MB) are a content pack, openglcontext-hill-runner/assets, attached to
a GitHub release and not carried in the wheel. The first run lists the pack's size and terms and asks before it
fetches it; a checkout runs from its own copy and asks nothing. release-assets.py builds the pack and writes the
registry the game ships (packs.json); tools/publish-content.py in the workspace attaches it to its release.
Metadata
Release files for openglcontext-hill-runner 0.1.0a4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| openglcontext_hill_runner-0.1.0a4.tar.gz | 179.7 kB | With provenance | Details for openglcontext_hill_runner-0.1.0a4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| openglcontext_hill_runner-0.1.0a4-py3-none-any.whl | Python 3 | none | any | Details for openglcontext_hill_runner-0.1.0a4-py3-none-any.whl |
Total release size: 296.0 kB
Release files / openglcontext_hill_runner-0.1.0a4.tar.gz
| Download URL | openglcontext_hill_runner-0.1.0a4.tar.gz |
|---|---|
| Size | 179.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
38901f9443741e67d159ccd7c2ce14abd4c8fdc7b252e57c81a14519e8362203
|
|
BLAKE2b-256 checksum How to use checksums |
4b1f37c2c4ca0d9271abb9d096fd466ea9db4d8335064c341da985b395d5913b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logRelease files / openglcontext_hill_runner-0.1.0a4-py3-none-any.whl
| Download URL | openglcontext_hill_runner-0.1.0a4-py3-none-any.whl |
|---|---|
| Size | 116.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
73febaaa2bddba8b8c5a633b08fc0a86424fd37e689a58269fe3399cf7802d34
|
|
BLAKE2b-256 checksum How to use checksums |
46135e27424a78b7f869d1467ef7e598d98ad8a7d56f2050a7c02211e586cb65
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logSecurity for openglcontext-hill-runner 0.1.0a4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for openglcontext_hill_runner-0.1.0a4.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.