moon2d
A 2D platformer written in Delphi in 2008, rebuilt from scratch on modern Delphi + SDL2 eighteen years later. Same game, same arithmetic.
- Active · last change 11-4-2026
- ★ 5 on GitHub
README
Moon 2D
A 2D platformer written in Delphi in 2008. Rebuilt from an empty folder in 2026. Every line of code is new. Nothing about the game changed.
↑ 90 seconds of trailer. Every frame is the running build.

The short version
In 2008 a teenager wrote a platformer in Delphi 2005. One file. Immediate-mode
OpenGL. Level geometry in a hand-rolled binary format. A 20 ms WM_Timer doing
duty as a game loop.
It ran. Most of it ran for reasons that were never written down anywhere.
Eighteen years later the whole thing was deleted and built again from nothing. New renderer, new file formats, new asset pipeline, new soundtrack, new language — Delphi 2005 and current Delphi are separated by the Unicode break, generics, inline variables, and the retirement of immediate-mode OpenGL.
And the game that came out the other side is the same game. Same jump arc, same frame counts, same monsters doing the same wrong-looking thing on screen fourteen. Same arithmetic: where the 2008 source held a formula, that formula was carried over verbatim, with a comment naming the file and line it came from. Not simplified. Not tidied. Cited, like a source in a paper.
Replace the entire machine. Change nothing the player can feel. That was the rule, and it is the only interesting thing about this project.
| Torn out and rebuilt | Carried across untouched |
|---|---|
| OpenGL → SDL2, D3D11 backend | Every sprite, drawn by hand in 2008 |
WM_Timer → fixed 33 Hz timestep |
Hero physics, constant for constant |
| Binary level blobs → readable JSON | Collision oracles, including the asymmetric ones |
Loose PNGs → .mset sprite containers |
Weapon patterns, all five of them |
Hardcoded monsters → monsters.json |
The bullet-spawner particle hacks |
| Third-party music → an original score | The bitmap font, glyph for glyph |
| One long file → a unit per responsibility | The feel. Which is the whole point. |
Eight deliberate departures from the original exist. They are enumerated in
PORTING.md. Anything else that differs is a bug, not an
improvement — a closed list is the only thing that stops a rewrite from drifting
into a remake.
The game
An elemental master lands on a lunar mining complex that stopped answering three days ago. The transmitter is on the far side of it. Between him and the transmitter is everything that came up out of the shafts.
You walk right. You shoot. Monsters keep coming — the game's own design document would have said "как из рога изобилия" if it had had one. Somewhere in the middle of it a transformation sequence lifted wholesale from Japanese tokusatsu television happens to you, and the screen fills with five converging waves of bullets, and it is completely unearned and absolutely correct.
Two levels ship today — the entire 2008 campaign, start to finish, in English and Russian. Eight more are coming. The tenth does not end on the Moon.
Controls
| Move | Arrow keys |
| Jump | Up |
| Aim | Mouse |
| Fire | Left mouse button |
| Use the held bonus | Right mouse button |
| Pause / menu | Esc |
The health monitor and the bonus charge in the top corners, and the health rows over the hero and the monsters, are 2026 work, drawn with rectangles rather than sprites. The original's health orbs and score line live on in the 2.5.3 "Farewell 2008" release, the last one with the 2008 mode.
Greatest hits from the excavation
The port turned up things the original author — me — had no memory of writing.
Full accounts in PORTING.md; the highlights:
- The timer lied for eighteen years. The loop asked Windows for 20 ms.
WM_Timerdelivered about 33. Every constant in the game — jump height, monster speed, fire rate — was tuned by hand against a number that appears nowhere in the source. The specification lived in an OS scheduler. - The font atlas has been stored sideways since 2008, and it was correct. The old texture loader transposed on upload. The glyphs were authored through that same broken path — drawn, rotated until they looked right, saved. A bug compensated elsewhere is not a bug. It is a load-bearing wall.
- Two ground probes,
y-3andy+3, that must never be unified. One for the hero, one for monsters, written years apart, each correct inside its own caller. Merge them and monsters sink into floors. They look like the same function. They are not the same function. Both were ported, both kept, both commented with a warning. - The animation counter runs backwards.
9 - Round(CurrentSprite). Preserved. AndRoundin Delphi is banker's rounding, which quietly picks a different frame than a naive reading expects — documented, not fixed. - Every fourth frame was going somewhere. SDL2 still defaults to the Direct3D 9 renderer on Windows. The hint that fixes it is ignored silently if you set it after creating the renderer.
- ...and naming the driver cost us the batching. Set
SDL_RENDER_DRIVERand SDL quietly stops batching draw calls - it assumes you now talk to the GPU yourself. Nobody noticed for three months, until a henshin threw a few thousand glows into one frame and an RTX 4070 dropped to seven frames a second. One more hint put it back.
Read the code
That is half the reason this repository is public. It is modern Object Pascal
written the way modern Object Pascal should look — guard clauses, records for
data that travels together, implementation uses by default, no with, no
aligned columns, no u prefixes. Every unit declares one responsibility.
Good places to start:
Game/Hero.pas— physics, weapons, death. The densest concentration of cited-2008 arithmetic in the project, and the clearest picture of what "preserve the formula" means in practice.Game/Bullets.pas— the particle system, which in 2008 was five different abuses of the projectile list. A 180-fragment explosion fan; 768 slow bullets raining on a grid; a motionless cloud of them used as a shield. All still hacks. All now named.Game/Game.Moon.pas- the game-flow state machine (TMoonGame), the game the program runs;Moon2D.dpris the start-up alone.docs/CODEBASE-MAP.md— the whole layout, unit by unit, if you would rather orient before diving.
Playing
Take the release archive, unpack it, run Moon2D.exe. Nothing installs, nothing
registers, nothing phones anywhere. The one file written outside the game
folder is %APPDATA%\Moon2D\settings.json - what you chose in the game
(difficulty, language, fullscreen), laid over the shipped config.json.
Starts fullscreen; Alt+Enter switches to a window and back, and the choice is
remembered. If the screen stays black, press Alt+Enter. Failing that, set
"fullscreen" to false in config.json and delete settings.json if it
exists - it overrides config.json.
System requirements
Worked out from the code and the assets; not yet tried on genuinely old hardware.
| OS | Windows 7 SP1 or later (Direct3D 11 needs the Platform Update on 7) |
| Graphics | DirectX 9 class with Shader Model 2 (GeForce 6, Radeon X-series, Intel GMA 950) or newer; 256 MB. Smooth from Intel HD Graphics 3000 |
| Memory | 1 GB |
| Processor | any dual core |
| Disk | 90 MB |
No sprite is wider or taller than 2048 pixels, the texture limit of the oldest DirectX 9 cards, so those cards can run the game; what slows the oldest of them down is fill - additive glows and full-screen flashes drawn at window resolution.
Cloning and building works too, and the clone is complete — the soundtrack is
in the repository now that the tracks are finished and mine. If you delete
bin/music/ anyway, the game does not care: the audio DLL is a delayed import,
so a missing library or a missing track degrades to silence rather than to a
crash.
Building
Developed on the current RAD Studio release, Win32 target. No third-party components.
- Open
Moon2D.dproj - Build and run — output lands in
bin\, next to the assets
The real floor is inline variable declarations, which arrived in Delphi 10.3
Rio — anything from there upward builds this. Nothing newer is used
deliberately, and that includes the free Community Edition. The .dproj is
saved by a recent IDE and may want its version attribute nudged before an older
one will open it.
SDL2.dll, SDL2_image.dll and SDL2_mixer.dll live in bin\ and are in the
repository already.
Layout
Moon2D.dpr, .dproj the start-up and the project
Core/ shared with the tools and the level editor: SDL,
sprite sets, rendering, level and monster models
Game/ the game (Game.Moon), hero, monsters, bullets, sound,
the game loop
Hud/ everything drawn over the playfield, the story screen
Menu/ the main menu and its sky
bin/ everything the game reads at runtime, and nothing else
Moon2D.exe
SDL2*.dll renderer, image and mixer
level1.json geometry, backgrounds, entities, triggers, per screen
level2.json
monsters.json movement, attacks, spawn tables, pickup effects
config.json shipped defaults: window, tick rate, difficulty, language
lang/ en.json, ru.json
sprites/ *.mset containers: manifest + packed frames, one file
per subject - a monster, a tile theme, the hero,
one level's screen backdrops
sounds/ WAV one-shots
music/ OGG tracks
tools/ SpritePackCli (asset packer), TitleCard (trailer text)
docs/ working notes, codebase map, screenshotsbin/ is the entire shipping surface. The release packager copies that folder
rather than consulting a list, because a directory layout that encodes the rule
cannot drift out of sync with it.
Where this goes
Version 2 finished the original game. Phase two adds eight levels under one rule: every level must require new engine code. A level that rearranges existing tiles is content. A level that exists because a mechanic was written for it is progress.
Queued:
- An ore transport train running the tunnels below the complex
- A level editor — sharing the game's own level and render units, previewing through SDL in a VCL host
- Render interpolation for the game world (the menu already has it)
- Levels 3 through 10, and the place the tenth one ends
Documentation
PORTING.md— what breaks when you move 2008 Delphi and OpenGL to modern Delphi and SDL2. Coordinate systems, asymmetric probes, a rotated font atlas, and a rendering backend quietly costing you frames.ASSETS.md— where the art and audio came from, honestly.docs/CODEBASE-MAP.md— unit-by-unit structural map.docs/PORTING-NOTES.md— the unedited working log kept during the port. Russian, session by session, written for nobody.
Related
- dark-planet-win — a 1998 DOS game reverse-engineered and ported to Delphi VCL. Same universe, earlier archaeology, worse tooling.
License
Source code: MIT. Art and audio are not covered by it — see
ASSETS.md.
Ilia Kuzmin · github.com/rm3g25 · YouTube: Still Running


