v0.2.15

Project Overview

Even Steven

Home
Engine Godot 4.3 · pure 2D Genre Chip-Firing Puzzle Platforms Windows · macOS · Linux · Android Status In Development Tests 573
00

The Game

numbered levels, each a little harder than the last
573
automated tests, most of them on the pure rules code
4
export targets: Windows, macOS, Linux, Android

EVEN STEVEN is a pure 2D puzzle built on graph chip-firing. The board is a graph; every vertex holds an amount of money, and it can go negative. You win by making every vertex hold the same amount. Every generated board sums to $0, so the amount to reach is always $0. There is no way to lose and no undo: every move is made for good, and running long only costs points.

Every player gets the same board for a given level number, because levels are seeded from the number and nothing in the game is random per player.

01

How It Plays

Two moves, one goal. Vertices draw green once they hold their share and red until they do, so the whole board turns green at exactly the moment it is won.

Move · pay

Fire

A vertex gives $1 to each neighbour and pays $1 per incident edge. Firing a degree-3 vertex pays out $3.

Left-click · touch tap · keyboard Enter · gamepad A or left shoulder

Move · steal

Borrow

The exact inverse: the vertex takes $1 from each neighbour. Green dollars travel for a fire, red ones for a borrow.

Right-click · long-press or second finger · keyboard B · gamepad right shoulder

Goal

Even Steven

Reach a position where every vertex holds the same amount. Each level is timed, and a win raises a banner with stars and the score.

Restart or Next: button, keyboard N, or gamepad Y

The easiest thing to get wrong

Money dividing evenly is necessary, not sufficient. The positions a board can reach by firing form one class out of exactly as many as it has spanning trees. A tree has one, so there dividing evenly is the whole condition. Anything with a cycle has more, and which class the money sits in decides the answer.

On a triangle, (1, −1, 0) sums to $0 and can never be evened out, while (1, 1, −2) and (2, −1, −1) both can. The solver decides this exactly; the cheap counting check answers “undetermined” for any board with a cycle rather than guessing.

02

Current State

The game is playable end to end. What is left is polish, tuning and the shape of the release, listed under the roadmap below.

Flow
Splash, then the menu, then the board, then the next level, over an endless run of numbered levels.
Input
Mouse, touch, keyboard and gamepad all play the board. Arrow keys or the d-pad move a cursor between vertices; the cursor stays hidden until a directional press asks for it.
Progression
The level reached and the best run on every level beaten are saved between sessions. Replaying an earlier level can improve its score but never skips you past levels you have not reached.
Level select
One section of 12 levels at a time, with Prev and Next. Locked levels are greyed, beaten ones show their stars, and every tile shows a difficulty tier.
High Scores
A personal scoreboard, one row per beaten level: stars, score, moves and time, with the career total above. It is a progress view, not a leaderboard.
Career total
Shown in the header while you play, and the win banner says what the win just added to it.
Settings
Fullscreen, three volumes (master, music, effects) and a Reset-progress confirmation. Reset is the only destructive action in the game, so it is the only one that asks first.
Level editor
A separate project under leveleditor/ that exports versioned JSON, with its own copy of the difficulty rating.
Dev importer
“Import Level (dev)” on the menu plays editor exports from user://levels/. It is visible on debug builds only and is meant to be deleted before release.
Platforms
Windows x86_64macOS universal Linux x86_64Android arm64-v8a
Android runs the custom Gradle build and is verified on a Pixel 8. All four are exported by ./release.sh.
03

Architecture

The line that matters: the rules, geometry and easing live in pure classes with no scene dependency, and the board scene only draws and forwards input. That is what keeps them testable.

PieceFileJob
DollarGraphscripts/dollar_graph.gdThe rules: fire, borrow, the share check. Validates a graph on build and returns null on a bad one.
DollarSolverscripts/dollar_solver.gdDecides winnability exactly with Dhar's burning algorithm, and returns the true fewest moves (par).
LevelGeneratorscripts/level_generator.gdRuns the game backwards from an all-$0 board, so every level is winnable by construction.
BoardLayoutscripts/board_layout.gdWhere vertices sit and which one a click hit. A spring layout that cuts crossings but cannot remove them on dense boards.
FirePulsefx/fire_pulse.gdTiming and easing for one move. The engine moves the money instantly; the pulse only delays the sight of it.
BoardViewscenes/board.tscnThe playing field. Draws the graph and forwards input, owns no rules, geometry or easing.
LevelScorescripts/level_score.gdStars and score from moves against par and the time on the clock.
LevelDifficultyscripts/level_difficulty.gdRates the board itself, not the player, into five tiers. Also vendored into the level editor.
GameProgressautoload/game_progress.gdLevel reached and best score, stars, time and moves per level, saved to user://progress.cfg.
GameSettingsautoload/game_settings.gdFullscreen and the three volumes, kept apart so resetting progress leaves them alone.
Two ways to ask “can this be won?” and they are not interchangeable: DollarGraph.winnability() answers cheaply from counting and returns undetermined for any cycle, while DollarSolver.is_winnable() costs a reduction and is exact.
04

