Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Bug report
description: Something renders wrong, crashes, or does not build
labels: [bug]
body:
- type: textarea
id: what
attributes:
label: What happened
description: What you expected, and what you saw instead. A screenshot helps for anything visual.
validations:
required: true
- type: input
id: command
attributes:
label: Command
placeholder: cargo run --release --example convergence
validations:
required: true
- type: input
id: platform
attributes:
label: OS and GPU
placeholder: Windows 11, RTX 3070 / macOS 14, M2 / Ubuntu 24.04, Intel UHD
validations:
required: true
- type: input
id: rust
attributes:
label: rustc --version
- type: textarea
id: log
attributes:
label: Log output
description: Run with RUST_LOG=info and paste the output around the problem.
render: text
16 changes: 16 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Feature request
description: Suggest a new primitive, effect, demo or API
labels: [enhancement]
body:
- type: textarea
id: want
attributes:
label: What you want to do
description: The effect or program you are trying to build, and what stops you today.
validations:
required: true
- type: textarea
id: idea
attributes:
label: Proposed approach
description: Optional. Which module it would live in, and the math behind it if it has any.
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CI

on:
push:
branches: [main]
pull_request:

env:
CARGO_TERM_COLOR: always
# The crate is large; incremental artifacts only bloat the cache.
CARGO_INCREMENTAL: 0

jobs:
test:
name: Build and test (ubuntu)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: System libraries (ALSA for cpal)
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libasound2-dev pkg-config
- uses: Swatinem/rust-cache@v2
# Builds the library, examples, benches, integration tests and the
# proof_editor binary. Nothing here opens a window or needs a GPU.
- name: Build all targets
run: cargo build --all-targets
- name: Library unit tests
run: sh ci/test-lib.sh
- name: Integration and binary tests
run: cargo test --test '*' --bins
- name: Doc tests
run: cargo test --doc

