Ledger is a deterministic gameplay ability system. Write your game's stats, abilities and
effects as content, check them with plays, and ask the running world why any value is what it is. The core
knows no engine; a Godot adapter and editor addon come with it.
#10 tick 0 Health −125, sent 160, 125 after its rules, from #1 by Strike
Attack
Hits scale Physical+4160 → 164
Hits scale Fire+12164 → 176
Hits scale Cold+4176 → 180
Hits scale Lightning+4180 → 184
Armour
Hits scale Physical−22184 → 162
Resistance
Hits scale Physical0162 → 162
Hits scale Fire−26162 → 136
Hits scale Cold−11136 → 125
Hits scale Lightning0125 → 125
Health−1251000 → 875
The Attack rule read holder.Damage. Its receipt: --why 23 a.Damage.Fire
base 1 from Damage.Fire1
Searing: percent+0.2
Sharpened: percent+0.1
(1 + 0) x (1 + 0.3) x 11.3
== at the end, tick 0: #2 Brute, Health
Health = 875 of 1000
anchored at 875 at tick 0, no drift
at least 0, at most 1000
its drift crosses no line
its changes to Health, newest first:
#10 tick 0: Health -125 (sent 160, 125 after its rules), 1000 -> 875, from #1 by Strike
Hits scale Physical 4: 160 -> 164
Hits scale Fire 12: 164 -> 176
Hits scale Cold 4: 176 -> 180
Hits scale Lightning 4: 180 -> 184
Hits scale Physical -22: 184 -> 162
Hits scale Physical 0: 162 -> 162
Hits scale Fire -26: 162 -> 136
Hits scale Cold -11: 136 -> 125
Hits scale Lightning 0: 125 -> 125
== at the end, tick 0: #1 Exile, Damage.Fire
Damage.Fire = 1.3
base 1 from Damage.Fire
Searing: percent +0.2
Sharpened: percent +0.1
(1 + 0) x (1 + 0.3) x 1
An Exile carrying two affixes strikes a Brute for 40 of each of four types. Every line item
names the rule that made it, the rule's line names the stat it read, and the stat's receipt names the
affixes. Hover a line item to see the content behind it. The stage labels come from
ledger pipeline; the rest is the tool's own output, laid out as a ledger.
The loop
Edit, check, play. Stop at the first that fails.
Each step answers one question about your game. Run them in this order after any change. Errors
come back at their file, line and column, all of them at once.
1
Edit
The rules live in content\*.ledger; what they should do lives in a
.play beside them.
A recipe from the cookbook is the usual starting point.
The Godot dock lists errors, runs plays and shows a pool's pipeline.
The inspector reads the running game.
Plays
Tests that read like the design doc.
A play makes actors, gives orders, moves time and expects values. Every expected number says
where it comes from, so a reviewer can check the arithmetic and ledger play checks the content.
6play"wound up at (10, 0): nothing lands or is paid until 0.8 s; then 30 to each foe within 2.5, none to one 3 away, 20 Mana paid, each one hit burning"
At 60 ticks a second, advance 0.5s is 30 ticks. Jumping time and
stepping it run the same code, so a play can skip ahead without missing a beat.
Refusals are outcomes
Every order gets an outcome. expect refused OnCooldown checks
the refusal by name, the same outcome the game receives.
save mid-play
At any step, save saves the world and plays on from the
restored copy, as a quickload would. Every step after it checks the round trip.
Ask why
Ask why, and get the answer the world used.
The code that changes a value writes down how it changed. A tooltip, a debugger and a replay all
read that same record, so the answer never drifts from what actually happened.
> ledger play --why 23 a.Damage.Fire content/cookbook/backbone.play
== at the end, tick 0: #1 Exile, Damage.Fire
Damage.Fire = 1.3
base 1 from Damage.Fire
Searing: percent +0.2
Sharpened: percent +0.1
(1 + 0) x (1 + 0.3) x 1
Why a stat is what it is
Its base, each term and the effect that gave it, the strongest of each category, and its bounds. Two
percents sum before they multiply, the way an ARPG's increases do.
In a running game, the inspector shows the same receipt for the actor you pick, each effect a button to its
line.
> ledger play --why 23 d.Health content/cookbook/backbone.play
== at the end, tick 0: #2 Brute, Health
Health = 875 of 1000
anchored at 875 at tick 0, no drift
at least 0, at most 1000
its drift crosses no line
its changes to Health, newest first:
#10 tick 0: Health -125 (sent 160, 125 after its rules), 1000 -> 875, from #1 by Strike
Hits scale Physical 4: 160 -> 164
Hits scale Fire 12: 164 -> 176
…
Why a pool is where it is
A pool's anchor, its drift, its bounds and bands, and the next line its drift will cross. Then every change
the journal holds, newest first, each with its line items: the rule, the verb, the portion and the amount,
and the hit before and after.
Regeneration is closed form, so the receipt says where it was anchored instead of listing every tick.
> ledger pipeline content/cookbook/backbone.ledger Health
Health: a change to it meets 3 rules, in this order
before, at Attack
Hits: Damage by holder at Attack (content/cookbook/backbone.ledger:20)
scales it; held by Exile, Brute
before, at Armour
Hits: Damage of holder having Physical at Armour (content/cookbook/backbone.ledger:22)
scales it; held by Exile, Brute
before, at Resistance
Hits: Damage of holder at Resistance (content/cookbook/backbone.ledger:24)
scales it; held by Exile, Brute
no archetype lacks a layer its like hold
Every rule a hit will meet
Each stage's shields, armours and resistances in the order a hit meets them, who holds each, and any
archetype missing a layer its like hold.
Add --hit Fire 40 Brute to send one hit of a type and an amount through a fresh actor and see
what each rule left.
> ledger play --why 6 h.Fireball content/cookbook/mechanics/windup.play
== at the end, tick 48: #3, Fireball
Fireball, newest first:
#3/1 Fireball, by order: its Activate #7 at tick 0
windup on the world's clock: 48 long, from reading 0
ticks 0 to 48 at 1: 48 run
#8 tick 48: Commit
adds up: 48 run from reading 0 to 48 as its clock holds it, less 0 pushed, 48 of its 48
Why a cast went off when it did
Each stretch of its clock at each rate, every push and beat, a flight's speed and time, its end, and a
check that it adds up.
A 0.8 s windup at 60 ticks a second is 48 ticks. Under a haste or a slow, the receipt shows each stretch at
its own rate.
Godot
The editor reads your content too.
The Godot adapter hosts the world and ticks it from _PhysicsProcess, and its kit
draws what a player may see. The editor addon puts the command line's answers in a dock, next to the game
they describe.
A presentation kit
An ability bar, a cast bar and tooltips that preview what the
world would do, and cues bound to tags your content already writes. An ability added in content alone shows
up and can be used with no C#.
Completion in content files
Highlighting, and the words and names that fit
what you typed, each with its kind. After holder. it offers what an actor holds.
The dock
Checks, plays, a preview, the Pipeline tab and the Run tab. Every rule
and name is a button to its line.
The inspector
Pick an actor in the running game and read its pools' and stats'
receipts and the changes the journal holds.
Reload while it runs
Save a content file and it reloads into the running world
between two ticks, with its report as Godot warnings.
ChecksPlaysWordsPipelineRun
Health: a change to it meets 3 rules, in this order
before, at AttackHits: Damage by holder at Attack backbone.ledger:20scales it; held by Exile, Brute
before, at ArmourHits: Damage of holder having Physical at Armour backbone.ledger:22scales it; held by Exile, Brute
before, at ResistanceHits: Damage of holder at Resistance backbone.ledger:24scales it; held by Exile, Brute
In a running game the tab narrows to the inspector's picked actor, with each
when shown true or false as it stands.
A sketch of the Pipeline tab. It lists the lines ledger pipeline prints.
The principles
Four rules the kernel keeps, each one checked by a machine.
A rule like these only holds if something refuses to let it slip. Each comes with the check that
keeps it.
Deterministic
Time is ticks. Randomness is seeded, drawn from streams the world owns, and saved. Nothing reads the
machine: no clock, no culture, no hash order, no floating-point function whose answer differs by machine.
Two runs on two machines end in identical state.
Checked bythe same seeds replayed under another culture, time zone and operating
system; every outside call the core makes held to an allowlist; its compiled code scanned for floats.
A steady tick allocates nothing
Games with hundreds of actors stutter on garbage collection, and the cost is invisible until it isn't. So
it is measured in every build a game runs.
Measureda strike through a build of ten affixes: 3.4 to 3.5 µs, 0 bytes allocated.
Everything round-trips through a save
A restored world holds exactly what the saved one held and plays on exactly as it would have. Saves,
quickloads, replays and lockstep joins all depend on it.
Checked byroundtrip: saved again at once, the same file; then both
played on to one save.
Every number has a receipt
A game can ask why a value is what it is, and why an action was refused, and get the real answer the
world used. The author, the player reading a tooltip and the tester reading a replay all see the same one.
Asked with--why, ledger pipeline, ledger why,
and the inspector.
An engine-free core. Ledger knows no game engine. Space, AI and presentation stay
the game's, behind ports, and an adapter joins the core to an engine. Godot's comes first.
The cookbook
43 recipes, lifted from real game slices.
Each recipe is a .ledger and its .play that load alone, cut down to one
mechanic from a game built on Ledger. Find the one nearest what you want, copy it, then change its numbers and
names.
Action RPG11 recipes
Tower defence9 recipes
Shooter6 recipes
Tactics5 recipes
RTS5 recipes
Raid4 recipes
Cards3 recipes
Straight answers
Where Ledger stops.
Better to know before you start a game on it.
Not a game engine
Ledger keeps the numbers on a game's actors and every way they change.
Physics, AI and rendering stay your game's. Space sits behind a port: use the plane space it ships with, or
bring your own.
Not floating point
Every value is a Num, a fixed-point decimal. The platform's
floating-point maths is kept out of the core, since its last digit can differ by machine. The host's trig reads
a committed table instead.
Not a network transport
Rollback in place and client prediction under server authority are
built, with side views so each player is sent only what their side can see. Ledger makes the bytes; sending
them is your game's job. A game moved by Godot's float physics can't be a rollback or lockstep peer.
Not a node graph
Content is plain text. You can diff it, review it and grep it, and a coding agent
can write it.
Quick start
A game that builds, checks and plays from the first minute.
ledger new makes a Godot C# game with its content, its plays and a headless runner,
ready for the loop above.
For coding agents
Your assistant gets the same guide you do.
The authoring guide is an agent skill, ledger-author, and every new game's
CLAUDE.md points to it. ledger check --json and ledger play --json
print one object a line, each with a stable id, the line as written and a fix where one is known.
1
Make a game
Add --turns for a turn-based game, or --space own to
bring your own space.
ledger new Arena
2
Change the content
Edit content\Arena.ledger and its play, then check and
play them.