Afterglow

an original precision platformer.

▶ Run in browser

Tags: 3d game platformer particles feel

Afterglow: original precision platformer (SimVX flagship demo)

Afterglow

You are the Wisp, a fading mote of light climbing to relight the Aether Spire. Dash through resonance crystals, gather glow orbs to phase through light-gates, and ride springs and moving platforms ever upward across eighteen single-screen rooms. Every death is instant and every respawn is quick: precision is the whole game. Chase the light.

Afterglow is the flagship demo for the SimVX engine and original IP. It is built as a clean split: a deterministic, headless-testable pure-logic 2D simulation (afterglow/sim/) presented by a lit, post-processed 3D “diorama” view (afterglow/view/), with GPU-particle game feel, a full menu flow, and persistent progression. The sim never imports the view or the assets, so the game’s logic is fully unit-tested without a GPU.

Signature mechanics

Three ideas, each taught by level design rather than text, then braided together:

  • Dash through crystals (refills and chains). The air-dash is a fixed-length burst you normally get once per grounded reset. Dashing through a resonance crystal refills the dash mid-air, so a line of crystals becomes a dash-chain: cross a gap no single dash could clear by refuelling at each crystal in turn.

  • Glow orbs to timed glow to light-gates. Touching a glow orb lights the Wisp for a few seconds. A light-gate is solid only while the Wisp glows, so the loop is: grab the orb, race to the gate, and cross before the glow fades. Later rooms put the orb and the gate far enough apart that you must dash-chain to arrive in time.

  • Movement feel. Variable-height jumps, coyote time and jump buffering, wall-slide and wall-jump, springs, and moving platforms. Tight, forgiving, and the foundation every other mechanic is layered on.

Each room hides one optional shard tucked off the main path for an expert-only challenge, and tracks your best time and death count.

Controls

Every device feeds the same named input actions, so the simulation sees identical intent regardless of how you play.

Action

Keyboard

Gamepad

Move

Arrows / WASD

D-pad / left stick

Aim up-dash

Up / W

Up

Jump

Space

A

Dash

Shift / J

X

Restart room

R

-

Pause / back

Escape

Start

Web and mobile touch. On a small or portrait viewport the game shows an on-screen overlay: a virtual joystick (movement and dash-aim) bottom-left, jump / dash buttons bottom-right, and a pause button top-right that opens the pause menu (and from there restart, options and quit). Touches arrive as left-clicks in the web runtime, so the virtual pad drives the exact same actions as a physical press. Everything is playable with mouse or touch alone.

How to run

Desktop (windowed):

# In your own copy of this directory
python main.py

# From the root of a repository checkout
uv run python examples/demos/afterglow/main.py

Web export (an HTML page plus its font-atlas PNG; serve both over http):

# In your own copy of this directory
simvx export web main.py -o afterglow.html

# From the root of a repository checkout
uv run simvx export web examples/demos/afterglow/main.py -o afterglow.html

Run the demo’s logic tests (no GPU required):

# In your own copy of this directory
pytest tests -q

# From the root of a repository checkout
uv run --package simvx-core pytest examples/demos/afterglow/tests/ -q

The three worlds

Eighteen rooms across three worlds, six rooms each, on a difficulty curve that hands you one new idea at a time and then asks you to combine them:

  1. The Glade (movement). Run, variable jump, coyote/buffer over gaps, wall-slide and wall-jump, then a climb that combines all of it. No dash, crystals, orbs, or gates yet.

  2. The Caverns (the dash and crystals). The air-dash alone, then over a gap, then resonance crystals for a single mid-air refill, then a two-crystal chain, then a constellation that demands a full dash-chain.

  3. The Aether Spire (glow orbs and light-gates). Glow orbs and timed light-gates introduced gently, then combined with dash-chains so you must reach a gate fast and cross it before the glow fades, ending on a finale that braids every mechanic together.

Placeholder assets