examples:
name: Build examples (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Build examples
run: cargo build --examples
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Changelog

## 0.2.0 (unreleased)

Engine work that was developed alongside [chaos-rpg](https://github.com/Mattbusel/chaos-rpg) and had not been pushed, plus CI and a new demo.

### Added
- `ProofEngine::run_ui` for UI-driven games (update before draw, UI layer cleared each frame), `render_size`, and `save_frame` for reading the framebuffer back to a BMP.
- HDR scene buffers and a two-pass UI layer (`UiPass::World` painted before post-processing, `UiPass::Hud` painted sharp after it).
- `engine.fx` (`ScreenFx`): shockwaves, flashes, light shafts, floor reflection, heat haze, screen-space lights with shadows.
- Bloom pyramid, FXAA, `render_scale`, motion-trail persistence, camera shake that leaves the HUD still.
- GPU density entities (`init_gpu_density`, `queue_gpu_density_entity`) and particle skinning (`anim::particle_skin`).
- Synthesiser voices on `MathAudioSource`: pitch envelope, second partial, noise, filters, drive, reverb send, start delay; music and effects buses with ducking and a limiter.
- `sky` example: a day cycle lit by the Nishita sky model (`nishita_sky`), the first of the advanced lighting modules used by a demo.
- CI on GitHub Actions: build of every target, library, integration and doc tests on Ubuntu; example builds on Windows and macOS.

### Fixed
- Bloom: each pyramid level had one shared half-resolution scratch texture, so smaller levels were blurred with three quarters of the clear colour and the bloom showed a visible seam at the screen's centre lines. Each level now has its own scratch target.
- Integer overflow panics in debug builds in the hash functions of `particle::density_entity`, `volumetric_fog`, `editor::terrain` and `editor::map_editor` loot rolls (16 unit tests now pass).
- `particle_bench` did not compile after `MathParticle` gained fields.
- Five doc examples (`ai::steering`, `animation`, `debug::console`, `game::transitions`, `replay`) did not compile against the current API.

### Changed
- `MathParticle` has new public fields; struct literals need `..Default::default()`.

### Known issues
- 79 library unit tests fail and are listed in `ci/known-failing-tests.txt`; CI skips exactly those.

## 0.1.1

Initial crates.io release.
26 changes: 16 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ Thank you for your interest in contributing to Proof Engine! This document provi
git clone https://github.com/YOUR_USERNAME/proof-engine.git
cd proof-engine
```
3. **Build** the project:
3. **Build** the project and run a demo:
```bash
cargo check
cargo test
cargo build --all-targets
cargo run --release --example hello_glyph
```
On Linux, install the ALSA headers first: `sudo apt install libasound2-dev pkg-config`.

## Development Setup

Expand Down Expand Up @@ -49,10 +50,12 @@ Thank you for your interest in contributing to Proof Engine! This document provi
- Use `std` only for new subsystems unless an external crate is truly necessary
- Keep modules self-contained with clear public APIs

3. **Ensure it compiles cleanly**:
3. **Run what CI runs**:
```bash
cargo check
cargo test
cargo build --all-targets
sh ci/test-lib.sh # library unit tests, minus the known failures
cargo test --test '*' --bins # integration tests
cargo test --doc
```

4. **Commit** with a clear message:
Expand Down Expand Up @@ -98,10 +101,13 @@ Each module should:
### Testing

- Add tests for new functionality in the same file or a `tests` submodule
- Run the full test suite before submitting:
```bash
cargo test
```
- Run the suite before submitting: `sh ci/test-lib.sh`, then `cargo test --test '*' --bins` and `cargo test --doc`.
- A plain `cargo test --lib` currently reports failures: the tests named in
`ci/known-failing-tests.txt` fail on `main` and CI skips exactly those. Fixing
one is a good first contribution: make it pass, delete its line from the
file, and say in the PR whether the code or the test was wrong.
`sh ci/run-known-failing.sh` runs only those tests.
- Tests must not need a window, a GPU or an audio device; CI has none of them.
- Performance-sensitive code should have benchmarks in `benches/`

### What We're Looking For
Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "proof-engine"
version = "0.1.1"
version = "0.2.0"
edition = "2021"
authors = ["Matthew Busel <matt@tensorust.com>"]
license = "MIT"
Expand All @@ -10,6 +10,8 @@ homepage = "https://github.com/Mattbusel/proof-engine"
keywords = ["game-engine", "rendering", "mathematics", "ascii", "opengl"]
categories = ["game-engines", "graphics", "rendering"]
readme = "README.md"
# Screenshots, GIFs and repo tooling stay on GitHub; the README links resolve there.
exclude = ["*.png", "*.gif", "assets/", "*.py", ".github/", "ci/"]

[dependencies]
# OpenGL / windowing — glutin-winit bridges the two and handles version alignment
Expand Down Expand Up @@ -109,6 +111,9 @@ name = "sculptor"
[[example]]
name = "apotheosis"

[[example]]
name = "sky"

[[bin]]
name = "proof_editor"
path = "src/bin/proof_editor.rs"
106 changes: 97 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Proof Engine

[![CI](https://github.com/Mattbusel/proof-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/Mattbusel/proof-engine/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/proof-engine.svg)](https://crates.io/crates/proof-engine)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Expand All @@ -25,30 +26,116 @@ A Lorenz attractor on screen looks like a Lorenz attractor because its particles
- **Procedural generation**: tectonics, erosion, climate, biomes, rivers, caves, settlements, history, language and quest generation, plus ecology models (Lotka-Volterra, SIR).
- **Proof Editor**: an egui scene editor for placing glyphs, force fields and entities, with an inspector, hierarchy, post-FX presets, undo/redo and JSON scenes.

The source tree also contains modules for more advanced lighting (a sparse voxel octree GI cone tracer, Nishita sky scattering, tiled and deferred lighting, volumetric fog, a wgpu backend). Those exist as code but are not yet connected to the demos shown above.
The source tree also contains modules for more advanced lighting (a sparse voxel octree GI cone tracer, Nishita sky scattering, tiled and deferred lighting, volumetric fog, a wgpu backend). The Nishita sky model drives the `sky` example; the others exist as code but are not yet connected to any demo.

## The screen pipeline

What actually runs on the GPU every frame, in order:

```text
scene FBO (RGBA16F x2: colour, emission) at render_scale
3D glyph pass ─┐
UI world pass ─┤ (UiPass::World: particle clouds, filled rects, panel fills)
├─ emission ─► bloom pyramid: soft-knee threshold, blur down, tent up
└─ colour ────► composite ─► [FXAA] ─► screen ─► UI HUD pass
(UiPass::Hud: text, borders, bars, sprites)
```

The scene buffers are half-float, so a few hundred thousand overlapping
emissive particles accumulate real light instead of clipping at white. The
composite is the one place the range comes down, through ACES.

**Two UI passes.** `engine.ui` routes every command to a pass. Particle
clouds and filled rectangles default to the world pass, which is painted into
the HDR buffer before post-processing; text, outlines, bars and sprites
default to the HUD pass, painted sharp on top afterwards. A panel splits: fill
to the world, border to the HUD. `ui.begin_world()`, `ui.begin_hud()` and
`ui.end_pass()` override the default for a run of commands. A game that draws
its whole picture as screen-space matter gets bloom, grade, lens and grain on
all of it, and a readable interface over that.

**What the composite does, in order:** shockwave refraction, heat haze, barrel
lens, chromatic aberration, unsharp mask, floor reflection, exposure,
screen-space indirect light (matter near a lit thing is lit by it), bloom,
halation, light shafts, lens flare, flash, ACES tonemap, lift/gain grade,
tint, contrast, saturation, vignette, shadow-weighted grain, ordered dither,
scanlines. Every standing parameter is a field on `RenderConfig`; the moments
are on `engine.fx`.

**`engine.fx` (ScreenFx).** Fire-and-forget effects that decay on their own:
`shockwave(x, y, strength)`, `flash(color, strength)`,
`light_shaft_at(x, y, strength)` or `auto_shafts = true` to stream from
whatever is brightest on screen, `reflect_at(y, strength, fade)` for a glossy
floor, and `haze` for heat shimmer. Coordinates are UI pixels.

**Lights and shadows.** `engine.fx.light(x, y, radius, color, intensity)`
and `engine.fx.ambient`. The glyph pass writes an occluder buffer (matter,
never floors or panel fills); the light pass marches shadows through it
from every light and the composite multiplies the scene by the result.
Emissive matter lights itself. `config.persistence` keeps a decaying copy
of last frame's scene under this one, for motion trails.

**GPU density entities.** `engine.init_gpu_density(n)` and
`engine.queue_gpu_density_entity(data)`: sixteen bones become millions of
particles derived in the vertex shader from the instance index, with
breathing, jitter and matter that comes loose as `hp` falls. Nothing per
particle ever leaves the GPU. See `examples/colossus.rs`.

**Sound.** A `MathAudioSource` now carries a pitch envelope, a second
partial, a noise mix, biquad or comb filters, drive, a reverb send, and a
start delay, and the output thread honours all of it, with separate music
and effects buses, ducking, a master reverb and a soft limiter. A blow is a
crack, a thud and a ring; before, it was a sine.

**Also:** `render_scale` renders the scene at a fraction of the window and
upsamples; `fxaa` runs a real FXAA 3.11 pass between the composite and the
HUD; `shake_pixels` moves the world pass with camera trauma while the HUD
stays put; `vsync` waits for the display.

## Quick start

Requires a Rust toolchain and an OpenGL 3.3 capable GPU.
You need a Rust toolchain (stable) and a GPU with OpenGL 3.3 or newer.

- **Windows:** nothing else.
- **macOS:** nothing else. macOS stops at OpenGL 4.1, so `apotheosis` (which uses 4.3 compute shaders) will not run there; every other demo does.
- **Linux:** the audio backend needs the ALSA headers: `sudo apt install libasound2-dev pkg-config` (Debian/Ubuntu) or `sudo dnf install alsa-lib-devel` (Fedora).

```bash
git clone https://github.com/Mattbusel/proof-engine.git
cd proof-engine
cargo run --release --example hello_glyph # smallest possible program
cargo run --release --example galaxy
cargo run --release --example supernova
cargo run --release --example convergence # the demo in the screenshots
```

Other examples: `chaos_field`, `particle_demo`, `force_fields`, `amorphous_entity`, `strange_attractors`, `full_combat`, `math_rain`, `heartbeat`, `showcase`, `playground`, `sculptor`, `apotheosis`, `colossus`. Some of the heavier ones (`apotheosis`, `colossus`) allocate millions of GPU particles and need a strong GPU.
The first build compiles the whole engine and takes a few minutes. Use `--release`: the demos simulate tens of thousands of particles per frame and a debug build is too slow to judge them by.

## Demos

Every demo is `cargo run --release --example <name>`. Close the window (or press Esc where noted) to quit.

| Example | What you see |
| --- | --- |
| `convergence` | Two particle-built fighters in a circular arena with an orbiting camera; combat loops forever and hits knock matter loose. The screenshots at the top of this page. |
| `supernova` | A star pulses, collapses under a gravity field, explodes into debris and settles into a Lorenz-attractor nebula. The GIF at the top. |
| `galaxy` | 3000+ glyphs on golden-ratio spiral arms around a central black hole, with nebula clouds on Perlin noise. |
| `sky` | A day passing over a mountain range. Every sky cell is the Nishita Rayleigh + Mie scattering integral for its view direction, recomputed each frame. Space pauses, Up/Down change speed, Esc quits. |
| `math_rain` | Digital rain where each column follows a different function: linear, sine, logistic map, Collatz, Perlin. |
| `strange_attractors` | Seven attractors (Lorenz, Rossler, Chen, Halvorsen, Aizawa, Thomas, Dadras) side by side as particle trails. |
| `hello_glyph` | The smallest program: one breathing `@` and a gravity field. Start here when reading code. |
| `playground` | Interactive sandbox: place glyphs, fields and entities with the mouse, cycle attractors and palettes. |
| `colossus` | GPU density entities: millions of particles derived in the vertex shader from sixteen bones. Needs a strong GPU. |
| `apotheosis` | A particle-rendered character built on signed distance fields, about 10.8 million GPU particles. Needs OpenGL 4.3 and a strong GPU. |

Also: `chaos_field`, `particle_demo`, `force_fields`, `amorphous_entity`, `particle_entity`, `full_combat`, `heartbeat`, `showcase`, `sculptor`.

![Convergence, close up](assets/convergence-close.png)

Benchmarks: `cargo bench` (Criterion: `particle_bench`, `glyph_bench`).

### Use as a library

```toml
[dependencies]
proof-engine = "0.1"
proof-engine = "0.2"
```

```rust
Expand Down Expand Up @@ -116,7 +203,8 @@ Roughly 660,000 lines of Rust across the engine (`src/`), the editor (`editor/`)
| `scripting` | lexer, parser, compiler, bytecode VM |
| `terrain`, `worldgen`, `ecology`, `narrative` | procedural generation |
| `game` | boss AI, cloth, debris, achievements |
| `svogi`, `nishita_sky`, `volumetric_fog`, `wgpu_backend` | advanced lighting, not yet wired into the demos |
| `nishita_sky` | physical sky scattering, used by the `sky` example |
| `svogi`, `volumetric_fog`, `tiled_lighting`, `wgpu_backend` | advanced lighting, not yet wired into the demos |

### The `apotheosis` example

Expand All @@ -128,7 +216,7 @@ Roughly 660,000 lines of Rust across the engine (`src/`), the editor (`editor/`)

## Status

Early (0.1.x) and moving fast. The public API is not stable, there is no CI workflow in this repository, and some subsystems are further along than others. Contributions: see [CONTRIBUTING.md](CONTRIBUTING.md).
Early (0.1.x) and moving fast. The public API is not stable and some subsystems are further along than others. CI builds every target and runs the unit, integration and doc tests on Linux, and builds the examples on Windows and macOS. About 4,800 library unit tests pass; the ones that do not yet are listed by name in [`ci/known-failing-tests.txt`](ci/known-failing-tests.txt), and each one fixed is a line deleted from that file. Contributions: see [CONTRIBUTING.md](CONTRIBUTING.md).

## License

Expand Down
Binary file removed Screenshot 2026-03-25 102146.png
Binary file not shown.
Binary file added assets/convergence-close.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions benches/particle_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn make_particle(i: usize) -> MathParticle {
origin: Vec3::new(i as f32 * 0.1, 0.0, 0.0),
age: 0.0,
lifetime: 5.0,
..Default::default()
}
}

Expand Down
Loading
Loading