Difficulty

Measured, not guessed. Par is the true fewest fires and borrows a board needs, and the generator scrambles until the board is genuinely that much work, so the number asked for is the number that comes out.

Level 22 · board reaches ten verticespar about 22
Level 100par about 28
Level 300par about 42
Level 600par about 46
Bars are par against a 60-move scale, the ceiling the curve is tuned to stay under through level 600. Occasional unlucky scrambles run higher.
Up to level 22
The board gains a vertex every third level and a cycle every fourth, until it hits its ceilings of 10 vertices and 8 extra edges. The target work goes up by exactly one each level.
After level 22
A sawtooth: 24 levels of real climbing, then a reset that gives back part of the climb, plus a periodic easier breather level. It keeps every par under 60 and every board's money under $100 until level 523.
Tiers
GentleEasyMediumHardBrutal
Rated from vertex count, cycles, how far the money has spread, and how the minimal solution is spread across vertices and between giving and stealing. The direction-mix weight is a bet on the player, not a graph fact.
05

Scoring

What a solved level is worth. The tests assert the shape of the scoring, not its totals, so the constants can move freely.

Stars
Up to 3, from moves against par. Par is the true minimum, so no run beats it; matching it exactly is the best any run can do.
Score
From how close to par the run came and how fast. Full marks on the clock need par × 6 seconds, and the time bonus is gone past par × 24 seconds.
The clock
Starts when the level is on screen and ready, not on the first move, so a one-move level cannot read 0:00. Running long costs points, never the level.
Bests
Score, stars, time and moves are kept per level, all from the same run that set the best score. The career total is summed from the bests rather than stored, so replaying cannot farm it.
Trust model
Local only, no server and no online toplist. Levels are deterministic and the solver ships in the client, so scores are a personal record, never a verified ranking.
The constants are guessed, not measured. Nobody has played far enough yet to say whether the clock allowance is generous or mean.
06

Roadmap

A lean, not a verdict

The score display is finished, so the most useful next input is not code but play: put the current build in front of a few people and see how far they get. That settles whether the scoring constants and the difficulty curve are right, and both feed the level-pack and Demo decisions below.

The activation effect is the cheapest visible upgrade if you want something to work on before then.

ItemStatusNotes
Activation effect on fire and stealScopedSquash-stretch, a shockwave ring or a particle burst on top of the existing glow. See docs/activation-effect-scope-2026-09-08.md.
Level packs and groupingScopedLevels grouped so they can be sold as packs. See docs/level-grouping-scope-2026-09-08.md.
MonetizationDirection setTwo builds, a free Demo and a paid full game, rather than one app with an in-app unlock. Scope docs cover the desktop Demo and the mobile IAP alternative.
Tune the score constantsNeeds playtestsBase, par bonus and time allowance need real played runs behind them.
Re-grade old savesNot freePar is never stored, so a star-scale change would mean re-solving every beaten level once.
Delete the dev importer UIBefore releaseRemove the screen and menu button; keep the JSON parser, importer and open_imported.
07

Dev Guide

TaskCommand
Testgodot --headless -s res://addons/gut/gut_cmdln.gd -gexit
Lintgdlint scripts/ scenes/ tests/ ui/ fx/ autoload/
Formatgdformat scripts/ scenes/ tests/ ui/ fx/ autoload/
Run (virtual display)DISPLAY=:99 godot --path .
Release./release.sh
Repo layout
scenes/scripts/ui/fx/ autoload/tests/data/docs/ leveleditor/assets/addons/
Never use :0
Never run the game against DISPLAY=:0. That is the live desktop, and a window torn down abruptly there can leave keyboard focus stuck until you log out. Use Xvfb on :99 and close the game gracefully.
Working rules
Work directly on main, use Conventional Commits, and keep tests green. Tests that touch GameProgress or GameSettings must put them back, since both are global and write to disk.