All art and audio are generated procedurally in code under afterglow/assets/ (textures.py, sprite_gen.py, audio.py): tileable PBR-ish tile textures, the Wisp sprite sheet, and synthesized SFX plus per-world ambient music (no copyrighted samples, no file I/O). They are deliberate placeholders. Because the simulation never imports the assets and the view consumes them through a single narrow contract, you can swap any of them for real PNGs or audio files without touching gameplay.

Source files

File

Summary

Lines

main.py

Afterglow: an original precision platformer.

37

afterglow/__init__.py

Afterglow: original precision platformer (SimVX flagship demo).

1

afterglow/assets/__init__.py

Generated/loaded assets for the diorama view.

1

afterglow/assets/audio.py

Procedural audio for Afterglow: synthesized SFX + per-world ambient music.

412

afterglow/assets/sprite_gen.py

Procedural Wisp sprite-sheet generator for the Afterglow diorama view.

198

afterglow/assets/textures.py

Procedural placeholder PBR textures for the Afterglow diorama view.

192

afterglow/game.py

Afterglow game root: the full state machine that hosts the flagship demo.

831

afterglow/progress.py

Afterglow save / progression layer (plain JSON).

252

afterglow/sim/__init__.py

Logical 2D simulation: pure Python + numpy, GPU-free, deterministic, headless-testable.

1

afterglow/sim/entities.py

Afterglow entities + the Player controller (pure Python, GPU-free).

377

afterglow/sim/room.py

Room: ASCII parser, fixed-timestep step, swept tile collision, events.

337

afterglow/sim/rooms_data.py

Authored room data for Afterglow: the full game, three worlds.

795

afterglow/sim/tiles.py

Tile legend + flag helpers for the Afterglow logical sim.

41

afterglow/ui/__init__.py

HUD, menus, and overlays built on simvx.core.ui widgets.

1

afterglow/ui/controls.py

Afterglow input layer: the one canonical cross-platform player read.

346

afterglow/ui/hud.py

Afterglow in-game HUD overlay.

265

afterglow/ui/menus.py

Afterglow menu screens.

904

afterglow/view/__init__.py

3D diorama presentation of the logical sim. Never read by the sim.

1

afterglow/view/camera.py

GameCamera: a Camera3D that smoothly follows the Wisp and frames the room.

184

afterglow/view/diorama.py

Diorama: turn a sim Room’s static tile grid into one lit 3D mesh scene.

266

afterglow/view/effects.py

Game-feel (juice) layer for Afterglow: particles, screenshake, hitstop, SFX.

539

afterglow/view/environment.py

Per-world WorldEnvironment presets for the Afterglow diorama.

165

afterglow/view/lighting.py

Per-world 3D lighting rigs for the Afterglow diorama.

202

afterglow/view/sprites.py

EntityView: billboarded visuals for every dynamic sim entity, synced each frame.

410

afterglow/view/title_backdrop.py

TitleBackdrop: a clean, intentional abstract backdrop for the title menu.

86

Source

 1"""Afterglow: an original precision platformer.
 2
 3# /// simvx
 4# tags = ["3d", "game", "platformer", "particles", "feel"]
 5# screenshot_frame = 46
 6# web = { width = 960, height = 540, root = "GameRoot", responsive = true }
 7# ///
 8
 9A complete precision platformer built on SimVX. The game logic is a deterministic
10pure-logic sim; the presentation is a lit, post-processed 3D diorama with billboard
11sprites. Textures, sprites, and audio are generated procedurally, and the game has a
12full menu flow with progression saved across three worlds.
13
14Chase the light: dash through resonance crystals, gather glow orbs to open light
15gates, and race the clock across eighteen single-screen rooms.
16
17Controls:
18    Arrows / WASD    Move (Up/W aims an up-dash)
19    Space            Jump
20    Shift / J        Dash
21    R                Restart the room
22    Escape           Pause / back
23    (Gamepad and on-screen touch controls are supported.)
24"""
25
26from afterglow.game import GameRoot
27
28from simvx.graphics import App
29
30
31def main() -> None:
32    app = App(width=960, height=540, title="Afterglow")
33    app.run(GameRoot(name="GameRoot"))
34
35
36if __name__ == "__main__":
37    main()